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