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