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}