Package org.mindswap.pellet.taxonomy

Source Code of org.mindswap.pellet.taxonomy.Taxonomy$SimpleImmutableEntry

// Portions Copyright (c) 2006 - 2008, Clark & Parsia, LLC. <http://www.clarkparsia.com>
// Clark & Parsia, LLC parts of this source code are available under the terms of the Affero General Public License v3.
//
// Please see LICENSE.txt for full license terms, including the availability of proprietary exceptions.
// Questions, comments, or requests for clarification: licensing@clarkparsia.com
//
// ---
// Portions Copyright (c) 2003 Ron Alford, Mike Grove, Bijan Parsia, Evren Sirin
// Alford, Grove, Parsia, Sirin parts of this source code are available under the terms of the MIT License.
//
// The MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

package org.mindswap.pellet.taxonomy;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.mindswap.pellet.exceptions.InternalReasonerException;
import org.mindswap.pellet.utils.Bool;

import com.clarkparsia.pellet.utils.CollectionUtils;

/*
* Created on Aug 13, 2003
*/

/**
* @author Evren Sirin
*/
public class Taxonomy<T> {
  private class DatumEquivalentsPairIterator<U> implements Iterator<Map.Entry<Set<U>, Object>> {

    private Iterator<TaxonomyNode<U>>  i;
    private Object            key;

    public DatumEquivalentsPairIterator(Taxonomy<U> t, Object key) {
      this.key = key;
      i = t.getNodes().iterator();
    }

    public boolean hasNext() {
      return i.hasNext();
    }

    public Entry<Set<U>, Object> next() {
      TaxonomyNode<U> current = i.next();
      return new SimpleImmutableEntry<Set<U>, Object>( Collections.unmodifiableSet( current
          .getEquivalents() ), current.getDatum( key ) );
    }

    public void remove() {
      throw new UnsupportedOperationException();
    }
  }

  private class DepthFirstDatumOnlyIterator<U> implements Iterator<Object> {

    private Object          key;
    private List<TaxonomyNode<U>>  pending;
    private Set<TaxonomyNode<U>>  visited;

    public DepthFirstDatumOnlyIterator(Taxonomy<U> t, U u, Object key) {
      this.key = key;
      visited = new HashSet<TaxonomyNode<U>>();
      pending = new ArrayList<TaxonomyNode<U>>();
      TaxonomyNode<U> node = t.getNode( u );
      if( node != null )
        pending.add( node );
    }

    public boolean hasNext() {
      return !pending.isEmpty();
    }

    public Object next() {
      if( pending.isEmpty() )
        throw new NoSuchElementException();

      TaxonomyNode<U> current = pending.remove( pending.size() - 1 );
      visited.add( current );
      for( TaxonomyNode<U> sub : current.getSubs() ) {
        if( !visited.contains( sub ) )
          pending.add( sub );
      }

      return current.getDatum( key );
    }

    public void remove() {
      throw new UnsupportedOperationException();
    }
  }

  private class SimpleImmutableEntry<K, V> implements Map.Entry<K, V> {

    private K  key;
    private V  value;

    public SimpleImmutableEntry(K key, V value) {
      super();
      this.key = key;
      this.value = value;
    }

    public K getKey() {
      return key;
    }

    public V getValue() {
      return value;
    }

    public V setValue(V value) {
      throw new UnsupportedOperationException();
    }
  }

  public static final Logger      log      = Logger.getLogger( Taxonomy.class.getName() );

  private static final boolean    SUB      = true;

  private static final boolean    SUPER    = false;

  public static final boolean      TOP_DOWN  = true;

  protected TaxonomyNode<T>      bottomNode;
  protected Map<T, TaxonomyNode<T>>  nodes;
  protected TaxonomyNode<T>      topNode;
 
  protected short depth = 0;
  protected int totalBranching = 0;

  public Taxonomy() {
    this( null, null, null );
  }

