Package org.graphstream.ui.graphicGraph

Source Code of org.graphstream.ui.graphicGraph.StyleGroupSet$NodeSet

/*
* Copyright 2006 - 2013
*     Stefan Balev     <stefan.balev@graphstream-project.org>
*     Julien Baudry    <julien.baudry@graphstream-project.org>
*     Antoine Dutot    <antoine.dutot@graphstream-project.org>
*     Yoann Pigné      <yoann.pigne@graphstream-project.org>
*     Guilhelm Savin   <guilhelm.savin@graphstream-project.org>
*
* This file is part of GraphStream <http://graphstream-project.org>.
*
* GraphStream is a library whose purpose is to handle static or dynamic
* graph, create them from scratch, file or any source and display them.
*
* This program is free software distributed under the terms of two licenses, the
* CeCILL-C license that fits European law, and the GNU Lesser General Public
* License. You can  use, modify and/ or redistribute the software under the terms
* of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
* URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
*/
package org.graphstream.ui.graphicGraph;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.graphstream.graph.Edge;
import org.graphstream.graph.Element;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.ui.graphicGraph.stylesheet.Rule;
import org.graphstream.ui.graphicGraph.stylesheet.Selector;
import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.ShadowMode;
import org.graphstream.ui.graphicGraph.stylesheet.StyleSheet;
import org.graphstream.ui.graphicGraph.stylesheet.StyleSheetListener;

/**
* A set of style groups.
*
* <p>
* This class is in charge or storing all the style groups and to update them.
* Each time an element is added or removed the groups are updated. Each time
* the style sheet changes the groups are updated.
* </p>
*
* @author Antoine Dutot
*/
public class StyleGroupSet implements StyleSheetListener {
  // Attribute

  /**
   * The style sheet.
   */
  protected StyleSheet stylesheet;

  /**
   * All the groups indexed by their unique identifier.
   */
  protected final Map<String, StyleGroup> groups = new TreeMap<String, StyleGroup>();

  /**
   * Allows to retrieve the group containing a node knowing the node id.
   */
  protected final Map<String, String> byNodeIdGroups = new TreeMap<String, String>();

  /**
   * Allows to retrieve the group containing an edge knowing the node id.
   */
  protected final Map<String, String> byEdgeIdGroups = new TreeMap<String, String>();

  /**
   * Allows to retrieve the group containing a sprite knowing the node id.
   */
  protected final Map<String, String> bySpriteIdGroups = new TreeMap<String, String>();

  /**
   * Allows to retrieve the group containing a graph knowing the node id.
   */
  protected final Map<String, String> byGraphIdGroups = new TreeMap<String, String>();

  /**
   * Virtual set of nodes. This set provides fake methods to make it appear as
   * a set of nodes whereas it only maps on the node style groups.
   */
  protected NodeSet nodeSet = new NodeSet();

  /**
   * Virtual set of edges. This set provides fake methods to make it appear as
   * a set of edges whereas it only maps on the edge style groups.
   */
  protected EdgeSet edgeSet = new EdgeSet();

  /**
   * Virtual set of sprites. This set provides fake methods to make it appear
   * as a set of sprites whereas it only maps on the sprite style groups.
   */
  protected SpriteSet spriteSet = new SpriteSet();

  /**
   * Virtual set of graphs. This set provides fake methods to make it appear
   * as a set of graphs whereas it only maps on the graph style groups.
   */
  protected GraphSet graphSet = new GraphSet();

  /**
   * The set of events actually occurring.
   */
  protected EventSet eventSet = new EventSet();

  /**
   * The groups sorted by their Z index.
   */
  protected ZIndex zIndex = new ZIndex();

  /**
   * Set of groups that cast shadow.
   */
  protected ShadowSet shadow = new ShadowSet();

  /**
   * Remove groups if they become empty?.
   */
  protected boolean removeEmptyGroups = true;

  /**
   * Set of listeners.
   */
  protected ArrayList<StyleGroupListener> listeners = new ArrayList<StyleGroupListener>();

  // Construction

  /**
   * New empty style group set, using the given style sheet to create style
   * groups. The group set installs itself as a listener of the style sheet.
   * So in order to completely stop using such a group, you must call
   * {@link #release()}.
   *
   * @param stylesheet
   *            The style sheet to use to create groups.
   */
  public StyleGroupSet(StyleSheet stylesheet) {
    this.stylesheet = stylesheet;

    stylesheet.addListener(this);
  }

  // Access

  /**
   * Number of groups.
   *
   * @return The number of groups.
   */
  public int getGroupCount() {
    return groups.size();
  }

