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