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