  /**
   * Return a group by its unique identifier. The way group identifier are
   * constructed reflects their contents.
   *
   * @param groupId
   *            The group identifier.
   * @return The corresponding group or null if not found.
   */
  public StyleGroup getGroup(String groupId) {
    return groups.get(groupId);
  }

  /**
   * Iterator on the set of groups in no particular order.
   *
   * @return An iterator on the group set.
   */
  public Iterator<? extends StyleGroup> getGroupIterator() {
    return groups.values().iterator();
  }

  /**
   * Iterable set of groups elements, in no particular order.
   *
   * @return An iterable on the set of groups.
   */
  public Iterable<? extends StyleGroup> groups() {
    return groups.values();
  }

  /**
   * Iterator on the Z index.
   *
   * @return The z index iterator.
   */
  public Iterator<HashSet<StyleGroup>> getZIterator() {
    return zIndex.getIterator();
  }

  /**
   * Iterable set of "subsets of groups" sorted by Z level. Each subset of
   * groups is at the same Z level.
   *
   * @return The z levels.
   */
  public Iterable<HashSet<StyleGroup>> zIndex() {
    return zIndex;
  }

  /**
   * Iterator on the style groups that cast a shadow.
   *
   * @return The shadow groups iterator.
   */
  public Iterator<StyleGroup> getShadowIterator() {
    return shadow.getIterator();
  }

  /**
   * Iterable set of groups that cast shadow.
   *
   * @return All the groups that cast a shadow.
   */
  public Iterable<StyleGroup> shadows() {
    return shadow;
  }

  /**
   * True if the set contains and styles the node whose identifier is given.
   *
   * @param id
   *            The node identifier.
   * @return True if the node is in this set.
   */
  public boolean containsNode(String id) {
    return byNodeIdGroups.containsKey(id);
  }

  /**
   * True if the set contains and styles the edge whose identifier is given.
   *
   * @param id
   *            The edge identifier.
   * @return True if the edge is in this set.
   */
  public boolean containsEdge(String id) {
    return byEdgeIdGroups.containsKey(id);
  }

  /**
   * True if the set contains and styles the sprite whose identifier is given.
   *
   * @param id
   *            The sprite identifier.
   * @return True if the sprite is in this set.
   */
  public boolean containsSprite(String id) {
    return bySpriteIdGroups.containsKey(id);
  }

  /**
   * True if the set contains and styles the graph whose identifier is given.
   *
   * @param id
   *            The graph identifier.
   * @return True if the graph is in this set.
   */
  public boolean containsGraph(String id) {
    return byGraphIdGroups.containsKey(id);
  }

  /**
   * Get an element.
   *
   * @param id
   *            The element id.
   * @param elt2grp
   *            The kind of element.
   * @return The element or null if not found.
   */
  protected Element getElement(String id, Map<String, String> elt2grp) {
    String gid = elt2grp.get(id);

    if (gid != null) {
      StyleGroup group = groups.get(gid);
      return group.getElement(id);
    }

    return null;
  }

  /**
   * Get a node element knowing its identifier.
   *
   * @param id
   *            The node identifier.
   * @return The node if it is in this set, else null.
   */
  public Node getNode(String id) {
    return (Node) getElement(id, byNodeIdGroups);
  }

  /**
   * Get an edge element knowing its identifier.
   *
   * @param id
   *            The edge identifier.
   * @return The edge if it is in this set, else null.
   */
  public Edge getEdge(String id) {
    return (Edge) getElement(id, byEdgeIdGroups);
  }

  /**
   * Get a sprite element knowing its identifier.
   *
   * @param id
   *            The sprite identifier.
   * @return The sprite if it is in this set, else null.
   */
  public GraphicSprite getSprite(String id) {
    return (GraphicSprite) getElement(id, bySpriteIdGroups);
  }

  /**
   * Get a graph element knowing its identifier.
   *
   * @param id
   *            The graph identifier.
   * @return The graph if it is in this set, else null.
   */
  public Graph getGraph(String id) {
    return (Graph) getElement(id, byGraphIdGroups);
  }

  /**
   * The number of nodes referenced.
   *
   * @return The node count.
   */
  public int getNodeCount() {
    return byNodeIdGroups.size();
  }

  /**
   * The number of edges referenced.
   *
   * @return The edge count.
   */
  public int getEdgeCount() {
    return byEdgeIdGroups.size();
  }

  /**
   * The number of sprites referenced.
   *
   * @return The sprite count.
   */
  public int getSpriteCount() {
    return bySpriteIdGroups.size();
  }

