001package ca.uhn.fhir.context;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2019 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}