Graph View

* added simple Window for graph play
* added new function to interface
* all tests passes
* end graph
This commit is contained in:
2019-02-26 21:58:53 +01:00
parent b441871de0
commit cdc183754f
23 changed files with 2203 additions and 1053 deletions

BIN
.gitignore vendored

Binary file not shown.

View File

@@ -138,10 +138,23 @@ public interface Graph<V, W extends Number> extends Iterable<V> {
*/ */
void unMark(V vertex) throws NullPointerException, IllegalArgumentException; void unMark(V vertex) throws NullPointerException, IllegalArgumentException;
/**
* Get all the vertices that are marked with the specific mark passed.<br>
* If there aren't vertices with that mark then it is returned an empty set.<br>
* Note: depending on the implementation, modifying the returned collection<br>
* could affect the graph behavior and the changes could be reflected to the graph.
*
* @param mark the mark
* @return all the vertices that are marked with that specific mark
* @throws NullPointerException if the mark is null
*/
Collection<V> getMarkedWith(Object mark) throws NullPointerException;
/** /**
* Get all the marker of this vertex.<br> * Get all the marker of this vertex.<br>
* If the vertex doesn't have any mark, then it will return an empty set.<br> * If the vertex doesn't have any mark, then it will return an empty set.<br>
* Note: this set is linked to the marked vertex, so any changes to the set returned are reflected to the graph. * Note: depending on the implementation, modifying the returned collection<br>
* could affect the graph behavior and the changes could be reflected to the graph.
* *
* @param vertex the vertex * @param vertex the vertex
* @return all the mark to the vertex or an empty collection if none * @return all the mark to the vertex or an empty collection if none
@@ -307,7 +320,8 @@ public interface Graph<V, W extends Number> extends Iterable<V> {
/** /**
* Get all the vertices in the graph.<br> * Get all the vertices in the graph.<br>
* If the graph doesn't contains vertices, it'll return an empty collection.<br> * If the graph doesn't contains vertices, it'll return an empty collection.<br>
* Note that this collection is completely different the object used for the vertices, so any modification to this collection will not change the graph. * Note: depending on the implementation, modifying the returned collection<br>
* could affect the graph behavior and the changes could be reflected to the graph.
* *
* @return an array that include all the vertices * @return an array that include all the vertices
*/ */
@@ -316,7 +330,8 @@ public interface Graph<V, W extends Number> extends Iterable<V> {
/** /**
* Get all the edges in the graph.<br> * Get all the edges in the graph.<br>
* If the graph doesn't contains edges, it'll return an empty collection.<br> * If the graph doesn't contains edges, it'll return an empty collection.<br>
* Note that this collection is completely different than the object used for the edges, so any modification to this collection will not change the graph. * Note: depending on the implementation, modifying the returned collection<br>
* could affect the graph behavior and the changes could be reflected to the graph.
* *
* @return a collection that include all the edges * @return a collection that include all the edges
*/ */
@@ -324,7 +339,9 @@ public interface Graph<V, W extends Number> extends Iterable<V> {
/** /**
* Retrieve all the edges of a particular vertex.<br> * Retrieve all the edges of a particular vertex.<br>
* Note: the edges that are returned are the one that goes IN this vertex AND the edges that goes OUT of it. * Note: the edges that are returned are the one that goes IN this vertex AND the edges that goes OUT of it.<br>
* Note2: depending on the implementation, modifying the returned collection<br>
* could affect the graph behavior and the changes could be reflected to the graph.
* *
* @param vertex a vertex of the graph * @param vertex a vertex of the graph
* @return a collection of edges * @return a collection of edges
@@ -335,7 +352,9 @@ public interface Graph<V, W extends Number> extends Iterable<V> {
/** /**
* Retrieve all the edges of a particular vertex.<br> * Retrieve all the edges of a particular vertex.<br>
* Note: the edges that are returned are the one that have this vertex as destination and another as source. * Note: the edges that are returned are the one that have this vertex as destination and another as source.<br>
* Note2: depending on the implementation, modifying the returned collection<br>
* could affect the graph behavior and the changes could be reflected to the graph.
* *
* @param vertex a vertex of the graph * @param vertex a vertex of the graph
* @return a collection of edges * @return a collection of edges
@@ -346,7 +365,9 @@ public interface Graph<V, W extends Number> extends Iterable<V> {
/** /**
* Retrieve all the edges that goes OUT of a particular vertex.<br> * Retrieve all the edges that goes OUT of a particular vertex.<br>
* Note: the edges that are returned are the one that have this vertex as source and another one as destination. * Note: the edges that are returned are the one that have this vertex as source and another one as destination.<br>
* Note2: depending on the implementation, modifying the returned collection<br>
* could affect the graph behavior and the changes could be reflected to the graph.
* *
* @param vertex a vertex of the graph * @param vertex a vertex of the graph
* @return a collection of edges * @return a collection of edges
@@ -358,7 +379,9 @@ public interface Graph<V, W extends Number> extends Iterable<V> {
/** /**
* Get all the vertices that are children of the vertex passed as parameter.<br> * Get all the vertices that are children of the vertex passed as parameter.<br>
* The vertices V(0-N) that are 'children' of a vertex V1, are all the vertices that have an edge * The vertices V(0-N) that are 'children' of a vertex V1, are all the vertices that have an edge
* where V1 is the source of that edge. * where V1 is the source of that edge.<br>
* Note: depending on the implementation, modifying the returned collection<br>
* could affect the graph behavior and the changes could be reflected to the graph.
* *
* @param vertex the source vertex * @param vertex the source vertex
* @return an array of vertices * @return an array of vertices
@@ -369,7 +392,9 @@ public interface Graph<V, W extends Number> extends Iterable<V> {
/** /**
* Get all the vertices that have the vertex passed as their child.<br> * Get all the vertices that have the vertex passed as their child.<br>
* Basically is the opposite of {@link #getChildren(Object)} * Basically is the opposite of {@link #getChildren(Object)}<br>
* Note: depending on the implementation, modifying the returned collection<br>
* could affect the graph behavior and the changes could be reflected to the graph.
* *
* @param vertex a vertex of the graph * @param vertex a vertex of the graph
* @return an array of ancestors of the vertex * @return an array of ancestors of the vertex
@@ -442,7 +467,8 @@ public interface Graph<V, W extends Number> extends Iterable<V> {
/** /**
* This method will create a new Graph that is the transposed version of the original.<br> * This method will create a new Graph that is the transposed version of the original.<br>
* At the end of this method the new graph will have all the edges inverted in orientation.<br> * At the end of this method the new graph will have all the edges inverted in orientation.<br>
* Example: if the graph G contains (V1, V2, V3) as vertex, and (V1->V2, V3->V2) as edges, the transpose graph G' will contain (V1, V2, V3) as vertex, and (V2->V1, V2->V3) as edges. * Example: if the graph G contains (V1, V2, V3) as vertex, and (V1->V2, V3->V2) as edges,
* the transpose graph G' will contain (V1, V2, V3) as vertex, and (V2->V1, V2->V3) as edges.
* *
* @return a transposed graph of this instance * @return a transposed graph of this instance
*/ */
@@ -450,7 +476,8 @@ public interface Graph<V, W extends Number> extends Iterable<V> {
/** /**
* If the current graph is a DAG, it returns a topological sort of this graph.<br> * If the current graph is a DAG, it returns a topological sort of this graph.<br>
* A topological ordering of a graph is a linear ordering of its vertices such that for every directed edge (V1, V2) from vertex V1 to vertex V2, V2 comes before V1 in the ordering. * A topological ordering of a graph is a linear ordering of its vertices such that for
* every directed edge (V1, V2) from vertex V1 to vertex V2, V2 comes before V1 in the ordering.
* *
* @return an array containing the topological order of the vertices * @return an array containing the topological order of the vertices
* @throws UnsupportedOperationException if the graph is not a DAG (see {@link #isDAG()}) * @throws UnsupportedOperationException if the graph is not a DAG (see {@link #isDAG()})
@@ -458,7 +485,8 @@ public interface Graph<V, W extends Number> extends Iterable<V> {
List<V> topologicalSort() throws UnsupportedOperationException; List<V> topologicalSort() throws UnsupportedOperationException;
/** /**
* The strongly connected components or diconnected components of an arbitrary directed graph form a partition into subgraphs that are themselves strongly connected. * The strongly connected components or disconnected components of an arbitrary directed graph
* form a partition into subgraphs that are themselves strongly connected.
* *
* @return a collection containing the strongly connected components * @return a collection containing the strongly connected components
*/ */

View File

@@ -124,6 +124,18 @@ public class MapGraph<V, W extends Number> implements Graph<V, W> {
marked.get(vertex).clear(); marked.get(vertex).clear();
} }
@Override
public Collection<V> getMarkedWith(Object mark) throws NullPointerException {
checkNull(mark);
Collection<V> ret = new HashSet<V>();
marked.forEach((v, set) -> {
if(set.contains(mark))
ret.add(v);
});
return ret;
}
@Override @Override
public Set<Object> getMarks(V vertex) throws NullPointerException, IllegalArgumentException { public Set<Object> getMarks(V vertex) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex); checkNullAndExist(vertex);

View File

@@ -15,7 +15,7 @@ import java.util.function.Consumer;
* @param <V> the vertex * @param <V> the vertex
* @author Berack96 * @author Berack96
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings({"unchecked", "rawtypes"})
public class Vertex<V> { public class Vertex<V> {
public static final String REMOVED = "The vertex is no longer in the graph"; public static final String REMOVED = "The vertex is no longer in the graph";
@@ -240,7 +240,7 @@ public class Vertex<V> {
* @throws NullPointerException if the strategy is null * @throws NullPointerException if the strategy is null
* @throws UnsupportedOperationException if the vertex is not in the graph anymore * @throws UnsupportedOperationException if the vertex is not in the graph anymore
*/ */
public VisitInfo<V> visit(final VisitStrategy strategy, final Consumer<V> visit) throws NullPointerException, UnsupportedOperationException { public VisitInfo<V> visit(final VisitStrategy strategy, final Consumer<V> visit) throws NullPointerException, UnsupportedOperationException {
throwIfNotContained(); throwIfNotContained();
return graph.visit(vertex, (VisitStrategy<V, Number>) strategy, visit); return graph.visit(vertex, (VisitStrategy<V, Number>) strategy, visit);
} }

View File

@@ -0,0 +1,162 @@
package berack96.sim.util.graph.view;
import berack96.sim.util.graph.Graph;
import berack96.sim.util.graph.view.edge.EdgeListener;
import berack96.sim.util.graph.view.vertex.VertexListener;
import berack96.sim.util.graph.visit.VisitStrategy;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.util.*;
import java.util.List;
public class GraphInfo<V, W extends Number> extends JPanel {
private static final long serialVersionUID = 1L;
private final Map<String, VisitListener<V>> visits;
public GraphInfo(GraphPanel<V, W> graphPanel, VertexListener<V> vListener, EdgeListener<V, W> eListener, Set<VisitStrategy<V, W>> visits) {
this.visits = new HashMap<>();
/* ZERO (DESCRIPTION) */
JLabel listenerDescription = new JLabel();
JPanel panelDescription = new JPanel();
panelDescription.setOpaque(false);
panelDescription.add(listenerDescription);
/* FIRST (GRAPH INFO) */
JLabel vNumber = new JLabel(String.valueOf(graphPanel.getGraph().numberOfVertices()));
JLabel eNumber = new JLabel(String.valueOf(graphPanel.getGraph().numberOfEdges()));
JLabel gCyclic = new JLabel(String.valueOf(graphPanel.getGraph().isCyclic()));
List<Component> components = new LinkedList<>();
JLabel selected = new JLabel();
JComboBox<String> comboBox = new JComboBox<>();
comboBox.addItemListener(e -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
try {
String clazz = (String) e.getItem();
VisitListener<V> listener = this.visits.get(clazz);
selected.setText(listener != null? "visit":"nothing");
listenerDescription.setText(listener != null? listener.getDescription():"");
graphPanel.getGraph().unMarkAll();
graphPanel.repaint();
graphPanel.setGraphListener(listener);
} catch (Exception ignore) {}
}
});
comboBox.addItem("None");
for(VisitStrategy<V, W> strategy: visits) {
String clazz = strategy.getClass().getSimpleName();
VisitListener<V> visit = new VisitListener<>(graphPanel, strategy);
comboBox.addItem(clazz);
this.visits.put(clazz, visit);
}
components.add(new JLabel("Visit Strategy: "));
components.add(comboBox);
components.add(new JLabel("Selected modality: "));
components.add(selected);
components.add(new JLabel("Vertex Number: "));
components.add(vNumber);
components.add(new JLabel("Edge Number: "));
components.add(eNumber);
components.add(new JLabel("Is Cyclic: "));
components.add(gCyclic);
JPanel panelInfo = new JPanel();
panelInfo.setOpaque(false);
panelInfo.setBorder(BorderFactory.createLineBorder(Color.RED));
panelInfo.setLayout(new GridLayout(components.size()/2, 2, 2*2, 2*2));
components.forEach(panelInfo::add);
components.clear();
/* SECOND (VERTEX) */
JLabel vVertex = new JLabel();
JLabel vEdgesNumber = new JLabel();
JLabel vEdgesInNumber = new JLabel();
JLabel vEdgesOutNumber = new JLabel();
JButton modVertex = new JButton("Modify Vertices");
modVertex.addActionListener(a -> {
comboBox.setSelectedIndex(0);
listenerDescription.setText(vListener.getDescription());
graphPanel.setGraphListener(vListener);
graphPanel.getGraph().unMarkAll();
graphPanel.repaint();
selected.setText("vertices");
});
JButton modEdge = new JButton("Modify Edges");
modEdge.addActionListener(a -> {
comboBox.setSelectedIndex(0);
listenerDescription.setText(eListener.getDescription());
graphPanel.setGraphListener(eListener);
graphPanel.getGraph().unMarkAll();
graphPanel.repaint();
selected.setText("edges");
});
components.add(modVertex);
components.add(modEdge);
components.add(new JLabel("Vertex name: "));
components.add(vVertex);
components.add(new JLabel("Edges: "));
components.add(vEdgesNumber);
components.add(new JLabel("Edges IN: "));
components.add(vEdgesInNumber);
components.add(new JLabel("Edges OUT: "));
components.add(vEdgesOutNumber);
JPanel panelVertex = new JPanel();
panelVertex.setOpaque(false);
panelVertex.setLayout(new GridLayout(components.size()/2, 2, 2*2, 2*2));
components.forEach(panelVertex::add);
/* Save/Load
JPanel panelSave = new JPanel(); */
/* ADDING COMPONENTS */
this.setBackground(Color.LIGHT_GRAY);
this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
this.setOpaque(true);
this.setBorder(BorderFactory.createSoftBevelBorder(BevelBorder.RAISED, Color.GRAY, Color.DARK_GRAY));
this.add(panelDescription);
this.add(panelInfo);
this.add(panelVertex);
this.add(Box.createVerticalGlue());
modVertex.doClick();
graphPanel.addObserver((o, arg) -> {
Graph<V, W> graph = graphPanel.getGraph();
if(arg.equals(graph)) {
vNumber.setText(String.valueOf(graph.numberOfVertices()));
eNumber.setText(String.valueOf(graph.numberOfEdges()));
gCyclic.setText(String.valueOf(graph.isCyclic()));
/* There should be only one */
for(V v : graph.getMarkedWith("selected")) {
int inE = graph.getEdgesIn(v).size();
int outE = graph.getEdgesOut(v).size();
vEdgesInNumber.setText(String.valueOf(inE));
vEdgesOutNumber.setText(String.valueOf(outE));
vEdgesNumber.setText(String.valueOf(inE + outE));
vVertex.setText(v.toString());
}
}
});
}
}

View File

@@ -0,0 +1,27 @@
package berack96.sim.util.graph.view;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
/**
* An interface for creating a listener of the Graph.
*
* @author Berack96
*/
public interface GraphListener extends MouseListener, MouseMotionListener, KeyListener {
/**
* Remove the listener to the graph.
* This function is called when the listener is removed to the graph.
* Here you could remove any other thing that you have done.
*/
public void remove();
/**
* Get the description of this listener, in a way to interact with the user.
*
* @return a string describing the functionalities of this listener
*/
public String getDescription();
}

View File

@@ -0,0 +1,174 @@
package berack96.sim.util.graph.view;
import berack96.sim.util.graph.Graph;
import berack96.sim.util.graph.MapGraph;
import berack96.sim.util.graph.Vertex;
import berack96.sim.util.graph.view.edge.EdgeComponent;
import berack96.sim.util.graph.view.vertex.VertexComponent;
import java.awt.*;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Collection;
import java.util.HashSet;
import java.util.Observer;
import java.util.Set;
@SuppressWarnings("unchecked")
public class GraphPanel<V, W extends Number> extends Component {
private static final long serialVersionUID = 1L;
private final GraphicalView<VertexComponent<V>> vertexRender;
private final GraphicalView<EdgeComponent<V, W>> edgeRender;
private final Container vertices = new Container();
private final Container edges = new Container();
private final Graph<V, W> graph = new MapGraph<>();
private final Set<Observer> observers = new HashSet<>();
private GraphListener old = null;
public GraphPanel(GraphicalView<VertexComponent<V>> vertexRender, GraphicalView<EdgeComponent<V, W>> edgeRender) {
this.vertexRender = vertexRender;
this.edgeRender = edgeRender;
}
public Graph<V, W> getGraph() {
return graph;
}
public void setGraphListener(GraphListener listener) {
if(old != null)
old.remove();
for (MouseListener l : getMouseListeners())
removeMouseListener(l);
for (MouseMotionListener l : getMouseMotionListeners())
removeMouseMotionListener(l);
for (KeyListener l : getKeyListeners())
removeKeyListener(l);
old = listener;
addMouseListener(listener);
addMouseMotionListener(listener);
addKeyListener(listener);
}
public void addVertex(Point center, V vertex) {
VertexComponent<V> component = getVertexAt(center);
if (component == null) {
VertexComponent<V> v = new VertexComponent<>(new Vertex<>(graph, vertex));
if (!graph.contains(vertex)) {
v.vertex.addIfAbsent();
v.setBounds(vertexRender.getBox(v, center));
vertices.add(v);
}
}
}
public void removeVertex(Point center) {
try {
VertexComponent<V> component = getVertexAt(center);
component.vertex.remove();
vertices.remove(component);
} catch (Exception ignore) {}
}
public void moveVertex(VertexComponent<V> vertex, Point destination) {
Rectangle rectangle = vertexRender.getBox(vertex, destination);
vertex.setLocation(rectangle.x, rectangle.y);
}
public void addEdge(VertexComponent<V> source, VertexComponent<V> dest, W weight) {
try {
Point center = new Point(Math.abs(source.getX() - dest.getY()), Math.abs(source.getY() - dest.getY()));
EdgeComponent<V, W> edgeComponent = new EdgeComponent<>(source, dest, weight);
edgeComponent.setBounds(edgeRender.getBox(edgeComponent, center));
edges.add(edgeComponent);
graph.addEdge(edgeComponent.edge);
} catch (Exception ignore) {}
}
public void removeEdge(VertexComponent<V> source, VertexComponent<V> dest) {
try {
graph.removeEdge(source.vertex.getValue(), dest.vertex.getValue());
EdgeComponent<V, W> toRemove = null;
for (Component c : edges.getComponents()) {
EdgeComponent<V, W> edge = (EdgeComponent<V, W>) c;
if (edge.source.equals(source) && edge.destination.equals(dest))
toRemove = edge;
}
edges.remove(toRemove);
} catch (Exception ignore) {}
}
public void modEdge(VertexComponent<V> source, VertexComponent<V> dest, W weight) {
removeEdge(source, dest);
addEdge(source, dest, weight);
}
public VertexComponent<V> getVertexAt(Point point) {
Component component = vertices.getComponentAt(point);
return component instanceof VertexComponent ? (VertexComponent<V>) component : null;
}
public EdgeComponent<V, W> getEdgeAt(Point point) {
Component component = edges.getComponentAt(point);
return component instanceof EdgeComponent ? (EdgeComponent<V, W>) component : null;
}
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
vertices.setBounds(x, y, width, height);
edges.setBounds(x, y, width, height);
}
@Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Collection<EdgeComponent<V, W>> toRemove = new HashSet<>();
for (Component component : edges.getComponents()) {
EdgeComponent<V, W> edge = (EdgeComponent<V, W>) component;
Vertex<V> source = edge.source.vertex;
Vertex<V> dest = edge.destination.vertex;
if (source.isStillContained() && dest.isStillContained() && graph.containsEdge(source.getValue(), dest.getValue())) {
Point center = new Point(edge.getX() + edge.getWidth() / 2, edge.getY() + edge.getHeight() / 2);
edge.setBounds(edgeRender.getBox(edge, center));
edgeRender.paint((Graphics2D) g2.create(), edge, center);
}
else
toRemove.add(edge);
}
toRemove.forEach(edges::remove);
for (Component component : vertices.getComponents()) {
VertexComponent<V> vertex = (VertexComponent<V>) component;
if (graph.contains(vertex.vertex.getValue())) {
Point center = new Point(vertex.getX() + vertex.getWidth() / 2, vertex.getY() + vertex.getHeight() / 2);
vertexRender.paint((Graphics2D) g2.create(), vertex, center);
}
}
updateObservers();
}
private void updateObservers() {
observers.forEach(observer -> observer.update(null, this.graph));
}
}

View File

@@ -0,0 +1,65 @@
package berack96.sim.util.graph.view;
import berack96.sim.util.graph.view.edge.EdgeIntListener;
import berack96.sim.util.graph.view.edge.EdgeListener;
import berack96.sim.util.graph.view.edge.EdgeView;
import berack96.sim.util.graph.view.vertex.VertexIntListener;
import berack96.sim.util.graph.view.vertex.VertexListener;
import berack96.sim.util.graph.view.vertex.VertexView;
import berack96.sim.util.graph.visit.*;
import javax.swing.*;
import java.awt.*;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* This class is the Window that appear for building the graph and playing around with it
*
* @author Berack96
*/
public class GraphWindow<V, W extends Number> extends JFrame {
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
GraphPanel<Integer, Integer> panel = new GraphPanel<>(new VertexView<>(), new EdgeView<>());
GraphWindow<Integer, Integer> win = new GraphWindow<>(panel, new VertexIntListener(panel), new EdgeIntListener<>(panel));
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); // full screen
dim.setSize(dim.width / 2, dim.height / 2);
win.setSize(dim);
win.setLocationRelativeTo(null); //centered
win.visitRefresh(500);
win.setVisible(true);
}
private final GraphPanel<V, W> graphPanel;
private final GraphInfo<V, W> infoPanel;
private GraphWindow(GraphPanel<V, W> graphPanel, VertexListener<V> vListener, EdgeListener<V, W> eListener) {
this.setTitle("Grafo");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLayout(new BorderLayout());
Set<VisitStrategy<V, W>> strats = new LinkedHashSet<>();
strats.add(new DFS<>());
strats.add(new BFS<>());
strats.add(new Dijkstra<>());
strats.add(new Tarjan<>());
this.infoPanel = new GraphInfo<>(graphPanel, vListener, eListener, strats);
this.graphPanel = graphPanel;
this.add(infoPanel, BorderLayout.EAST);
this.add(graphPanel);
}
public void visitRefresh(int millisec) {
VisitListener.changeRefresh(millisec);
}
public GraphPanel<V, W> getGraphPanel() {
return graphPanel;
}
}