  public Taxonomy(Collection<T> elements, T top, T bottom) {
    nodes = CollectionUtils.makeMap();

    if( top == null )
      topNode = new TaxonomyNode<T>( (T) null, /* hidden = */true );
    else {
      topNode = new TaxonomyNode<T>( top, /* hidden = */false );
      nodes.put( top, topNode );
    }

    if( bottom == null )
      bottomNode = new TaxonomyNode<T>( (T) null, /* hidden = */true );
    else {
      bottomNode = new TaxonomyNode<T>( bottom, /* hidden = */false );
      nodes.put( bottom, bottomNode );
    }

    if( elements == null || elements.isEmpty() )
      topNode.addSub( bottomNode );
    else
      for( T t : elements ) {
        addNode( t, /* hidden = */false );
      }

    // precaution to avoid creating an invalid taxonomy is now done by
    // calling assertValid function because the taxonomy might be invalid
    // during the merge operation but it is guaranteed to be valid after
    // the merge is completed. so we check for validity at the very end
    // TOP_NODE.setSupers( Collections.EMPTY_LIST );
    // BOTTOM_NODE.setSubs( Collections.EMPTY_LIST );
  }

  public void addEquivalentNode(T t, TaxonomyNode<T> node) {
    node.addEquivalent( t );
    nodes.put( t, node );
  }

  /**
   * Add a collection of elements equivalent to an element already in the
   * taxonomy.
   */
  public void addEquivalents(T t, Collection<T> eqs) {

    assert nodes.keySet().contains( t ) : "Element " + t.toString() + " not in taxonomy";

    TaxonomyNode<T> node = nodes.get( t );
    for( T eq : eqs ) {
      assert !nodes.keySet().contains( eq ) : "Element " + eq.toString()
          + " alread in taxonomy";
      node.addEquivalent( eq );
      nodes.put( eq, node );
    }
  }
 
  /**
   * Add a node with known supers and subs. Any direct relations between subs
   * and supers are removed.
   *
   * @param equivalents
   *            a non-empty set of equivalent elements defining the node (one
   *            of which becomes the label)
   * @param sups
   *            collection of supers, all of which must already exist in the
   *            taxonomy
   * @param subs
   *            collection of subs, all of which must already exist in the
   *            taxonomy
   * @param hidden
   *            indicates hidden or not
   * @return the new node
   */
  public TaxonomyNode<T> addNode(Collection<T> equivalents, Collection<T> sups,
      Collection<T> subs, boolean hidden) {

    assert !equivalents.isEmpty() : "Taxonomy nodes must have at least one element";
    assert nodes.keySet().containsAll( sups ) : "At least one super element not in taxonomy";
    assert nodes.keySet().containsAll( subs ) : "At least one sub element not in taxonomy";

    TaxonomyNode<T> node = new TaxonomyNode<T>( equivalents, hidden );
    for( T t : equivalents ) {
      nodes.put( t, node );
    }
   
    short depth = 1;

    // Super handling
    {
      /*
       * Note the special case when no supers are provided and top is
       * hidden. Top points to the new node, but not the reverse
       */
      if( sups.isEmpty() ) {
        if( topNode.isHidden() ) {
          topNode.addSub( node );
          if( topNode.getSubs().size() == 2 )
            topNode.removeSub( bottomNode );
        }
        else
          node.addSupers( Collections.singleton( topNode ) );
       
        totalBranching += 1;
      }
      else {
        Set<TaxonomyNode<T>> supNodes = new HashSet<TaxonomyNode<T>>();
        for( T sup : sups ) {
          TaxonomyNode<T> supNode = nodes.get( sup );
          if( supNode.depth >= depth )
            depth = (short) (supNode.depth + 1);
          supNodes.add( supNode );
        }
        node.depth = depth;
        if( depth > this.depth )
          this.depth = depth;
        node.addSupers( supNodes );
       
        totalBranching += supNodes.size();
      }
    }

    // Sub handling
    {
      Set<TaxonomyNode<T>> subNodes;
      if( subs.isEmpty() ) {
        if( bottomNode.isHidden() ) {
          bottomNode.addSupers( Collections.singleton( node ) );
          bottomNode.getSupers().removeAll( node.getSupers() );
        }
        else
          node.addSub( bottomNode );
       
        totalBranching += 1;
      }
      else {
        subNodes = new HashSet<TaxonomyNode<T>>();
        for( T sub : subs ) {
          subNodes.add( nodes.get( sub ) );
        }
        node.addSubs( subNodes );
       
        totalBranching += subNodes.size();
      }
    }

    node.removeMultiplePaths();

    return node;
  }

