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