View File

@@ -0,0 +1,29 @@
package berack96.sim.util.graph.view;
import java.awt.*;
/**
* An interface for divide the "hitbox" and the "paint" of the various items
*
* @param <O> the object to paint
* @author Berack96
*/
public interface GraphicalView<O> {
/**
* Box where the object is sensible at listeners (like Hitbox)
*
* @param obj the object to draw
* @param center the center point of the object
* @return a rectangle where the object is sensible to the listeners
*/
Rectangle getBox(O obj, Point center);
/**
* The paint function, aka the part where you can draw things (like Mesh)
*
* @param g2 the graphics object used for painting
* @param obj the object to paint
* @param center the center point of the object
*/
void paint(Graphics2D g2, O obj, Point center);
}

View File

@@ -0,0 +1,112 @@
package berack96.sim.util.graph.view;
import berack96.sim.util.graph.Graph;
import berack96.sim.util.graph.view.vertex.VertexComponent;
import berack96.sim.util.graph.visit.VisitInfo;
import berack96.sim.util.graph.visit.VisitStrategy;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
public class VisitListener<V> implements GraphListener {
private final GraphPanel<V, ?> panel;
private final VisitStrategy<V, ?> strategy;
private final Set<Timer> timers = new HashSet<>();
private static int refreshTime = 1000;
public VisitListener(GraphPanel<V, ?> panel, VisitStrategy<V, ?> strategy) {
this.panel = panel;
this.strategy = strategy;
}
public static void changeRefresh(int millisec) {
refreshTime = millisec;
}
@Override
public void remove() {
timers.forEach(t -> t.stop());
timers.clear();
}
@Override
public String getDescription() {
return "<html>"
+ "Start a visit by pressing<br />"
+ "with the mouse SX on the root vertex<br />"
+ "</html>";
}
@Override
public void mousePressed(MouseEvent e) {
this.remove();
Graph<V, ?> graph = panel.getGraph();
graph.unMarkAll();
panel.repaint();
if (e.getButton() == MouseEvent.BUTTON1)
try {
VertexComponent<V> vertex = panel.getVertexAt(e.getPoint());
AtomicInteger count = new AtomicInteger(0);
VisitInfo<V> info = vertex.vertex.visit(strategy, null);
info.forEach(v -> {
final boolean visited = v.timeVisited == count.get();
Timer timer = new Timer(count.getAndIncrement() * refreshTime, e1 -> {
if (visited && v.parent !=null)
graph.mark(v.vertex, v.parent);
graph.mark(v.vertex, visited ? "visited" : "discovered");
panel.repaint();
});
timers.add(timer);
timer.setRepeats(false);
timer.start();
});
} catch (Exception ignore) {}
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseDragged(MouseEvent e) {
}
@Override
public void mouseMoved(MouseEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}

View File

@@ -0,0 +1,22 @@
package berack96.sim.util.graph.view.edge;
import berack96.sim.util.graph.Edge;
import berack96.sim.util.graph.view.vertex.VertexComponent;
import java.awt.*;
public class EdgeComponent<V, W extends Number> extends Component {
private static final long serialVersionUID = 1L;
public final VertexComponent<V> source;
public final VertexComponent<V> destination;
public final W weight;
public final Edge<V, W> edge;
public EdgeComponent(VertexComponent<V> source, VertexComponent<V> destination, W weight) {
this.source = source;
this.destination = destination;
this.weight = weight;
this.edge = new Edge<>(source.vertex.getValue(), destination.vertex.getValue(), weight);
}
}

View File

@@ -0,0 +1,24 @@
package berack96.sim.util.graph.view.edge;
import berack96.sim.util.graph.Vertex;
import berack96.sim.util.graph.view.GraphPanel;
public class EdgeIntListener<V> extends EdgeListener<V, Integer> {
public EdgeIntListener(GraphPanel<V, Integer> graphPanel) {
super(graphPanel);
}
@Override
public void remove() {}
@Override
protected Integer buildNewEdge(Vertex<V> vertex, Vertex<V> vertex1) {
return (int) (Math.random() * 100);
}
@Override
protected Integer buildEdgeFrom(String string) {
return Integer.parseInt(string.replaceAll("[^0-9]+", ""));
}
}

View File

@@ -0,0 +1,128 @@
package berack96.sim.util.graph.view.edge;
import berack96.sim.util.graph.Vertex;
import berack96.sim.util.graph.view.GraphListener;
import berack96.sim.util.graph.view.GraphPanel;
import berack96.sim.util.graph.view.vertex.VertexComponent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.concurrent.atomic.AtomicReference;
public abstract class EdgeListener<V, W extends Number> implements GraphListener {
private final GraphPanel<V, W> graphPanel;
private final AtomicReference<VertexComponent<V>> componentPressed = new AtomicReference<>();
private final AtomicReference<Integer> buttonPressed = new AtomicReference<>();
private final AtomicReference<EdgeComponent<V, W>> edge = new AtomicReference<>();
private final StringBuilder string = new StringBuilder();
public EdgeListener(GraphPanel<V, W> graphPanel) {
this.graphPanel = graphPanel;
}
protected abstract W buildNewEdge(Vertex<V> vertex, Vertex<V> vertex1);
protected abstract W buildEdgeFrom(String string);
@Override
public String getDescription() {
return "<html>"
+ "Modify edges with:<br />"
+ "mouse SX on vertex to another ==> add<br />"
+ "mouse DX on edge ==> change weigth<br />"
+ "(only numbers allowed)"
+ "</html>";
}
@Override
public void mousePressed(MouseEvent e) {
buttonPressed.set(e.getButton());
componentPressed.set(graphPanel.getVertexAt(e.getPoint()));
}
@Override
public void mouseReleased(MouseEvent e) {
try {
edge.get().source.vertex.unMark("modS");
edge.get().destination.vertex.unMark("modD");
edge.set(null);
} catch (Exception ignored) {}
if (buttonPressed.get() == MouseEvent.BUTTON1) {
try {
VertexComponent<V> source = componentPressed.get();
VertexComponent<V> destination = graphPanel.getVertexAt(e.getPoint());
if (!graphPanel.getGraph().containsEdge(source.vertex.getValue(), destination.vertex.getValue()))
graphPanel.addEdge(source, destination, buildNewEdge(source.vertex, destination.vertex));
} catch (Exception ignore) {
}
} else {
edge.set(graphPanel.getEdgeAt(e.getPoint()));
try {
edge.get().source.vertex.mark("modS");
edge.get().destination.vertex.mark("modD");
graphPanel.setFocusTraversalKeysEnabled(false);
graphPanel.requestFocusInWindow();
} catch (Exception ignored) {}
}
string.delete(0, string.length());
componentPressed.set(null);
graphPanel.repaint();
}
@Override
public void keyPressed(KeyEvent e) {
if (edge.get() != null && Character.isDigit(e.getKeyChar())) {
string.append(e.getKeyChar());
if (!edge.get().source.equals(edge.get().destination))
try {
graphPanel.modEdge(edge.get().source, edge.get().destination, buildEdgeFrom(string.toString()));
graphPanel.repaint();
} catch (Exception ignored) {
}
} else {
try {
edge.get().source.vertex.unMark("modS");
edge.get().destination.vertex.unMark("modD");
} catch (Exception ignored) {}
edge.set(null);
string.delete(0, string.length());
}
graphPanel.repaint();
}
@Override
public void mouseDragged(MouseEvent e) {
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseMoved(MouseEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}

View File

@@ -0,0 +1,109 @@
package berack96.sim.util.graph.view.edge;
import berack96.sim.util.graph.view.GraphicalView;
import berack96.sim.util.graph.view.stuff.Arrow;
import java.awt.*;
import java.awt.geom.Point2D;
import java.util.Collection;
public class EdgeView<V, W extends Number> implements GraphicalView<EdgeComponent<V, W>> {
private static final Font FONT = new Font("Papyrus", Font.BOLD, 14);
@Override
public Rectangle getBox(EdgeComponent<V, W> edge, Point center) {
/* CALCULATING BOUNDS AND ARROW STARTING AND ENDING POINTS */
Point srcLoc = edge.source.getLocation();
Point desLoc = edge.destination.getLocation();
/* getting the radius */
int srcRadius = edge.source.getHeight() / 2;
int desRadius = edge.destination.getHeight() / 2;
/* centering the location */
srcLoc.translate(srcRadius, srcRadius);
desLoc.translate(desRadius, desRadius);
/* using vector for moving to the edge of the circumference and finding location of int box */
final Point.Double vector = getVector(srcLoc, desLoc);
/* CALCULATING THE NUMBER SPACE */
int boxDistance = (int) (srcLoc.distance(desLoc) / 2.7);
FontMetrics metrics = edge.getFontMetrics(FONT);
int dimString = metrics.stringWidth(edge.weight.toString());
int dimRect = Math.max(dimString, metrics.getHeight());
return new Rectangle(
(int) ((desLoc.x - (vector.x * boxDistance)) - (dimRect / 2)),
(int) ((desLoc.y - (vector.y * boxDistance)) - (dimRect / 2)),
dimRect, dimRect);
}
@Override
public void paint(Graphics2D g2, EdgeComponent<V, W> edge, Point center) {
/* CALCULATING BOUNDS AND ARROW STARTING AND ENDING POINTS */
Point srcLoc = edge.source.getLocation();
Point desLoc = edge.destination.getLocation();
/* getting the radius */
int srcRadius = edge.source.getHeight() / 2;
int desRadius = edge.destination.getHeight() / 2;
/* centering the location */
srcLoc.translate(srcRadius, srcRadius);
desLoc.translate(desRadius, desRadius);
/* using vector for moving to the edge of the circumference and finding location of int box */
final Point.Double vector = getVector(srcLoc, desLoc);
/* CALCULATING THE NUMBER SPACE */
int boxDistance = (int) (srcLoc.distance(desLoc) / 2.7);
FontMetrics metrics = edge.getFontMetrics(FONT);
int dimString = metrics.stringWidth(edge.weight.toString());
int dimRect = Math.max(dimString, metrics.getHeight());
Rectangle integerRect = new Rectangle(
(int) ((desLoc.x - (vector.x * boxDistance)) - (dimRect / 2)),
(int) ((desLoc.y - (vector.y * boxDistance)) - (dimRect / 2)),
dimRect, dimRect);
/* moving to a distance R to the center */
srcLoc.translate((int) (vector.x * srcRadius), (int) (vector.y * srcRadius));
desLoc.translate((int) (-vector.x * desRadius), (int) (-vector.y * desRadius));
/* THE COLOR OF THE ARROW */
Collection<Object> marksD = edge.destination.vertex.getMarks();
Collection<Object> marksS = edge.source.vertex.getMarks();
boolean isChild = marksD.contains(edge.source.vertex.getValue());
boolean selected = marksS.contains("selected");
boolean isMod = marksD.contains("modD") && marksS.contains("modS");
Color arrowColor = isChild || selected ? Color.RED : Color.BLACK;
Color boxColor = isChild || selected || isMod ? Color.ORANGE : Color.BLUE;
Color stringColor = isChild || selected || isMod ? Color.BLACK : Color.CYAN;
g2.setFont(FONT);
/* draw all */
g2.setColor(arrowColor);
//g2d.drawLine(arrowStart.x, arrowStart.y, arrowEnd.x, arrowEnd.y);
Polygon arrow = new Arrow(srcLoc, desLoc, 1, 10);
//g2d.draw(arrow);
g2.fillPolygon(arrow);
/* draw the integer space */
g2.setColor(boxColor);
g2.fill(integerRect);
g2.setColor(arrowColor);
g2.draw(integerRect);
g2.setColor(stringColor);
g2.drawString(edge.weight.toString(), (float) (integerRect.x + (dimRect - dimString) / 2), (float) (integerRect.y + (dimRect + metrics.getHeight() / 2) / 2));
}
private Point.Double getVector(Point a, Point b) {
final Point.Double vector = new Point2D.Double(b.x - a.x, b.y - a.y);
/* normalizing vector */
double length = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
if(length != 0) {
vector.x = vector.x / length;
vector.y = vector.y / length;
}
return vector;
}
}

View File

@@ -0,0 +1,59 @@
package berack96.sim.util.graph.view.stuff;
import java.awt.*;
/**
* Class that create a Polygon that has a shape of an arrow
*
* @author Berack96
*/
public class Arrow extends Polygon {
private static final long serialVersionUID = 1L;
/**
* Create an arrow
*
* @param start the starting point of your arrow (the base)
* @param end the ending point of your arrow (the head)
* @param size the size of the arrow base
* @param headSize the size of the arrow's head
*/
public Arrow(Point start, Point end, final int size, final int headSize) {
final Point.Double vector = new Point.Double(end.x - start.x, end.y - start.y);
/* vectors normalization */
double length = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
vector.x = vector.x / length;
vector.y = vector.y / length;
final Point headStart = new Point((int) (end.x - vector.x * headSize), (int) (end.y - vector.y * headSize));
/* rotating vector for the parallels */
double cs = Math.cos(Math.PI / 2);
double sn = Math.sin(Math.PI / 2);
double x = vector.x * cs - vector.y * sn;
double y = vector.x * sn + vector.y * cs;
vector.setLocation(x, y);
/* TODO here use some magic for create a curve arrow if vector.x == vector.y && vector.x == 0 */
/* building arrow starting from A to G */
/*
C
|\
| \
A--------B \
| D
G--------F /
| /
|/
E
*/
addPoint((int) (start.x - vector.x * size), (int) (start.y - vector.y * size));
addPoint((int) (start.x + vector.x * size), (int) (start.y + vector.y * size));
addPoint((int) (headStart.x + vector.x * size), (int) (headStart.y + vector.y * size));
addPoint((int) (headStart.x + vector.x * headSize), (int) (headStart.y + vector.y * headSize));
addPoint(end.x, end.y);
addPoint((int) (headStart.x - vector.x * headSize), (int) (headStart.y - vector.y * headSize));
addPoint((int) (headStart.x - vector.x * size), (int) (headStart.y - vector.y * size));
}
}

View File

@@ -0,0 +1,15 @@
package berack96.sim.util.graph.view.vertex;
import berack96.sim.util.graph.Vertex;
import java.awt.*;
public class VertexComponent<V> extends Component {
private static final long serialVersionUID = 1L;
public final Vertex<V> vertex;
public VertexComponent(Vertex<V> vertex) {
this.vertex = vertex;
}
}

View File

@@ -0,0 +1,24 @@
package berack96.sim.util.graph.view.vertex;
import berack96.sim.util.graph.Graph;
import berack96.sim.util.graph.view.GraphPanel;
public class VertexIntListener extends VertexListener<Integer> {
private Integer counter = 0;
public VertexIntListener(GraphPanel<Integer, ?> panel) {
super(panel);
}
@Override
public void remove() {}
@Override
protected Integer buildNewVertex(Graph<Integer, ?> graph) {
if(graph.numberOfVertices() == 0)
counter = 0;
counter++;
return counter - 1;
}
}

View File

@@ -0,0 +1,91 @@
package berack96.sim.util.graph.view.vertex;
import berack96.sim.util.graph.Graph;
import berack96.sim.util.graph.view.GraphListener;
import berack96.sim.util.graph.view.GraphPanel;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.concurrent.atomic.AtomicReference;
public abstract class VertexListener<V> implements GraphListener {
protected final GraphPanel<V, ?> panel;
private final AtomicReference<VertexComponent<V>> componentPressed = new AtomicReference<>();
public VertexListener(GraphPanel<V, ?> panel) {
this.panel = panel;
}
protected abstract V buildNewVertex(Graph<V, ?> graph);
@Override
public String getDescription() {
return "<html>"
+ "Modify vertex with:<br />"
+ "mouse SX ==> add<br />"
+ "mouse SX on vertex ==> move<br />"
+ "mouse DX ==> remove<br />"
+ "</html>";
}
@Override
public void mousePressed(MouseEvent e) {
try {
if (e.getButton() == MouseEvent.BUTTON1)
componentPressed.set(panel.getVertexAt(e.getPoint()));
} catch (Exception ignore) {}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1 && componentPressed.get() == null)
panel.addVertex(e.getPoint(), buildNewVertex(panel.getGraph()));
else if (e.getButton() == MouseEvent.BUTTON3)
panel.removeVertex(e.getPoint());
panel.getGraph().unMarkAll("selected");
if(componentPressed.get() != null)
componentPressed.get().vertex.mark("selected");
panel.repaint();
componentPressed.set(null);
}
@Override
public void mouseDragged(MouseEvent e) {
if (componentPressed.get() != null) {
panel.moveVertex(componentPressed.get(), e.getPoint());
panel.repaint();
}
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseMoved(MouseEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}

View File

@@ -0,0 +1,42 @@
package berack96.sim.util.graph.view.vertex;
import berack96.sim.util.graph.view.GraphicalView;
import java.awt.*;
public class VertexView<V> implements GraphicalView<VertexComponent<V>> {
private static final Font FONT = new Font("Comic Sans MS", Font.BOLD, 17);
private static final int PADDING = 6;
@Override
public Rectangle getBox(VertexComponent<V> obj, Point center) {
FontMetrics metrics = obj.getFontMetrics(FONT);
int stringPixels = metrics.stringWidth(obj.vertex.getValue().toString());
int size = Math.max(stringPixels, metrics.getHeight()) + 2 * PADDING;
return new Rectangle(center.x - size / 2, center.y - size / 2, size, size);
}
@Override
public void paint(Graphics2D g2, VertexComponent<V> obj, Point center) {
boolean discovered = obj.vertex.getMarks().contains("discovered");
boolean visited = obj.vertex.getMarks().contains("visited");
boolean selected = obj.vertex.getMarks().contains("selected");
FontMetrics metrics = obj.getFontMetrics(FONT);
int stringPixels = metrics.stringWidth(obj.vertex.getValue().toString());
int size = Math.max(stringPixels, metrics.getHeight()) + 2 * PADDING;
center.x = center.x - size / 2;
center.y = center.y - size / 2;
g2.setFont(FONT);
g2.setColor(visited || selected ? Color.RED : Color.ORANGE);
g2.fillOval(center.x, center.y, size, size);
g2.setColor(visited || discovered || selected ? Color.ORANGE : Color.YELLOW);
g2.fillOval(center.x + PADDING / 2, center.y + PADDING / 2, size - PADDING, size - PADDING);
g2.setColor(Color.BLACK);
g2.drawString(obj.vertex.getValue().toString(), center.x + PADDING + (size - 2 * PADDING - stringPixels) / 2, center.y + (size) / 2 + PADDING);
}
}

View File

@@ -32,12 +32,12 @@ public class Dijkstra<V, W extends Number> implements VisitDistance<V, W> {
public VisitInfo<V> visit(Graph<V, W> graph, V source, Consumer<V> visit) throws NullPointerException, IllegalArgumentException { public VisitInfo<V> visit(Graph<V, W> graph, V source, Consumer<V> visit) throws NullPointerException, IllegalArgumentException {
VisitInfo<V> info = new VisitInfo<>(source); VisitInfo<V> info = new VisitInfo<>(source);
Queue<QueueEntry> queue = new PriorityQueue<>(); Queue<QueueEntry> queue = new PriorityQueue<>();
Map<V, Integer> dist = new HashMap<>(); Map<V, Double> dist = new HashMap<>();
Map<V, V> prev = new HashMap<>(); Map<V, V> prev = new HashMap<>();
this.source = source; this.source = source;
dist.put(source, 0); // Initialization dist.put(source, 0.0); // Initialization
queue.add(new QueueEntry(source, 0)); queue.add(new QueueEntry(source, 0.0));
while (!queue.isEmpty()) { // The main loop while (!queue.isEmpty()) { // The main loop
QueueEntry u = queue.poll(); // Remove and return best vertex QueueEntry u = queue.poll(); // Remove and return best vertex
@@ -49,8 +49,8 @@ public class Dijkstra<V, W extends Number> implements VisitDistance<V, W> {
graph.getEdgesOut(u.entry).forEach((edge) -> { graph.getEdgesOut(u.entry).forEach((edge) -> {
V child = edge.getDestination(); V child = edge.getDestination();
info.setDiscovered(child); info.setDiscovered(child);
int alt = dist.get(u.entry) + edge.getWeight().intValue(); double alt = dist.get(u.entry) + edge.getWeight().doubleValue();
Integer distCurrent = dist.get(child); Double distCurrent = dist.get(child);
if (distCurrent == null || alt < distCurrent) { if (distCurrent == null || alt < distCurrent) {
dist.put(child, alt); dist.put(child, alt);
prev.put(child, u.entry); prev.put(child, u.entry);
@@ -83,14 +83,15 @@ public class Dijkstra<V, W extends Number> implements VisitDistance<V, W> {
private class QueueEntry implements Comparable<QueueEntry> { private class QueueEntry implements Comparable<QueueEntry> {
final V entry; final V entry;
final Integer weight; final Double weight;
QueueEntry(V entry, Integer weight) { QueueEntry(V entry, Double weight) {
this.entry = entry; this.entry = entry;
this.weight = weight; this.weight = weight;
} }
@Override @SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
try { try {
return ((QueueEntry) obj).entry.equals(entry); return ((QueueEntry) obj).entry.equals(entry);
@@ -101,7 +102,8 @@ public class Dijkstra<V, W extends Number> implements VisitDistance<V, W> {
@Override @Override
public int compareTo(QueueEntry queueEntry) { public int compareTo(QueueEntry queueEntry) {
return this.weight - queueEntry.weight; double ret = this.weight - queueEntry.weight;
return ret==0? 0: ret<0? -1:1;
} }
} }
} }

View File

@@ -25,5 +25,5 @@ public interface VisitDistSourceDest<V, W extends Number> extends VisitStrategy<
* @throws NullPointerException if one of the vertex is null * @throws NullPointerException if one of the vertex is null
* @throws IllegalArgumentException if one of the vertex is not contained in the graph * @throws IllegalArgumentException if one of the vertex is not contained in the graph
*/ */
List<Edge<V, W>> distance(Graph graph, V source, V destination) throws NullPointerException, IllegalArgumentException; List<Edge<V, W>> distance(Graph<V, W> graph, V source, V destination) throws NullPointerException, IllegalArgumentException;
} }

View File

@@ -918,6 +918,7 @@ public class TestGraph {
shouldThrow(nullException, () -> graph.unMark("1", null)); shouldThrow(nullException, () -> graph.unMark("1", null));
shouldThrow(nullException, () -> graph.unMark(null, "blue")); shouldThrow(nullException, () -> graph.unMark(null, "blue"));
shouldThrow(nullException, () -> graph.unMarkAll(null)); shouldThrow(nullException, () -> graph.unMarkAll(null));
shouldThrow(nullException, () -> graph.getMarkedWith(null));
shouldThrow(notException, () -> graph.mark("324", "yellow")); shouldThrow(notException, () -> graph.mark("324", "yellow"));
shouldThrow(notException, () -> graph.unMark("32423")); shouldThrow(notException, () -> graph.unMark("32423"));
@@ -966,30 +967,54 @@ public class TestGraph {
graph.mark("2", "mark"); graph.mark("2", "mark");
graph.mark("3", "mark2"); graph.mark("3", "mark2");
graph.mark("1", "mark2"); graph.mark("1", "mark2");
shouldContain(graph.getMarks("1"), "mark", "mark2"); graph.mark("1", 3);
shouldContain(graph.getMarks("1"), "mark", "mark2", 3);
shouldContain(graph.getMarks("2"), "mark"); shouldContain(graph.getMarks("2"), "mark");
shouldContain(graph.getMarks("3"), "mark2"); shouldContain(graph.getMarks("3"), "mark2");
shouldContain(graph.getMarkedWith("mark"), "2", "1");
shouldContain(graph.getMarkedWith("mark2"), "1", "3");
shouldContain(graph.getMarkedWith(3), "1");
graph.unMark("1", "mark"); graph.unMark("1", "mark");
shouldContain(graph.getMarks("1"), "mark2"); shouldContain(graph.getMarks("1"), "mark2", 3);
shouldContain(graph.getMarks("2"), "mark"); shouldContain(graph.getMarks("2"), "mark");
shouldContain(graph.getMarks("3"), "mark2"); shouldContain(graph.getMarks("3"), "mark2");
shouldContain(graph.getMarkedWith("mark"), "2");
shouldContain(graph.getMarkedWith("mark2"), "1", "3");
shouldContain(graph.getMarkedWith(3), "1");
graph.unMarkAll("mark2"); graph.unMarkAll("mark2");
shouldContain(graph.getMarks("1")); shouldContain(graph.getMarks("1"), 3);
shouldContain(graph.getMarks("2"), "mark"); shouldContain(graph.getMarks("2"), "mark");
shouldContain(graph.getMarks("3")); shouldContain(graph.getMarks("3"));
shouldContain(graph.getMarkedWith("mark"), "2");
shouldContain(graph.getMarkedWith("mark2"));
shouldContain(graph.getMarkedWith(3), "1");
graph.unMark("1", "mark"); graph.unMark("1", "mark");
graph.unMark("2", "mark2"); graph.unMark("2", "mark2");
shouldContain(graph.getMarks("1")); shouldContain(graph.getMarks("1"), 3);
shouldContain(graph.getMarks("2"), "mark"); shouldContain(graph.getMarks("2"), "mark");
shouldContain(graph.getMarks("3")); shouldContain(graph.getMarks("3"));
shouldContain(graph.getMarkedWith("mark"), "2");
shouldContain(graph.getMarkedWith("mark2"));
shouldContain(graph.getMarkedWith(3), "1");
graph.unMark("2", "mark"); graph.unMark("2", "mark");
shouldContain(graph.getMarks("1"), 3);
shouldContain(graph.getMarks("2"));
shouldContain(graph.getMarks("3"));
shouldContain(graph.getMarkedWith("mark"));
shouldContain(graph.getMarkedWith("mark2"));
shouldContain(graph.getMarkedWith(3), "1");
graph.unMarkAll(3);
shouldContain(graph.getMarks("1")); shouldContain(graph.getMarks("1"));
shouldContain(graph.getMarks("2")); shouldContain(graph.getMarks("2"));
shouldContain(graph.getMarks("3")); shouldContain(graph.getMarks("3"));
shouldContain(graph.getMarkedWith("mark"));
shouldContain(graph.getMarkedWith("mark2"));
shouldContain(graph.getMarkedWith(3));
} }
@Test @Test