  public TaxonomyNode<T> addNode(T t, boolean hidden) {
    TaxonomyNode<T> node = new TaxonomyNode<T>( t, hidden );
    topNode.addSub( node );
    node.addSub( bottomNode );
    nodes.put( t, node );
    return node;
  }

  /**
   * Add a collection of elements as subs to an element
   */
  public void addSuper(Collection<T> subs, T sup) {

    assert nodes.keySet().containsAll( subs ) : "At least one sub element not in taxonomy";
    assert nodes.keySet().contains( sup ) : "Super element " + sup.toString()
        + " not in taxonomy";

    Set<TaxonomyNode<T>> subNodes = new HashSet<TaxonomyNode<T>>();
    for( T sub : subs ) {
      subNodes.add( nodes.get( sub ) );
    }
    TaxonomyNode<T> supNode = nodes.get( sup );

    for( TaxonomyNode<T> subNode : subNodes ) {
      if( subNode.getSupers().size() == 1 && subNode.getSupers().contains( topNode ) )
        topNode.removeSub( subNode );
    }

    if( supNode.getSubs().size() == 1 && supNode.getSubs().contains( bottomNode ) )
      supNode.removeSub( bottomNode );

    supNode.addSubs( subNodes );

  }

  /**
   * Add a sub/super relation
   */
  public void addSuper(T sub, T sup) {

    assert nodes.keySet().contains( sub ) : "Sub element " + sub.toString()
        + " not in taxonomy";
    assert nodes.keySet().contains( sup ) : "Super element " + sup.toString()
        + " not in taxonomy";

    TaxonomyNode<T> subNode = nodes.get( sub );
    TaxonomyNode<T> supNode = nodes.get( sup );
    if( subNode.equals( supNode ) )
      throw new InternalReasonerException(
          "Equivalent elements cannot have sub/super relationship" );

    if( subNode.getSupers().size() == 1 && subNode.getSupers().iterator().next() == topNode )
      topNode.removeSub( subNode );

    if( supNode.getSubs().size() == 1 && supNode.getSubs().iterator().next() == bottomNode )
      supNode.removeSub( bottomNode );

    supNode.addSub( subNode );
  }

  /**
   * Add a collection of supers to an element
   */
  public void addSupers(T sub, Collection<T> sups) {

    assert nodes.keySet().contains( sub ) : "Sub element " + sub.toString()
        + " not in taxonomy";
    assert nodes.keySet().containsAll( sups ) : "At least one super element not in taxonomy";

    TaxonomyNode<T> subNode = nodes.get( sub );
    Set<TaxonomyNode<T>> supNodes = new HashSet<TaxonomyNode<T>>();
    for( T sup : sups ) {
      supNodes.add( nodes.get( sup ) );
    }

    if( subNode.getSupers().size() == 1 && subNode.getSupers().contains( topNode ) )
      topNode.removeSub( subNode );

    for( TaxonomyNode<T> supNode : supNodes ) {
      if( supNode.getSubs().size() == 1 && supNode.getSubs().contains( bottomNode ) )
        supNode.removeSub( bottomNode );
    }

    subNode.addSupers( supNodes );
  }

  public void assertValid() {
    assert topNode.getSupers().isEmpty() : "Top node in the taxonomy has parents";
    assert bottomNode.getSubs().isEmpty() : "Bottom node in the taxonomy has children";
  }

