001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.context; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.model.api.annotation.Child; 024import ca.uhn.fhir.model.api.annotation.Description; 025import org.apache.commons.lang3.StringUtils; 026import org.hl7.fhir.instance.model.api.IBase; 027import org.hl7.fhir.instance.model.api.IBaseDatatype; 028import org.hl7.fhir.instance.model.api.IBaseReference; 029import org.hl7.fhir.instance.model.api.IBaseResource; 030import org.hl7.fhir.instance.model.api.IPrimitiveType; 031 032import java.lang.reflect.Field; 033import java.util.ArrayList; 034import java.util.Collections; 035import java.util.HashMap; 036import java.util.List; 037import java.util.Map; 038import java.util.Set; 039 040public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefinition { 041 042 private List<Class<? extends IBase>> myChoiceTypes; 043 private Map<String, BaseRuntimeElementDefinition<?>> myNameToChildDefinition; 044 private Map<Class<? extends IBase>, String> myDatatypeToElementName; 045 private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myDatatypeToElementDefinition; 046 private String myReferenceSuffix; 047 private List<Class<? extends IBaseResource>> myResourceTypes; 048 049 /** 050 * Constructor 051 */ 052 public RuntimeChildChoiceDefinition( 053 Field theField, 054 String theElementName, 055 Child theChildAnnotation, 056 Description theDescriptionAnnotation, 057 List<Class<? extends IBase>> theChoiceTypes) { 058 super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName); 059 060 myChoiceTypes = Collections.unmodifiableList(theChoiceTypes); 061 } 062 063 /** 064 * Constructor 065 * 066 * For extension, if myChoiceTypes will be set some other way 067 */ 068 RuntimeChildChoiceDefinition( 069 Field theField, String theElementName, Child theChildAnnotation, Description theDescriptionAnnotation) { 070 super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName); 071 } 072 073 void setChoiceTypes(List<Class<? extends IBase>> theChoiceTypes) { 074 myChoiceTypes = Collections.unmodifiableList(theChoiceTypes); 075 } 076 077 public List<Class<? extends IBase>> getChoices() { 078 return myChoiceTypes; 079 } 080 081 @Override 082 public Set<String> getValidChildNames() { 083 return myNameToChildDefinition.keySet(); 084 } 085 086 @Override 087 public BaseRuntimeElementDefinition<?> getChildByName(String theName) { 088 assert myNameToChildDefinition.containsKey(theName) 089 : "Can't find child '" + theName + "' in names: " + myNameToChildDefinition.keySet(); 090 091 return myNameToChildDefinition.get(theName); 092 } 093 094 @SuppressWarnings("unchecked") 095 @Override 096 void sealAndInitialize( 097 FhirContext theContext, 098 Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { 099 myNameToChildDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>(); 100 myDatatypeToElementName = new HashMap<Class<? extends IBase>, String>(); 101 myDatatypeToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>(); 102 myResourceTypes = new ArrayList<Class<? extends IBaseResource>>(); 103 104 myReferenceSuffix = "Reference"; 105 106 for (Class<? extends IBase> next : myChoiceTypes) { 107 108 String elementName = null; 109 BaseRuntimeElementDefinition<?> nextDef; 110 boolean nonPreferred = false; 111 if (IBaseResource.class.isAssignableFrom(next)) { 112 elementName = getElementName() + StringUtils.capitalize(next.getSimpleName()); 113 nextDef = findResourceReferenceDefinition(theClassToElementDefinitions); 114 115 myNameToChildDefinition.put(getElementName() + "Reference", nextDef); 116 myNameToChildDefinition.put(getElementName() + "Resource", nextDef); 117 118 myResourceTypes.add((Class<? extends IBaseResource>) next); 119 120 } else { 121 nextDef = theClassToElementDefinitions.get(next); 122 BaseRuntimeElementDefinition<?> nextDefForChoice = nextDef; 123 124 /* 125 * In HAPI 1.3 the following applied: 126 * Elements which are called foo[x] and have a choice which is a profiled datatype must use the 127 * unprofiled datatype as the element name. E.g. if foo[x] allows markdown as a datatype, it calls the 128 * element fooString when encoded, because markdown is a profile of string. This is according to the 129 * FHIR spec 130 * 131 * Note that as of HAPI 1.4 this applies only to non-primitive datatypes after discussion 132 * with Grahame. 133 */ 134 if (nextDef instanceof IRuntimeDatatypeDefinition) { 135 IRuntimeDatatypeDefinition nextDefDatatype = (IRuntimeDatatypeDefinition) nextDef; 136 if (nextDefDatatype.getProfileOf() != null && !IPrimitiveType.class.isAssignableFrom(next)) { 137 nextDefForChoice = null; 138 nonPreferred = true; 139 Class<? extends IBaseDatatype> profileType = nextDefDatatype.getProfileOf(); 140 BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(profileType); 141 elementName = getElementName() + StringUtils.capitalize(elementDef.getName()); 142 } 143 } 144 if (nextDefForChoice != null) { 145 elementName = getElementName() + StringUtils.capitalize(nextDefForChoice.getName()); 146 } 147 } 148 149 // I don't see how elementName could be null here, but eclipse complains.. 150 if (elementName != null) { 151 if (myNameToChildDefinition.containsKey(elementName) == false || !nonPreferred) { 152 myNameToChildDefinition.put(elementName, nextDef); 153 } 154 } 155 156 /* 157 * If this is a resource reference, the element name is "fooNameReference" 158 */ 159 if (IBaseResource.class.isAssignableFrom(next) || IBaseReference.class.isAssignableFrom(next)) { 160 next = theContext.getVersion().getResourceReferenceType(); 161 elementName = getElementName() + myReferenceSuffix; 162 myNameToChildDefinition.put(elementName, nextDef); 163 } 164 165 myDatatypeToElementDefinition.put(next, nextDef); 166 167 if (myDatatypeToElementName.containsKey(next)) { 168 String existing = myDatatypeToElementName.get(next); 169 if (!existing.equals(elementName)) { 170 throw new ConfigurationException( 171 Msg.code(1693) + "Already have element name " + existing + " for datatype " 172 + next.getSimpleName() + " in " + getElementName() + ", cannot add " + elementName); 173 } 174 } else { 175 myDatatypeToElementName.put(next, elementName); 176 } 177 } 178 179 myNameToChildDefinition = Collections.unmodifiableMap(myNameToChildDefinition); 180 myDatatypeToElementName = Collections.unmodifiableMap(myDatatypeToElementName); 181 myDatatypeToElementDefinition = Collections.unmodifiableMap(myDatatypeToElementDefinition); 182 myResourceTypes = Collections.unmodifiableList(myResourceTypes); 183 } 184 185 public List<Class<? extends IBaseResource>> getResourceTypes() { 186 return myResourceTypes; 187 } 188 189 @Override 190 public String getChildNameByDatatype(Class<? extends IBase> theDatatype) { 191 String retVal = myDatatypeToElementName.get(theDatatype); 192 return retVal; 193 } 194 195 @Override 196 public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theDatatype) { 197 return myDatatypeToElementDefinition.get(theDatatype); 198 } 199 200 public Set<Class<? extends IBase>> getValidChildTypes() { 201 return Collections.unmodifiableSet((myDatatypeToElementDefinition.keySet())); 202 } 203}