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