  /**
   * Given a list of concepts, find all the Least Common Ancestors (LCA). Note
   * that a taxonomy is DAG not a tree so we do not have a unique LCA but a
   * set of LCA.
   */
  public List<T> computeLCA(List<T> list) {
    // FIXME does not work when one of the elements is an ancestor of the
    // rest
    // TODO what to do with equivalent classes?
    // TODO improve efficiency

    if( list.isEmpty() )
      return null;

    // get the first concept
    T t = list.get( 0 );

    // add all its ancestor as possible LCA candidates
    List<T> ancestors = new ArrayList<T>( getFlattenedSupers( t, /* direct = */false ) );

    for( int i = 1; (i < list.size()) && (ancestors.size() > 0); i++ ) {
      t = list.get( i );

      // take the intersection of possible candidates to get rid of
      // uncommon ancestors
      ancestors.retainAll( getFlattenedSupers( t, /* direct = */false ) );
    }

    Set<T> toBeRemoved = new HashSet<T>();

    // we have all common ancestors now remove the ones that have
    // descendants in the list
    for( T a : ancestors ) {

      if( toBeRemoved.contains( a ) )
        continue;

      Set<T> supers = getFlattenedSupers( a, /* direct = */false );
      toBeRemoved.addAll( supers );
    }

    ancestors.removeAll( toBeRemoved );

    return ancestors;
  }

  public boolean contains(T t) {
    return nodes.containsKey( t );
  }

  /**
   * Iterate over nodes in taxonomy (no specific order)returning pair of
   * equivalence set and datum associated with {@code key} for each. Useful,
   * e.g., to collect equivalence sets matching some condition on the datum
   * (as in all classes which have a particular instances)
   *
   * @param key
   *            key associated with datum returned
   * @return iterator over equivalence set, datum pairs
   */
  public Iterator<Map.Entry<Set<T>, Object>> datumEquivalentsPair(Object key) {
    return new DatumEquivalentsPairIterator<T>( this, key );
  }

  /**
   * Iterate down taxonomy in a depth first traversal, beginning with class
   * {@code c}, returning only datum associated with {@code key} for each.
   * Useful, e.g., to collect datum values in a transitive closure (as in all
   * instances of a class).
   *
   * @param t
   *            starting location in taxonomy
   * @param key
   *            key associated with datum returned
   * @return datum iterator
   */
  public Iterator<Object> depthFirstDatumOnly(T t, Object key) {
    return new DepthFirstDatumOnlyIterator<T>( this, t, key );
  }

  /**
   * Returns all the classes that are equivalent to class c. Class c itself is
   * included in the result.
   *
   * @param t
   *            class whose equivalent classes are found
   * @return A set of ATerm objects
   */
  public Set<T> getAllEquivalents(T t) {
    TaxonomyNode<T> node = nodes.get( t );

    if( node == null )
      return new HashSet<T>();

    Set<T> result = new HashSet<T>( node.getEquivalents() );

    return result;
  }

  public TaxonomyNode<T> getBottom() {
    return bottomNode;
  }

  public Set<T> getClasses() {
    return nodes.keySet();
  }

  /**
   * Get datum on taxonomy elements associated with {@code key}
   *
   * @param t
   *            identifies the taxonomy element
   * @param key
   *            identifies the specific datum
   * @return the datum (or {@code null} if none is associated with {@code key})
   */
  public Object getDatum(T t, Object key) {
    TaxonomyNode<T> node = nodes.get( t );
    return (node == null)
      ? null
      : node.getDatum( key );
  }

  /**
   * Returns all the classes that are equivalent to class c. Class c itself is
   * NOT included in the result.
   *
   * @param t
   *            class whose equivalent classes are found
   * @return A set of ATerm objects
   */
  public Set<T> getEquivalents(T t) {
    Set<T> result = getAllEquivalents( t );
    result.remove( t );

    return result;
  }

  /**
   * As in {@link #getSubs(Object, boolean)} except the return value is the
   * union of nested sets
   */
  public Set<T> getFlattenedSubs(T t, boolean direct) {
    return getFlattenedSubSupers( t, direct, SUB );
  }

