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