/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * !# */ package net.ontopia.topicmaps.impl.basic; import java.util.Set; import java.util.HashSet; import java.util.Iterator; import java.util.Comparator; import java.util.Collection; import java.util.Collections; import java.util.ArrayList; import java.util.TreeSet; import net.ontopia.utils.UniqueSet; import net.ontopia.utils.ObjectUtils; import net.ontopia.utils.CompactHashSet; import net.ontopia.infoset.core.LocatorIF; import net.ontopia.topicmaps.core.TMObjectIF; import net.ontopia.topicmaps.core.AssociationIF; import net.ontopia.topicmaps.core.AssociationRoleIF; import net.ontopia.topicmaps.core.TopicNameIF; import net.ontopia.topicmaps.core.TopicMapIF; import net.ontopia.topicmaps.core.ConstraintViolationException; import net.ontopia.topicmaps.core.CrossTopicMapException; import net.ontopia.topicmaps.core.OccurrenceIF; import net.ontopia.topicmaps.core.ReifiableIF; import net.ontopia.topicmaps.core.TopicIF; import net.ontopia.topicmaps.impl.utils.ObjectStrings; /** * INTERNAL: The basic topic implementation. */ public class Topic extends TMObject implements TopicIF { static final long serialVersionUID = 6846760964906826812L; protected Set<LocatorIF> subjects; protected Set<LocatorIF> indicators; protected ReifiableIF reified; protected UniqueSet<TopicIF> scope; protected UniqueSet<TopicIF> types; protected Set<TopicNameIF> names; protected Set<OccurrenceIF> occurs; protected Set<AssociationRoleIF> roles; private static final Comparator<AssociationRoleIF> rolecomp = new RoleComparator(); protected Topic(TopicMap tm) { super(tm); Set<TopicIF> empty = Collections.emptySet(); types = topicmap.setpool.get(empty); names = topicmap.cfactory.makeSmallSet(); occurs = topicmap.cfactory.makeSmallSet(); roles = topicmap.cfactory.makeSmallSet(); } // ----------------------------------------------------------------------------- // TopicIF implementation // ----------------------------------------------------------------------------- /** * INTERNAL: Sets the topic map that the object belongs to. [parent] */ void setTopicMap(TopicMap parent) { // (De)reference pooled sets if (parent == null) { if (scope != null) topicmap.setpool.dereference(scope); topicmap.setpool.dereference(types); } else { if (scope != null) scope = topicmap.setpool.get(scope); types = topicmap.setpool.get(types); } // Set parent this.parent = parent; } public Collection<LocatorIF> getSubjectLocators() { if (subjects == null) return Collections.emptySet(); else return Collections.unmodifiableSet(subjects); } public void addSubjectLocator(LocatorIF subject_locator) throws ConstraintViolationException { if (subject_locator == null) throw new NullPointerException("null is not a valid argument."); // Notify topic map if (!isConnected()) throw new ConstraintViolationException("Cannot modify subject locator when topic isn't attached to a topic map."); if (subjects == null) subjects = topicmap.cfactory.makeSmallSet(); // Check to see if subject is already a subject locator of this topic. else if (subjects.contains(subject_locator)) return; // Notify listeners fireEvent(TopicIF.EVENT_ADD_SUBJECTLOCATOR, subject_locator, null); // Modify property subjects.add(subject_locator); } public void removeSubjectLocator(LocatorIF subject_locator) { if (subject_locator == null) throw new NullPointerException("null is not a valid argument."); // Notify topic map if (!isConnected()) throw new ConstraintViolationException("Cannot modify subject locator when topic isn't attached to a topic map."); // Check to see if subject locator is a subject locator of this topic. if (subjects == null || !subjects.contains(subject_locator)) return; // Notify listeners fireEvent(TopicIF.EVENT_REMOVE_SUBJECTLOCATOR, null, subject_locator); // Modify property subjects.remove(subject_locator); } public Collection<LocatorIF> getSubjectIdentifiers() { if (indicators == null) return Collections.emptySet(); else return Collections.unmodifiableSet(indicators); } public void addSubjectIdentifier(LocatorIF subject_indicator) throws ConstraintViolationException { if (subject_indicator == null) throw new NullPointerException("null is not a valid argument."); // Notify topic map if (!isConnected()) throw new ConstraintViolationException("Cannot modify subject indicator when topic isn't attached to a topic map."); if (indicators == null) indicators = topicmap.cfactory.makeSmallSet(); // Check to see if subject is already a subject indicator of this topic. else if (indicators.contains(subject_indicator)) return; // Notify listeners fireEvent(TopicIF.EVENT_ADD_SUBJECTIDENTIFIER, subject_indicator, null); // Modify property indicators.add(subject_indicator); } public void removeSubjectIdentifier(LocatorIF subject_indicator) { if (subject_indicator == null) throw new NullPointerException("null is not a valid argument."); // Notify topic map if (!isConnected()) throw new ConstraintViolationException("Cannot modify subject indicator when topic isn't attached to a topic map."); // Check to see if subject indicator is a subject indicator of this topic. if (indicators == null || !indicators.contains(subject_indicator)) return; // Notify listeners fireEvent(TopicIF.EVENT_REMOVE_SUBJECTIDENTIFIER, null, subject_indicator); // Modify property indicators.remove(subject_indicator); } public Collection<TopicNameIF> getTopicNames() { // Return names return Collections.unmodifiableSet(names); } protected void addTopicName(TopicNameIF _name) { TopicName name = (TopicName)_name; if (name == null) throw new NullPointerException("null is not a valid argument."); // Check to see if name is already a member of this topic if (name.parent == this) return; // Check if used elsewhere. if (name.parent != null) throw new ConstraintViolationException("Moving objects is not allowed."); // Notify listeners fireEvent(TopicIF.EVENT_ADD_TOPICNAME, name, null); // Set topic property name.setTopic(this); // Add name to list of names names.add(name); } protected void removeTopicName(TopicNameIF _name) { TopicName name = (TopicName)_name; if (name == null) throw new NullPointerException("null is not a valid argument."); // Check to see if name is not a member of this topic if (name.parent != this) return; // Notify listeners fireEvent(TopicIF.EVENT_REMOVE_TOPICNAME, null, name); // Unset topic property name.setTopic(null); // Remove name from list of names names.remove(name); } public Collection<TopicNameIF> getTopicNamesByType(TopicIF type) { Set<TopicNameIF> namesbytype = topicmap.cfactory.makeSmallSet(); for (TopicNameIF name : names) { if (name.getType().equals(type)) { namesbytype.add(name); } } return Collections.unmodifiableSet(namesbytype); } public Collection<OccurrenceIF> getOccurrences() { return Collections.unmodifiableSet(occurs); } public Collection<OccurrenceIF> getOccurrencesByType(TopicIF type) { Set<OccurrenceIF> occsbytype = topicmap.cfactory.makeSmallSet(); for (OccurrenceIF occ : occurs) { if (occ.getType().equals(type)) { occsbytype.add(occ); } } return Collections.unmodifiableSet(occsbytype); } protected void addOccurrence(OccurrenceIF _occurrence) { Occurrence occurrence = (Occurrence)_occurrence; if (occurrence == null) throw new NullPointerException("null is not a valid argument."); // Check to see if occurrence is already a member of this topic if (occurrence.parent == this) return; // Check if used elsewhere. if (occurrence.parent != null) throw new ConstraintViolationException("Moving objects is not allowed."); // Notify listeners fireEvent(TopicIF.EVENT_ADD_OCCURRENCE, occurrence, null); // Set topic property occurrence.setTopic(this); // Add occurrence to list of occurrences occurs.add(occurrence); } protected void removeOccurrence(OccurrenceIF _occurrence) { Occurrence occurrence = (Occurrence)_occurrence; if (occurrence == null) throw new NullPointerException("null is not a valid argument."); // Check to see if occurrence is not a member of this topic if (occurrence.parent != this) return; // Notify listeners fireEvent(TopicIF.EVENT_REMOVE_OCCURRENCE, null, occurrence); // Unset topic property occurrence.setTopic(null); // Remove occurrence from list of occurrences occurs.remove(occurrence); } public Collection<AssociationRoleIF> getRoles() { return Collections.unmodifiableSet(roles); } public Collection<AssociationRoleIF> getRolesByType(TopicIF roletype) { if (roletype == null) throw new NullPointerException("Role type cannot be null."); // see below for rationale for next line Collection<AssociationRoleIF> result = new ArrayList<AssociationRoleIF>(); synchronized (roles) { Iterator<AssociationRoleIF> iter = roles.iterator(); while (iter.hasNext()) { AssociationRoleIF role = iter.next(); if (role.getType() == roletype) result.add(role); } } return result; } public Collection<AssociationRoleIF> getRolesByType(TopicIF roletype, TopicIF assoc_type) { if (roletype == null) throw new NullPointerException("Role type cannot be null."); if (assoc_type == null) throw new NullPointerException("Association type cannot be null."); synchronized (roles) { // below are timing results from running a big query with // different data structures for the result collection. used // TologQuery --timeit and results indicate that uninitialized // CompactHashSet is the fastest. however, this is likely a // special case, so using uninitialized ArrayList. // ArrayList(size) 816 804 804 -> 808 // HashSet() 732 764 763 // ArrayList() 756 739 712 745 -> 738.0 // CompactHashSet() 733 712 726 730 -> 725.25 // CompactHashSet(size) 838 856 842 -> 845.33 Collection<AssociationRoleIF> result = new ArrayList<AssociationRoleIF>(); Iterator<AssociationRoleIF> iter = roles.iterator(); while (iter.hasNext()) { AssociationRoleIF role = iter.next(); if (role.getType() == roletype) { AssociationIF assoc = role.getAssociation(); if (assoc != null && assoc.getType() == assoc_type) result.add(role); } } return result; } } public Collection<AssociationIF> getAssociations() { Set<AssociationIF> assocs = new HashSet<AssociationIF>(); for (AssociationRoleIF role : roles) { assocs.add(role.getAssociation()); } return Collections.unmodifiableSet(assocs); } public Collection<AssociationIF> getAssociationsByType(TopicIF type) { Set<AssociationIF> assocs = new HashSet<AssociationIF>(); for (AssociationRoleIF role : roles) { AssociationIF assoc = role.getAssociation(); if (assoc.getType().equals(type)) { assocs.add(assoc); } } return Collections.unmodifiableSet(assocs); } public void merge(TopicIF topic) { CrossTopicMapException.check(topic, this); net.ontopia.topicmaps.utils.MergeUtils.mergeInto(this, topic); } /** * INTERNAL: Adds the association role to the set of association * roles in which the topic participates. */ protected void addRole(AssociationRoleIF assoc_role) { // Add association role to list of association roles if (roles.size() > 100 && roles instanceof CompactHashSet) { Set<AssociationRoleIF> new_roles = new TreeSet<AssociationRoleIF>(rolecomp); new_roles.addAll(roles); roles = new_roles; } roles.add(assoc_role); } /** * INTERNAL: Removes the association role from the set of * association roles in which the topic participates. */ protected void removeRole(AssociationRoleIF assoc_role) { // Remove association from list of associations roles.remove(assoc_role); } public void remove() { if (topicmap != null) topicmap.removeTopic(this); } public Collection<TopicIF> getTypes() { return types; } public void addType(TopicIF type) { if (type == null) throw new NullPointerException("null is not a valid argument."); CrossTopicMapException.check(type, this); // Notify listeners fireEvent(TopicIF.EVENT_ADD_TYPE, type, null); // Add type to list of types types = topicmap.setpool.add(types, type, true); } public void removeType(TopicIF type) { if (type == null) throw new NullPointerException("null is not a valid argument."); CrossTopicMapException.check(type, this); // Notify listeners fireEvent(TopicIF.EVENT_REMOVE_TYPE, null, type); // Remove type from list of types types = topicmap.setpool.remove(types, type, true); } public ReifiableIF getReified() { return reified; } void setReified(ReifiableIF reified) { ReifiableIF oldReified = getReified(); if (ObjectUtils.different(oldReified, reified)) { // remove reifier from old reifiable this.reified = reified; } } // ----------------------------------------------------------------------------- // Misc. methods // ----------------------------------------------------------------------------- public String toString() { return ObjectStrings.toString("basic.Topic", (TopicIF)this); } static class RoleComparator implements Comparator<AssociationRoleIF> { public int compare(AssociationRoleIF role1, AssociationRoleIF role2) { int c = role1.getType().hashCode() - role2.getType().hashCode(); if (c == 0) c = role1.getAssociation().getType().hashCode() - role2.getAssociation().getType().hashCode(); if (c == 0) { // have to do this the long-winded way, because of overflow issues int hc1 = role1.getAssociation().hashCode(); int hc2 = role2.getAssociation().hashCode(); if (hc1 < hc2) c = -1; else if (hc1 > hc2) c = 1; else c = 0; } return c; } } static abstract class AbstractFake implements TMObjectIF { public String getObjectId() { return null; } public boolean isReadOnly() { return true; } public TopicMapIF getTopicMap() { return null; } public Collection<LocatorIF> getItemIdentifiers() { return null; } public void addItemIdentifier(LocatorIF item_identifier) { } public void removeItemIdentifier(LocatorIF item_identifier) { } public void remove() { } } static class FakeRole extends AbstractFake implements AssociationRoleIF { private AssociationIF assoc; private TopicIF assoctype; private TopicIF roletype; private int assochc; public FakeRole(TopicIF roletype, TopicIF assoctype, int assochc) { this.roletype = roletype; this.assoctype = assoctype; this.assochc = assochc; } public AssociationIF getAssociation() { if (assoc == null) assoc = new FakeAssociation(assoctype, assochc); return assoc; } public TopicIF getType() { return roletype; } public TopicIF getPlayer() { return null; } public void setPlayer(TopicIF player) {} public TopicIF getReifier() { return null; } public void setReifier(TopicIF reifier) { } public void setType(TopicIF type) { } } static class FakeAssociation extends AbstractFake implements AssociationIF { private TopicIF type; private int hashcode; public FakeAssociation(TopicIF type, int hashcode) { this.type = type; this.hashcode = hashcode; } public TopicIF getType() { return type; } public int hashCode() { return hashcode; } public TopicIF getReifier() { return null; } public void setReifier(TopicIF reifier) { } public void setType(TopicIF type) { } public Collection<TopicIF> getRoleTypes() { return null; } public Collection<AssociationRoleIF> getRolesByType(TopicIF roletype) { return null; } public Collection<AssociationRoleIF> getRoles() { return null; } public Collection<TopicIF> getScope() { return null; } public void addTheme(TopicIF theme) { } public void removeTheme(TopicIF theme) { } } }