  /**
   * Iterator on the set of nodes.
   *
   * @return An iterator on all node elements contained in style groups.
   */
  public Iterator<? extends Node> getNodeIterator() {
    return new ElementIterator<Node>(byNodeIdGroups);
  }

  /**
   * Iterator on the set of graphs.
   *
   * @return An iterator on all graph elements contained in style groups.
   */
  public Iterator<? extends Graph> getGraphIterator() {
    return new ElementIterator<Graph>(byGraphIdGroups);
  }

  /**
   * Iterable set of nodes.
   *
   * @return The set of all nodes.
   */
  public Iterable<? extends Node> nodes() {
    return nodeSet;
  }

  /**
   * Iterable set of graphs.
   *
   * @return The set of all graphs.
   */
  public Iterable<? extends Graph> graphs() {
    return graphSet;
  }

  /**
   * Iterator on the set of edges.
   *
   * @return An iterator on all edge elements contained in style groups.
   */
  public Iterator<? extends Edge> getEdgeIterator() {
    return new ElementIterator<Edge>(byEdgeIdGroups);
  }

  /**
   * Iterable set of edges.
   *
   * @return The set of all edges.
   */
  public Iterable<? extends Edge> edges() {
    return edgeSet;
  }

  /**
   * Iterator on the set of sprite.
   *
   * @return An iterator on all sprite elements contained in style groups.
   */
  public Iterator<? extends GraphicSprite> getSpriteIterator() {
    return new ElementIterator<GraphicSprite>(bySpriteIdGroups);
  }

  /**
   * Iterable set of sprites.
   *
   * @return The set of all sprites.
   */
  public Iterable<? extends GraphicSprite> sprites() {
    return spriteSet;
  }

  /**
   * Retrieve the group identifier of an element knowing the element
   * identifier.
   *
   * @param element
   *            The element to search for.
   * @return Identifier of the group containing the element.
   */
  public String getElementGroup(Element element) {
    if (element instanceof Node) {
      return byNodeIdGroups.get(element.getId());
    } else if (element instanceof Edge) {
      return byEdgeIdGroups.get(element.getId());
    } else if (element instanceof GraphicSprite) {
      return bySpriteIdGroups.get(element.getId());
    } else if (element instanceof Graph) {
      return byGraphIdGroups.get(element.getId());
    } else {
      throw new RuntimeException("What ?");
    }
  }

  /**
   * Get the style of an element.
   *
   * @param element
   *            The element to search for.
   * @return The style group of the element (which is also a style).
   */
  public StyleGroup getStyleForElement(Element element) {
    String gid = getElementGroup(element);

    return groups.get(gid);
  }

  /**
   * Get the style of a given node.
   *
   * @param node
   *            The node to search for.
   * @return The node style.
   */
  public StyleGroup getStyleFor(Node node) {
    String gid = byNodeIdGroups.get(node.getId());
    return groups.get(gid);
  }

  /**
   * Get the style of a given edge.
   *
   * @param edge
   *            The edge to search for.
   * @return The edge style.
   */
  public StyleGroup getStyleFor(Edge edge) {
    String gid = byEdgeIdGroups.get(edge.getId());
    return groups.get(gid);
  }

  /**
   * Get the style of a given sprite.
   *
   * @param sprite
   *            The node to search for.
   * @return The sprite style.
   */
  public StyleGroup getStyleFor(GraphicSprite sprite) {
    String gid = bySpriteIdGroups.get(sprite.getId());
    return groups.get(gid);
  }

  /**
   * Get the style of a given graph.
   *
   * @param graph
   *            The node to search for.
   * @return The graph style.
   */
  public StyleGroup getStyleFor(Graph graph) {
    String gid = byGraphIdGroups.get(graph.getId());
    return groups.get(gid);
  }

  /**
   * True if groups are removed when becoming empty. This setting allows to
   * keep empty group when the set of elements is quite dynamic. This allows
   * to avoid recreting groups when an element appears and disappears
   * regularly.
   *
   * @return True if the groups are removed when empty.
   */
  public boolean areEmptyGroupRemoved() {
    return removeEmptyGroups;
  }

  /**
   * The Z index object.
   *
   * @return The Z index.
   */
  public ZIndex getZIndex() {
    return zIndex;
  }

  /**
   * The set of style groups that cast a shadow.
   *
   * @return The set of shadowed style groups.
   */
  public ShadowSet getShadowSet() {
    return shadow;
  }

  // Command

  /**
   * Release any dependency to the style sheet.
   */
  public void release() {
    stylesheet.removeListener(this);
  }

