/*
* EuroCarbDB, a framework for carbohydrate bioinformatics
*
* Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
* A copy of this license accompanies this distribution in the file LICENSE.txt.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* Last commit: $Rev: 1500 $ by $Author: glycoslave $ on $Date:: 2009-07-14 #$
*/
package org.eurocarbdb.util.graph;
import java.util.Set;
import java.util.List;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ListIterator;
import java.io.Serializable;
import org.apache.log4j.Logger;
import org.eurocarbdb.util.BitSet;
import org.eurocarbdb.util.graph.Edge;
import org.eurocarbdb.util.graph.Vertex;
import org.eurocarbdb.util.graph.Graph;
import static org.eurocarbdb.util.StringUtils.join;
/* class Path *//**************************************************
*<p>
* Abstracts the notion of an ordered traversal through {@link Vertex}es
* and {@link Edge}s in a {@link Graph}.
*</p>
*<p>
* Paths have {@link List}-like semantics, in the sense they have
* an implicit order, and may contain (traverse) the same vertex
* or edge multiple times.
*</p>
*<p>
* {@link Path}s may traverse over just vertexes, or vertexes + edges.
* No restrictions are placed on the continuity of vertexes in a Path,
* however edges that are added must be continuous with a previously-
* added vertex.
*</p>
*<p>
* Like edges, this class is <em>parameterised</em> by {@link Edge}
* (E) and Vertex (V) type.
*</p>
*
* @see Graph
* @see Graphs
* @see Edge
* @see Vertex
*
* @version $Rev: 1500 $
* @author mjh
*/
public class Path<E,V> implements Serializable //, Iterable<Edge<E,V>>
{
/** logging handle */
static Logger log = Logger.getLogger( Graph.class );
//~~~~~~~~~~~~~~~~~~~~~~~~ FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~~~~//
/** The graph being walked */
private final Graph<E,V> graph;
/** The path through the graph, may be either {@link Edge}
* or {@link Vertex} instances */
private List<Object> path;
/** True indexes point to Edge instances, otherwise they are Vertexes. */
private BitSet edges;
//~~~~~~~~~~~~~~~~~~~~~~ CONSTRUCTORS ~~~~~~~~~~~~~~~~~~~~~~~//
/** Sole constructor. */
public Path( Graph<E,V> g )
{
graph = g;
edges = new BitSet();
path = new ArrayList<Object>();
}
//~~~~~~~~~~~~~~~~~~~~~~~~ METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~~//
/*
public int compareTo( Vertex<E,V> other )
{
return ((Integer) this.index).compareTo( other.index );
}
public boolean equals( Vertex<E,V> other )
{
return this.index == other.index;
}
*/
/** Add a {@link Vertex} to this {@link Path}. */
public void add( Vertex<E,V> v )
{
if ( ! graph.contains( v ) )
{
throw new IllegalArgumentException(
"vertex not in graph");
// graph.addVertex( v );
}
edges.set( path.size(), false );
path.add( v );
}
/**
* Add an {@link Edge} to this {@link Path}.
*
* @throws NullPointerException
* if Edge argument is null.
* @throws IllegalArgumentException
* if Edge is not continuous with last added Edge or Vertex.
*/
public void add( Edge<E,V> e )
throws NullPointerException, IllegalArgumentException
{
Object x = getLast();
if ( x != null )
{
if ( edges.get( path.size() - 1 ) )
{
// x == Edge
Edge<E,V> edge = (Edge<E,V>) x;
if (
edge.getParent() != e.getParent()
&& edge.getParent() != e.getChild()
&& edge.getChild() != e.getParent()
&& edge.getChild() != e.getChild()
)
throw new IllegalArgumentException(
"Edge not continuous with last added Edge");
}
else
{
// x == Vertex
Vertex<E,V> v = (Vertex<E,V>) x;
if ( v != e.getParent() && v != e.getChild() )
throw new IllegalArgumentException(
"Edge not continuous with last added Vertex");
}
}
edges.set( path.size(), true );
path.add( e );
}
/**
* Returns the {@link Edge} or {@link Vertex} at the given index.
*
* @throws IndexOutOfBoundsException if given index is negative
* or greater than/equal to {@link #size()}.
*/
public Object get( int i )
throws IndexOutOfBoundsException
{
return path.get(i);
}
/**
* Returns the <em>value</em> of the {@link Edge} or
* {@link Vertex} at the given index.
*
* @throws IndexOutOfBoundsException if given index is negative
* or greater than/equal to {@link #size()}.
*/
public Object getValue( int i )
{
return edges.get(i)
? ((Edge<E,V>) path.get(i)).getValue()
: ((Vertex<E,V>) path.get(i)).getValue();
}
/** Returns the {@link Graph} through which this {@link Path} traverses. */
public Graph<E,V> getGraph()
{
return graph;
}
/**
* Returns a {@link List} of all the {@link Edge}s
* in this {@link Path}, in order.
*/
public List<Edge<E,V>> edges()
{
List<Edge<E,V>> edge_list
= new ArrayList<Edge<E,V>>( countEdges() );
for ( int i : edges )
{
edge_list.add( (Edge<E,V>) path.get(i) );
}
return edge_list;
}
/**
* Returns a {@link List} of all the {@link Vertex}es
* in this {@link Path}, in order.
*/
public List<Vertex<E,V>> vertexes()
{
List<Vertex<E,V>> vertexes
= new ArrayList<Vertex<E,V>>( countVertexes() );
for ( int i = 0; i < path.size(); i++ )
{
if ( edges.get(i) )
continue;
vertexes.add( (Vertex<E,V>) path.get(i) );
}
return vertexes;
}
/**
* Returns an unmodifiable {@link List} of the {@link Edge}s
* and {@link Vertex}es in this {@link Path}, in order.
*/
public List<?> elements()
{
return Collections.unmodifiableList( path );
}
public <T> List<T> elements( List<T> list )
{
for ( int i = 0; i < path.size(); i++ )
list.add( (T) path.get( i ) );
return list;
}
/**
* Returns a {@link List} of the <em>values</em> each of the
* {@link Edge}s and {@link Vertex}es in this {@link Path}, in order.
*/
public List<?> values()
{
return values( new ArrayList<Object>( path.size() ) );
}
/**
* Similar to {@link #values()}, in that it returns the {@link List}
* of {@link Edge} and {@link Vertex} values in order, but
* using the passed {@link List} as the recipient and return value.
* This allows client code to pass in a specific {@link List}
* implementation.
*/
public <T> List<T> values( List<T> list )
{
for ( int i = 0; i < path.size(); i++ )
list.add( (T) getValue( i ) );
return list;
}
/**
* Returns the <em>i</em>th {@link Edge} in the {@link Path}, when
* Edges are traversed in order.
*
* @throws IndexOutOfBoundsException
* if the given count is less than or equal to zero,
* or greater than {@link #countEdges()}.
*/
public Edge<E,V> getEdge( int count )
throws IndexOutOfBoundsException
{
int c = 0;
for ( int i : edges )
if ( ++c == count )
return (Edge<E,V>) path.get(i);
throw new IndexOutOfBoundsException(
"Invalid index: " + count + "; valid indexes are: " + edges );
}
/**
* Returns the <em>i</em>th {@link Vertex} in the {@link Path}, when
* Vertexes are traversed in order.
*
* @throws IndexOutOfBoundsException
* if the given count is less than or equal to zero,
* or greater than {@link #countEdges()}.
*/
public Vertex<E,V> getVertex( int count )
{
int c = 0;
for ( int i = 0; i < path.size(); i++ )
if ( edges.get(i) == false )
if ( ++c == count )
return (Vertex<E,V>) path.get(i);
throw new IndexOutOfBoundsException(
"Invalid index: "
+ count
+ "; valid indexes are: "
+ edges.bitSlice( 0, path.size() ).bitComplementEquals()
);
}
/**
* Returns the last {@link Edge} added, or null if there
* are no Edges or {@link #size} == 0.
*/
public Edge<E,V> getLastEdge()
{
if ( path.size() == 0 )
return null;
int i = edges.highestSetBit();
if ( i == -1 )
return null;
return (Edge<E,V>) path.get( i );
}
/**
* Returns the last {@link Vertex} added, or null if there
* are no Vertexes or {@link #size} == 0.
*/
public Vertex<E,V> getLastVertex()
{
for ( int i = path.size() - 1; i > -1; i-- )
if ( ! edges.get(i) )
return (Vertex<E,V>) path.get( i );
return null;
}
/** Returns the last-added {@link Edge} or {@link Vertex}. */
public Object getLast()
{
if ( path.size() == 0 )
return null;
return path.get( path.size() - 1 );
}
/** Reverses the order of this entire {@link Path}. */
public void reverse()
{
Collections.reverse( path );
// reverse edges bitmask as well
int size = path.size();
for (
int left = 0, mid = size >> 1, right = size - 1;
left < mid;
left++, right-- )
{
boolean b = edges.get( left );
edges.set( left, edges.get( right ) );
edges.set( right, b );
}
}
/** Returns the number of elements in this {@link Path}. */
public int size()
{
return path.size();
}
/** Returns the number of {@link Edge}s in this {@link Path}. */
public int countEdges()
{
return edges.size();
}
/** Returns the number of {@link Vertex}es in this {@link Path}. */
public int countVertexes()
{
return path.size() - edges.size();
}
/** Returns the {@link String} form of each of the elements of
* this {@link Path}, concatenated with the string " -> ". */
public String toString()
{
return join( " -> ", path );
}
/**
*
*/
public class Iterator implements java.util.Iterator<Object>
{
final Path path;
final ListIterator iter;
public Iterator( Path p )
{
path = p;
iter = p.path.listIterator();
}
public void add( Object x ) { NOT_IMPLEMENTED(); }
public void remove() { NOT_IMPLEMENTED(); }
public void set( Object x ) { NOT_IMPLEMENTED(); }
public boolean hasNext()
{
return iter.hasNext();
}
public boolean hasEdgeNext()
{
return ( iter.hasNext() && edges.get( iter.nextIndex() ) );
}
public boolean hasVertexNext()
{
return ! hasEdgeNext();
}
public Object next()
{
return iter.next();
}
public int nextIndex()
{
return iter.nextIndex();
}
public Edge<E,V> nextEdge()
{
return (Edge<E,V>) next();
}
public Vertex<E,V> nextVertex()
{
return (Vertex<E,V>) next();
}
public boolean hasPrevious()
{
return iter.hasPrevious();
}
public Object previous()
{
return iter.hasPrevious();
}
private final void NOT_IMPLEMENTED()
{
throw new UnsupportedOperationException(
"method not supported");
}
} // end inner class
} // end class Path -------------------------------------------