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