ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition.java Source code

Java tutorial

Introduction

Here is the source code for ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition.java

Source

package ca.uhn.fhir.context;

import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/*
 * #%L
 * HAPI FHIR - Core Library
 * %%
 * Copyright (C) 2014 - 2016 University Health Network
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.instance.model.api.IPrimitiveType;

import ca.uhn.fhir.model.api.IBoundCodeableConcept;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.IResourceBlock;
import ca.uhn.fhir.model.api.IValueSetEnumBinder;
import ca.uhn.fhir.model.api.annotation.Binding;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ChildOrder;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.base.composite.BaseContainedDt;
import ca.uhn.fhir.model.base.composite.BaseNarrativeDt;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.util.ReflectionUtil;

public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase>
        extends BaseRuntimeElementDefinition<T> {

    private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory
            .getLogger(BaseRuntimeElementCompositeDefinition.class);
    private Map<String, Integer> forcedOrder = null;
    private List<BaseRuntimeChildDefinition> myChildren = new ArrayList<BaseRuntimeChildDefinition>();
    private List<BaseRuntimeChildDefinition> myChildrenAndExtensions;
    private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions;
    private FhirContext myContext;
    private Map<String, BaseRuntimeChildDefinition> myNameToChild = new HashMap<String, BaseRuntimeChildDefinition>();
    private List<ScannedField> myScannedFields = new ArrayList<BaseRuntimeElementCompositeDefinition.ScannedField>();
    private volatile boolean mySealed;

    @SuppressWarnings("unchecked")
    public BaseRuntimeElementCompositeDefinition(String theName, Class<? extends T> theImplementingClass,
            boolean theStandardType, FhirContext theContext,
            Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
        super(theName, theImplementingClass, theStandardType);

        myContext = theContext;
        myClassToElementDefinitions = theClassToElementDefinitions;

        /*
         * We scan classes for annotated fields in the class but also all of its superclasses
         */
        Class<? extends IBase> current = theImplementingClass;
        LinkedList<Class<? extends IBase>> classes = new LinkedList<Class<? extends IBase>>();
        do {
            if (forcedOrder == null) {
                ChildOrder childOrder = current.getAnnotation(ChildOrder.class);
                if (childOrder != null) {
                    forcedOrder = new HashMap<String, Integer>();
                    for (int i = 0; i < childOrder.names().length; i++) {
                        forcedOrder.put(childOrder.names()[i], i);
                    }
                }
            }
            classes.push(current);
            if (IBase.class.isAssignableFrom(current.getSuperclass())) {
                current = (Class<? extends IBase>) current.getSuperclass();
            } else {
                current = null;
            }
        } while (current != null);

        Set<Field> fields = new HashSet<Field>();
        for (Class<? extends IBase> nextClass : classes) {
            int fieldIndexInClass = 0;
            for (Field next : nextClass.getDeclaredFields()) {
                if (fields.add(next)) {
                    ScannedField scannedField = new ScannedField(next, theImplementingClass,
                            fieldIndexInClass == 0);
                    if (scannedField.getChildAnnotation() != null) {
                        myScannedFields.add(scannedField);
                        fieldIndexInClass++;
                    }
                }
            }
        }

    }

    void addChild(BaseRuntimeChildDefinition theNext) {
        if (theNext == null) {
            throw new NullPointerException();
        }
        if (theNext.getExtensionUrl() != null) {
            throw new IllegalArgumentException("Shouldn't haven an extension URL, use addExtension instead");
        }
        myChildren.add(theNext);
    }

    public BaseRuntimeChildDefinition getChildByName(String theName) {
        validateSealed();
        BaseRuntimeChildDefinition retVal = myNameToChild.get(theName);
        return retVal;
    }

    public BaseRuntimeChildDefinition getChildByNameOrThrowDataFormatException(String theName)
            throws DataFormatException {
        validateSealed();
        BaseRuntimeChildDefinition retVal = myNameToChild.get(theName);
        if (retVal == null) {
            throw new DataFormatException("Unknown child name '" + theName + "' in element " + getName()
                    + " - Valid names are: " + new TreeSet<String>(myNameToChild.keySet()));
        }
        return retVal;
    }

    public List<BaseRuntimeChildDefinition> getChildren() {
        validateSealed();
        return myChildren;
    }

    public List<BaseRuntimeChildDefinition> getChildrenAndExtension() {
        validateSealed();
        return myChildrenAndExtensions;
    }

    /**
     * Has this class been sealed
     */
    public boolean isSealed() {
        return mySealed;
    }

    @SuppressWarnings("unchecked")
    void populateScanAlso(Set<Class<? extends IBase>> theScanAlso) {
        for (ScannedField next : myScannedFields) {
            if (IBase.class.isAssignableFrom(next.getElementType())) {
                if (next.getElementType().isInterface() == false
                        && Modifier.isAbstract(next.getElementType().getModifiers()) == false) {
                    theScanAlso.add((Class<? extends IBase>) next.getElementType());
                }
            }
            for (Class<? extends IBase> nextChildType : next.getChoiceTypes()) {
                if (nextChildType.isInterface() == false
                        && Modifier.isAbstract(nextChildType.getModifiers()) == false) {
                    theScanAlso.add(nextChildType);
                }
            }
        }
    }

    private void scanCompositeElementForChildren() {
        Set<String> elementNames = new HashSet<String>();
        TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
        TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();

        scanCompositeElementForChildren(elementNames, orderToElementDef, orderToExtensionDef);

        if (forcedOrder != null) {
            /* 
             * Find out how many elements don't match any entry in the list
             * for forced order. Those elements come first.
             */
            TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> newOrderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
            int unknownCount = 0;
            for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
                if (!forcedOrder.containsKey(nextEntry.getElementName())) {
                    newOrderToExtensionDef.put(unknownCount, nextEntry);
                    unknownCount++;
                }
            }
            for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
                if (forcedOrder.containsKey(nextEntry.getElementName())) {
                    Integer newOrder = forcedOrder.get(nextEntry.getElementName());
                    newOrderToExtensionDef.put(newOrder + unknownCount, nextEntry);
                }
            }
            orderToElementDef = newOrderToExtensionDef;
        }

        // while (orderToElementDef.size() > 0 && orderToElementDef.firstKey() <
        // 0) {
        // BaseRuntimeDeclaredChildDefinition elementDef =
        // orderToElementDef.remove(orderToElementDef.firstKey());
        // if (elementDef.getElementName().equals("identifier")) {
        // orderToElementDef.put(theIdentifierOrder, elementDef);
        // } else {
        // throw new ConfigurationException("Don't know how to handle element: "
        // + elementDef.getElementName());
        // }
        // }

        TreeSet<Integer> orders = new TreeSet<Integer>();
        orders.addAll(orderToElementDef.keySet());
        orders.addAll(orderToExtensionDef.keySet());

        for (Integer i : orders) {
            BaseRuntimeChildDefinition nextChild = orderToElementDef.get(i);
            if (nextChild != null) {
                this.addChild(nextChild);
            }
            BaseRuntimeDeclaredChildDefinition nextExt = orderToExtensionDef.get(i);
            if (nextExt != null) {
                this.addExtension((RuntimeChildDeclaredExtensionDefinition) nextExt);
            }
        }

    }

    @SuppressWarnings("unchecked")
    private void scanCompositeElementForChildren(Set<String> elementNames,
            TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToElementDef,
            TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToExtensionDef) {
        int baseElementOrder = 0;

        for (ScannedField next : myScannedFields) {
            if (next.isFirstFieldInNewClass()) {
                baseElementOrder = theOrderToElementDef.isEmpty() ? 0
                        : theOrderToElementDef.lastEntry().getKey() + 1;
            }

            Class<?> declaringClass = next.getField().getDeclaringClass();

            Description descriptionAnnotation = ModelScanner.pullAnnotation(next.getField(), Description.class);

            TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderMap = theOrderToElementDef;
            Extension extensionAttr = ModelScanner.pullAnnotation(next.getField(), Extension.class);
            if (extensionAttr != null) {
                orderMap = theOrderToExtensionDef;
            }

            Child childAnnotation = next.getChildAnnotation();
            Field nextField = next.getField();
            String elementName = childAnnotation.name();
            int order = childAnnotation.order();
            boolean childIsChoiceType = false;
            boolean orderIsReplaceParent = false;

            if (order == Child.REPLACE_PARENT) {

                if (extensionAttr != null) {

                    for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
                        BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
                        if (nextDef instanceof RuntimeChildDeclaredExtensionDefinition) {
                            if (nextDef.getExtensionUrl().equals(extensionAttr.url())) {
                                orderIsReplaceParent = true;
                                order = nextEntry.getKey();
                                orderMap.remove(nextEntry.getKey());
                                elementNames.remove(elementName);
                                break;
                            }
                        }
                    }
                    if (order == Child.REPLACE_PARENT) {
                        throw new ConfigurationException("Field " + nextField.getName() + "' on target type "
                                + declaringClass.getSimpleName() + " has order() of REPLACE_PARENT ("
                                + Child.REPLACE_PARENT + ") but no parent element with extension URL "
                                + extensionAttr.url() + " could be found on type "
                                + nextField.getDeclaringClass().getSimpleName());
                    }

                } else {

                    for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
                        BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
                        if (elementName.equals(nextDef.getElementName())) {
                            orderIsReplaceParent = true;
                            order = nextEntry.getKey();
                            BaseRuntimeDeclaredChildDefinition existing = orderMap.remove(nextEntry.getKey());
                            elementNames.remove(elementName);

                            /*
                             * See #350 - If the original field (in the superclass) with the given name is a choice, then we need to make sure
                             * that the field which replaces is a choice even if it's only a choice of one type - this is because the
                             * element name when serialized still needs to reflect the datatype
                             */
                            if (existing instanceof RuntimeChildChoiceDefinition) {
                                childIsChoiceType = true;
                            }
                            break;
                        }
                    }
                    if (order == Child.REPLACE_PARENT) {
                        throw new ConfigurationException("Field " + nextField.getName() + "' on target type "
                                + declaringClass.getSimpleName() + " has order() of REPLACE_PARENT ("
                                + Child.REPLACE_PARENT + ") but no parent element with name " + elementName
                                + " could be found on type " + nextField.getDeclaringClass().getSimpleName());
                    }

                }

            }

            if (order < 0 && order != Child.ORDER_UNKNOWN) {
                throw new ConfigurationException("Invalid order '" + order + "' on @Child for field '"
                        + nextField.getName() + "' on target type: " + declaringClass);
            }

            if (order != Child.ORDER_UNKNOWN && !orderIsReplaceParent) {
                order = order + baseElementOrder;
            }
            // int min = childAnnotation.min();
            // int max = childAnnotation.max();

            /*
             * 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
             */
            if (order == Child.ORDER_UNKNOWN) {
                order = Integer.valueOf(0);
                while (orderMap.containsKey(order)) {
                    order++;
                }
            }

            List<Class<? extends IBase>> choiceTypes = next.getChoiceTypes();

            if (orderMap.containsKey(order)) {
                throw new ConfigurationException("Detected duplicate field order '" + childAnnotation.order()
                        + "' for element named '" + elementName + "' in type '" + declaringClass.getCanonicalName()
                        + "' - Already had: " + orderMap.get(order).getElementName());
            }

            if (elementNames.contains(elementName)) {
                throw new ConfigurationException("Detected duplicate field name '" + elementName + "' in type '"
                        + declaringClass.getCanonicalName() + "'");
            }

            Class<?> nextElementType = next.getElementType();

            BaseRuntimeDeclaredChildDefinition def;
            if (childAnnotation.name().equals("extension")
                    && IBaseExtension.class.isAssignableFrom(nextElementType)) {
                def = new RuntimeChildExtension(nextField, childAnnotation.name(), childAnnotation,
                        descriptionAnnotation);
            } else if (childAnnotation.name().equals("modifierExtension")
                    && IBaseExtension.class.isAssignableFrom(nextElementType)) {
                def = new RuntimeChildExtension(nextField, childAnnotation.name(), childAnnotation,
                        descriptionAnnotation);
            } else if (BaseContainedDt.class.isAssignableFrom(nextElementType)
                    || (childAnnotation.name().equals("contained")
                            && IBaseResource.class.isAssignableFrom(nextElementType))) {
                /*
                 * Child is contained resources
                 */
                def = new RuntimeChildContainedResources(nextField, childAnnotation, descriptionAnnotation,
                        elementName);
            } else if (IAnyResource.class.isAssignableFrom(nextElementType)
                    || IResource.class.equals(nextElementType)) {
                /*
                 * Child is a resource as a direct child, as in Bundle.entry.resource
                 */
                def = new RuntimeChildDirectResource(nextField, childAnnotation, descriptionAnnotation,
                        elementName);
            } else {
                childIsChoiceType |= choiceTypes.size() > 1;
                if (childIsChoiceType && !BaseResourceReferenceDt.class.isAssignableFrom(nextElementType)
                        && !IBaseReference.class.isAssignableFrom(nextElementType)) {
                    def = new RuntimeChildChoiceDefinition(nextField, elementName, childAnnotation,
                            descriptionAnnotation, choiceTypes);
                } else if (extensionAttr != null) {
                    /*
                     * Child is an extension
                     */
                    Class<? extends IBase> et = (Class<? extends IBase>) nextElementType;

                    Object binder = null;
                    if (BoundCodeDt.class.isAssignableFrom(nextElementType)
                            || IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
                        binder = ModelScanner.getBoundCodeBinder(nextField);
                    }

                    def = new RuntimeChildDeclaredExtensionDefinition(nextField, childAnnotation,
                            descriptionAnnotation, extensionAttr, elementName, extensionAttr.url(), et, binder);

                    if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
                        ((RuntimeChildDeclaredExtensionDefinition) def).setEnumerationType(
                                ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(nextField));
                    }
                } else if (BaseResourceReferenceDt.class.isAssignableFrom(nextElementType)
                        || IBaseReference.class.isAssignableFrom(nextElementType)) {
                    /*
                     * Child is a resource reference
                     */
                    List<Class<? extends IBaseResource>> refTypesList = new ArrayList<Class<? extends IBaseResource>>();
                    for (Class<? extends IElement> nextType : childAnnotation.type()) {
                        if (IBaseReference.class.isAssignableFrom(nextType)) {
                            refTypesList.add(myContext.getVersion().getVersion().isRi() ? IAnyResource.class
                                    : IResource.class);
                            continue;
                        } else if (IBaseResource.class.isAssignableFrom(nextType) == false) {
                            throw new ConfigurationException("Field '" + nextField.getName() + "' in class '"
                                    + nextField.getDeclaringClass().getCanonicalName() + "' is of type "
                                    + BaseResourceReferenceDt.class + " but contains a non-resource type: "
                                    + nextType.getCanonicalName());
                        }
                        refTypesList.add((Class<? extends IBaseResource>) nextType);
                    }
                    def = new RuntimeChildResourceDefinition(nextField, elementName, childAnnotation,
                            descriptionAnnotation, refTypesList);

                } else if (IResourceBlock.class.isAssignableFrom(nextElementType)
                        || IBaseBackboneElement.class.isAssignableFrom(nextElementType)
                        || IBaseDatatypeElement.class.isAssignableFrom(nextElementType)) {
                    /*
                     * Child is a resource block (i.e. a sub-tag within a resource) TODO: do these have a better name according to HL7?
                     */

                    Class<? extends IBase> blockDef = (Class<? extends IBase>) nextElementType;
                    def = new RuntimeChildResourceBlockDefinition(myContext, nextField, childAnnotation,
                            descriptionAnnotation, elementName, blockDef);
                } else if (IDatatype.class.equals(nextElementType) || IElement.class.equals(nextElementType)
                        || "Type".equals(nextElementType.getSimpleName())
                        || IBaseDatatype.class.equals(nextElementType)) {

                    def = new RuntimeChildAny(nextField, elementName, childAnnotation, descriptionAnnotation);
                } else if (IDatatype.class.isAssignableFrom(nextElementType)
                        || IPrimitiveType.class.isAssignableFrom(nextElementType)
                        || ICompositeType.class.isAssignableFrom(nextElementType)
                        || IBaseDatatype.class.isAssignableFrom(nextElementType)
                        || IBaseExtension.class.isAssignableFrom(nextElementType)) {
                    Class<? extends IBase> nextDatatype = (Class<? extends IBase>) nextElementType;

                    if (IPrimitiveType.class.isAssignableFrom(nextElementType)) {
                        if (nextElementType.equals(BoundCodeDt.class)) {
                            IValueSetEnumBinder<Enum<?>> binder = ModelScanner.getBoundCodeBinder(nextField);
                            Class<? extends Enum<?>> enumType = ModelScanner
                                    .determineEnumTypeForBoundField(nextField);
                            def = new RuntimeChildPrimitiveBoundCodeDatatypeDefinition(nextField, elementName,
                                    childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
                        } else if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
                            Class<? extends Enum<?>> binderType = ModelScanner
                                    .determineEnumTypeForBoundField(nextField);
                            def = new RuntimeChildPrimitiveEnumerationDatatypeDefinition(nextField, elementName,
                                    childAnnotation, descriptionAnnotation, nextDatatype, binderType);
                        } else {
                            def = new RuntimeChildPrimitiveDatatypeDefinition(nextField, elementName,
                                    descriptionAnnotation, childAnnotation, nextDatatype);
                        }
                    } else {
                        if (IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
                            IValueSetEnumBinder<Enum<?>> binder = ModelScanner.getBoundCodeBinder(nextField);
                            Class<? extends Enum<?>> enumType = ModelScanner
                                    .determineEnumTypeForBoundField(nextField);
                            def = new RuntimeChildCompositeBoundDatatypeDefinition(nextField, elementName,
                                    childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
                        } else if (BaseNarrativeDt.class.isAssignableFrom(nextElementType)
                                || INarrative.class.isAssignableFrom(nextElementType)) {
                            def = new RuntimeChildNarrativeDefinition(nextField, elementName, childAnnotation,
                                    descriptionAnnotation, nextDatatype);
                        } else {
                            def = new RuntimeChildCompositeDatatypeDefinition(nextField, elementName,
                                    childAnnotation, descriptionAnnotation, nextDatatype);
                        }
                    }

                } else {
                    throw new ConfigurationException(
                            "Field '" + elementName + "' in type '" + declaringClass.getCanonicalName()
                                    + "' is not a valid child type: " + nextElementType);
                }

                Binding bindingAnnotation = ModelScanner.pullAnnotation(nextField, Binding.class);
                if (bindingAnnotation != null) {
                    if (isNotBlank(bindingAnnotation.valueSet())) {
                        def.setBindingValueSet(bindingAnnotation.valueSet());
                    }
                }

            }

            orderMap.put(order, def);
            elementNames.add(elementName);
        }
    }

    @Override
    void sealAndInitialize(FhirContext theContext,
            Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
        if (mySealed) {
            return;
        }
        mySealed = true;

        scanCompositeElementForChildren();

        super.sealAndInitialize(theContext, theClassToElementDefinitions);

        for (BaseRuntimeChildDefinition next : myChildren) {
            next.sealAndInitialize(theContext, theClassToElementDefinitions);
        }

        myNameToChild = new HashMap<String, BaseRuntimeChildDefinition>();
        for (BaseRuntimeChildDefinition next : myChildren) {
            if (next instanceof RuntimeChildChoiceDefinition) {
                String key = ((RuntimeChildChoiceDefinition) next).getElementName() + "[x]";
                myNameToChild.put(key, next);
            }
            for (String nextName : next.getValidChildNames()) {
                if (myNameToChild.containsKey(nextName)) {
                    throw new ConfigurationException(
                            "Duplicate child name[" + nextName + "] in Element[" + getName() + "]");
                } else {
                    myNameToChild.put(nextName, next);
                }
            }
        }

        myChildren = Collections.unmodifiableList(myChildren);
        myNameToChild = Collections.unmodifiableMap(myNameToChild);

        List<BaseRuntimeChildDefinition> children = new ArrayList<BaseRuntimeChildDefinition>();
        children.addAll(myChildren);

        /*
         * Because of the way the type hierarchy works for DSTU2 resources,
         * things end up in the wrong order
         */
        if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU2) {
            int extIndex = findIndex(children, "extension", false);
            int containedIndex = findIndex(children, "contained", false);
            if (containedIndex != -1 && extIndex != -1 && extIndex < containedIndex) {
                BaseRuntimeChildDefinition extension = children.remove(extIndex);
                if (containedIndex > children.size()) {
                    children.add(extension);
                } else {
                    children.add(containedIndex, extension);
                }
                int modIndex = findIndex(children, "modifierExtension", false);
                if (modIndex < containedIndex) {
                    extension = children.remove(modIndex);
                    if (containedIndex > children.size()) {
                        children.add(extension);
                    } else {
                        children.add(containedIndex, extension);
                    }
                }
            }
        }

        /*
         * Add declared extensions alongside the undeclared ones
         */
        if (getExtensionsNonModifier().isEmpty() == false) {
            children.addAll(findIndex(children, "extension", true), getExtensionsNonModifier());
        }
        if (getExtensionsModifier().isEmpty() == false) {
            children.addAll(findIndex(children, "modifierExtension", true), getExtensionsModifier());
        }

        myChildrenAndExtensions = Collections.unmodifiableList(children);
    }

    @Override
    protected void validateSealed() {
        if (!mySealed) {
            synchronized (myContext) {
                sealAndInitialize(myContext, myClassToElementDefinitions);
            }
        }
    }

    private static int findIndex(List<BaseRuntimeChildDefinition> theChildren, String theName,
            boolean theDefaultAtEnd) {
        int index = theDefaultAtEnd ? theChildren.size() : -1;
        for (ListIterator<BaseRuntimeChildDefinition> iter = theChildren.listIterator(); iter.hasNext();) {
            if (iter.next().getElementName().equals(theName)) {
                index = iter.previousIndex();
                break;
            }
        }
        return index;
    }

    private static class ScannedField {
        private Child myChildAnnotation;

        private List<Class<? extends IBase>> myChoiceTypes = new ArrayList<Class<? extends IBase>>();
        private Class<?> myElementType;
        private Field myField;
        private boolean myFirstFieldInNewClass;

        public ScannedField(Field theField, Class<?> theClass, boolean theFirstFieldInNewClass) {
            myField = theField;
            myFirstFieldInNewClass = theFirstFieldInNewClass;

            Child childAnnotation = ModelScanner.pullAnnotation(theField, Child.class);
            if (childAnnotation == null) {
                ourLog.trace("Ignoring non @Child field {} on target type {}", theField.getName(), theClass);
                return;
            }
            if (Modifier.isFinal(theField.getModifiers())) {
                ourLog.trace("Ignoring constant {} on target type {}", theField.getName(), theClass);
                return;
            }

            myChildAnnotation = childAnnotation;
            myElementType = ModelScanner.determineElementType(theField);

            for (Class<? extends IBase> nextChoiceType : childAnnotation.type()) {
                myChoiceTypes.add(nextChoiceType);
            }
        }

        public Child getChildAnnotation() {
            return myChildAnnotation;
        }

        public List<Class<? extends IBase>> getChoiceTypes() {
            return myChoiceTypes;
        }

        public Class<?> getElementType() {
            return myElementType;
        }

        public Field getField() {
            return myField;
        }

        public boolean isFirstFieldInNewClass() {
            return myFirstFieldInNewClass;
        }
    }

}