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