  /**
   * Empties this style group set. The style sheet is listener is not removed,
   * use {@link #release()} to do that.
   */
  public void clear() {
    byEdgeIdGroups.clear();
    byNodeIdGroups.clear();
    bySpriteIdGroups.clear();
    byGraphIdGroups.clear();
    groups.clear();
    zIndex.clear();
    shadow.clear();
  }

  /**
   * Remove or keep groups that becomes empty, if true the groups are removed.
   * If this setting was set to false, and is now true, the group set is
   * purged of the empty groups.
   *
   * @param on
   *            If true the groups will be removed.
   */
  public void setRemoveEmptyGroups(boolean on) {
    if (removeEmptyGroups == false && on == true) {
      Iterator<? extends StyleGroup> i = groups.values().iterator();

      while (i.hasNext()) {
        StyleGroup g = i.next();

        if (g.isEmpty())
          i.remove();
      }
    }

    removeEmptyGroups = on;
  }

  protected StyleGroup addGroup(String id, ArrayList<Rule> rules,
      Element firstElement) {
    StyleGroup group = new StyleGroup(id, rules, firstElement, eventSet);

    groups.put(id, group);
    zIndex.groupAdded(group);
    shadow.groupAdded(group);

    return group;
  }

  protected void removeGroup(StyleGroup group) {
    zIndex.groupRemoved(group);
    shadow.groupRemoved(group);
    groups.remove(group.getId());
    group.release();
  }

  /**
   * Add an element and bind it to its style group. The group is created if
   * needed.
   *
   * @param element
   *            The element to add.
   * @return The style group where the element was added.
   */
  public StyleGroup addElement(Element element) {
    StyleGroup group = addElement_(element);

    for (StyleGroupListener listener : listeners)
      listener.elementStyleChanged(element, null, group);

    return group;
  }

  protected StyleGroup addElement_(Element element) {
    ArrayList<Rule> rules = stylesheet.getRulesFor(element);
    String gid = stylesheet.getStyleGroupIdFor(element, rules);
    StyleGroup group = groups.get(gid);

    if (group == null)
      group = addGroup(gid, rules, element);
    else
      group.addElement(element);

    addElementToReverseSearch(element, gid);

    return group;
  }

  /**
   * Remove an element from the group set. If the group becomes empty after
   * the element removal, depending on the setting of
   * {@link #areEmptyGroupRemoved()}, the group is deleted or kept. Keeping
   * groups allows to handle faster elements that constantly appear and
   * disappear.
   *
   * @param element
   *            The element to remove.
   */
  public void removeElement(Element element) {
    String gid = getElementGroup(element);
        if (null == gid)
        {
            return;
        }
    StyleGroup group = groups.get(gid);

    if (group != null) {
      group.removeElement(element);
      removeElementFromReverseSearch(element);

      if (removeEmptyGroups && group.isEmpty())
        removeGroup(group);
    }
  }

  /**
   * Check if an element need to change from a style group to another.
   *
   * <p>
   * When an element can have potentially changed style due to some of its
   * attributes (ui.class for example), instead of removing it then reading
   * it, use this method to move the element from its current style group to a
   * potentially different style group.
   * </p>
   *
   * <p>
   * Explanation of this method : checking the style of an element may be done
   * by removing it ({@link #removeElement(Element)}) and then re-adding it (
   * {@link #addElement(Element)}). This must be done by the element since it
   * knows when to check this. However you cannot only remove and add, since
   * the style group inside which the element is can have events occurring on
   * it, and these events must be passed from its old style to its new style.
   * This method does all this information passing.
   * </p>
   *
   * @param element
   *            The element to move.
   */
  public void checkElementStyleGroup(Element element) {
    StyleGroup oldGroup = getGroup(getElementGroup(element));

    // Get the old element "dynamic" status.

    boolean isDyn = false;

    // Get the old event set for the given element.

    StyleGroup.ElementEvents events = null;

    if (oldGroup != null) {
      isDyn = oldGroup.isElementDynamic(element);
      events = oldGroup.getEventsFor(element);
    }

    // Remove the element from its old style and add it to insert it in the
    // correct style.

    removeElement(element);
    addElement_(element);

    // Eventually push the events on the new style group.

    StyleGroup newGroup = getGroup(getElementGroup(element));

    if (newGroup != null && events != null) {
      for (String event : events.events)
        pushEventFor(element, event);
    }

    for (StyleGroupListener listener : listeners)
      listener.elementStyleChanged(element, oldGroup, newGroup);

    // Eventually set the element as dynamic, if it was.

    if (newGroup != null && isDyn)
      newGroup.pushElementAsDynamic(element);
  }