  /**
   * Use {@link #getFlattenedSubs(Object, boolean)} or
   *             {@link #getFlattenedSupers(Object, boolean)} this method will
   *             become private
   */
  private Set<T> getFlattenedSubSupers(T t, boolean direct, boolean subOrSuper) {
    TaxonomyNode<T> node = nodes.get( t );

    Set<T> result = new HashSet<T>();

    List<TaxonomyNode<T>> visit = new ArrayList<TaxonomyNode<T>>();
    visit.addAll( (subOrSuper == SUB)
      ? node.getSubs()
      : node.getSupers() );

    for( int i = 0; i < visit.size(); i++ ) {
      node = visit.get( i );

      if( node.isHidden() )
        continue;

      Set<T> add = node.getEquivalents();
      result.addAll( add );

      if( !direct )
        visit.addAll( (subOrSuper == SUB)
          ? node.getSubs()
          : node.getSupers() );
    }

    return result;
  }

  /**
   * As in {@link #getSupers(Object, boolean)} except the return value is the
   * union of nested sets
   */
  public Set<T> getFlattenedSupers(T t, boolean direct) {
    return getFlattenedSubSupers( t, direct, SUPER );
  }

  public TaxonomyNode<T> getNode(T t) {
    return nodes.get( t );
  }

  public Collection<TaxonomyNode<T>> getNodes() {
    return nodes.values();
  }

  /**
   * Returns all the (named) subclasses of class c. The class c itself is not
   * included in the list but all the other classes that are equivalent to c
   * are put into the list. Also note that the returned list will always have
   * at least one element, that is the BOTTOM concept. By definition BOTTOM
   * concept is subclass of every concept. This function is equivalent to
   * calling getSubClasses(c, true).
   *
   * @param t
   *            class whose subclasses are returned
   * @return A set of sets, where each set in the collection represents an
   *         equivalence class. The elements of the inner class are ATermAppl
   *         objects.
   */
  public Set<Set<T>> getSubs(T t) {
    return getSubs( t, false );
  }

  /**
   * Returns the (named) subclasses of class c. Depending on the second
   * parameter the resulting list will include either all subclasses or only
   * the direct subclasses. A class d is a direct subclass of c iff
   * <ol>
   * <li>d is subclass of c</li>
   * <li>there is no other class x different from c and d such that x is
   * subclass of c and d is subclass of x</li>
   * </ol>
   * The class c itself is not included in the list but all the other classes
   * that are sameAs c are put into the list. Also note that the returned list
   * will always have at least one element. The list will either include one
   * other concept from the hierarchy or the BOTTOM concept if no other class
   * is subsumed by c. By definition BOTTOM concept is subclass of every
   * concept.
   *
   * @param t
   *            Class whose subclasses are found
   * @param direct
   *            If true return only direct subclasses elese return all the
   *            subclasses
   * @return A set of sets, where each set in the collection represents an
   *         equivalence class. The elements of the inner class are ATermAppl
   *         objects.
   */
  public Set<Set<T>> getSubs(T t, boolean direct) {
    return getSubSupers( t, direct, SUB );
  }

  /**
   *  Use {@link #getSubs(Object, boolean)} or
   *             {@link #getSupers(Object, boolean)} this method will become
   *             private
   */
  private Set<Set<T>> getSubSupers(T t, boolean direct, boolean subOrSuper) {
    TaxonomyNode<T> node = nodes.get( t );

    if( node == null )
      return Collections.emptySet();

    Set<Set<T>> result = new HashSet<Set<T>>();

    List<TaxonomyNode<T>> visit = new ArrayList<TaxonomyNode<T>>();
    visit.addAll( (subOrSuper == SUB)
      ? node.getSubs()
      : node.getSupers() );

    for( int i = 0; i < visit.size(); i++ ) {
      node = visit.get( i );

      if( node.isHidden() )
        continue;

      Set<T> add = new HashSet<T>( node.getEquivalents() );
      if( !add.isEmpty() ) {
        result.add( add );
      }

      if( !direct )
        visit.addAll( (subOrSuper == SUB)
          ? node.getSubs()
          : node.getSupers() );
    }

    return result;
  }

