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