  protected void addElementToReverseSearch(Element element, String groupId) {
    if (element instanceof Node) {
      byNodeIdGroups.put(element.getId(), groupId);
    } else if (element instanceof Edge) {
      byEdgeIdGroups.put(element.getId(), groupId);
    } else if (element instanceof GraphicSprite) {
      bySpriteIdGroups.put(element.getId(), groupId);
    } else if (element instanceof Graph) {
      byGraphIdGroups.put(element.getId(), groupId);
    } else {
      throw new RuntimeException("What ?");
    }
  }

  protected void removeElementFromReverseSearch(Element element) {
    if (element instanceof Node) {
      byNodeIdGroups.remove(element.getId());
    } else if (element instanceof Edge) {
      byEdgeIdGroups.remove(element.getId());
    } else if (element instanceof GraphicSprite) {
      bySpriteIdGroups.remove(element.getId());
    } else if (element instanceof Graph) {
      byGraphIdGroups.remove(element.getId());
    } else {
      throw new RuntimeException("What ?");
    }
  }

  /**
   * Push a global event on the event stack. Events trigger the replacement of
   * a style by an alternative style (or meta-class) when possible. If an
   * event is on the event stack, each time a style has an alternative
   * corresponding to the event, the alternative is used instead of the style.
   *
   * @param event
   *            The event to push.
   */
  public void pushEvent(String event) {
    eventSet.pushEvent(event);
  }

  /**
   * Push an event specifically for a given element. This is normally done
   * automatically by the graphic element.
   *
   * @param element
   *            The element considered.
   * @param event
   *            The event to push.
   */
  public void pushEventFor(Element element, String event) {
    StyleGroup group = getGroup(getElementGroup(element));

    if (group != null)
      group.pushEventFor(element, event);
  }

  /**
   * Pop a global event from the event set.
   *
   * @param event
   *            The event to remove.
   */
  public void popEvent(String event) {
    eventSet.popEvent(event);
  }

  /**
   * Pop an event specifically for a given element. This is normally done
   * automatically by the graphic element.
   *
   * @param element
   *            The element considered.
   * @param event
   *            The event to pop.
   */
  public void popEventFor(Element element, String event) {
    StyleGroup group = getGroup(getElementGroup(element));

    if (group != null)
      group.popEventFor(element, event);
  }

  /**
   * Specify the given element has dynamic style attribute values. This is
   * normally done automatically by the graphic element.
   *
   * @param element
   *            The element to add to the dynamic subset.
   */
  public void pushElementAsDynamic(Element element) {
    StyleGroup group = getGroup(getElementGroup(element));

    if (group != null)
      group.pushElementAsDynamic(element);
  }

  /**
   * Remove the given element from the subset of elements having dynamic style
   * attribute values. This is normally done automatically by the graphic
   * element.
   *
   * @param element
   *            The element to remove from the dynamic subset.
   */
  public void popElementAsDynamic(Element element) {
    StyleGroup group = getGroup(getElementGroup(element));

    if (group != null)
      group.popElementAsDynamic(element);
  }

  /**
   * Add a listener for element style changes.
   *
   * @param listener
   *            The listener to add.
   */
  public void addListener(StyleGroupListener listener) {
    listeners.add(listener);
  }

  /**
   * Remove a style change listener.
   *
   * @param listener
   *            The listener to remove.
   */
  public void removeListener(StyleGroupListener listener) {
    int index = listeners.lastIndexOf(listener);

    if (index >= 0) {
      listeners.remove(index);
    }
  }

  // Listener -- What to do when a change occurs in the style sheet.

  public void styleAdded(Rule oldRule, Rule newRule) {
    // When a style change, we need to update groups.
    // Several cases :
    // 1. The style already exists
    // * Nothing to do in fact. All the elements are still in place.
    // No style rule (selectors) changed, and therefore we do not have
    // to change the groups since they are built using the selectors.
    // 2. The style is new
    // * we need to check all the groups concerning this kind of element (we
    // can
    // restrict our search to these groups, since other will not be
    // impacted),
    // and check all elements of these groups.

    if (oldRule == null)
      checkForNewStyle(newRule); // no need to check Z and shadow, done
                    // when adding/changing group.
    else
      checkZIndexAndShadow(oldRule, newRule);
  }

