001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2023 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(Field theField, String theElementName, Child theChildAnnotation, Description theDescriptionAnnotation, List<Class<? extends IBase>> theChoiceTypes) { 053 super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName); 054 055 myChoiceTypes = Collections.unmodifiableList(theChoiceTypes); 056 } 057 058 /** 059 * Constructor 060 * 061 * For extension, if myChoiceTypes will be set some other way 062 */ 063 RuntimeChildChoiceDefinition(Field theField, String theElementName, Child theChildAnnotation, Description theDescriptionAnnotation) { 064 super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName); 065 } 066 067 void setChoiceTypes(List<Class<? extends IBase>> theChoiceTypes) { 068 myChoiceTypes = Collections.unmodifiableList(theChoiceTypes); 069 } 070 071 public List<Class<? extends IBase>> getChoices() { 072 return myChoiceTypes; 073 } 074 075 @Override 076 public Set<String> getValidChildNames() { 077 return myNameToChildDefinition.keySet(); 078 } 079 080 @Override 081 public BaseRuntimeElementDefinition<?> getChildByName(String theName) { 082 assert myNameToChildDefinition.containsKey(theName) : "Can't find child '" + theName + "' in names: " + myNameToChildDefinition.keySet(); 083 084 return myNameToChildDefinition.get(theName); 085 } 086 087 @SuppressWarnings("unchecked") 088 @Override 089 void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { 090 myNameToChildDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>(); 091 myDatatypeToElementName = new HashMap<Class<? extends IBase>, String>(); 092 myDatatypeToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>(); 093 myResourceTypes = new ArrayList<Class<? extends IBaseResource>>(); 094 095 myReferenceSuffix = "Reference"; 096 097 for (Class<? extends IBase> next : myChoiceTypes) { 098 099 String elementName = null; 100 BaseRuntimeElementDefinition<?> nextDef; 101 boolean nonPreferred = false; 102 if (IBaseResource.class.isAssignableFrom(next)) { 103 elementName = getElementName() + StringUtils.capitalize(next.getSimpleName()); 104 nextDef = findResourceReferenceDefinition(theClassToElementDefinitions); 105 106 myNameToChildDefinition.put(getElementName() + "Reference", nextDef); 107 myNameToChildDefinition.put(getElementName() + "Resource", nextDef); 108 109 myResourceTypes.add((Class<? extends IBaseResource>) next); 110 111 } else { 112 nextDef = theClassToElementDefinitions.get(next); 113 BaseRuntimeElementDefinition<?> nextDefForChoice = nextDef; 114 115 /* 116 * In HAPI 1.3 the following applied: 117 * Elements which are called foo[x] and have a choice which is a profiled datatype must use the 118 * unprofiled datatype as the element name. E.g. if foo[x] allows markdown as a datatype, it calls the 119 * element fooString when encoded, because markdown is a profile of string. This is according to the 120 * FHIR spec 121 * 122 * Note that as of HAPI 1.4 this applies only to non-primitive datatypes after discussion 123 * with Grahame. 124 */ 125 if (nextDef instanceof IRuntimeDatatypeDefinition) { 126 IRuntimeDatatypeDefinition nextDefDatatype = (IRuntimeDatatypeDefinition) nextDef; 127 if (nextDefDatatype.getProfileOf() != null && !IPrimitiveType.class.isAssignableFrom(next)) { 128 nextDefForChoice = null; 129 nonPreferred = true; 130 Class<? extends IBaseDatatype> profileType = nextDefDatatype.getProfileOf(); 131 BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(profileType); 132 elementName = getElementName() + StringUtils.capitalize(elementDef.getName()); 133 } 134 } 135 if (nextDefForChoice != null) { 136 elementName = getElementName() + StringUtils.capitalize(nextDefForChoice.getName()); 137 } 138 } 139 140 // I don't see how elementName could be null here, but eclipse complains.. 141 if (elementName != null) { 142 if (myNameToChildDefinition.containsKey(elementName) == false || !nonPreferred) { 143 myNameToChildDefinition.put(elementName, nextDef); 144 } 145 } 146 147 /* 148 * If this is a resource reference, the element name is "fooNameReference" 149 */ 150 if (IBaseResource.class.isAssignableFrom(next) || IBaseReference.class.isAssignableFrom(next)) { 151 next = theContext.getVersion().getResourceReferenceType(); 152 elementName = getElementName() + myReferenceSuffix; 153 myNameToChildDefinition.put(elementName, nextDef); 154 } 155 156 myDatatypeToElementDefinition.put(next, nextDef); 157 158 if (myDatatypeToElementName.containsKey(next)) { 159 String existing = myDatatypeToElementName.get(next); 160 if (!existing.equals(elementName)) { 161 throw new ConfigurationException(Msg.code(1693) + "Already have element name " + existing + " for datatype " + next.getSimpleName() + " in " + getElementName() + ", cannot add " + elementName); 162 } 163 } else { 164 myDatatypeToElementName.put(next, elementName); 165 } 166 } 167 168 myNameToChildDefinition = Collections.unmodifiableMap(myNameToChildDefinition); 169 myDatatypeToElementName = Collections.unmodifiableMap(myDatatypeToElementName); 170 myDatatypeToElementDefinition = Collections.unmodifiableMap(myDatatypeToElementDefinition); 171 myResourceTypes = Collections.unmodifiableList(myResourceTypes); 172 } 173 174 175 public List<Class<? extends IBaseResource>> getResourceTypes() { 176 return myResourceTypes; 177 } 178 179 @Override 180 public String getChildNameByDatatype(Class<? extends IBase> theDatatype) { 181 String retVal = myDatatypeToElementName.get(theDatatype); 182 return retVal; 183 } 184 185 @Override 186 public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theDatatype) { 187 return myDatatypeToElementDefinition.get(theDatatype); 188 } 189 190 public Set<Class<? extends IBase>> getValidChildTypes() { 191 return Collections.unmodifiableSet((myDatatypeToElementDefinition.keySet())); 192 } 193 194}