001package systems.dmx.core.impl; 002 003import systems.dmx.core.model.AssociationDefinitionModel; 004import systems.dmx.core.model.ChildTopicsModel; 005import systems.dmx.core.model.RelatedTopicModel; 006import systems.dmx.core.model.SimpleValue; 007import systems.dmx.core.model.TopicModel; 008import systems.dmx.core.service.ModelFactory; 009import systems.dmx.core.util.DMXUtils; 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 DMXObjectModel.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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final 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 final Iterator<String> iterator() { 476 return childTopics.keySet().iterator(); 477 } 478 479 480 481 // === 482 483 @Override 484 public final 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, DMXUtils.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 failed", e); 500 } 501 } 502 503 504 505 // **************** 506 // *** Java API *** 507 // **************** 508 509 510 511 @Override 512 public final 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 // ----------------------------------------------------------------------------------------- Package Private Methods 531 532 533 534 // === Mmemory Access === 535 536 // --- Read --- 537 538 /** 539 * For multiple-valued childs: looks in the attached object cache for a child topic by ID. ### FIXDOC 540 */ 541 RelatedTopicModelImpl findChildTopicById(long childTopicId, AssociationDefinitionModel assocDef) { 542 List<RelatedTopicModelImpl> childTopics = getTopicsOrNull(assocDef.getAssocDefUri()); 543 if (childTopics != null) { 544 for (RelatedTopicModelImpl childTopic : childTopics) { 545 if (childTopic.getId() == childTopicId) { 546 return childTopic; 547 } 548 } 549 } 550 return null; 551 } 552 553 /** 554 * For multiple-valued childs: looks in the attached object cache for the child topic the given reference refers to. 555 * ### FIXDOC 556 * 557 * @param assocDef the child topics according to this association definition are considered. 558 */ 559 RelatedTopicModelImpl findChildTopicByRef(TopicReferenceModelImpl topicRef, AssociationDefinitionModel assocDef) { 560 List<? extends RelatedTopicModel> childTopics = getTopicsOrNull(assocDef.getAssocDefUri()); 561 if (childTopics != null) { 562 return topicRef.findReferencedTopic(childTopics); 563 } 564 return null; 565 } 566 567 // --- 568 569 /** 570 * Checks if a child is contained in this ChildTopicsModel. 571 */ 572 boolean has(String assocDefUri) { 573 return childTopics.containsKey(assocDefUri); 574 } 575 576 /** 577 * Returns the number of childs contained in this ChildTopicsModel. 578 * Multiple-valued childs count as one. 579 */ 580 int size() { 581 return childTopics.size(); 582 } 583 584 // --- Write Helper --- 585 586 /** 587 * For single-valued childs 588 */ 589 void putInChildTopics(RelatedTopicModel childTopic, AssociationDefinitionModel assocDef) { 590 put(assocDef.getAssocDefUri(), childTopic); 591 } 592 593 /** 594 * For single-valued childs 595 */ 596 void removeChildTopic(AssociationDefinitionModel assocDef) { 597 remove(assocDef.getAssocDefUri()); 598 } 599 600 /** 601 * For multiple-valued childs 602 */ 603 void addToChildTopics(RelatedTopicModel childTopic, AssociationDefinitionModel assocDef) { 604 add(assocDef.getAssocDefUri(), childTopic); 605 } 606 607 /** 608 * For multiple-valued childs 609 */ 610 void removeFromChildTopics(RelatedTopicModel childTopic, AssociationDefinitionModel assocDef) { 611 remove(assocDef.getAssocDefUri(), childTopic); 612 } 613 614 615 616 // ------------------------------------------------------------------------------------------------- Private Methods 617 618 private void throwInvalidSingleAccess(String assocDefUri, ClassCastException e) { 619 if (e.getMessage().startsWith("java.util.ArrayList cannot be cast to")) { 620 throw new RuntimeException("\"" + assocDefUri + "\" is accessed as single but is defined as multi", e); 621 } else { 622 throw new RuntimeException("Accessing \"" + assocDefUri + "\" failed", e); 623 } 624 } 625 626 private void throwInvalidMultiAccess(String assocDefUri, ClassCastException e) { 627 if (e.getMessage().endsWith("cannot be cast to java.util.List")) { 628 throw new RuntimeException("\"" + assocDefUri + "\" is accessed as multi but is defined as single", e); 629 } else { 630 throw new RuntimeException("Accessing \"" + assocDefUri + " failed", e); 631 } 632 } 633}