  public void styleSheetCleared() {
    ArrayList<Element> elements = new ArrayList<Element>();

    for (Element element : graphs())
      elements.add(element);

    for (Element element : nodes())
      elements.add(element);

    for (Element element : edges())
      elements.add(element);

    for (Element element : sprites())
      elements.add(element);

    clear();

    for (Element element : elements)
      removeElement(element);

    for (Element element : elements)
      addElement(element);
  }

  /**
   * Check each group that may have changed, for example to rebuild the Z
   * index and the shadow set.
   *
   * @param oldRule
   *            The old rule that changed.
   * @param newRule
   *            The new rule that participated in the change.
   */
  protected void checkZIndexAndShadow(Rule oldRule, Rule newRule) {
    if (oldRule != null) {
      if (oldRule.selector.getId() != null
          || oldRule.selector.getClazz() != null) {
        // We may accelerate things a bit when a class or id style is
        // modified,
        // since only the groups listed in the style are concerned (we
        // are at the
        // bottom of the inheritance tree).
        if (oldRule.getGroups() != null)
          for (String s : oldRule.getGroups()) {
            StyleGroup group = groups.get(s);
            if (group != null) {
              zIndex.groupChanged(group);
              shadow.groupChanged(group);
            }
          }
      } else {
        // For kind styles "NODE", "EDGE", "GRAPH", "SPRITE", we must
        // reset
        // the whole Z and shadows for the kind, since several styles
        // may
        // have changed.

        Selector.Type type = oldRule.selector.type;

        for (StyleGroup group : groups.values()) {
          if (group.getType() == type) {
            zIndex.groupChanged(group);
            shadow.groupChanged(group);
          }
        }
      }
    }
  }

  /**
   * We try to avoid at most to affect anew styles to elements and to recreate
   * groups, which is time consuming.
   *
   * Two cases :
   * <ol>
   * <li>The style is an specific (id) style. In this case a new group may be
   * added.
   * <ul>
   * <li>check an element matches the style and in this case create the group
   * by adding the element.</li>
   * <li>else do nothing.</li>
   * </ul>
   * </li>
   * <li>The style is a kind or class style.
   * <ul>
   * <li>check all the groups in the kind of the style (graph, node, edge,
   * sprite) and only in this kind (since other will never be affected).</li>
   * <li>remove all groups of this kind.</li>
   * <li>add all elements of this kind anew to recreate the group.</li>
   * </ul>
   * </li>
   * </ol>
   */
  protected void checkForNewStyle(Rule newRule) {
    switch (newRule.selector.type) {
    case GRAPH:
      if (newRule.selector.getId() != null)
        checkForNewIdStyle(newRule, byGraphIdGroups);
      else
        checkForNewStyle(newRule, byGraphIdGroups);
      break;
    case NODE:
      if (newRule.selector.getId() != null)
        checkForNewIdStyle(newRule, byNodeIdGroups);
      else
        checkForNewStyle(newRule, byNodeIdGroups);
      break;
    case EDGE:
      if (newRule.selector.getId() != null)
        checkForNewIdStyle(newRule, byEdgeIdGroups);
      else
        checkForNewStyle(newRule, byEdgeIdGroups);
      break;
    case SPRITE:
      if (newRule.selector.getId() != null)
        checkForNewIdStyle(newRule, bySpriteIdGroups);
      else
        checkForNewStyle(newRule, bySpriteIdGroups);
      break;
    case ANY:
    default:
      throw new RuntimeException("What ?");
    }
  }

  /**
   * Check for a new specific style (applies only to one element).
   *
   * @param newRule
   *            The new style rule.
   * @param elt2grp
   *            The name space.
   */
  protected void checkForNewIdStyle(Rule newRule,
      Map<String, String> elt2grp) {
    // There is only one element that matches the identifier.

    Element element = getElement(newRule.selector.getId(), elt2grp);

    if (element != null) {
      checkElementStyleGroup(element);
      // removeElement( element ); // Remove the element from its old
      // group. Potentially delete a group.
      // addElement( element ); // Add the element to its new own group
      // (since this is an ID style).
    }
  }

  /**
   * Check for a new kind or class style in a given name space (node, edge,
   * sprite, graph).
   *
   * @param newRule
   *            The new style rule.
   * @param elt2grp
   *            The name space.
   */
  protected void checkForNewStyle(Rule newRule,
      Map<String, String> elt2grp) {
    Collection<Element> elementsToCheck = new ArrayList<Element>();

    for (String eltId : elt2grp.keySet())
      elementsToCheck.add(getElement(eltId, elt2grp));

    for (Element element : elementsToCheck) {
      checkElementStyleGroup(element);
      // removeElement( element );
      // addElement( element );
    }
  }

  // Utility

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();

