001 package de.deepamehta.core.impl; 002 003 import de.deepamehta.core.AssociationDefinition; 004 import de.deepamehta.core.ChildTopics; 005 import de.deepamehta.core.RelatedTopic; 006 import de.deepamehta.core.Topic; 007 import de.deepamehta.core.model.ChildTopicsModel; 008 import de.deepamehta.core.model.DeepaMehtaObjectModel; 009 import de.deepamehta.core.model.RelatedTopicModel; 010 import de.deepamehta.core.model.SimpleValue; 011 import de.deepamehta.core.model.TopicDeletionModel; 012 import de.deepamehta.core.model.TopicModel; 013 import de.deepamehta.core.model.TopicReferenceModel; 014 015 import java.util.ArrayList; 016 import java.util.HashMap; 017 import java.util.Iterator; 018 import java.util.List; 019 import java.util.Map; 020 import java.util.logging.Logger; 021 022 023 024 /** 025 * A child topics model that is attached to the DB. 026 */ 027 class AttachedChildTopics implements ChildTopics { 028 029 // ---------------------------------------------------------------------------------------------- Instance Variables 030 031 private ChildTopicsModel model; // underlying model 032 033 private AttachedDeepaMehtaObject parent; // attached object cache 034 035 /** 036 * Attached object cache. 037 * Key: child type URI (String), value: RelatedTopic or List<RelatedTopic> 038 */ 039 private Map<String, Object> childTopics = new HashMap(); // attached object cache 040 041 private EmbeddedService dms; 042 043 private Logger logger = Logger.getLogger(getClass().getName()); 044 045 // ---------------------------------------------------------------------------------------------------- Constructors 046 047 AttachedChildTopics(ChildTopicsModel model, AttachedDeepaMehtaObject parent, EmbeddedService dms) { 048 this.model = model; 049 this.parent = parent; 050 this.dms = dms; 051 initAttachedObjectCache(); 052 } 053 054 // -------------------------------------------------------------------------------------------------- Public Methods 055 056 057 058 // ********************************** 059 // *** ChildTopics Implementation *** 060 // ********************************** 061 062 063 064 // === Accessors === 065 066 @Override 067 public RelatedTopic getTopic(String childTypeUri) { 068 loadChildTopics(childTypeUri); 069 return _getTopic(childTypeUri); 070 } 071 072 @Override 073 public List<RelatedTopic> getTopics(String childTypeUri) { 074 loadChildTopics(childTypeUri); 075 return _getTopics(childTypeUri); 076 } 077 078 // --- 079 080 @Override 081 public Object get(String childTypeUri) { 082 return childTopics.get(childTypeUri); 083 } 084 085 @Override 086 public boolean has(String childTypeUri) { 087 return childTopics.containsKey(childTypeUri); 088 } 089 090 @Override 091 public int size() { 092 return childTopics.size(); 093 } 094 095 // --- 096 097 @Override 098 public ChildTopicsModel getModel() { 099 return model; 100 } 101 102 103 104 // === Convenience Accessors === 105 106 @Override 107 public String getString(String childTypeUri) { 108 return getTopic(childTypeUri).getSimpleValue().toString(); 109 } 110 111 @Override 112 public int getInt(String childTypeUri) { 113 return getTopic(childTypeUri).getSimpleValue().intValue(); 114 } 115 116 @Override 117 public long getLong(String childTypeUri) { 118 return getTopic(childTypeUri).getSimpleValue().longValue(); 119 } 120 121 @Override 122 public double getDouble(String childTypeUri) { 123 return getTopic(childTypeUri).getSimpleValue().doubleValue(); 124 } 125 126 @Override 127 public boolean getBoolean(String childTypeUri) { 128 return getTopic(childTypeUri).getSimpleValue().booleanValue(); 129 } 130 131 @Override 132 public Object getObject(String childTypeUri) { 133 return getTopic(childTypeUri).getSimpleValue().value(); 134 } 135 136 // --- 137 138 @Override 139 public ChildTopics getChildTopics(String childTypeUri) { 140 return getTopic(childTypeUri).getChildTopics(); 141 } 142 143 // Note: there are no convenience accessors for a multiple-valued child. 144 145 146 147 // === Manipulators === 148 149 // --- Single-valued Childs --- 150 151 @Override 152 public ChildTopics set(String childTypeUri, TopicModel value) { 153 return _updateOne(childTypeUri, new RelatedTopicModel(value)); 154 } 155 156 // --- 157 158 @Override 159 public ChildTopics set(String childTypeUri, Object value) { 160 return _updateOne(childTypeUri, new RelatedTopicModel(childTypeUri, new SimpleValue(value))); 161 } 162 163 @Override 164 public ChildTopics set(String childTypeUri, ChildTopicsModel value) { 165 return _updateOne(childTypeUri, new RelatedTopicModel(childTypeUri, value)); 166 } 167 168 // --- 169 170 @Override 171 public ChildTopics setRef(String childTypeUri, long refTopicId) { 172 return _updateOne(childTypeUri, new TopicReferenceModel(refTopicId)); 173 } 174 175 @Override 176 public ChildTopics setRef(String childTypeUri, long refTopicId, ChildTopicsModel relatingAssocChildTopics) { 177 return _updateOne(childTypeUri, new TopicReferenceModel(refTopicId, relatingAssocChildTopics)); 178 } 179 180 @Override 181 public ChildTopics setRef(String childTypeUri, String refTopicUri) { 182 return _updateOne(childTypeUri, new TopicReferenceModel(refTopicUri)); 183 } 184 185 @Override 186 public ChildTopics setRef(String childTypeUri, String refTopicUri, ChildTopicsModel relatingAssocChildTopics) { 187 return _updateOne(childTypeUri, new TopicReferenceModel(refTopicUri, relatingAssocChildTopics)); 188 } 189 190 // --- 191 192 @Override 193 public ChildTopics setDeletionRef(String childTypeUri, long refTopicId) { 194 return _updateOne(childTypeUri, new TopicDeletionModel(refTopicId)); 195 } 196 197 @Override 198 public ChildTopics setDeletionRef(String childTypeUri, String refTopicUri) { 199 return _updateOne(childTypeUri, new TopicDeletionModel(refTopicUri)); 200 } 201 202 // --- Multiple-valued Childs --- 203 204 @Override 205 public ChildTopics add(String childTypeUri, TopicModel value) { 206 return _updateMany(childTypeUri, new RelatedTopicModel(value)); 207 } 208 209 // --- 210 211 @Override 212 public ChildTopics add(String childTypeUri, Object value) { 213 return _updateMany(childTypeUri, new RelatedTopicModel(childTypeUri, new SimpleValue(value))); 214 } 215 216 @Override 217 public ChildTopics add(String childTypeUri, ChildTopicsModel value) { 218 return _updateMany(childTypeUri, new RelatedTopicModel(childTypeUri, value)); 219 } 220 221 // --- 222 223 @Override 224 public ChildTopics addRef(String childTypeUri, long refTopicId) { 225 return _updateMany(childTypeUri, new TopicReferenceModel(refTopicId)); 226 } 227 228 @Override 229 public ChildTopics addRef(String childTypeUri, long refTopicId, ChildTopicsModel relatingAssocChildTopics) { 230 return _updateMany(childTypeUri, new TopicReferenceModel(refTopicId, relatingAssocChildTopics)); 231 } 232 233 @Override 234 public ChildTopics addRef(String childTypeUri, String refTopicUri) { 235 return _updateMany(childTypeUri, new TopicReferenceModel(refTopicUri)); 236 } 237 238 @Override 239 public ChildTopics addRef(String childTypeUri, String refTopicUri, ChildTopicsModel relatingAssocChildTopics) { 240 return _updateMany(childTypeUri, new TopicReferenceModel(refTopicUri, relatingAssocChildTopics)); 241 } 242 243 // --- 244 245 @Override 246 public ChildTopics addDeletionRef(String childTypeUri, long refTopicId) { 247 return _updateMany(childTypeUri, new TopicDeletionModel(refTopicId)); 248 } 249 250 @Override 251 public ChildTopics addDeletionRef(String childTypeUri, String refTopicUri) { 252 return _updateMany(childTypeUri, new TopicDeletionModel(refTopicUri)); 253 } 254 255 256 257 // === Iterable Implementation === 258 259 @Override 260 public Iterator<String> iterator() { 261 return childTopics.keySet().iterator(); 262 } 263 264 265 266 // ----------------------------------------------------------------------------------------- Package Private Methods 267 268 void update(ChildTopicsModel newComp) { 269 try { 270 for (AssociationDefinition assocDef : parent.getType().getAssocDefs()) { 271 String childTypeUri = assocDef.getChildTypeUri(); 272 String cardinalityUri = assocDef.getChildCardinalityUri(); 273 RelatedTopicModel newChildTopic = null; // only used for "one" 274 List<RelatedTopicModel> newChildTopics = null; // only used for "many" 275 if (cardinalityUri.equals("dm4.core.one")) { 276 newChildTopic = newComp.getTopic(childTypeUri, null); // defaultValue=null 277 // skip if not contained in update request 278 if (newChildTopic == null) { 279 continue; 280 } 281 } else if (cardinalityUri.equals("dm4.core.many")) { 282 newChildTopics = newComp.getTopics(childTypeUri, null); // defaultValue=null 283 // skip if not contained in update request 284 if (newChildTopics == null) { 285 continue; 286 } 287 } else { 288 throw new RuntimeException("\"" + cardinalityUri + "\" is an unexpected cardinality URI"); 289 } 290 // 291 updateChildTopics(newChildTopic, newChildTopics, assocDef); 292 } 293 // 294 refreshParentLabel(); 295 // 296 } catch (Exception e) { 297 throw new RuntimeException("Updating the child topics of " + parent.className() + " " + parent.getId() + 298 " failed (newComp=" + newComp + ")", e); 299 } 300 } 301 302 // Note: the given association definition must not necessarily originate from the parent object's type definition. 303 // It may originate from a facet definition as well. 304 // Called from AttachedDeepaMehtaObject.updateChildTopic() and AttachedDeepaMehtaObject.updateChildTopics(). 305 void updateChildTopics(RelatedTopicModel newChildTopic, List<RelatedTopicModel> newChildTopics, 306 AssociationDefinition assocDef) { 307 // Note: updating the child topics requires them to be loaded 308 loadChildTopics(assocDef); 309 // 310 String assocTypeUri = assocDef.getTypeUri(); 311 boolean one = newChildTopic != null; 312 if (assocTypeUri.equals("dm4.core.composition_def")) { 313 if (one) { 314 updateCompositionOne(newChildTopic, assocDef); 315 } else { 316 updateCompositionMany(newChildTopics, assocDef); 317 } 318 } else if (assocTypeUri.equals("dm4.core.aggregation_def")) { 319 if (one) { 320 updateAggregationOne(newChildTopic, assocDef); 321 } else { 322 updateAggregationMany(newChildTopics, assocDef); 323 } 324 } else { 325 throw new RuntimeException("Association type \"" + assocTypeUri + "\" not supported"); 326 } 327 } 328 329 // --- 330 331 void loadChildTopics() { 332 for (AssociationDefinition assocDef : parent.getType().getAssocDefs()) { 333 loadChildTopics(assocDef); 334 } 335 } 336 337 void loadChildTopics(String childTypeUri) { 338 loadChildTopics(getAssocDef(childTypeUri)); 339 } 340 341 342 343 // ------------------------------------------------------------------------------------------------- Private Methods 344 345 /** 346 * Recursively loads child topics (model) and updates this attached object cache accordingly. 347 * If the child topics are loaded already nothing is performed. 348 * 349 * @param assocDef the child topics according to this association definition are loaded. 350 * <p> 351 * Note: the association definition must not necessarily originate from the parent object's 352 * type definition. It may originate from a facet definition as well. 353 */ 354 private void loadChildTopics(AssociationDefinition assocDef) { 355 String childTypeUri = assocDef.getChildTypeUri(); 356 if (!has(childTypeUri)) { 357 logger.fine("### Lazy-loading \"" + childTypeUri + "\" child topic(s) of " + parent.className() + " " + 358 parent.getId()); 359 dms.valueStorage.fetchChildTopics(parent.getModel(), assocDef.getModel()); 360 initAttachedObjectCache(childTypeUri); 361 } 362 } 363 364 private void refreshParentLabel() { 365 DeepaMehtaObjectModel parent = this.parent.getModel(); 366 // 367 for (String childTypeUri : dms.valueStorage.getLabelChildTypeUris(parent)) { 368 loadChildTopics(childTypeUri); 369 } 370 // 371 dms.valueStorage.refreshLabel(parent); 372 } 373 374 // --- 375 376 // Note 1: we need to explicitly declare the arg as RelatedTopicModel. When declared as TopicModel instead the 377 // JVM would invoke the ChildTopicsModel's put()/add() which takes a TopicModel object even if at runtime a 378 // RelatedTopicModel or even a TopicReferenceModel is passed. This is because Java method overloading involves 379 // no dynamic dispatch. See the methodOverloading tests in JavaAPITest.java (in module dm4-test). 380 381 // Note 2: calling parent.update(..) would not work. The JVM would call the update() method of the base class 382 // (AttachedDeepaMehtaObject), not the subclass's update() method. This is related to Java's (missing) multiple 383 // dispatch. Note that 2 inheritance hierarchies are involved here: the DM object hierarchy and the DM model 384 // hierarchy. See the missingMultipleDispatch tests in JavaAPITest.java (in module dm4-test). 385 386 private ChildTopics _updateOne(String childTypeUri, RelatedTopicModel newChildTopic) { 387 parent.updateChildTopics(new ChildTopicsModel().put(childTypeUri, newChildTopic)); 388 return this; 389 } 390 391 private ChildTopics _updateMany(String childTypeUri, RelatedTopicModel newChildTopic) { 392 parent.updateChildTopics(new ChildTopicsModel().add(childTypeUri, newChildTopic)); 393 return this; 394 } 395 396 397 398 // === Update Child Topics === 399 400 // --- Composition --- 401 402 private void updateCompositionOne(RelatedTopicModel newChildTopic, AssociationDefinition assocDef) { 403 RelatedTopic childTopic = _getTopic(assocDef.getChildTypeUri(), null); 404 // Note: for cardinality one the simple request format is sufficient. The child's topic ID is not required. 405 // ### TODO: possibly sanity check: if child's topic ID *is* provided it must match with the fetched topic. 406 if (newChildTopic instanceof TopicDeletionModel) { 407 deleteChildTopicOne(childTopic, assocDef, true); // deleteChild=true 408 } else if (newChildTopic instanceof TopicReferenceModel) { 409 createAssignmentOne(childTopic, (TopicReferenceModel) newChildTopic, assocDef, true); // deleteChild=true 410 } else if (childTopic != null) { 411 updateRelatedTopic(childTopic, newChildTopic); 412 } else { 413 createChildTopicOne(newChildTopic, assocDef); 414 } 415 } 416 417 private void updateCompositionMany(List<RelatedTopicModel> newChildTopics, AssociationDefinition assocDef) { 418 for (RelatedTopicModel newChildTopic : newChildTopics) { 419 long childTopicId = newChildTopic.getId(); 420 if (newChildTopic instanceof TopicDeletionModel) { 421 deleteChildTopicMany(childTopicId, assocDef, true); // deleteChild=true 422 } else if (newChildTopic instanceof TopicReferenceModel) { 423 createAssignmentMany((TopicReferenceModel) newChildTopic, assocDef); 424 } else if (childTopicId != -1) { 425 updateChildTopicMany(newChildTopic, assocDef); 426 } else { 427 createChildTopicMany(newChildTopic, assocDef); 428 } 429 } 430 } 431 432 // --- Aggregation --- 433 434 private void updateAggregationOne(RelatedTopicModel newChildTopic, AssociationDefinition assocDef) { 435 RelatedTopic childTopic = _getTopic(assocDef.getChildTypeUri(), null); 436 // ### TODO: possibly sanity check: if child's topic ID *is* provided it must match with the fetched topic. 437 if (newChildTopic instanceof TopicDeletionModel) { 438 deleteChildTopicOne(childTopic, assocDef, false); // deleteChild=false 439 } else if (newChildTopic instanceof TopicReferenceModel) { 440 createAssignmentOne(childTopic, (TopicReferenceModel) newChildTopic, assocDef, false); // deleteChild=false 441 } else if (newChildTopic.getId() != -1) { 442 updateChildTopicOne(newChildTopic, assocDef); 443 } else { 444 if (childTopic != null) { 445 childTopic.getRelatingAssociation().delete(); 446 } 447 createChildTopicOne(newChildTopic, assocDef); 448 } 449 } 450 451 private void updateAggregationMany(List<RelatedTopicModel> newChildTopics, AssociationDefinition assocDef) { 452 for (RelatedTopicModel newChildTopic : newChildTopics) { 453 long childTopicId = newChildTopic.getId(); 454 if (newChildTopic instanceof TopicDeletionModel) { 455 deleteChildTopicMany(childTopicId, assocDef, false); // deleteChild=false 456 } else if (newChildTopic instanceof TopicReferenceModel) { 457 createAssignmentMany((TopicReferenceModel) newChildTopic, assocDef); 458 } else if (childTopicId != -1) { 459 updateChildTopicMany(newChildTopic, assocDef); 460 } else { 461 createChildTopicMany(newChildTopic, assocDef); 462 } 463 } 464 } 465 466 // --- Update --- 467 468 private void updateChildTopicOne(RelatedTopicModel newChildTopic, AssociationDefinition assocDef) { 469 RelatedTopic childTopic = _getTopic(assocDef.getChildTypeUri(), null); 470 // 471 if (childTopic == null || childTopic.getId() != newChildTopic.getId()) { 472 throw new RuntimeException("Topic " + newChildTopic.getId() + " is not a child of " + 473 parent.className() + " " + parent.getId() + " according to " + assocDef); 474 } 475 // 476 updateRelatedTopic(childTopic, newChildTopic); 477 // Note: memory is already up-to-date. The child topic is updated in-place of parent. 478 } 479 480 private void updateChildTopicMany(RelatedTopicModel newChildTopic, AssociationDefinition assocDef) { 481 RelatedTopic childTopic = findChildTopicById(newChildTopic.getId(), assocDef); 482 // 483 if (childTopic == null) { 484 throw new RuntimeException("Topic " + newChildTopic.getId() + " is not a child of " + 485 parent.className() + " " + parent.getId() + " according to " + assocDef); 486 } 487 // 488 updateRelatedTopic(childTopic, newChildTopic); 489 // Note: memory is already up-to-date. The child topic is updated in-place of parent. 490 } 491 492 // --- 493 494 private void updateRelatedTopic(RelatedTopic childTopic, RelatedTopicModel newChildTopic) { 495 // update topic 496 ((AttachedTopic) childTopic)._update(newChildTopic); 497 // update association 498 updateRelatingAssociation(childTopic, newChildTopic); 499 } 500 501 private void updateRelatingAssociation(RelatedTopic childTopic, RelatedTopicModel newChildTopic) { 502 childTopic.getRelatingAssociation().update(newChildTopic.getRelatingAssociation()); 503 } 504 505 // --- Create --- 506 507 private void createChildTopicOne(RelatedTopicModel newChildTopic, AssociationDefinition assocDef) { 508 // update DB 509 RelatedTopic childTopic = createAndAssociateChildTopic(newChildTopic, assocDef); 510 // update memory 511 putInChildTopics(childTopic, assocDef); 512 } 513 514 private void createChildTopicMany(RelatedTopicModel newChildTopic, AssociationDefinition assocDef) { 515 // update DB 516 RelatedTopic childTopic = createAndAssociateChildTopic(newChildTopic, assocDef); 517 // update memory 518 addToChildTopics(childTopic, assocDef); 519 } 520 521 // --- 522 523 private RelatedTopic createAndAssociateChildTopic(RelatedTopicModel childTopic, AssociationDefinition assocDef) { 524 dms.createTopic(childTopic); 525 return associateChildTopic(childTopic, assocDef); 526 } 527 528 // --- Assignment --- 529 530 private void createAssignmentOne(RelatedTopic childTopic, TopicReferenceModel newChildTopic, 531 AssociationDefinition assocDef, boolean deleteChildTopic) { 532 if (childTopic != null) { 533 if (newChildTopic.isReferingTo(childTopic)) { 534 updateRelatingAssociation(childTopic, newChildTopic); 535 // Note: memory is already up-to-date. The association is updated in-place of parent. 536 return; 537 } 538 if (deleteChildTopic) { 539 childTopic.delete(); 540 } else { 541 childTopic.getRelatingAssociation().delete(); 542 } 543 } 544 // update DB 545 RelatedTopic topic = resolveRefAndAssociateChildTopic(newChildTopic, assocDef); 546 // update memory 547 putInChildTopics(topic, assocDef); 548 } 549 550 private void createAssignmentMany(TopicReferenceModel newChildTopic, AssociationDefinition assocDef) { 551 RelatedTopic childTopic = findChildTopicByRef(newChildTopic, assocDef); 552 if (childTopic != null) { 553 // Note: "create assignment" is an idempotent operation. A create request for an assignment which 554 // exists already is not an error. Instead, nothing is performed. 555 updateRelatingAssociation(childTopic, newChildTopic); 556 // Note: memory is already up-to-date. The association is updated in-place of parent. 557 return; 558 } 559 // update DB 560 RelatedTopic topic = resolveRefAndAssociateChildTopic(newChildTopic, assocDef); 561 // update memory 562 addToChildTopics(topic, assocDef); 563 } 564 565 // --- 566 567 /** 568 * Creates an association between our parent object ("Parent" role) and the referenced topic ("Child" role). 569 * The association type is taken from the given association definition. 570 * 571 * @return the resolved child topic. 572 */ 573 RelatedTopic resolveRefAndAssociateChildTopic(TopicReferenceModel childTopicRef, AssociationDefinition assocDef) { 574 dms.valueStorage.resolveReference(childTopicRef); 575 return associateChildTopic(childTopicRef, assocDef); 576 } 577 578 private RelatedTopic associateChildTopic(RelatedTopicModel childTopic, AssociationDefinition assocDef) { 579 dms.valueStorage.associateChildTopic(parent.getModel(), childTopic, assocDef.getModel()); 580 return instantiateRelatedTopic(childTopic); 581 } 582 583 // --- Delete --- 584 585 private void deleteChildTopicOne(RelatedTopic childTopic, AssociationDefinition assocDef, 586 boolean deleteChildTopic) { 587 if (childTopic == null) { 588 // Note: "delete child"/"delete assignment" is an idempotent operation. A delete request for a 589 // child/assignment which has been deleted already (resp. is non-existing) is not an error. 590 // Instead, nothing is performed. 591 return; 592 } 593 // update DB 594 if (deleteChildTopic) { 595 childTopic.delete(); 596 } else { 597 childTopic.getRelatingAssociation().delete(); 598 } 599 // update memory 600 removeChildTopic(assocDef); 601 } 602 603 private void deleteChildTopicMany(long childTopicId, AssociationDefinition assocDef, boolean deleteChildTopic) { 604 RelatedTopic childTopic = findChildTopicById(childTopicId, assocDef); 605 if (childTopic == null) { 606 // Note: "delete child"/"delete assignment" is an idempotent operation. A delete request for a 607 // child/assignment which has been deleted already (resp. is non-existing) is not an error. 608 // Instead, nothing is performed. 609 return; 610 } 611 // update DB 612 if (deleteChildTopic) { 613 childTopic.delete(); 614 } else { 615 childTopic.getRelatingAssociation().delete(); 616 } 617 // update memory 618 removeFromChildTopics(childTopic, assocDef); 619 } 620 621 622 623 // === Attached Object Cache === 624 625 // --- Access --- 626 627 private RelatedTopic _getTopic(String childTypeUri) { 628 RelatedTopic topic = (RelatedTopic) childTopics.get(childTypeUri); 629 // error check 630 if (topic == null) { 631 throw new RuntimeException("Child topic of type \"" + childTypeUri + "\" not found in " + childTopics); 632 } 633 // 634 return topic; 635 } 636 637 private RelatedTopic _getTopic(String childTypeUri, RelatedTopic defaultTopic) { 638 RelatedTopic topic = (RelatedTopic) childTopics.get(childTypeUri); 639 return topic != null ? topic : defaultTopic; 640 } 641 642 // --- 643 644 private List<RelatedTopic> _getTopics(String childTypeUri) { 645 try { 646 List<RelatedTopic> topics = (List<RelatedTopic>) childTopics.get(childTypeUri); 647 // error check 648 if (topics == null) { 649 throw new RuntimeException("Child topics of type \"" + childTypeUri + "\" not found in " + childTopics); 650 } 651 // 652 return topics; 653 } catch (ClassCastException e) { 654 getModel().throwInvalidAccess(childTypeUri, e); 655 return null; // never reached 656 } 657 } 658 659 private List<RelatedTopic> _getTopics(String childTypeUri, List<RelatedTopic> defaultValue) { 660 try { 661 List<RelatedTopic> topics = (List<RelatedTopic>) childTopics.get(childTypeUri); 662 return topics != null ? topics : defaultValue; 663 } catch (ClassCastException e) { 664 getModel().throwInvalidAccess(childTypeUri, e); 665 return null; // never reached 666 } 667 } 668 669 // --- 670 671 /** 672 * For multiple-valued childs: looks in the attached object cache for a child topic by ID. 673 */ 674 private RelatedTopic findChildTopicById(long childTopicId, AssociationDefinition assocDef) { 675 List<RelatedTopic> childTopics = _getTopics(assocDef.getChildTypeUri(), new ArrayList()); 676 for (RelatedTopic childTopic : childTopics) { 677 if (childTopic.getId() == childTopicId) { 678 return childTopic; 679 } 680 } 681 return null; 682 } 683 684 /** 685 * For multiple-valued childs: looks in the attached object cache for the child topic the given reference refers to. 686 * 687 * @param assocDef the child topics according to this association definition are considered. 688 */ 689 private RelatedTopic findChildTopicByRef(TopicReferenceModel topicRef, AssociationDefinition assocDef) { 690 return topicRef.findReferencedTopic(_getTopics(assocDef.getChildTypeUri(), new ArrayList())); 691 } 692 693 // --- 694 695 private AssociationDefinition getAssocDef(String childTypeUri) { 696 // Note: doesn't work for facets 697 return parent.getType().getAssocDef(childTypeUri); 698 } 699 700 // --- Update attached object cache + underlying model --- 701 702 /** 703 * For single-valued childs 704 */ 705 private void putInChildTopics(RelatedTopic childTopic, AssociationDefinition assocDef) { 706 String childTypeUri = assocDef.getChildTypeUri(); 707 put(childTypeUri, childTopic); // attached object cache 708 getModel().put(childTypeUri, childTopic.getModel()); // underlying model 709 } 710 711 /** 712 * For single-valued childs 713 */ 714 private void removeChildTopic(AssociationDefinition assocDef) { 715 String childTypeUri = assocDef.getChildTypeUri(); 716 remove(childTypeUri); // attached object cache 717 getModel().remove(childTypeUri); // underlying model 718 } 719 720 /** 721 * For multiple-valued childs 722 */ 723 private void addToChildTopics(RelatedTopic childTopic, AssociationDefinition assocDef) { 724 String childTypeUri = assocDef.getChildTypeUri(); 725 add(childTypeUri, childTopic); // attached object cache 726 getModel().add(childTypeUri, childTopic.getModel()); // underlying model 727 } 728 729 /** 730 * For multiple-valued childs 731 */ 732 private void removeFromChildTopics(Topic childTopic, AssociationDefinition assocDef) { 733 String childTypeUri = assocDef.getChildTypeUri(); 734 remove(childTypeUri, childTopic); // attached object cache 735 getModel().remove(childTypeUri, childTopic.getModel()); // underlying model 736 } 737 738 // --- Update attached object cache --- 739 740 /** 741 * Puts a single-valued child. An existing value is overwritten. 742 */ 743 private void put(String childTypeUri, Topic topic) { 744 childTopics.put(childTypeUri, topic); 745 } 746 747 /** 748 * Removes a single-valued child. 749 */ 750 private void remove(String childTypeUri) { 751 childTopics.remove(childTypeUri); 752 } 753 754 /** 755 * Adds a value to a multiple-valued child. 756 */ 757 private void add(String childTypeUri, RelatedTopic topic) { 758 List<RelatedTopic> topics = _getTopics(childTypeUri, null); // defaultValue=null 759 // Note: topics just created have no child topics yet 760 if (topics == null) { 761 topics = new ArrayList(); 762 childTopics.put(childTypeUri, topics); 763 } 764 topics.add(topic); 765 } 766 767 /** 768 * Removes a value from a multiple-valued child. 769 */ 770 private void remove(String childTypeUri, Topic topic) { 771 List<RelatedTopic> topics = _getTopics(childTypeUri, null); // defaultValue=null 772 if (topics != null) { 773 topics.remove(topic); 774 } 775 } 776 777 // --- Initialization --- 778 779 /** 780 * Initializes this attached object cache. Creates a hierarchy of attached topics (recursively) that is isomorph 781 * to the underlying model. 782 */ 783 private void initAttachedObjectCache() { 784 for (String childTypeUri : model) { 785 initAttachedObjectCache(childTypeUri); 786 } 787 } 788 789 /** 790 * Initializes this attached object cache selectively. Creates a hierarchy of attached topics (recursively) that is 791 * isomorph to the underlying model, starting at the given child sub-tree. 792 */ 793 private void initAttachedObjectCache(String childTypeUri) { 794 Object value = model.get(childTypeUri); 795 // Note: topics just created have no child topics yet 796 if (value == null) { 797 return; 798 } 799 // Note: no direct recursion takes place here. Recursion is indirect: attached topics are created here, this 800 // implies creating further AttachedChildTopics objects, which in turn calls this method again but for the next 801 // child-level. Finally attached topics are created for all child-levels. 802 if (value instanceof RelatedTopicModel) { 803 RelatedTopicModel childTopic = (RelatedTopicModel) value; 804 childTopics.put(childTypeUri, instantiateRelatedTopic(childTopic)); 805 } else if (value instanceof List) { 806 List<RelatedTopic> topics = new ArrayList(); 807 childTopics.put(childTypeUri, topics); 808 for (RelatedTopicModel childTopic : (List<RelatedTopicModel>) value) { 809 topics.add(instantiateRelatedTopic(childTopic)); 810 } 811 } else { 812 throw new RuntimeException("Unexpected value in a ChildTopicsModel: " + value); 813 } 814 } 815 816 /** 817 * Creates an attached topic to be put in this attached object cache. 818 */ 819 private RelatedTopic instantiateRelatedTopic(RelatedTopicModel model) { 820 try { 821 return new AttachedRelatedTopic(model, dms); 822 } catch (Exception e) { 823 throw new RuntimeException("RelatedTopic instantiation failed (" + model + ")", e); 824 } 825 } 826 }