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}