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}