  /**
   * Returns all the superclasses (implicitly or explicitly defined) of class
   * c. The class c itself is not included in the list. but all the other
   * classes that are sameAs c are put into the list. Also note that the
   * returned list will always have at least one element, that is TOP concept.
   * By definition TOP concept is superclass of every concept. This function
   * is equivalent to calling getSuperClasses(c, true).
   *
   * @param t
   *            class whose superclasses are returned
   * @return A set of sets, where each set in the collection represents an
   *         equivalence class. The elements of the inner class are ATermAppl
   *         objects.
   */
  public Set<Set<T>> getSupers(T t) {
    return getSupers( t, false );
  }

  /**
   * Returns the (named) superclasses of class c. Depending on the second
   * parameter the resulting list will include either all or only the direct
   * superclasses. A class d is a direct superclass of c iff
   * <ol>
   * <li> d is superclass of c </li>
   * <li> there is no other class x such that x is superclass of c and d is
   * superclass of x </li>
   * </ol>
   * The class c itself is not included in the list but all the other classes
   * that are sameAs c are put into the list. Also note that the returned list
   * will always have at least one element. The list will either include one
   * other concept from the hierarchy or the TOP concept if no other class
   * subsumes c. By definition TOP concept is superclass of every concept.
   *
   * @param t
   *            Class whose subclasses are found
   * @param direct
   *            If true return all the superclasses else return only direct
   *            superclasses
   * @return A set of sets, where each set in the collection represents an
   *         equivalence class. The elements of the inner class are ATermAppl
   *         objects.
   */
  public Set<Set<T>> getSupers(T t, boolean direct) {
    return getSubSupers( t, direct, SUPER );
  }

  public TaxonomyNode<T> getTop() {
    return topNode;
  }

  /**
   * Checks if x is equivalent to y
   *
   * @param x
   *            Name of the first class
   * @param y
   *            Name of the second class
   * @return true if x is equivalent to y
   */
  public Bool isEquivalent(T x, T y) {
    TaxonomyNode<T> nodeX = nodes.get( x );
    TaxonomyNode<T> nodeY = nodes.get( y );

    if( nodeX == null || nodeY == null )
      return Bool.UNKNOWN;
    else if( nodeX.equals( nodeY ) )
      return Bool.TRUE;
    else
      return Bool.FALSE;
  }

  /**
   * Checks if x has an ancestor y.
   *
   * @param x
   *            Name of the node
   * @param y
   *            Name of the ancestor ode
   * @return true if x has an ancestor y
   */
  public Bool isSubNodeOf(T x, T y) {
    TaxonomyNode<T> nodeX = nodes.get( x );
    TaxonomyNode<T> nodeY = nodes.get( y );

    if( nodeX == null || nodeY == null )
      return Bool.UNKNOWN;
    else if( nodeX.equals( nodeY ) )
      return Bool.TRUE;

    if( nodeX.isHidden() ) {
      if( nodeY.isHidden() )
        return Bool.UNKNOWN;
      else
        return getFlattenedSupers( x, /* direct = */false ).contains( y )
          ? Bool.TRUE
          : Bool.FALSE;
    }
    else
      return getFlattenedSubs( y, false ).contains( x )
        ? Bool.TRUE
        : Bool.FALSE;
  }

  public void merge(TaxonomyNode<T> node1, TaxonomyNode<T> node2) {
    List<TaxonomyNode<T>> mergeList = new ArrayList<TaxonomyNode<T>>( 2 );
    mergeList.add( node1 );
    mergeList.add( node2 );

    TaxonomyNode<T> node = mergeNodes( mergeList );

    removeCycles( node );
  }

