package prefuse.data.tuple; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import prefuse.data.Table; import prefuse.data.Tuple; import prefuse.data.event.TupleSetListener; import prefuse.data.expression.Expression; import prefuse.data.expression.Predicate; import prefuse.data.expression.parser.ExpressionParser; import prefuse.util.collections.CompositeIterator; /** * <p>TupleSet implementation for treating a collection of tuple sets * as a single, composite tuple set. This composite does not take * overlap between contained TupleSets into account.</p> * * <p>The {@link TupleSet#addTuple(Tuple)} and {@link #setTuple(Tuple)} * methods are not supported by this class, and calling these methods will * result in a UnsupportedOperationException. Instead, use the add or set * methods on the desired non-composite tuple set.</p> * * @author <a href="http://jheer.org">jeffrey heer</a> */ public class CompositeTupleSet extends AbstractTupleSet { private static final Logger s_logger = Logger.getLogger(CompositeTupleSet.class.getName()); private Map m_map; // map names to tuple sets private Set m_sets; // support quick reverse lookup private int m_count; // count of total tuples private Listener m_lstnr; /** * Create a new, empty CompositeTupleSet */ public CompositeTupleSet() { this(true); } protected CompositeTupleSet(boolean listen) { m_map = new LinkedHashMap(); m_sets = new HashSet(); m_count = 0; m_lstnr = listen ? new Listener() : null; } /** * Add a TupleSet to this composite. * @param name the name of the TupleSet to add * @param set the set to add */ public void addSet(String name, TupleSet set) { if ( hasSet(name) ) { throw new IllegalArgumentException("Name already in use: "+name); } m_map.put(name, set); m_sets.add(set); m_count += set.getTupleCount(); if ( m_lstnr != null ) set.addTupleSetListener(m_lstnr); } /** * Indicates if this composite contains a TupleSet with the given name. * @param name the name to look for * @return true if a TupleSet with the given name is found, false otherwise */ public boolean hasSet(String name) { return m_map.containsKey(name); } /** * Indicates if this composite contains the given TupleSet. * @param set the TupleSet to check for containment * @return true if the TupleSet is contained in this composite, * false otherwise */ public boolean containsSet(TupleSet set) { return m_sets.contains(set); } /** * Get the TupleSet associated with the given name. * @param name the name of the TupleSet to get * @return the associated TupleSet, or null if not found */ public TupleSet getSet(String name) { return (TupleSet)m_map.get(name); } /** * Get an iterator over the names of all the TupleSets in this composite. * @return the iterator over contained set names. */ public Iterator setNames() { return m_map.keySet().iterator(); } /** * Get an iterator over all the TupleSets in this composite. * @return the iterator contained sets. */ public Iterator sets() { return m_map.values().iterator(); } /** * Remove the TupleSet with the given name from this composite. * @param name the name of the TupleSet to remove * @return the removed TupleSet, or null if not found */ public TupleSet removeSet(String name) { TupleSet ts = (TupleSet)m_map.remove(name); if ( ts != null ) { m_sets.remove(ts); if ( m_lstnr != null ) ts.removeTupleSetListener(m_lstnr); } return ts; } /** * Remove all contained TupleSets from this composite. */ public void removeAllSets() { Iterator sets = m_map.entrySet().iterator(); while ( sets.hasNext() ) { Map.Entry entry = (Map.Entry)sets.next(); TupleSet ts = (TupleSet)entry.getValue(); sets.remove(); m_sets.remove(ts); if ( m_lstnr != null ) ts.removeTupleSetListener(m_lstnr); } m_count = 0; } /** * Clear this TupleSet, calling clear on all contained TupleSet * instances. All contained TupleSets remain members of this * composite, but they have their data cleared. * @see prefuse.data.tuple.TupleSet#clear() */ public void clear() { Iterator sets = m_map.entrySet().iterator(); while ( sets.hasNext() ) { Map.Entry entry = (Map.Entry)sets.next(); ((TupleSet)entry.getValue()).clear(); } m_count = 0; } // ------------------------------------------------------------------------ // TupleSet Interface /** * Not supported. * @see prefuse.data.tuple.TupleSet#addTuple(prefuse.data.Tuple) */ public Tuple addTuple(Tuple t) { throw new UnsupportedOperationException(); } /** * Not supported. * @see prefuse.data.tuple.TupleSet#setTuple(prefuse.data.Tuple) */ public Tuple setTuple(Tuple t) { throw new UnsupportedOperationException(); } /** * Removes the tuple from its source set if that source set is contained * within this composite. * @see prefuse.data.tuple.TupleSet#removeTuple(prefuse.data.Tuple) */ public boolean removeTuple(Tuple t) { Table table = t.getTable(); if ( m_sets.contains(table) ) { return table.removeTuple(t); } else { return false; } } /** * @see prefuse.data.tuple.TupleSet#containsTuple(prefuse.data.Tuple) */ public boolean containsTuple(Tuple t) { Iterator it = m_map.entrySet().iterator(); while ( it.hasNext() ) { Map.Entry entry = (Map.Entry)it.next(); TupleSet ts = (TupleSet)entry.getValue(); if ( ts.containsTuple(t) ) return true; } return false; } /** * @see prefuse.data.tuple.TupleSet#getTupleCount() */ public int getTupleCount() { if ( m_lstnr != null ) { return m_count; } else { int count = 0; Iterator it = m_map.entrySet().iterator(); for ( int i=0; it.hasNext(); ++i ) { Map.Entry entry = (Map.Entry)it.next(); TupleSet ts = (TupleSet)entry.getValue(); count += ts.getTupleCount(); } return count; } } /** * @see prefuse.data.tuple.TupleSet#tuples() */ public Iterator tuples() { CompositeIterator ci = new CompositeIterator(m_map.size()); Iterator it = m_map.entrySet().iterator(); for ( int i=0; it.hasNext(); ++i ) { Map.Entry entry = (Map.Entry)it.next(); TupleSet ts = (TupleSet)entry.getValue(); ci.setIterator(i, ts.tuples()); } return ci; } /** * @see prefuse.data.tuple.TupleSet#tuples(prefuse.data.expression.Predicate) */ public Iterator tuples(Predicate filter) { CompositeIterator ci = new CompositeIterator(m_map.size()); Iterator it = m_map.entrySet().iterator(); for ( int i=0; it.hasNext(); ++i ) { Map.Entry entry = (Map.Entry)it.next(); TupleSet ts = (TupleSet)entry.getValue(); ci.setIterator(i, ts.tuples(filter)); } return ci; } // -- Data Field Methods -------------------------------------------------- /** * Returns true. * @see prefuse.data.tuple.TupleSet#isAddColumnSupported() */ public boolean isAddColumnSupported() { return true; } /** * Adds the value to all contained TupleSets that return a true value for * {@link TupleSet#isAddColumnSupported()}. * @see prefuse.data.tuple.TupleSet#addColumn(java.lang.String, java.lang.Class, java.lang.Object) */ public void addColumn(String name, Class type, Object defaultValue) { Iterator it = m_map.entrySet().iterator(); while ( it.hasNext() ) { Map.Entry entry = (Map.Entry)it.next(); TupleSet ts = (TupleSet)entry.getValue(); if ( ts.isAddColumnSupported() ) { try { ts.addColumn(name, type, defaultValue); } catch ( IllegalArgumentException iae ) { // already exists } } else { s_logger.fine("Skipped addColumn for "+entry.getKey()); } } } /** * Adds the value to all contained TupleSets that return a true value for * {@link TupleSet#isAddColumnSupported()}. * @see prefuse.data.tuple.TupleSet#addColumn(java.lang.String, java.lang.Class) */ public void addColumn(String name, Class type) { Iterator it = m_map.entrySet().iterator(); while ( it.hasNext() ) { Map.Entry entry = (Map.Entry)it.next(); TupleSet ts = (TupleSet)entry.getValue(); if ( ts.isAddColumnSupported() ) { try { ts.addColumn(name, type); } catch ( IllegalArgumentException iae ) { // already exists } } else { s_logger.fine("Skipped addColumn for "+entry.getKey()); } } } /** * Adds the value to all contained TupleSets that return a true value for * {@link TupleSet#isAddColumnSupported()}. * @see prefuse.data.tuple.TupleSet#addColumn(java.lang.String, prefuse.data.expression.Expression) */ public void addColumn(String name, Expression expr) { Iterator it = m_map.entrySet().iterator(); while ( it.hasNext() ) { Map.Entry entry = (Map.Entry)it.next(); TupleSet ts = (TupleSet)entry.getValue(); if ( ts.isAddColumnSupported() ) { try { ts.addColumn(name, expr); } catch ( IllegalArgumentException iae ) { // already exists } } else { s_logger.fine("Skipped addColumn for "+entry.getKey()); } } } /** * Adds the value to all contained TupleSets that return a true value for * {@link TupleSet#isAddColumnSupported()}. * @see prefuse.data.tuple.TupleSet#addColumn(java.lang.String, java.lang.String) */ public void addColumn(String name, String expr) { Expression ex = ExpressionParser.parse(expr); Throwable t = ExpressionParser.getError(); if ( t != null ) { throw new RuntimeException(t); } else { addColumn(name, ex); } } // ------------------------------------------------------------------------ // Internal TupleSet Listener /** * Listener that relays tuple set change events as they occur and updates * the total tuple count appropriately. */ private class Listener implements TupleSetListener { public void tupleSetChanged(TupleSet tset, Tuple[] add, Tuple[] rem) { m_count += add.length - rem.length; fireTupleEvent(add, rem); } } } // end of class CompositeTupleSet