001package de.deepamehta.core.impl; 002 003import de.deepamehta.core.model.AssociationDefinitionModel; 004import de.deepamehta.core.model.ChildTopicsModel; 005import de.deepamehta.core.model.RelatedTopicModel; 006import de.deepamehta.core.model.SimpleValue; 007import de.deepamehta.core.model.TopicModel; 008import de.deepamehta.core.service.ModelFactory; 009import de.deepamehta.core.util.DeepaMehtaUtils; 010 011import org.codehaus.jettison.json.JSONObject; 012 013import java.util.ArrayList; 014import java.util.Iterator; 015import java.util.List; 016import java.util.Map; 017import java.util.logging.Logger; 018 019 020 021class ChildTopicsModelImpl implements ChildTopicsModel { 022 023 // ---------------------------------------------------------------------------------------------- Instance Variables 024 025 /** 026 * Internal representation. 027 * Key: assoc def URI (String), value: RelatedTopicModel or List<RelatedTopicModel> 028 */ 029 private Map<String, Object> childTopics; 030 // Note: it must be List<RelatedTopicModel>, not Set<RelatedTopicModel>. 031 // There may be several TopicModels with the same ID. That occurrs if the webclient user adds several new topics 032 // at once (by the means of an "Add" button). In this case the ID is -1. TopicModel equality is defined solely as 033 // ID equality (see DeepaMehtaObjectModel.equals()). 034 035 private ModelFactory mf; 036 037 private Logger logger = Logger.getLogger(getClass().getName()); 038 039 // ---------------------------------------------------------------------------------------------------- Constructors 040 041 ChildTopicsModelImpl(Map<String, Object> childTopics, ModelFactory mf) { 042 this.childTopics = childTopics; 043 this.mf = mf; 044 } 045 046 ChildTopicsModelImpl(ChildTopicsModelImpl childTopics) { 047 this(childTopics.childTopics, childTopics.mf); 048 } 049 050 // -------------------------------------------------------------------------------------------------- Public Methods 051 052 053 054 // === Accessors === 055 056 @Override 057 public RelatedTopicModelImpl getTopic(String assocDefUri) { 058 RelatedTopicModelImpl topic = getTopicOrNull(assocDefUri); 059 // error check 060 if (topic == null) { 061 throw new RuntimeException("Assoc Def URI \"" + assocDefUri + "\" not found in " + childTopics.keySet()); 062 } 063 // 064 return topic; 065 } 066 067 @Override 068 public RelatedTopicModelImpl getTopicOrNull(String assocDefUri) { 069 try { 070 return (RelatedTopicModelImpl) childTopics.get(assocDefUri); 071 } catch (ClassCastException e) { 072 throwInvalidSingleAccess(assocDefUri, e); 073 return null; // never reached 074 } 075 } 076 077 // --- 078 079 @Override 080 public List<RelatedTopicModelImpl> getTopics(String assocDefUri) { 081 List<RelatedTopicModelImpl> topics = getTopicsOrNull(assocDefUri); 082 // error check 083 if (topics == null) { 084 throw new RuntimeException("Assoc Def URI \"" + assocDefUri + "\" not found in " + childTopics.keySet()); 085 } 086 // 087 return topics; 088 } 089 090 @Override 091 public List<RelatedTopicModelImpl> getTopicsOrNull(String assocDefUri) { 092 try { 093 return (List<RelatedTopicModelImpl>) childTopics.get(assocDefUri); 094 } catch (ClassCastException e) { 095 throwInvalidMultiAccess(assocDefUri, e); 096 return null; // never reached 097 } 098 } 099 100 // --- 101 102 /** 103 * Accesses a child generically, regardless of single-valued or multiple-valued. 104 * Returns null if there is no such child. 105 * 106 * @return A RelatedTopicModel or List<RelatedTopicModel>, or null if there is no such child. 107 */ 108 @Override 109 public Object get(String assocDefUri) { 110 return childTopics.get(assocDefUri); 111 } 112 113 114 115 // === Convenience Accessors === 116 117 /** 118 * Convenience accessor for the *simple* value of a single-valued child. 119 * Throws if the child doesn't exist. 120 */ 121 @Override 122 public String getString(String assocDefUri) { 123 return getTopic(assocDefUri).getSimpleValue().toString(); 124 } 125 126 /** 127 * Convenience accessor for the *simple* value of a single-valued child. 128 * Returns a default value if the child doesn't exist. 129 */ 130 @Override 131 public String getString(String assocDefUri, String defaultValue) { 132 TopicModel topic = getTopicOrNull(assocDefUri); 133 return topic != null ? topic.getSimpleValue().toString() : defaultValue; 134 } 135 136 // --- 137 138 /** 139 * Convenience accessor for the *simple* value of a single-valued child. 140 * Throws if the child doesn't exist. 141 */ 142 @Override 143 public int getInt(String assocDefUri) { 144 return getTopic(assocDefUri).getSimpleValue().intValue(); 145 } 146 147 /** 148 * Convenience accessor for the *simple* value of a single-valued child. 149 * Returns a default value if the child doesn't exist. 150 */ 151 @Override 152 public int getInt(String assocDefUri, int defaultValue) { 153 TopicModel topic = getTopicOrNull(assocDefUri); 154 return topic != null ? topic.getSimpleValue().intValue() : defaultValue; 155 } 156 157 // --- 158 159 /** 160 * Convenience accessor for the *simple* value of a single-valued child. 161 * Throws if the child doesn't exist. 162 */ 163 @Override 164 public long getLong(String assocDefUri) { 165 return getTopic(assocDefUri).getSimpleValue().longValue(); 166 } 167 168 /** 169 * Convenience accessor for the *simple* value of a single-valued child. 170 * Returns a default value if the child doesn't exist. 171 */ 172 @Override 173 public long getLong(String assocDefUri, long defaultValue) { 174 TopicModel topic = getTopicOrNull(assocDefUri); 175 return topic != null ? topic.getSimpleValue().longValue() : defaultValue; 176 } 177 178 // --- 179 180 /** 181 * Convenience accessor for the *simple* value of a single-valued child. 182 * Throws if the child doesn't exist. 183 */ 184 @Override 185 public double getDouble(String assocDefUri) { 186 return getTopic(assocDefUri).getSimpleValue().doubleValue(); 187 } 188 189 /** 190 * Convenience accessor for the *simple* value of a single-valued child. 191 * Returns a default value if the child doesn't exist. 192 */ 193 @Override 194 public double getDouble(String assocDefUri, double defaultValue) { 195 TopicModel topic = getTopicOrNull(assocDefUri); 196 return topic != null ? topic.getSimpleValue().doubleValue() : defaultValue; 197 } 198 199 // --- 200 201 /** 202 * Convenience accessor for the *simple* value of a single-valued child. 203 * Throws if the child doesn't exist. 204 */ 205 @Override 206 public boolean getBoolean(String assocDefUri) { 207 return getTopic(assocDefUri).getSimpleValue().booleanValue(); 208 } 209 210 /** 211 * Convenience accessor for the *simple* value of a single-valued child. 212 * Returns a default value if the child doesn't exist. 213 */ 214 @Override 215 public boolean getBoolean(String assocDefUri, boolean defaultValue) { 216 TopicModel topic = getTopicOrNull(assocDefUri); 217 return topic != null ? topic.getSimpleValue().booleanValue() : defaultValue; 218 } 219 220 // --- 221 222 /** 223 * Convenience accessor for the *simple* value of a single-valued child. 224 * Throws if the child doesn't exist. 225 */ 226 @Override 227 public Object getObject(String assocDefUri) { 228 return getTopic(assocDefUri).getSimpleValue().value(); 229 } 230 231 /** 232 * Convenience accessor for the *simple* value of a single-valued child. 233 * Returns a default value if the child doesn't exist. 234 */ 235 @Override 236 public Object getObject(String assocDefUri, Object defaultValue) { 237 TopicModel topic = getTopicOrNull(assocDefUri); 238 return topic != null ? topic.getSimpleValue().value() : defaultValue; 239 } 240 241 // --- 242 243 /** 244 * Convenience accessor for the *composite* value of a single-valued child. 245 * Throws if the child doesn't exist. 246 */ 247 @Override 248 public ChildTopicsModel getChildTopicsModel(String assocDefUri) { 249 return getTopic(assocDefUri).getChildTopicsModel(); 250 } 251 252 /** 253 * Convenience accessor for the *composite* value of a single-valued child. 254 * Returns a default value if the child doesn't exist. 255 */ 256 @Override 257 public ChildTopicsModel getChildTopicsModel(String assocDefUri, ChildTopicsModel defaultValue) { 258 RelatedTopicModel topic = getTopicOrNull(assocDefUri); 259 return topic != null ? topic.getChildTopicsModel() : defaultValue; 260 } 261 262 // Note: there are no convenience accessors for a multiple-valued child. 263 264 265 266 // === Manipulators === 267 268 // --- Single-valued Childs --- 269 270 /** 271 * Puts a value in a single-valued child. 272 * An existing value is overwritten. 273 */ 274 @Override 275 public ChildTopicsModel put(String assocDefUri, RelatedTopicModel value) { 276 try { 277 // check argument 278 if (value == null) { 279 throw new IllegalArgumentException("Tried to put null in a ChildTopicsModel"); 280 } 281 // 282 childTopics.put(assocDefUri, value); 283 return this; 284 } catch (Exception e) { 285 throw new RuntimeException("Putting a value in a ChildTopicsModel failed (assocDefUri=\"" + 286 assocDefUri + "\", value=" + value + ")", e); 287 } 288 } 289 290 @Override 291 public ChildTopicsModel put(String assocDefUri, TopicModel value) { 292 return put(assocDefUri, mf.newRelatedTopicModel(value)); 293 } 294 295 // --- 296 297 /** 298 * Convenience method to put a *simple* value in a single-valued child. 299 * An existing value is overwritten. 300 * 301 * @param value a String, Integer, Long, Double, or a Boolean. 302 * 303 * @return this ChildTopicsModel. 304 */ 305 @Override 306 public ChildTopicsModel put(String assocDefUri, Object value) { 307 try { 308 return put(assocDefUri, mf.newTopicModel(mf.childTypeUri(assocDefUri), new SimpleValue(value))); 309 } catch (Exception e) { 310 throw new RuntimeException("Putting a value in a ChildTopicsModel failed (assocDefUri=\"" + 311 assocDefUri + "\", value=" + value + ")", e); 312 } 313 } 314 315 /** 316 * Convenience method to put a *composite* value in a single-valued child. 317 * An existing value is overwritten. 318 * 319 * @return this ChildTopicsModel. 320 */ 321 @Override 322 public ChildTopicsModel put(String assocDefUri, ChildTopicsModel value) { 323 return put(assocDefUri, mf.newTopicModel(mf.childTypeUri(assocDefUri), value)); 324 } 325 326 // --- 327 328 /** 329 * Puts a by-ID topic reference in a single-valued child. 330 * An existing reference is overwritten. 331 */ 332 @Override 333 public ChildTopicsModel putRef(String assocDefUri, long refTopicId) { 334 put(assocDefUri, mf.newTopicReferenceModel(refTopicId)); 335 return this; 336 } 337 338 /** 339 * Puts a by-URI topic reference in a single-valued child. 340 * An existing reference is overwritten. 341 */ 342 @Override 343 public ChildTopicsModel putRef(String assocDefUri, String refTopicUri) { 344 put(assocDefUri, mf.newTopicReferenceModel(refTopicUri)); 345 return this; 346 } 347 348 // --- 349 350 /** 351 * Puts a by-ID topic deletion reference to a single-valued child. 352 * An existing value is overwritten. 353 */ 354 @Override 355 public ChildTopicsModel putDeletionRef(String assocDefUri, long refTopicId) { 356 put(assocDefUri, mf.newTopicDeletionModel(refTopicId)); 357 return this; 358 } 359 360 /** 361 * Puts a by-URI topic deletion reference to a single-valued child. 362 * An existing value is overwritten. 363 */ 364 @Override 365 public ChildTopicsModel putDeletionRef(String assocDefUri, String refTopicUri) { 366 put(assocDefUri, mf.newTopicDeletionModel(refTopicUri)); 367 return this; 368 } 369 370 // --- 371 372 /** 373 * Removes a single-valued child. 374 */ 375 @Override 376 public ChildTopicsModel remove(String assocDefUri) { 377 childTopics.remove(assocDefUri); // ### TODO: throw if not in map? 378 return this; 379 } 380 381 // --- Multiple-valued Childs --- 382 383 /** 384 * Adds a value to a multiple-valued child. 385 */ 386 @Override 387 public ChildTopicsModel add(String assocDefUri, RelatedTopicModel value) { 388 List<RelatedTopicModelImpl> topics = getTopicsOrNull(assocDefUri); 389 // Note: topics just created have no child topics yet 390 if (topics == null) { 391 topics = new ArrayList(); 392 childTopics.put(assocDefUri, topics); 393 } 394 // 395 topics.add((RelatedTopicModelImpl) value); 396 // 397 return this; 398 } 399 400 @Override 401 public ChildTopicsModel add(String assocDefUri, TopicModel value) { 402 return add(assocDefUri, mf.newRelatedTopicModel(value)); 403 } 404 405 /** 406 * Sets the values of a multiple-valued child. 407 * Existing values are overwritten. 408 */ 409 @Override 410 public ChildTopicsModel put(String assocDefUri, List<RelatedTopicModel> values) { 411 childTopics.put(assocDefUri, values); 412 return this; 413 } 414 415 /** 416 * Removes a value from a multiple-valued child. 417 */ 418 @Override 419 public ChildTopicsModel remove(String assocDefUri, TopicModel value) { 420 List<RelatedTopicModelImpl> topics = getTopicsOrNull(assocDefUri); 421 if (topics != null) { 422 topics.remove(value); 423 } 424 return this; 425 } 426 427 // --- 428 429 /** 430 * Adds a by-ID topic reference to a multiple-valued child. 431 */ 432 @Override 433 public ChildTopicsModel addRef(String assocDefUri, long refTopicId) { 434 add(assocDefUri, mf.newTopicReferenceModel(refTopicId)); 435 return this; 436 } 437 438 /** 439 * Adds a by-URI topic reference to a multiple-valued child. 440 */ 441 @Override 442 public ChildTopicsModel addRef(String assocDefUri, String refTopicUri) { 443 add(assocDefUri, mf.newTopicReferenceModel(refTopicUri)); 444 return this; 445 } 446 447 // --- 448 449 /** 450 * Adds a by-ID topic deletion reference to a multiple-valued child. 451 */ 452 @Override 453 public ChildTopicsModel addDeletionRef(String assocDefUri, long refTopicId) { 454 add(assocDefUri, mf.newTopicDeletionModel(refTopicId)); 455 return this; 456 } 457 458 /** 459 * Adds a by-URI topic deletion reference to a multiple-valued child. 460 */ 461 @Override 462 public ChildTopicsModel addDeletionRef(String assocDefUri, String refTopicUri) { 463 add(assocDefUri, mf.newTopicDeletionModel(refTopicUri)); 464 return this; 465 } 466 467 468 469 // === Iterable Implementation === 470 471 /** 472 * Returns an interator which iterates this ChildTopicsModel's assoc def URIs. 473 */ 474 @Override 475 public Iterator<String> iterator() { 476 return childTopics.keySet().iterator(); 477 } 478 479 480 481 // === 482 483 @Override 484 public JSONObject toJSON() { 485 try { 486 JSONObject json = new JSONObject(); 487 for (String assocDefUri : this) { 488 Object value = get(assocDefUri); 489 if (value instanceof RelatedTopicModel) { 490 json.put(assocDefUri, ((RelatedTopicModel) value).toJSON()); 491 } else if (value instanceof List) { 492 json.put(assocDefUri, DeepaMehtaUtils.toJSONArray((List<RelatedTopicModel>) value)); 493 } else { 494 throw new RuntimeException("Unexpected value in a ChildTopicsModel: " + value); 495 } 496 } 497 return json; 498 } catch (Exception e) { 499 throw new RuntimeException("Serialization of a ChildTopicsModel failed (" + this + ")", e); 500 } 501 } 502 503 504 505 // **************** 506 // *** Java API *** 507 // **************** 508 509 510 511 @Override 512 public ChildTopicsModel clone() { 513 ChildTopicsModel clone = mf.newChildTopicsModel(); 514 for (String assocDefUri : this) { 515 Object value = get(assocDefUri); 516 if (value instanceof RelatedTopicModel) { 517 RelatedTopicModel model = (RelatedTopicModel) value; 518 clone.put(assocDefUri, model.clone()); 519 } else if (value instanceof List) { 520 for (RelatedTopicModel model : (List<RelatedTopicModel>) value) { 521 clone.add(assocDefUri, model.clone()); 522 } 523 } else { 524 throw new RuntimeException("Unexpected value in a ChildTopicsModel: " + value); 525 } 526 } 527 return clone; 528 } 529 530 @Override 531 public String toString() { 532 return childTopics.toString(); 533 } 534 535 // ----------------------------------------------------------------------------------------- Package Private Methods 536 537 538 539 // === Mmemory Access === 540 541 // --- Read --- 542 543 /** 544 * For multiple-valued childs: looks in the attached object cache for a child topic by ID. ### FIXDOC 545 */ 546 RelatedTopicModelImpl findChildTopicById(long childTopicId, AssociationDefinitionModel assocDef) { 547 List<RelatedTopicModelImpl> childTopics = getTopicsOrNull(assocDef.getAssocDefUri()); 548 if (childTopics != null) { 549 for (RelatedTopicModelImpl childTopic : childTopics) { 550 if (childTopic.getId() == childTopicId) { 551 return childTopic; 552 } 553 } 554 } 555 return null; 556 } 557 558 /** 559 * For multiple-valued childs: looks in the attached object cache for the child topic the given reference refers to. 560 * ### FIXDOC 561 * 562 * @param assocDef the child topics according to this association definition are considered. 563 */ 564 RelatedTopicModelImpl findChildTopicByRef(TopicReferenceModelImpl topicRef, AssociationDefinitionModel assocDef) { 565 List<? extends RelatedTopicModel> childTopics = getTopicsOrNull(assocDef.getAssocDefUri()); 566 if (childTopics != null) { 567 return topicRef.findReferencedTopic(childTopics); 568 } 569 return null; 570 } 571 572 // --- 573 574 /** 575 * Checks if a child is contained in this ChildTopicsModel. 576 */ 577 boolean has(String assocDefUri) { 578 return childTopics.containsKey(assocDefUri); 579 } 580 581 /** 582 * Returns the number of childs contained in this ChildTopicsModel. 583 * Multiple-valued childs count as one. 584 */ 585 int size() { 586 return childTopics.size(); 587 } 588 589 // --- Write Helper --- 590 591 /** 592 * For single-valued childs 593 */ 594 void putInChildTopics(RelatedTopicModel childTopic, AssociationDefinitionModel assocDef) { 595 put(assocDef.getAssocDefUri(), childTopic); 596 } 597 598 /** 599 * For single-valued childs 600 */ 601 void removeChildTopic(AssociationDefinitionModel assocDef) { 602 remove(assocDef.getAssocDefUri()); 603 } 604 605 /** 606 * For multiple-valued childs 607 */ 608 void addToChildTopics(RelatedTopicModel childTopic, AssociationDefinitionModel assocDef) { 609 add(assocDef.getAssocDefUri(), childTopic); 610 } 611 612 /** 613 * For multiple-valued childs 614 */ 615 void removeFromChildTopics(RelatedTopicModel childTopic, AssociationDefinitionModel assocDef) { 616 remove(assocDef.getAssocDefUri(), childTopic); 617 } 618 619 620 621 // ------------------------------------------------------------------------------------------------- Private Methods 622 623 private void throwInvalidSingleAccess(String assocDefUri, ClassCastException e) { 624 if (e.getMessage().startsWith("java.util.ArrayList cannot be cast to")) { 625 throw new RuntimeException("\"" + assocDefUri + "\" is accessed as single but is defined as multi", e); 626 } else { 627 throw new RuntimeException("Accessing \"" + assocDefUri + "\" failed", e); 628 } 629 } 630 631 private void throwInvalidMultiAccess(String assocDefUri, ClassCastException e) { 632 if (e.getMessage().endsWith("cannot be cast to java.util.List")) { 633 throw new RuntimeException("\"" + assocDefUri + "\" is accessed as multi but is defined as single", e); 634 } else { 635 throw new RuntimeException("Accessing \"" + assocDefUri + " failed", e); 636 } 637 } 638}