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 - 2019 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 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 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 elementNames.remove(elementName); 297 298 /* 299 * See #350 - If the original field (in the superclass) with the given name is a choice, then we need to make sure 300 * that the field which replaces is a choice even if it's only a choice of one type - this is because the 301 * element name when serialized still needs to reflect the datatype 302 */ 303 if (existing instanceof RuntimeChildChoiceDefinition) { 304 childIsChoiceType = true; 305 } 306 break; 307 } 308 } 309 if (order == Child.REPLACE_PARENT) { 310 throw new ConfigurationException("Field " + nextField.getName() + "' on target type " + declaringClass.getSimpleName() + " has order() of REPLACE_PARENT (" + Child.REPLACE_PARENT 311 + ") but no parent element with name " + elementName + " could be found on type " + nextField.getDeclaringClass().getSimpleName()); 312 } 313 314 } 315 316 } 317 318 if (order < 0 && order != Child.ORDER_UNKNOWN) { 319 throw new ConfigurationException("Invalid order '" + order + "' on @Child for field '" + nextField.getName() + "' on target type: " + declaringClass); 320 } 321 322 if (order != Child.ORDER_UNKNOWN && !orderIsReplaceParent) { 323 order = order + baseElementOrder; 324 } 325 // int min = childAnnotation.min(); 326 // int max = childAnnotation.max(); 327 328 /* 329 * 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 330 */ 331 if (order == Child.ORDER_UNKNOWN) { 332 order = 0; 333 while (orderMap.containsKey(order)) { 334 order++; 335 } 336 } 337 338 List<Class<? extends IBase>> choiceTypes = next.getChoiceTypes(); 339 340 if (orderMap.containsKey(order)) { 341 throw new ConfigurationException("Detected duplicate field order '" + childAnnotation.order() + "' for element named '" + elementName + "' in type '" + declaringClass.getCanonicalName() + "' - Already had: " + orderMap.get(order).getElementName()); 342 } 343 344 if (elementNames.contains(elementName)) { 345 throw new ConfigurationException("Detected duplicate field name '" + elementName + "' in type '" + declaringClass.getCanonicalName() + "'"); 346 } 347 348 Class<?> nextElementType = next.getElementType(); 349 350 BaseRuntimeDeclaredChildDefinition def; 351 if (childAnnotation.name().equals("extension") && IBaseExtension.class.isAssignableFrom(nextElementType)) { 352 def = new RuntimeChildExtension(nextField, childAnnotation.name(), childAnnotation, descriptionAnnotation); 353 } else if (childAnnotation.name().equals("modifierExtension") && IBaseExtension.class.isAssignableFrom(nextElementType)) { 354 def = new RuntimeChildExtension(nextField, childAnnotation.name(), childAnnotation, descriptionAnnotation); 355 } else if (BaseContainedDt.class.isAssignableFrom(nextElementType) || (childAnnotation.name().equals("contained") && IBaseResource.class.isAssignableFrom(nextElementType))) { 356 /* 357 * Child is contained resources 358 */ 359 def = new RuntimeChildContainedResources(nextField, childAnnotation, descriptionAnnotation, elementName); 360 } else if (IAnyResource.class.isAssignableFrom(nextElementType) || IResource.class.equals(nextElementType)) { 361 /* 362 * Child is a resource as a direct child, as in Bundle.entry.resource 363 */ 364 def = new RuntimeChildDirectResource(nextField, childAnnotation, descriptionAnnotation, elementName); 365 } else { 366 childIsChoiceType |= choiceTypes.size() > 1; 367 if (extensionAttr == null && childIsChoiceType && !BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) && !IBaseReference.class.isAssignableFrom(nextElementType)) { 368 def = new RuntimeChildChoiceDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, choiceTypes); 369 } else if (extensionAttr != null) { 370 /* 371 * Child is an extension 372 */ 373 Class<? extends IBase> et = (Class<? extends IBase>) nextElementType; 374 375 Object binder = null; 376 if (BoundCodeDt.class.isAssignableFrom(nextElementType) || IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) { 377 binder = ModelScanner.getBoundCodeBinder(nextField); 378 } 379 380 def = new RuntimeChildDeclaredExtensionDefinition(nextField, childAnnotation, descriptionAnnotation, extensionAttr, elementName, extensionAttr.url(), et, binder); 381 382 if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) { 383 ((RuntimeChildDeclaredExtensionDefinition)def).setEnumerationType(ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(nextField)); 384 } 385 } else if (BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) || IBaseReference.class.isAssignableFrom(nextElementType)) { 386 /* 387 * Child is a resource reference 388 */ 389 List<Class<? extends IBaseResource>> refTypesList = new ArrayList<>(); 390 for (Class<? extends IElement> nextType : childAnnotation.type()) { 391 if (IBaseReference.class.isAssignableFrom(nextType)) { 392 refTypesList.add(myContext.getVersion().getVersion().isRi() ? IAnyResource.class : IResource.class); 393 continue; 394 } else if (IBaseResource.class.isAssignableFrom(nextType) == false) { 395 throw new ConfigurationException("Field '" + nextField.getName() + "' in class '" + nextField.getDeclaringClass().getCanonicalName() + "' is of type " + BaseResourceReferenceDt.class + " but contains a non-resource type: " + nextType.getCanonicalName()); 396 } 397 refTypesList.add((Class<? extends IBaseResource>) nextType); 398 } 399 def = new RuntimeChildResourceDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, refTypesList); 400 401 } else if (IResourceBlock.class.isAssignableFrom(nextElementType) || IBaseBackboneElement.class.isAssignableFrom(nextElementType) 402 || IBaseDatatypeElement.class.isAssignableFrom(nextElementType)) { 403 /* 404 * Child is a resource block (i.e. a sub-tag within a resource) TODO: do these have a better name according to HL7? 405 */ 406 407 Class<? extends IBase> blockDef = (Class<? extends IBase>) nextElementType; 408 def = new RuntimeChildResourceBlockDefinition(myContext, nextField, childAnnotation, descriptionAnnotation, elementName, blockDef); 409 } else if (IDatatype.class.equals(nextElementType) || IElement.class.equals(nextElementType) || "Type".equals(nextElementType.getSimpleName()) 410 || IBaseDatatype.class.equals(nextElementType)) { 411 412 def = new RuntimeChildAny(nextField, elementName, childAnnotation, descriptionAnnotation); 413 } else if (IDatatype.class.isAssignableFrom(nextElementType) || IPrimitiveType.class.isAssignableFrom(nextElementType) || ICompositeType.class.isAssignableFrom(nextElementType) 414 || IBaseDatatype.class.isAssignableFrom(nextElementType) || IBaseExtension.class.isAssignableFrom(nextElementType)) { 415 Class<? extends IBase> nextDatatype = (Class<? extends IBase>) nextElementType; 416 417 if (IPrimitiveType.class.isAssignableFrom(nextElementType)) { 418 if (nextElementType.equals(BoundCodeDt.class)) { 419 IValueSetEnumBinder<Enum<?>> binder = ModelScanner.getBoundCodeBinder(nextField); 420 Class<? extends Enum<?>> enumType = ModelScanner.determineEnumTypeForBoundField(nextField); 421 def = new RuntimeChildPrimitiveBoundCodeDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType); 422 } else if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) { 423 Class<? extends Enum<?>> binderType = ModelScanner.determineEnumTypeForBoundField(nextField); 424 def = new RuntimeChildPrimitiveEnumerationDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binderType); 425 } else { 426 def = new RuntimeChildPrimitiveDatatypeDefinition(nextField, elementName, descriptionAnnotation, childAnnotation, nextDatatype); 427 } 428 } else { 429 if (IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) { 430 IValueSetEnumBinder<Enum<?>> binder = ModelScanner.getBoundCodeBinder(nextField); 431 Class<? extends Enum<?>> enumType = ModelScanner.determineEnumTypeForBoundField(nextField); 432 def = new RuntimeChildCompositeBoundDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType); 433 } else if (BaseNarrativeDt.class.isAssignableFrom(nextElementType) || INarrative.class.isAssignableFrom(nextElementType)) { 434 def = new RuntimeChildNarrativeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype); 435 } else { 436 def = new RuntimeChildCompositeDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype); 437 } 438 } 439 440 } else { 441 throw new ConfigurationException("Field '" + elementName + "' in type '" + declaringClass.getCanonicalName() + "' is not a valid child type: " + nextElementType); 442 } 443 444 Binding bindingAnnotation = ModelScanner.pullAnnotation(nextField, Binding.class); 445 if (bindingAnnotation != null) { 446 if (isNotBlank(bindingAnnotation.valueSet())) { 447 def.setBindingValueSet(bindingAnnotation.valueSet()); 448 } 449 } 450 451 } 452 453 orderMap.put(order, def); 454 elementNames.add(elementName); 455 } 456 } 457 @Override 458 void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { 459 if (mySealed) { 460 return; 461 } 462 mySealed = true; 463 464 scanCompositeElementForChildren(); 465 466 super.sealAndInitialize(theContext, theClassToElementDefinitions); 467 468 for (BaseRuntimeChildDefinition next : myChildren) { 469 next.sealAndInitialize(theContext, theClassToElementDefinitions); 470 } 471 472 myNameToChild = new HashMap<>(); 473 for (BaseRuntimeChildDefinition next : myChildren) { 474 if (next instanceof RuntimeChildChoiceDefinition) { 475 String key = next.getElementName()+"[x]"; 476 myNameToChild.put(key, next); 477 } 478 for (String nextName : next.getValidChildNames()) { 479 if (myNameToChild.containsKey(nextName)) { 480 throw new ConfigurationException("Duplicate child name[" + nextName + "] in Element[" + getName() + "]"); 481 } 482 myNameToChild.put(nextName, next); 483 } 484 } 485 486 myChildren = Collections.unmodifiableList(myChildren); 487 myNameToChild = Collections.unmodifiableMap(myNameToChild); 488 489 List<BaseRuntimeChildDefinition> children = new ArrayList<>(); 490 children.addAll(myChildren); 491 492 /* 493 * Because of the way the type hierarchy works for DSTU2 resources, 494 * things end up in the wrong order 495 */ 496 if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU2) { 497 int extIndex = findIndex(children, "extension", false); 498 int containedIndex = findIndex(children, "contained", false); 499 if (containedIndex != -1 && extIndex != -1 && extIndex < containedIndex) { 500 BaseRuntimeChildDefinition extension = children.remove(extIndex); 501 if (containedIndex > children.size()) { 502 children.add(extension); 503 } else { 504 children.add(containedIndex, extension); 505 } 506 int modIndex = findIndex(children, "modifierExtension", false); 507 if (modIndex < containedIndex) { 508 extension = children.remove(modIndex); 509 if (containedIndex > children.size()) { 510 children.add(extension); 511 } else { 512 children.add(containedIndex, extension); 513 } 514 } 515 } 516 } 517 518 /* 519 * Add declared extensions alongside the undeclared ones 520 */ 521 if (getExtensionsNonModifier().isEmpty() == false) { 522 children.addAll(findIndex(children, "extension", true), getExtensionsNonModifier()); 523 } 524 if (getExtensionsModifier().isEmpty() == false) { 525 children.addAll(findIndex(children, "modifierExtension", true), getExtensionsModifier()); 526 } 527 528 myChildrenAndExtensions=Collections.unmodifiableList(children); 529 } 530 531 532 @Override 533 protected void validateSealed() { 534 if (!mySealed) { 535 synchronized(myContext) { 536 if(!mySealed) { 537 sealAndInitialize(myContext, myClassToElementDefinitions); 538 } 539 } 540 } 541 } 542 543 private static int findIndex(List<BaseRuntimeChildDefinition> theChildren, String theName, boolean theDefaultAtEnd) { 544 int index = theDefaultAtEnd ? theChildren.size() : -1; 545 for (ListIterator<BaseRuntimeChildDefinition> iter = theChildren.listIterator(); iter.hasNext(); ) { 546 if (iter.next().getElementName().equals(theName)) { 547 index = iter.previousIndex(); 548 break; 549 } 550 } 551 return index; 552 } 553 554 private static class ScannedField { 555 private Child myChildAnnotation; 556 557 private List<Class<? extends IBase>> myChoiceTypes = new ArrayList<>(); 558 private Class<?> myElementType; 559 private Field myField; 560 private boolean myFirstFieldInNewClass; 561 ScannedField(Field theField, Class<?> theClass, boolean theFirstFieldInNewClass) { 562 myField = theField; 563 myFirstFieldInNewClass = theFirstFieldInNewClass; 564 565 Child childAnnotation = ModelScanner.pullAnnotation(theField, Child.class); 566 if (childAnnotation == null) { 567 ourLog.trace("Ignoring non @Child field {} on target type {}", theField.getName(), theClass); 568 return; 569 } 570 if (Modifier.isFinal(theField.getModifiers())) { 571 ourLog.trace("Ignoring constant {} on target type {}", theField.getName(), theClass); 572 return; 573 } 574 575 myChildAnnotation = childAnnotation; 576 myElementType = ModelScanner.determineElementType(theField); 577 578 Collections.addAll(myChoiceTypes, childAnnotation.type()); 579 } 580 581 public Child getChildAnnotation() { 582 return myChildAnnotation; 583 } 584 585 public List<Class<? extends IBase>> getChoiceTypes() { 586 return myChoiceTypes; 587 } 588 589 public Class<?> getElementType() { 590 return myElementType; 591 } 592 593 public Field getField() { 594 return myField; 595 } 596 597 public boolean isFirstFieldInNewClass() { 598 return myFirstFieldInNewClass; 599 } 600 } 601 602}