/* * #! * 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.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Set; import net.ontopia.infoset.core.LocatorIF; import net.ontopia.topicmaps.core.AssociationIF; import net.ontopia.topicmaps.core.AssociationRoleIF; import net.ontopia.topicmaps.core.ConstraintViolationException; import net.ontopia.topicmaps.core.CrossTopicMapException; import net.ontopia.topicmaps.core.DuplicateReificationException; import net.ontopia.topicmaps.core.ReifiableIF; import net.ontopia.topicmaps.core.TMObjectIF; import net.ontopia.topicmaps.core.TopicIF; import net.ontopia.topicmaps.core.TopicMapIF; import net.ontopia.topicmaps.core.TopicMapBuilderIF; import net.ontopia.topicmaps.core.TopicMapStoreIF; import net.ontopia.topicmaps.impl.utils.TopicMapTransactionIF; import net.ontopia.topicmaps.impl.utils.EventListenerIF; import net.ontopia.topicmaps.impl.utils.EventManagerIF; import net.ontopia.topicmaps.impl.utils.ObjectStrings; import net.ontopia.topicmaps.impl.utils.DeletionUtils; import net.ontopia.utils.CollectionFactoryIF; import net.ontopia.utils.UniqueSet; /** * INTERNAL: The basic topic map implementation. */ public class TopicMap extends TMObject implements TopicMapIF, EventManagerIF { static final long serialVersionUID = -1334945778216658155L; transient InMemoryTopicMapTransaction txn; transient CollectionFactoryIF cfactory; public transient UniqueSet<TopicIF> setpool = new UniqueSet<TopicIF>(); protected transient SubjectIdentityCache sicache; protected TopicIF reifier; protected UniqueSet<TopicIF> scope; protected Set<TopicIF> topics; protected Set<AssociationIF> assocs; //protected Map object_ids; // unused //protected Map id_objects; // unused protected Map<String, EventListenerIF[]> listeners; TopicMap(InMemoryTopicMapTransaction txn) { this.txn = txn; this.topicmap = this; this.parent = this; this.cfactory = txn.getCollectionFactory(); //object_ids = cfactory.makeLargeMap(); // unused //id_objects = cfactory.makeLargeMap(); // unused scope = setpool.get(Collections.<TopicIF>emptySet()); topics = cfactory.makeLargeSet(); assocs = cfactory.makeLargeSet(); // Initialize listeners listeners = cfactory.makeLargeMap(); } // ----------------------------------------------------------------------------- // TMObjectIF implementation // ----------------------------------------------------------------------------- public TopicMapIF getTopicMap() { return this; } // ----------------------------------------------------------------------------- // TopicMapIF implementation // ----------------------------------------------------------------------------- public TopicMapStoreIF getStore() { return txn.getStore(); } public TopicMapTransactionIF getTransaction() { return txn; } public TopicMapBuilderIF getBuilder() { return getTransaction().getBuilder(); } public Object getIndex(String name) { return getTransaction().getIndexManager().getIndex(name); } SubjectIdentityCache getSubjectIdentityCache() { return sicache; } void setSubjectIdentityCache(SubjectIdentityCache sicache) { // Unregister topic map with old subject identity cache if (this.sicache != null) this.sicache.unregisterObject(this); // Register topic map with new subject identity cache sicache.registerObject(this); this.sicache = sicache; } public Collection<TopicIF> getTopics() { return Collections.unmodifiableSet(topics); } /** * Adds a topic to the set of topics. */ public void addTopic(TopicIF _topic) { Topic topic = (Topic)_topic; // Check to see if topic is already a member of this topic map if (topic.parent == this) return; // Check if used elsewhere. if (topic.parent != null) throw new ConstraintViolationException("Moving objects is not allowed."); // Notify listeners fireEvent(TopicMapIF.EVENT_ADD_TOPIC, topic, null); // Set topic map property topic.setTopicMap(this); // Register topic with topic map topics.add(topic); // Notify topic listener txn.te.addedTopic(topic); // // Assign object id. Note that this is an exception to the object // // assignment rule. Only topics will be assigned ids when they're // // added to the topic map. The was added because of bug #89. // getObjectId(topic); // Asking for the object id is the same as assigning } /** * Removes a topic from the set of topics. */ public void removeTopic(TopicIF _topic) { Topic topic = (Topic)_topic; // Check to see if topic is not a member of this topic map if (topic.parent != this) return; // Remove dependencies DeletionUtils.removeDependencies(topic); // Notify listeners fireEvent(TopicMapIF.EVENT_REMOVE_TOPIC, null, topic); // Notify topic listener txn.te.removingTopic(topic); // Unset topic map property topic.setTopicMap(null); // Unregister topic with topic map topics.remove(topic); } public Collection<AssociationIF> getAssociations() { return Collections.unmodifiableSet(assocs); } /** * Adds an association to the set of associations. */ public void addAssociation(AssociationIF _association) { Association association = (Association)_association; // Check to see if association is already a member of this topic map if (association.parent == this) return; // Check if used elsewhere. if (association.parent != null) throw new ConstraintViolationException("Moving objects is not allowed."); // Notify listeners fireEvent(TopicMapIF.EVENT_ADD_ASSOCIATION, association, null); // Set topic map property association.setTopicMap(this); // Register association with topic map assocs.add(association); // Make sure roles are added to player's list Collection<AssociationRoleIF> roles = association.getRoles(); synchronized (roles) { Iterator<AssociationRoleIF> iter = roles.iterator(); while (iter.hasNext()) { AssociationRoleIF role = iter.next(); Topic player = (Topic) role.getPlayer(); if (player != null) player.addRole(role); } } } /** * Removes an associations from the set of associations. */ public void removeAssociation(AssociationIF _association) { Association association = (Association)_association; // Check to see if association is not a member of this topic map if (association.parent != this) return; // Notify listeners fireEvent(TopicMapIF.EVENT_REMOVE_ASSOCIATION, null, association); // Remove players of the association roles Collection<AssociationRoleIF> roles = association.roles; synchronized (roles) { Iterator<AssociationRoleIF> iter = roles.iterator(); while (iter.hasNext()) { AssociationRoleIF role = iter.next(); Topic player = (Topic) role.getPlayer(); if (player != null) player.removeRole(role); } } // Unset topic map property association.setTopicMap(null); // Unregister association with topic map assocs.remove(association); } public void remove() { getStore().delete(true); } public void clear() { DeletionUtils.clear(this); } public TMObjectIF getObjectById(String object_id) { if (object_id == null) throw new NullPointerException("null is not a valid argument."); return sicache.getObjectById(object_id); } public TMObjectIF getObjectByItemIdentifier(LocatorIF locator) { if (locator == null) throw new NullPointerException("null is not a valid argument."); return sicache.getObjectByItemIdentifier(locator); } public TopicIF getTopicBySubjectLocator(LocatorIF locator) { if (locator == null) throw new NullPointerException("null is not a valid argument."); return sicache.getTopicBySubjectLocator(locator); } public TopicIF getTopicBySubjectIdentifier(LocatorIF locator) { if (locator == null) throw new NullPointerException("null is not a valid argument."); return sicache.getTopicBySubjectIdentifier(locator); } // ----------------------------------------------------------------------------- // ReifiableIF implementation // ----------------------------------------------------------------------------- public TopicIF getReifier() { return reifier; } public void setReifier(TopicIF _reifier) { if (_reifier != null) CrossTopicMapException.check(_reifier, this); if (DuplicateReificationException.check(this, _reifier)) { return; } // Notify listeners Topic reifier = (Topic)_reifier; Topic oldReifier = (Topic)getReifier(); fireEvent(ReifiableIF.EVENT_SET_REIFIER, reifier, oldReifier); this.reifier = reifier; if (oldReifier != null) oldReifier.setReified(null); if (reifier != null) reifier.setReified(this); } // ----------------------------------------------------------------------------- // Misc. methods // ----------------------------------------------------------------------------- protected void fireEvent(String event, Object new_value, Object old_value) { processEvent(this, event, new_value, old_value); } public String toString() { return ObjectStrings.toString("basic.TopicMap", (TopicMapIF)this); } // ----------------------------------------------------------------------------- // EventManagerIF implementation // ----------------------------------------------------------------------------- public void addListener(EventListenerIF listener, String event) { // Adding itself causes infinite loops. if (listener == this) return; // Initialize event entry synchronized (listeners) { // Add listener to list of event entry listeners. This is not // very elegant, but it works. if (!listeners.containsKey(event)) listeners.put(event, new EventListenerIF[0]); Collection<EventListenerIF> event_listeners = new ArrayList<EventListenerIF>(Arrays.asList(listeners.get(event))); event_listeners.add(listener); listeners.put(event, event_listeners.toArray(new EventListenerIF[0])); } } public void removeListener(EventListenerIF listener, String event) { synchronized (listeners) { if (listeners.containsKey(event)) { // Remove listener from list of event entry listeners. This is // not very elegant, but it works. Collection<EventListenerIF> event_listeners = new ArrayList<EventListenerIF>(Arrays.asList(listeners.get(event))); event_listeners.remove(listener); if (event_listeners.isEmpty()) listeners.remove(event); else listeners.put(event, event_listeners.toArray(new EventListenerIF[1])); } } } // ----------------------------------------------------------------------------- // EventListenerIF // ----------------------------------------------------------------------------- public void processEvent(Object object, String event, Object new_value, Object old_value) { // Look up event listeners EventListenerIF[] event_listeners = listeners.get(event); if (event_listeners != null) { // Loop over event listeners int size = event_listeners.length; for (int i=0; i < size; i++) // Notify listener (event_listeners[i]).processEvent(object, event, new_value, old_value); } } }