/* * #! * 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.utils; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import net.ontopia.topicmaps.core.AssociationIF; import net.ontopia.topicmaps.core.AssociationRoleIF; import net.ontopia.topicmaps.core.TMObjectIF; import net.ontopia.topicmaps.core.TopicIF; import net.ontopia.topicmaps.core.TypedIF; import net.ontopia.utils.DeciderIF; /** * INTERNAL: This class provides utility functions for traversing class * hierarchies which are expressed using the Published Subject * Indicators (PSIs) defined by the XTM 1.0 Specification. Operations * provided by this class are not thread-safe.</p> */ public class TypeHierarchyUtils { private AssociationWalker supertypesWalker; private AssociationWalker subtypesWalker; /** * INTERNAL: Creates and initialises a new instance of the utility class. */ public TypeHierarchyUtils() { DeciderIF<AssociationIF> assocDecider = new SubjectIdentityDecider<AssociationIF>(PSI.getXTMSuperclassSubclass()); DeciderIF<AssociationRoleIF> subclassDecider = new SubjectIdentityDecider<AssociationRoleIF>(PSI.getXTMSubclass()); DeciderIF<AssociationRoleIF> superclassDecider = new SubjectIdentityDecider<AssociationRoleIF>(PSI.getXTMSuperclass()); supertypesWalker = new AssociationWalker(assocDecider, subclassDecider, superclassDecider); subtypesWalker = new AssociationWalker(assocDecider, superclassDecider, subclassDecider); } /** * INTERNAL: Determines if the singly-typed object <code>typed</code> * is an instance of the type <code>klass</code>. This function * returns true if <code>typed</code> is a direct instance of * <code>klass</code> or if it is an instance of some subtype of * <code>klass</code>. * * @param typed the given typedIF object * @param klass a topicIF object; the given type * @return boolean; true iff the given typedIF is an instance of the * given type or an instance of a subtype of the given type */ public boolean isInstanceOf(TypedIF typed, TopicIF klass) { TopicIF _type = typed.getType(); if (klass != null && _type != null && (klass.equals(_type) || supertypesWalker.isAssociated(_type, klass))) { return true; } return false; } /** * INTERNAL: Determines if the <code>typed</code> * is an instance of the type <code>klass</code>. This function * returns true if <code>typed</code> is a direct instance of * <code>klass</code> or if it is an instance of some subclass of * <code>klass</code>. * * @param typed the given typedIF object * @param klass a topicIF object; the given type * @return boolean; true if the given object is an instance of the given type or an instance of a * subtype of the given type */ public boolean isInstanceOf(TopicIF typed, TopicIF klass) { Collection<TopicIF> _types = typed.getTypes(); if (_types.contains(klass)) return true; Iterator<TopicIF> it = _types.iterator(); while (it.hasNext()) { if (supertypesWalker.isAssociated(it.next(), klass)) { return true; } } return false; } /** * INTERNAL: Returns true if the two topics are directly or indirectly * associated under the association type and rolespec definitions * provided in the constructor for this walker. The calculation is * performed using a depth-first traversal of the tree formed by the * associations concerned, which aborts as soon as the associated * topic is found. * * @param start The topic to begin computation from; an object * implementing TopicIF. * @param associated The topic to be found in the association; an * object implementing TopicIF. * @return Boolean: true if the given topics are directly or * indirectly associated */ public boolean isAssociatedWith(TopicIF start, TopicIF associated) { Collection<AssociationRoleIF> roles = start.getRoles(); Iterator<AssociationRoleIF> itRoles = roles.iterator(); AssociationRoleIF assocRole; AssociationIF assoc; Collection<AssociationRoleIF> assocRoles; Iterator<AssociationRoleIF> itAssocRoles; AssociationRoleIF otherAssocRole; // try all roles the start topic plays while (itRoles.hasNext()) { assocRole = itRoles.next(); assoc = assocRole.getAssociation(); assocRoles = assoc.getRoles(); itAssocRoles = assocRoles.iterator(); // get all associations within this association role while (itAssocRoles.hasNext()) { otherAssocRole = itAssocRoles.next(); // stop further processing if found association TopicIF player = otherAssocRole.getPlayer(); if (player != null && player.equals(associated)) return true; } } // while itRoles return false; } /** * INTERNAL: Returns the topics which are supertypes of the given typing topic * * @param klass a topicIF; the given typing topic * @return an unmodifiable collection of topicIF objects; the * supertypes of the given topic */ public Collection<TopicIF> getSuperclasses(TopicIF klass) { return supertypesWalker.walkTopics(klass); } /** * INTERNAL: Returns the topics which are supertypes of the given * typing topic up to a given level. If the level is 1 only the * immediate superclasses are returned, at level 2 immediate * superclasses and their immediate superclasses are returned, and * so on. To get all superclasses, regardless of level, use the * variant of this method which has no numeric parameter. * * @param klass a topicIF; the given typing topic * @param level the level to which superclasses are to be found * @return an unmodifiable collection of topicIF objects; the * supertypes of the given topic * @since 1.2.5 */ public Collection<TopicIF> getSuperclasses(TopicIF klass, int level) { Collection<TopicIF> supers = new ArrayList<TopicIF>(); Iterator<List<TMObjectIF>> it = supertypesWalker.walkPaths(klass).iterator(); while (it.hasNext()) { List<TMObjectIF> path = it.next(); for (int ix = 2; ix < path.size() && ix <= level*2; ix += 2) supers.add((TopicIF) path.get(ix)); } return supers; } /** * INTERNAL: Returns the topics which are subtypes of the topic klass. * * @param klass a topicIF; the given typing topic * @return a collection of topicIF objects; the subtypes of the given topic */ public Collection<TopicIF> getSubclasses(TopicIF klass) { return subtypesWalker.walkTopics(klass); } /** * INTERNAL: Returns the topics which are subtypes of the given typing * topic down to a given level. If the level is 1 only the immediate * subclasses are returned, at level 2 immediate subclasses and * their immediate subclasses are returned, and so on. To get all * subclasses, regardless of level, use the variant of this method * which has no numeric parameter. * * @param klass a topicIF; the given typing topic * @param level the level to which subclasses are to be found * @return a collection of topicIF objects; the subtypes of the given topic * @since 1.2.5 */ public Collection<TopicIF> getSubclasses(TopicIF klass, int level) { Collection<TopicIF> subs = new ArrayList<TopicIF>(); Iterator<List<TMObjectIF>> it = subtypesWalker.walkPaths(klass).iterator(); while (it.hasNext()) { List<TMObjectIF> path = it.next(); for (int ix = 2; ix < path.size() && ix <= level*2; ix += 2) subs.add((TopicIF) path.get(ix)); } return subs; } /** * INTERNAL: Returns the topic which types the singly typed object * <code>typed</code> and all of its supertypes. * * @param typed a typedIF object * @return a collection of topicIF objects; the type and all * supertypes of the given typedIF object. */ public Collection<TopicIF> getSupertypes(TypedIF typed) { if (typed.getType() == null) return new ArrayList<TopicIF>(); return supertypesWalker.walkTopics(typed.getType()); } /** * INTERNAL: Returns the topics which types of the object * <code>typed</code> and all their supertypes. * * @param typed a topic * @return a collection of topicIF objects; the type and all supertypes * of the given object */ public Collection<TopicIF> getSupertypes(TopicIF typed) { return getSupertypes(typed, false); } /** * INTERNAL: Returns the topics which types the object * <code>typed</code> (if not <code>excludeTypes</code> is set to * true) and all their supertypes. * * @param typed a topic * @param excludeTypes types of specified topic are not in returned * collection * @return a collection of topicIF objects; the type and all * supertypes of the given object * @since 1.1 */ public Collection<TopicIF> getSupertypes(TopicIF typed, boolean excludeTypes) { Set<TopicIF> ret = new HashSet<TopicIF>(); Iterator<TopicIF> it = typed.getTypes().iterator(); while (it.hasNext()) { TopicIF type = it.next(); // add type (if wanted) if (!excludeTypes) ret.add(type); // add supertypes of this type ret.addAll(supertypesWalker.walkTopics(type)); } return ret; } }