001package ca.uhn.fhir.context;
002
003import static org.apache.commons.lang3.StringUtils.isNotBlank;
004
005import java.lang.reflect.Field;
006import java.lang.reflect.Modifier;
007
008/*
009 * #%L
010 * HAPI FHIR - Core Library
011 * %%
012 * Copyright (C) 2014 - 2017 University Health Network
013 * %%
014 * Licensed under the Apache License, Version 2.0 (the "License");
015 * you may not use this file except in compliance with the License.
016 * You may obtain a copy of the License at
017 * 
018 *      http://www.apache.org/licenses/LICENSE-2.0
019 * 
020 * Unless required by applicable law or agreed to in writing, software
021 * distributed under the License is distributed on an "AS IS" BASIS,
022 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
023 * See the License for the specific language governing permissions and
024 * limitations under the License.
025 * #L%
026 */
027
028import java.util.ArrayList;
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.HashSet;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.ListIterator;
035import java.util.Map;
036import java.util.Map.Entry;
037import java.util.Set;
038import java.util.TreeMap;
039import java.util.TreeSet;
040
041import org.hl7.fhir.instance.model.api.IAnyResource;
042import org.hl7.fhir.instance.model.api.IBase;
043import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
044import org.hl7.fhir.instance.model.api.IBaseDatatype;
045import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
046import org.hl7.fhir.instance.model.api.IBaseEnumeration;
047import org.hl7.fhir.instance.model.api.IBaseExtension;
048import org.hl7.fhir.instance.model.api.IBaseReference;
049import org.hl7.fhir.instance.model.api.IBaseResource;
050import org.hl7.fhir.instance.model.api.ICompositeType;
051import org.hl7.fhir.instance.model.api.INarrative;
052import org.hl7.fhir.instance.model.api.IPrimitiveType;
053
054import ca.uhn.fhir.model.api.IBoundCodeableConcept;
055import ca.uhn.fhir.model.api.IDatatype;
056import ca.uhn.fhir.model.api.IElement;
057import ca.uhn.fhir.model.api.IResource;
058import ca.uhn.fhir.model.api.IResourceBlock;
059import ca.uhn.fhir.model.api.IValueSetEnumBinder;
060import ca.uhn.fhir.model.api.annotation.Binding;
061import ca.uhn.fhir.model.api.annotation.Child;
062import ca.uhn.fhir.model.api.annotation.ChildOrder;
063import ca.uhn.fhir.model.api.annotation.Description;
064import ca.uhn.fhir.model.api.annotation.Extension;
065import ca.uhn.fhir.model.base.composite.BaseContainedDt;
066import ca.uhn.fhir.model.base.composite.BaseNarrativeDt;
067import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
068import ca.uhn.fhir.model.primitive.BoundCodeDt;
069import ca.uhn.fhir.parser.DataFormatException;
070import ca.uhn.fhir.util.ReflectionUtil;
071
072public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> extends BaseRuntimeElementDefinition<T> {
073
074        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseRuntimeElementCompositeDefinition.class);
075        private Map<String, Integer> forcedOrder = null;
076        private List<BaseRuntimeChildDefinition> myChildren = new ArrayList<BaseRuntimeChildDefinition>();
077        private List<BaseRuntimeChildDefinition> myChildrenAndExtensions;
078        private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions;
079        private FhirContext myContext;
080        private Map<String, BaseRuntimeChildDefinition> myNameToChild = new HashMap<String, BaseRuntimeChildDefinition>();
081        private List<ScannedField> myScannedFields = new ArrayList<BaseRuntimeElementCompositeDefinition.ScannedField>();
082        private volatile boolean mySealed;
083
084        @SuppressWarnings("unchecked")
085        public BaseRuntimeElementCompositeDefinition(String theName, Class<? extends T> theImplementingClass, boolean theStandardType, FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
086                super(theName, theImplementingClass, theStandardType);
087                
088                myContext = theContext;
089                myClassToElementDefinitions = theClassToElementDefinitions;
090                
091                /*
092                 * We scan classes for annotated fields in the class but also all of its superclasses
093                 */
094                Class<? extends IBase> current = theImplementingClass;
095                LinkedList<Class<? extends IBase>> classes = new LinkedList<Class<? extends IBase>>();
096                do {
097                        if (forcedOrder == null) {
098                                ChildOrder childOrder = current.getAnnotation(ChildOrder.class);
099                                if (childOrder != null) {
100                                        forcedOrder = new HashMap<String, Integer>();
101                                        for (int i = 0; i < childOrder.names().length; i++) {
102                                                forcedOrder.put(childOrder.names()[i], i);
103                                        }
104                                }
105                        }
106                        classes .push(current);
107                        if (IBase.class.isAssignableFrom(current.getSuperclass())) {
108                                current = (Class<? extends IBase>) current.getSuperclass();
109                        } else {
110                                current = null;
111                        }
112                } while (current != null);
113
114                Set<Field> fields = new HashSet<Field>();
115                for (Class<? extends IBase> nextClass : classes) {
116                        int fieldIndexInClass = 0;
117                        for (Field next : nextClass.getDeclaredFields()) {
118                                if (fields.add(next)) {
119                                        ScannedField scannedField = new ScannedField(next, theImplementingClass, fieldIndexInClass == 0);
120                                        if (scannedField.getChildAnnotation() != null) {
121                                                myScannedFields.add(scannedField);
122                                                fieldIndexInClass++;
123                                        }
124                                }
125                        }
126                }
127
128        }
129
130        void addChild(BaseRuntimeChildDefinition theNext) {
131                if (theNext == null) {
132                        throw new NullPointerException();
133                }
134                if (theNext.getExtensionUrl() != null) {
135                        throw new IllegalArgumentException("Shouldn't haven an extension URL, use addExtension instead");
136                }
137                myChildren.add(theNext);
138        }
139
140        public BaseRuntimeChildDefinition getChildByName(String theName){
141                validateSealed();
142                BaseRuntimeChildDefinition retVal = myNameToChild.get(theName);
143                return retVal;
144        }
145
146        public BaseRuntimeChildDefinition getChildByNameOrThrowDataFormatException(String theName) throws DataFormatException {
147                validateSealed();
148                BaseRuntimeChildDefinition retVal = myNameToChild.get(theName);
149                if (retVal == null) {
150                        throw new DataFormatException("Unknown child name '" + theName + "' in element " + getName() + " - Valid names are: " + new TreeSet<String>(myNameToChild.keySet()));
151                }
152                return retVal;
153        }
154
155        public List<BaseRuntimeChildDefinition> getChildren() {
156                validateSealed();
157                return myChildren;
158        }
159        
160        
161        public List<BaseRuntimeChildDefinition> getChildrenAndExtension() {
162                validateSealed();
163                return myChildrenAndExtensions;
164        }
165
166
167        /**
168         * Has this class been sealed
169         */
170        public boolean isSealed() {
171                return mySealed;
172        }
173
174        @SuppressWarnings("unchecked")
175        void populateScanAlso(Set<Class<? extends IBase>> theScanAlso) {
176                for (ScannedField next : myScannedFields) {
177                        if (IBase.class.isAssignableFrom(next.getElementType())) {
178                                if (next.getElementType().isInterface() == false && Modifier.isAbstract(next.getElementType().getModifiers()) == false) {
179                                        theScanAlso.add((Class<? extends IBase>) next.getElementType());
180                                }
181                        }
182                        for (Class<? extends IBase> nextChildType : next.getChoiceTypes()) {
183                                if (nextChildType.isInterface() == false && Modifier.isAbstract(nextChildType.getModifiers()) == false) {
184                                        theScanAlso.add(nextChildType);
185                                }
186                        }
187                }
188        }
189        
190        private void scanCompositeElementForChildren() {
191                Set<String> elementNames = new HashSet<String>();
192                TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
193                TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
194
195                scanCompositeElementForChildren(elementNames, orderToElementDef, orderToExtensionDef);
196
197                if (forcedOrder != null) {
198                        /* 
199                         * Find out how many elements don't match any entry in the list
200                         * for forced order. Those elements come first.
201                         */
202                        TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> newOrderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
203                        int unknownCount = 0;
204                        for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
205                                if (!forcedOrder.containsKey(nextEntry.getElementName())) {
206                                        newOrderToExtensionDef.put(unknownCount, nextEntry);
207                                        unknownCount++;
208                                }
209                        }
210                        for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
211                                if (forcedOrder.containsKey(nextEntry.getElementName())) {
212                                        Integer newOrder = forcedOrder.get(nextEntry.getElementName());
213                                        newOrderToExtensionDef.put(newOrder + unknownCount, nextEntry);
214                                }
215                        }
216                        orderToElementDef = newOrderToExtensionDef;
217                }
218                
219                TreeSet<Integer> orders = new TreeSet<Integer>();
220                orders.addAll(orderToElementDef.keySet());
221                orders.addAll(orderToExtensionDef.keySet());
222
223                for (Integer i : orders) {
224                        BaseRuntimeChildDefinition nextChild = orderToElementDef.get(i);
225                        if (nextChild != null) {
226                                this.addChild(nextChild);
227                        }
228                        BaseRuntimeDeclaredChildDefinition nextExt = orderToExtensionDef.get(i);
229                        if (nextExt != null) {
230                                this.addExtension((RuntimeChildDeclaredExtensionDefinition) nextExt);
231                        }
232                }
233
234        }
235        
236        @SuppressWarnings("unchecked")
237        private void scanCompositeElementForChildren(Set<String> elementNames, TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToElementDef,
238                        TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToExtensionDef) {
239                int baseElementOrder = 0; 
240
241                for (ScannedField next : myScannedFields) {
242                        if (next.isFirstFieldInNewClass()) {
243                                baseElementOrder = theOrderToElementDef.isEmpty() ? 0 : theOrderToElementDef.lastEntry().getKey() + 1;
244                        }
245                        
246                        Class<?> declaringClass = next.getField().getDeclaringClass(); 
247
248                        Description descriptionAnnotation = ModelScanner.pullAnnotation(next.getField(), Description.class);
249
250                        TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderMap = theOrderToElementDef;
251                        Extension extensionAttr = ModelScanner.pullAnnotation(next.getField(), Extension.class);
252                        if (extensionAttr != null) {
253                                orderMap = theOrderToExtensionDef;
254                        }
255
256                        Child childAnnotation = next.getChildAnnotation();
257                        Field nextField = next.getField();
258                        String elementName = childAnnotation.name();
259                        int order = childAnnotation.order();
260                        boolean childIsChoiceType = false;
261                        boolean orderIsReplaceParent = false;
262                        
263                        if (order == Child.REPLACE_PARENT) {
264                                
265                                if (extensionAttr != null) {
266
267                                        for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
268                                                BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
269                                                if (nextDef instanceof RuntimeChildDeclaredExtensionDefinition) {
270                                                        if (nextDef.getExtensionUrl().equals(extensionAttr.url())) {
271                                                                orderIsReplaceParent = true;
272                                                                order = nextEntry.getKey();
273                                                                orderMap.remove(nextEntry.getKey());
274                                                                elementNames.remove(elementName);
275                                                                break;
276                                                        }
277                                                }
278                                        }
279                                        if (order == Child.REPLACE_PARENT) {
280                                                throw new ConfigurationException("Field " + nextField.getName() + "' on target type " + declaringClass.getSimpleName() + " has order() of REPLACE_PARENT (" + Child.REPLACE_PARENT
281                                                                + ") but no parent element with extension URL " + extensionAttr.url() + " could be found on type " + nextField.getDeclaringClass().getSimpleName());
282                                        }
283
284                                } else {
285
286                                        for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
287                                                BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
288                                                if (elementName.equals(nextDef.getElementName())) {
289                                                        orderIsReplaceParent = true;
290                                                        order = nextEntry.getKey();
291                                                        BaseRuntimeDeclaredChildDefinition existing = orderMap.remove(nextEntry.getKey());
292                                                        elementNames.remove(elementName);
293                                                        
294                                                        /*
295                                                         * See #350 - If the original field (in the superclass) with the given name is a choice, then we need to make sure
296                                                         * that the field which replaces is a choice even if it's only a choice of one type - this is because the
297                                                         * element name when serialized still needs to reflect the datatype
298                                                         */
299                                                        if (existing instanceof RuntimeChildChoiceDefinition) {
300                                                                childIsChoiceType = true;
301                                                        }
302                                                        break;
303                                                }
304                                        }
305                                        if (order == Child.REPLACE_PARENT) {
306                                                throw new ConfigurationException("Field " + nextField.getName() + "' on target type " + declaringClass.getSimpleName() + " has order() of REPLACE_PARENT (" + Child.REPLACE_PARENT
307                                                                + ") but no parent element with name " + elementName + " could be found on type " + nextField.getDeclaringClass().getSimpleName());
308                                        }
309
310                                }
311
312                        }
313                        
314                        if (order < 0 && order != Child.ORDER_UNKNOWN) {
315                                throw new ConfigurationException("Invalid order '" + order + "' on @Child for field '" + nextField.getName() + "' on target type: " + declaringClass);
316                        }
317                        
318                        if (order != Child.ORDER_UNKNOWN && !orderIsReplaceParent) {
319                                order = order + baseElementOrder;
320                        }
321                        // int min = childAnnotation.min();
322                        // int max = childAnnotation.max();
323
324                        /*
325                         * Anything that's marked as unknown is given a new ID that is <0 so that it doesn't conflict with any given IDs and can be figured out later
326                         */
327                        if (order == Child.ORDER_UNKNOWN) {
328                                order = Integer.valueOf(0);
329                                while (orderMap.containsKey(order)) {
330                                        order++;
331                                }
332                        }
333
334                        List<Class<? extends IBase>> choiceTypes = next.getChoiceTypes();
335                        
336                        if (orderMap.containsKey(order)) {
337                                throw new ConfigurationException("Detected duplicate field order '" + childAnnotation.order() + "' for element named '" + elementName + "' in type '" + declaringClass.getCanonicalName() + "' - Already had: " + orderMap.get(order).getElementName());
338                        }
339
340                        if (elementNames.contains(elementName)) {
341                                throw new ConfigurationException("Detected duplicate field name '" + elementName + "' in type '" + declaringClass.getCanonicalName() + "'");
342                        }
343
344                        Class<?> nextElementType = next.getElementType();
345
346                        BaseRuntimeDeclaredChildDefinition def;
347                        if (childAnnotation.name().equals("extension") && IBaseExtension.class.isAssignableFrom(nextElementType)) {
348                                def = new RuntimeChildExtension(nextField, childAnnotation.name(), childAnnotation, descriptionAnnotation);
349                        } else if (childAnnotation.name().equals("modifierExtension") && IBaseExtension.class.isAssignableFrom(nextElementType)) {
350                                def = new RuntimeChildExtension(nextField, childAnnotation.name(), childAnnotation, descriptionAnnotation);
351                        } else if (BaseContainedDt.class.isAssignableFrom(nextElementType) || (childAnnotation.name().equals("contained") && IBaseResource.class.isAssignableFrom(nextElementType))) {
352                                /*
353                                 * Child is contained resources
354                                 */
355                                def = new RuntimeChildContainedResources(nextField, childAnnotation, descriptionAnnotation, elementName);
356                        } else if (IAnyResource.class.isAssignableFrom(nextElementType) || IResource.class.equals(nextElementType)) {
357                                /*
358                                 * Child is a resource as a direct child, as in Bundle.entry.resource
359                                 */
360                                def = new RuntimeChildDirectResource(nextField, childAnnotation, descriptionAnnotation, elementName);
361                        } else {
362                                childIsChoiceType |= choiceTypes.size() > 1;
363                                if (extensionAttr == null && childIsChoiceType && !BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) && !IBaseReference.class.isAssignableFrom(nextElementType)) {
364                                        def = new RuntimeChildChoiceDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, choiceTypes);
365                                } else if (extensionAttr != null) {
366                                        /*
367                                         * Child is an extension
368                                         */
369                                        Class<? extends IBase> et = (Class<? extends IBase>) nextElementType;
370
371                                        Object binder = null;
372                                        if (BoundCodeDt.class.isAssignableFrom(nextElementType) || IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
373                                                binder = ModelScanner.getBoundCodeBinder(nextField);
374                                        }
375
376                                        def = new RuntimeChildDeclaredExtensionDefinition(nextField, childAnnotation, descriptionAnnotation, extensionAttr, elementName, extensionAttr.url(), et,                                                       binder);
377
378                                        if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
379                                                ((RuntimeChildDeclaredExtensionDefinition)def).setEnumerationType(ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(nextField));
380                                        }
381                                } else if (BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) || IBaseReference.class.isAssignableFrom(nextElementType)) {
382                                        /*
383                                         * Child is a resource reference
384                                         */
385                                        List<Class<? extends IBaseResource>> refTypesList = new ArrayList<Class<? extends IBaseResource>>();
386                                        for (Class<? extends IElement> nextType : childAnnotation.type()) {
387                                                if (IBaseReference.class.isAssignableFrom(nextType)) {
388                                                        refTypesList.add(myContext.getVersion().getVersion().isRi() ? IAnyResource.class : IResource.class);
389                                                        continue;
390                                                } else if (IBaseResource.class.isAssignableFrom(nextType) == false) {
391                                                        throw new ConfigurationException("Field '" + nextField.getName() + "' in class '" + nextField.getDeclaringClass().getCanonicalName() + "' is of type " + BaseResourceReferenceDt.class + " but contains a non-resource type: " + nextType.getCanonicalName());
392                                                }
393                                                refTypesList.add((Class<? extends IBaseResource>) nextType);
394                                        }
395                                        def = new RuntimeChildResourceDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, refTypesList);
396
397                                } else if (IResourceBlock.class.isAssignableFrom(nextElementType) || IBaseBackboneElement.class.isAssignableFrom(nextElementType)
398                                                || IBaseDatatypeElement.class.isAssignableFrom(nextElementType)) {
399                                        /*
400                                         * Child is a resource block (i.e. a sub-tag within a resource) TODO: do these have a better name according to HL7?
401                                         */
402
403                                        Class<? extends IBase> blockDef = (Class<? extends IBase>) nextElementType;
404                                        def = new RuntimeChildResourceBlockDefinition(myContext, nextField, childAnnotation, descriptionAnnotation, elementName, blockDef);
405                                } else if (IDatatype.class.equals(nextElementType) || IElement.class.equals(nextElementType) || "Type".equals(nextElementType.getSimpleName())
406                                                || IBaseDatatype.class.equals(nextElementType)) {
407
408                                        def = new RuntimeChildAny(nextField, elementName, childAnnotation, descriptionAnnotation);
409                                } else if (IDatatype.class.isAssignableFrom(nextElementType) || IPrimitiveType.class.isAssignableFrom(nextElementType) || ICompositeType.class.isAssignableFrom(nextElementType)
410                                                || IBaseDatatype.class.isAssignableFrom(nextElementType) || IBaseExtension.class.isAssignableFrom(nextElementType)) {
411                                        Class<? extends IBase> nextDatatype = (Class<? extends IBase>) nextElementType;
412
413                                        if (IPrimitiveType.class.isAssignableFrom(nextElementType)) {
414                                                if (nextElementType.equals(BoundCodeDt.class)) {
415                                                        IValueSetEnumBinder<Enum<?>> binder = ModelScanner.getBoundCodeBinder(nextField);
416                                                        Class<? extends Enum<?>> enumType = ModelScanner.determineEnumTypeForBoundField(nextField);
417                                                        def = new RuntimeChildPrimitiveBoundCodeDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
418                                                } else if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
419                                                        Class<? extends Enum<?>> binderType = ModelScanner.determineEnumTypeForBoundField(nextField);
420                                                        def = new RuntimeChildPrimitiveEnumerationDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binderType);
421                                                } else {
422                                                        def = new RuntimeChildPrimitiveDatatypeDefinition(nextField, elementName, descriptionAnnotation, childAnnotation, nextDatatype);
423                                                }
424                                        } else {
425                                                if (IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
426                                                        IValueSetEnumBinder<Enum<?>> binder = ModelScanner.getBoundCodeBinder(nextField);
427                                                        Class<? extends Enum<?>> enumType = ModelScanner.determineEnumTypeForBoundField(nextField);
428                                                        def = new RuntimeChildCompositeBoundDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
429                                                } else if (BaseNarrativeDt.class.isAssignableFrom(nextElementType) || INarrative.class.isAssignableFrom(nextElementType)) {
430                                                        def = new RuntimeChildNarrativeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
431                                                } else {
432                                                        def = new RuntimeChildCompositeDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
433                                                }
434                                        }
435
436                                } else {
437                                        throw new ConfigurationException("Field '" + elementName + "' in type '" + declaringClass.getCanonicalName() + "' is not a valid child type: " + nextElementType);
438                                }
439
440                                Binding bindingAnnotation = ModelScanner.pullAnnotation(nextField, Binding.class);
441                                if (bindingAnnotation != null) {
442                                        if (isNotBlank(bindingAnnotation.valueSet())) {
443                                                def.setBindingValueSet(bindingAnnotation.valueSet());
444                                        }
445                                }
446                                
447                        }
448
449                        orderMap.put(order, def);
450                        elementNames.add(elementName);
451                }
452        }
453        @Override 
454        void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
455                if (mySealed) {
456                        return;
457                }
458                mySealed = true;
459
460                scanCompositeElementForChildren();
461                
462                super.sealAndInitialize(theContext, theClassToElementDefinitions);
463
464                for (BaseRuntimeChildDefinition next : myChildren) {
465                        next.sealAndInitialize(theContext, theClassToElementDefinitions);
466                }
467
468                myNameToChild = new HashMap<String, BaseRuntimeChildDefinition>();
469                for (BaseRuntimeChildDefinition next : myChildren) {    
470                        if (next instanceof RuntimeChildChoiceDefinition) {
471                                String key = ((RuntimeChildChoiceDefinition) next).getElementName()+"[x]";
472                                myNameToChild.put(key, next);
473                        }
474                        for (String nextName : next.getValidChildNames()) {
475                                if (myNameToChild.containsKey(nextName)) {
476                                        throw new ConfigurationException("Duplicate child name[" + nextName + "] in Element[" + getName() + "]");
477                                }
478                                myNameToChild.put(nextName, next);
479                        }
480                }
481
482                myChildren = Collections.unmodifiableList(myChildren);
483                myNameToChild = Collections.unmodifiableMap(myNameToChild);
484                
485                List<BaseRuntimeChildDefinition> children = new ArrayList<BaseRuntimeChildDefinition>();
486                children.addAll(myChildren);
487                
488                /*
489                 * Because of the way the type hierarchy works for DSTU2 resources,
490                 * things end up in the wrong order
491                 */
492                if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU2) {
493                        int extIndex = findIndex(children, "extension", false);
494                        int containedIndex = findIndex(children, "contained", false);
495                        if (containedIndex != -1 && extIndex != -1 && extIndex < containedIndex) {
496                                BaseRuntimeChildDefinition extension = children.remove(extIndex);
497                                if (containedIndex > children.size()) {
498                                        children.add(extension);
499                                } else {
500                                        children.add(containedIndex, extension);
501                                }
502                                int modIndex = findIndex(children, "modifierExtension", false);
503                                if (modIndex < containedIndex) {
504                                        extension = children.remove(modIndex);
505                                        if (containedIndex > children.size()) {
506                                                children.add(extension);
507                                        } else {
508                                                children.add(containedIndex, extension);
509                                        }
510                                }
511                        }
512                }
513                
514                /*
515                 * Add declared extensions alongside the undeclared ones
516                 */
517                if (getExtensionsNonModifier().isEmpty() == false) {
518                        children.addAll(findIndex(children, "extension", true), getExtensionsNonModifier());
519                }
520                if (getExtensionsModifier().isEmpty() == false) {
521                        children.addAll(findIndex(children, "modifierExtension", true), getExtensionsModifier());
522                }
523                
524                myChildrenAndExtensions=Collections.unmodifiableList(children);
525        }
526
527        
528        @Override
529        protected void validateSealed() {
530                if (!mySealed) {
531                        synchronized(myContext) {
532                                if(!mySealed) {
533                                        sealAndInitialize(myContext, myClassToElementDefinitions);
534                                }
535                        }
536                }
537        }
538
539        private static int findIndex(List<BaseRuntimeChildDefinition> theChildren, String theName, boolean theDefaultAtEnd) {
540                int index = theDefaultAtEnd ? theChildren.size() : -1;
541                for (ListIterator<BaseRuntimeChildDefinition> iter = theChildren.listIterator(); iter.hasNext(); ) {
542                        if (iter.next().getElementName().equals(theName)) {
543                                index = iter.previousIndex();
544                                break;
545                        }
546                }
547                return index;
548        }
549
550        private static class ScannedField {
551                private Child myChildAnnotation;
552
553                private List<Class<? extends IBase>> myChoiceTypes = new ArrayList<Class<? extends IBase>>();
554                private Class<?> myElementType;
555                private Field myField;
556                private boolean myFirstFieldInNewClass;
557                public ScannedField(Field theField, Class<?> theClass, boolean theFirstFieldInNewClass) {
558                        myField = theField;
559                        myFirstFieldInNewClass = theFirstFieldInNewClass;
560
561                        Child childAnnotation = ModelScanner.pullAnnotation(theField, Child.class);
562                        if (childAnnotation == null) {
563                                ourLog.trace("Ignoring non @Child field {} on target type {}", theField.getName(), theClass);
564                                return;
565                        }
566                        if (Modifier.isFinal(theField.getModifiers())) {
567                                ourLog.trace("Ignoring constant {} on target type {}", theField.getName(), theClass);
568                                return;
569                        }
570                        
571                        myChildAnnotation = childAnnotation;
572                        myElementType = ModelScanner.determineElementType(theField);
573                        
574                        for (Class<? extends IBase> nextChoiceType : childAnnotation.type()) {
575                                myChoiceTypes.add(nextChoiceType);
576                        }
577                }
578                
579                public Child getChildAnnotation() {
580                        return myChildAnnotation;
581                }
582
583                public List<Class<? extends IBase>> getChoiceTypes() {
584                        return myChoiceTypes;
585                }
586
587                public Class<?> getElementType() {
588                        return myElementType;
589                }
590
591                public Field getField() {
592                        return myField;
593                }
594
595                public boolean isFirstFieldInNewClass() {
596                        return myFirstFieldInNewClass;
597                }
598        }
599
600}