    builder.append(String.format("Style groups (%d) :%n", groups.size()));

    for (StyleGroup group : groups.values()) {
      builder.append(group.toString(1));
      builder.append(String.format("%n"));
    }

    return builder.toString();
  }

  // Inner classes

  /**
   * Set of events (meta-classes) actually active.
   *
   * <p>
   * The event set contains the set of events actually occurring. This is used
   * to select alternate styles. The events actually occurring are in
   * precedence order. The last one is the most important.
   * </p>
   *
   * @author Antoine Dutot
   */
  public class EventSet {
    public ArrayList<String> eventSet = new ArrayList<String>();

    public String events[] = new String[0];

    /**
     * Add an event to the set.
     *
     * @param event
     *            The event to add.
     */
    public void pushEvent(String event) {
      eventSet.add(event);
      events = eventSet.toArray(events);
    }

    /**
     * Remove an event from the set.
     *
     * @param event
     *            The event to remove.
     */
    public void popEvent(String event) {
      int index = eventSet.lastIndexOf(event);

      if (index >= 0)
        eventSet.remove(index);

      events = eventSet.toArray(events);
    }

    /**
     * The set of events in order, the most important at the end.
     *
     * @return The event set.
     */
    public String[] getEvents() {
      return events;
    }
  }

  /**
   * All the style groups sorted by their Z index.
   *
   * <p>
   * This structure is maintained by each time a group is added or removed, or
   * when the style of a group changed.
   * </p>
   *
   * @author Antoine Dutot
   */
  public class ZIndex implements Iterable<HashSet<StyleGroup>> {
    /**
     * Ordered set of groups.
     */
    public ArrayList<HashSet<StyleGroup>> zIndex = new ArrayList<HashSet<StyleGroup>>();

    /**
     * Knowing a group, tell if its Z index.
     */
    public HashMap<String, Integer> reverseZIndex = new HashMap<String, Integer>();

    /**
     * New empty Z index.
     */
    public ZIndex() {
      initZIndex();
    }

    protected void initZIndex() {
      zIndex.ensureCapacity(256);

      for (int i = 0; i < 256; i++)
        zIndex.add(null);
    }

    /**
     * Iterator on the set of Z index cells. Each item is a set of style
     * groups that pertain to the same Z index.
     *
     * @return Iterator on the Z index.
     */
    protected Iterator<HashSet<StyleGroup>> getIterator() {
      return new ZIndexIterator();
    }

    public Iterator<HashSet<StyleGroup>> iterator() {
      return getIterator();
    }

    /**
     * A new group appeared, put it in the z index.
     *
     * @param group
     *            The group to add.
     */
    protected void groupAdded(StyleGroup group) {
      int z = convertZ(group.getZIndex());

      if (zIndex.get(z) == null)
        zIndex.set(z, new HashSet<StyleGroup>());

      zIndex.get(z).add(group);
      reverseZIndex.put(group.getId(), z);
    }

    /**
     * A group eventually changed, check its location.
     *
     * @param group
     *            The group to check.
     */
    protected void groupChanged(StyleGroup group) {
      int oldZ = reverseZIndex.get(group.getId());
      int newZ = convertZ(group.getZIndex());

      if (oldZ != newZ) {
        HashSet<StyleGroup> map = zIndex.get(oldZ);

        if (map != null) {
          map.remove(group);
          reverseZIndex.remove(group.getId());

          if (map.isEmpty())
            zIndex.set(oldZ, null);
        }

        groupAdded(group);
      }
    }

    /**
     * A group was removed, remove it from the Z index.
     *
     * @param group
     *            The group to remove.
     */
    protected void groupRemoved(StyleGroup group) {
      int z = convertZ(group.getZIndex());

      HashSet<StyleGroup> map = zIndex.get(z);

      if (map != null) {
        map.remove(group);
        reverseZIndex.remove(group.getId());

        if (map.isEmpty())
          zIndex.set(z, null);
      } else {
        throw new RuntimeException("Inconsistency in Z-index");
      }
    }

    public void clear() {
      zIndex.clear();
      reverseZIndex.clear();
      initZIndex();
    }

    /**
     * Convert a [-127,127] value into a [0,255] value and check bounds.
     *
     * @param z
     *            The Z value to convert.
     * @return The Z value converted and bounded to [0,255].
     */
    protected int convertZ(int z) {
      z += 127;

      if (z < 0)
        z = 0;
      else if (z > 255)
        z = 255;

      return z;
    }

    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder();

      sb.append(String.format("Z index :%n"));

      for (int i = 0; i < 256; i++) {
        if (zIndex.get(i) != null) {
          sb.append(String.format("    * %d -> ", i - 127));

          HashSet<StyleGroup> map = zIndex.get(i);

          for (StyleGroup g : map)
            sb.append(String.format("%s ", g.getId()));

          sb.append(String.format("%n"));
        }
      }

      return sb.toString();
    }

    public class ZIndexIterator implements Iterator<HashSet<StyleGroup>> {
      public int index = 0;

      public ZIndexIterator() {
        zapUntilACell();
      }

      protected void zapUntilACell() {
        while (index < 256 && zIndex.get(index) == null)
          index++;
      }

      public boolean hasNext() {
        return (index < 256);
      }

      public HashSet<StyleGroup> next() {
        if (hasNext()) {
          HashSet<StyleGroup> cell = zIndex.get(index);
          index++;
          zapUntilACell();
          return cell;
        }

        return null;
      }

      public void remove() {
        throw new RuntimeException(
            "This iterator does not support removal.");
      }
    }
  }

  /**
   * Set of groups that cast a shadow.
   *
   * @author Antoine Dutot
   */
  public class ShadowSet implements Iterable<StyleGroup> {
    /**
     * The set of groups casting shadow.
     */
    protected HashSet<StyleGroup> shadowSet = new HashSet<StyleGroup>();

    /**
     * Iterator on the set of groups that cast a shadow.
     *
     * @return An iterator on the shadow style group set.
     */
    protected Iterator<StyleGroup> getIterator() {
      return shadowSet.iterator();
    }

    public Iterator<StyleGroup> iterator() {
      return getIterator();
    }

    /**
     * A group appeared, check its shadow status.
     *
     * @param group
     *            The group added.
     */
    protected void groupAdded(StyleGroup group) {
      if (group.getShadowMode() != ShadowMode.NONE)
        shadowSet.add(group);
    }

    /**
     * A group eventually changed, check its shadow status.
     *
     * @param group
     *            The group that changed.
     */
    protected void groupChanged(StyleGroup group) {
      if (group.getShadowMode() == ShadowMode.NONE)
        shadowSet.remove(group);
      else
        shadowSet.add(group);
    }

    /**
     * A group was removed, remove it from the shadow if needed.
     *
     * @param group
     *            The group removed.
     */
    protected void groupRemoved(StyleGroup group) {
      // Faster than to first test its existence or shadow status :

      shadowSet.remove(group);
    }

    protected void clear() {
      shadowSet.clear();
    }
  }

  /**
   * Iterator that allows to browse all graph elements of a given kind (nodes,
   * edges, sprites, graphs) as if they where in a single set, whereas they
   * are in style groups.
   *
   * @author Antoine Dutot
   * @param <E>
   *            The kind of graph element.
   */
  protected class ElementIterator<E extends Element> implements Iterator<E> {
    protected Map<String, String> elt2grp;

    protected Iterator<String> elts;

    public ElementIterator(final Map<String, String> elements2groups) {
      elt2grp = elements2groups;
      elts = elements2groups.keySet().iterator();
    }

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

    @SuppressWarnings("unchecked")
    public E next() {
      String eid = elts.next();
      String gid = elt2grp.get(eid);
      StyleGroup grp = groups.get(gid);

      return (E) grp.getElement(eid);
    }

    public void remove() {
      throw new RuntimeException(
          "remove not implemented in this iterator");
    }
  }

  /**
   * Dummy set of nodes.
   */
  protected class NodeSet implements Iterable<Node> {
    @SuppressWarnings("unchecked")
    public Iterator<Node> iterator() {
      return (Iterator<Node>) getNodeIterator();
    }
  }

  /**
   * Dummy set of edges.
   */
  protected class EdgeSet implements Iterable<Edge> {
    @SuppressWarnings("unchecked")
    public Iterator<Edge> iterator() {
      return (Iterator<Edge>) getEdgeIterator();
    }
  }

  /**
   * Dummy set of sprites.
   */
  protected class SpriteSet implements Iterable<GraphicSprite> {
    @SuppressWarnings("unchecked")
    public Iterator<GraphicSprite> iterator() {
      return (Iterator<GraphicSprite>) getSpriteIterator();
    }
  }

  protected class GraphSet implements Iterable<GraphicGraph> {
    @SuppressWarnings("unchecked")
    public Iterator<GraphicGraph> iterator() {
      return (Iterator<GraphicGraph>) getGraphIterator();
    }
  }

}
TOP

Related Classes of org.graphstream.ui.graphicGraph.StyleGroupSet$NodeSet

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.