  private TaxonomyNode<T> mergeNodes(List<TaxonomyNode<T>> mergeList) {

    assert mergeList.size() > 1 : "Attempt to merge less than two nodes";

    if( log.isLoggable( Level.FINER ) )
      log.finer( "Merge " + mergeList );

    TaxonomyNode<T> node = null;
    if( mergeList.contains( topNode ) ) {
      node = topNode;
    }
    else if( mergeList.contains( bottomNode ) ) {
      node = bottomNode;
    }
    else
      node = mergeList.get( 0 );

    Set<TaxonomyNode<T>> merged = new HashSet<TaxonomyNode<T>>();
    merged.add( node );

    for( TaxonomyNode<T> other : mergeList ) {

      if( merged.contains( other ) )
        continue;
      else
        merged.add( other );

      for( TaxonomyNode<T> sub : other.getSubs() ) {
        if( (sub != bottomNode) && !mergeList.contains( sub ) ) {
          if( (node.getSubs().size() == 1)
              && (node.getSubs().iterator().next() == bottomNode) )
            node.removeSub( bottomNode );
          node.addSub( sub );
        }
      }

      for( TaxonomyNode<T> sup : other.getSupers() ) {
        if( (sup != topNode) && !mergeList.contains( sup ) ) {
          if( (node.getSupers().size() == 1)
              && (node.getSupers().iterator().next() == topNode) )
            topNode.removeSub( node );
          sup.addSub( node );
        }
      }

      other.disconnect();

      for( T t : other.getEquivalents() ) {
        addEquivalentNode( t, node );
      }

    }

    node.clearData();

    if( node != topNode && node.getSupers().isEmpty() )
      topNode.addSub( node );

    if( node != bottomNode && node.getSubs().isEmpty() )
      node.addSub( bottomNode );

    return node;
  }

  /**
   * Set a datum value associated with {@code key} on a taxonomy element
   *
   * @param t
   *            identifies the taxonomy element
   * @param key
   *            identifies the datum
   * @param value
   *            the datum
   * @return previous value of datum or {@code null} if not set
   */
  public Object putDatum(T t, Object key, Object value) {
    TaxonomyNode<T> node = nodes.get( t );
    if( node == null )
      throw new RuntimeException( t + " is an unknown class!" );

    return node.putDatum( key, value );
  }

  /**
   * Remove an element from the taxonomy.
   */
  public void remove(T t) {
    assert nodes.containsKey( t ) : "Element not contained in taxonomy";

    TaxonomyNode<T> node = nodes.remove( t );
    if( node.getEquivalents().size() == 1 ) {
      Collection<TaxonomyNode<T>> subs = node.getSubs();
      Collection<TaxonomyNode<T>> supers = node.getSupers();
      node.disconnect();
      for( TaxonomyNode<T> sup : supers )
        sup.addSubs( subs );
    }
    else
      node.removeEquivalent( t );
  }

  /**
   * Walk through the super nodes of the given node and when a cycle is
   * detected merge all the nodes in that path
   */
  public void removeCycles(TaxonomyNode<T> node) {
    if( !nodes.get( node.getName() ).equals( node ) )
      throw new InternalReasonerException( "This node does not exist in the taxonomy: "
          + node.getName() );
    removeCycles( node, new ArrayList<TaxonomyNode<T>>() );
  }

  /**
   * Given a node and (a possibly empty) path of sub nodes, remove cycles by
   * merging all the nodes in the path.
   */
  private boolean removeCycles(TaxonomyNode<T> node, List<TaxonomyNode<T>> path) {
    // cycle detected
    if( path.contains( node ) ) {
      mergeNodes( path );
      return true;
    }
    else {
      // no cycle yet, add this node to the path and continue
      path.add( node );
     
      List<TaxonomyNode<T>> supers = new ArrayList<TaxonomyNode<T>>( node.getSupers() );
      for( int i = 0; i < supers.size(); ) {
        TaxonomyNode<T> sup = supers.get( i );
        // remove cycles involving super node
        removeCycles( sup, path );
        // if the super has been removed then no need
        // to increment the index
        if( i < supers.size() && supers.get( i ).equals( sup ) )
          i++;
      }
     
      // remove the node from the path
      path.remove( path.size() - 1 );
     
      return false;
    }
  }

  public Object removeDatum(T t, Object key) {
    return getNode( t ).removeDatum( key );
  }
 
  /**
   * Clear existing supers for an element and set to a new collection
   */
  public void resetSupers(T t, Collection<T> supers) {

    assert nodes.keySet().contains( t ) : "Element " + t.toString() + " not in taxonomy";
    assert nodes.keySet().containsAll( supers ) : "Supers not all contained in taxonomy";

    TaxonomyNode<T> node = nodes.get( t );

    List<TaxonomyNode<T>> initial = new ArrayList<TaxonomyNode<T>>( node.getSupers() );
    for( TaxonomyNode<T> n : initial )
      n.removeSub( node );

    if( supers.isEmpty() ) {
      topNode.addSub( node );
    }
    else {
      Set<TaxonomyNode<T>> added = new HashSet<TaxonomyNode<T>>();
      for( T sup : supers ) {
        TaxonomyNode<T> n = nodes.get( sup );
        if( added.add( n ) )
          n.addSub( node );
      }
    }
  }

 
  /**
   * Sort the nodes in the taxonomy using topological ordering starting from
   * top to bottom.
   *
   * @param includeEquivalents
   *            If false the equivalents in a node will be ignored and only
   *            the name of the node will be added to the result
   * @return List of node names sorted in topological ordering
   */ 
  public List<T> topologocialSort(boolean includeEquivalents) {
    return topologocialSort( includeEquivalents, null );
  }
 
  /**
   * Sort the nodes in the taxonomy using topological ordering starting from
   * top to bottom.
   *
   * @param includeEquivalents
   *            If false the equivalents in a node will be ignored and only
   *            the name of the node will be added to the result
   * @param comparator
   *            comparator to use sort the nodes at same level,
   *            <code>null</code> if no special ordering is needed
   * @return List of node names sorted in topological ordering
   */
  public List<T> topologocialSort(boolean includeEquivalents, Comparator<? super T> comparator) {
    Map<TaxonomyNode<T>, Integer> degrees = new HashMap<TaxonomyNode<T>, Integer>();
    Map<T,TaxonomyNode<T>> nodesPending = comparator == null
      ? new HashMap<T, TaxonomyNode<T>>()
      : new TreeMap<T, TaxonomyNode<T>>( comparator );
    Set<TaxonomyNode<T>> nodesLeft = new HashSet<TaxonomyNode<T>>();
    List<T> nodesSorted = new ArrayList<T>();

    log.fine( "Topological sort..." );

    for( TaxonomyNode<T> node : nodes.values() ) {
      if (node.isHidden())
        continue;
     
      nodesLeft.add( node );
      int degree = node.getSupers().size();
      if( degree == 0 ) {
        nodesPending.put( node.getName(), node );
        degrees.put( node, 0 );
      }
      else
        degrees.put( node, Integer.valueOf( degree ) );
    }

    for( int i = 0, size = nodesLeft.size(); i < size; i++ ) {
      if( nodesPending.isEmpty() )
        throw new InternalReasonerException( "Cycle detected in the taxonomy!" );

      TaxonomyNode<T> node = nodesPending.values().iterator().next();

      int deg = degrees.get( node );
      if( deg != 0 )
        throw new InternalReasonerException( "Cycle detected in the taxonomy " + node + " "
            + deg + " " + nodesSorted.size() + " " + nodes.size() );

      nodesPending.remove( node.getName() );
      nodesLeft.remove( node );
      if( includeEquivalents )
        nodesSorted.addAll( node.getEquivalents() );
      else
        nodesSorted.add( node.getName() );

      for( TaxonomyNode<T> sub : node.getSubs() ) {
        int degree = degrees.get( sub );
        if( degree == 1 ) {
          nodesPending.put( sub.getName(), sub );
          degrees.put( sub, 0 );
        }
        else
          degrees.put( sub, degree - 1 );
      }
    }

    if( !nodesLeft.isEmpty() )
      throw new InternalReasonerException( "Failed to sort elements: " + nodesLeft );

    log.fine( "done" );

    return nodesSorted;
  }
}
TOP

Related Classes of org.mindswap.pellet.taxonomy.Taxonomy$SimpleImmutableEntry

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.