1 /*******************************************************************************
2 * Copyright (c) 2015 École Polytechnique de Montréal
4 * All rights reserved. This program and the accompanying materials are made
5 * available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * Francis Giraldeau - Initial implementation and API
11 * Geneviève Bastien - Initial implementation and API
12 *******************************************************************************/
14 package org
.eclipse
.tracecompass
.analysis
.graph
.core
.base
;
16 import java
.util
.HashMap
;
17 import java
.util
.HashSet
;
18 import java
.util
.List
;
21 import java
.util
.Stack
;
22 import java
.util
.concurrent
.CountDownLatch
;
24 import org
.eclipse
.jdt
.annotation
.Nullable
;
25 import org
.eclipse
.tracecompass
.analysis
.graph
.core
.base
.TmfEdge
.EdgeType
;
26 import org
.eclipse
.tracecompass
.analysis
.graph
.core
.base
.TmfVertex
.EdgeDirection
;
27 import org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
;
28 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.ITmfTimestamp
;
30 import com
.google
.common
.collect
.ArrayListMultimap
;
31 import com
.google
.common
.collect
.ImmutableSet
;
32 import com
.google
.common
.collect
.ListMultimap
;
35 * Undirected, unweighed, timed graph data type for dependencies between
36 * elements of a system.
38 * Vertices are timed: each vertex has a timestamp associated, so the vertex
39 * belongs to an object (the key of the multimap) at a given time. This is why
40 * we use a ListMultimap to represent the graph, instead of a simple list.
42 * @author Francis Giraldeau
43 * @author Geneviève Bastien
45 public class TmfGraph
{
47 private final ListMultimap
<IGraphWorker
, TmfVertex
> fNodeMap
;
48 private final Map
<TmfVertex
, IGraphWorker
> fReverse
;
50 /* Latch tracking if the graph is done building or not */
51 private final CountDownLatch fFinishedLatch
= new CountDownLatch(1);
57 fNodeMap
= NonNullUtils
.checkNotNull(ArrayListMultimap
.create());
58 fReverse
= new HashMap
<>();
62 * Add node to the provided object without linking
65 * The key of the object the vertex belongs to
69 public void add(IGraphWorker worker
, TmfVertex vertex
) {
70 List
<TmfVertex
> list
= fNodeMap
.get(worker
);
72 fReverse
.put(vertex
, worker
);
76 * Add node to object's list and make horizontal link with tail.
79 * The key of the object the vertex belongs to
82 * @return The edge constructed
84 public @Nullable TmfEdge
append(IGraphWorker worker
, TmfVertex vertex
) {
85 return append(worker
, vertex
, EdgeType
.DEFAULT
);
89 * Add node to object's list and make horizontal link with tail.
92 * The key of the object the vertex belongs to
96 * The type of edge to create
97 * @return The edge constructed
99 public @Nullable TmfEdge
append(IGraphWorker worker
, TmfVertex vertex
, EdgeType type
) {
100 List
<TmfVertex
> list
= fNodeMap
.get(worker
);
101 TmfVertex tail
= getTail(worker
);
104 link
= tail
.linkHorizontal(vertex
);
108 fReverse
.put(vertex
, worker
);
113 * Add a link between two vertices of the graph. The from vertex must be in
114 * the graph. If the 'to' vertex is not in the graph, it will be appended to
115 * the object the 'from' vertex is for. Otherwise a vertical or horizontal
116 * link will be created between the vertices.
118 * Caution: this will remove without warning any previous link from the
124 * The destination vertex
125 * @return The newly created edge
127 public TmfEdge
link(TmfVertex from
, TmfVertex to
) {
128 return link(from
, to
, EdgeType
.DEFAULT
);
132 * Add a link between two vertices of the graph. The from vertex must be in
133 * the graph. If the 'to' vertex is not in the graph, it will be appended to
134 * the object the 'from' vertex is for. Otherwise a vertical or horizontal
135 * link will be created between the vertices.
137 * Caution: this will remove without warning any previous link from the
143 * The destination vertex
145 * The type of edge to create
146 * @return The newly created edge
148 public TmfEdge
link(TmfVertex from
, TmfVertex to
, EdgeType type
) {
149 IGraphWorker ofrom
= fReverse
.get(from
);
150 IGraphWorker oto
= fReverse
.get(to
);
152 throw new IllegalArgumentException(Messages
.TmfGraph_FromNotInGraph
);
155 /* to vertex not in the graph, add it to ofrom */
162 if (oto
.equals(ofrom
)) {
163 link
= from
.linkHorizontal(to
);
165 link
= from
.linkVertical(to
);
172 * Returns tail node of the provided object
175 * The key of the object the vertex belongs to
176 * @return The last vertex of obj
178 public @Nullable TmfVertex
getTail(IGraphWorker worker
) {
179 List
<TmfVertex
> list
= fNodeMap
.get(worker
);
180 if (!list
.isEmpty()) {
181 return list
.get(list
.size() - 1);
187 * Removes the last vertex of the provided object
190 * The key of the object the vertex belongs to
191 * @return The removed vertex
193 public @Nullable TmfVertex
removeTail(IGraphWorker worker
) {
194 List
<TmfVertex
> list
= fNodeMap
.get(worker
);
195 if (!list
.isEmpty()) {
196 TmfVertex last
= list
.remove(list
.size() - 1);
197 fReverse
.remove(last
);
204 * Returns head node of the provided object. This is the very first node of
208 * The key of the object the vertex belongs to
209 * @return The head vertex
211 public @Nullable TmfVertex
getHead(IGraphWorker worker
) {
212 IGraphWorker ref
= worker
;
213 List
<TmfVertex
> list
= fNodeMap
.get(ref
);
214 if (!list
.isEmpty()) {
221 * Returns the head node of the object of the nodeMap that has the earliest
224 * @return The head vertex
226 public @Nullable TmfVertex
getHead() {
227 if (fNodeMap
.isEmpty()) {
230 IGraphWorker headWorker
= fNodeMap
.keySet().stream()
231 .filter(k
-> !fNodeMap
.get(k
).isEmpty())
232 .sorted((k1
, k2
) -> fNodeMap
.get(k1
).get(0).compareTo(fNodeMap
.get(k2
).get(0)))
235 return getHead(headWorker
);
239 * Returns head vertex from a given node. That is the first of the current
240 * sequence of edges, the one with no left edge when going back through the
241 * original vertex's left edge
244 * The vertex for which to get the head
245 * @return The head vertex from the requested vertex
247 public TmfVertex
getHead(TmfVertex vertex
) {
248 TmfVertex headNode
= vertex
;
249 TmfEdge edge
= headNode
.getEdge(EdgeDirection
.INCOMING_HORIZONTAL_EDGE
);
250 while (edge
!= null) {
251 headNode
= edge
.getVertexFrom();
252 if (headNode
== vertex
) {
253 throw new CycleDetectedException();
255 edge
= headNode
.getEdge(EdgeDirection
.INCOMING_HORIZONTAL_EDGE
);
261 * Returns all nodes of the provided object.
264 * The key of the object the vertex belongs to
265 * @return The list of vertices for the object
267 public List
<TmfVertex
> getNodesOf(IGraphWorker obj
) {
268 return fNodeMap
.get(obj
);
272 * Returns the object the vertex belongs to
275 * The vertex to get the parent for
276 * @return The object the vertex belongs to
278 public @Nullable IGraphWorker
getParentOf(TmfVertex node
) {
279 return fReverse
.get(node
);
283 * Returns the graph objects
285 * @return The vertex map
287 public Set
<IGraphWorker
> getWorkers() {
288 return ImmutableSet
.copyOf(fNodeMap
.keySet());
292 * Returns the number of vertices in the graph
294 * @return number of vertices
297 return fReverse
.size();
301 public String
toString() {
302 return NonNullUtils
.nullToEmptyString(String
.format("Graph { actors=%d, nodes=%d }", //$NON-NLS-1$
303 fNodeMap
.keySet().size(), fNodeMap
.values().size()));
307 * Dumps the full graph
309 * @return A string with the graph dump
311 public String
dump() {
312 StringBuilder str
= new StringBuilder();
313 for (IGraphWorker obj
: fNodeMap
.keySet()) {
314 str
.append(String
.format("%10s ", obj
)); //$NON-NLS-1$
315 str
.append(fNodeMap
.get(obj
));
316 str
.append("\n"); //$NON-NLS-1$
318 return NonNullUtils
.nullToEmptyString(str
.toString());
321 // ----------------------------------------------
322 // Graph operations and visits
323 // ----------------------------------------------
326 * Visits a graph from the start vertex and every vertex of the graph having
327 * a path to/from them that intersects the start vertex
329 * Each time the worker changes, it goes back to the beginning of the
330 * current horizontal sequence and visits all nodes from there.
332 * Parts of the graph that are totally disjoints from paths to/from start
333 * will not be visited by this method
336 * The vertex to start the scan for
340 public void scanLineTraverse(final @Nullable TmfVertex start
, final ITmfGraphVisitor visitor
) {
344 Stack
<TmfVertex
> stack
= new Stack
<>();
345 HashSet
<TmfVertex
> visited
= new HashSet
<>();
347 while (!stack
.isEmpty()) {
348 TmfVertex curr
= stack
.pop();
349 if (visited
.contains(curr
)) {
353 TmfVertex n
= getHead(curr
);
354 visitor
.visitHead(n
);
359 // Only visit links up-right, guarantee to visit once only
360 TmfEdge edge
= n
.getEdge(EdgeDirection
.OUTGOING_VERTICAL_EDGE
);
362 stack
.push(edge
.getVertexTo());
363 visitor
.visit(edge
, false);
365 edge
= n
.getEdge(EdgeDirection
.INCOMING_VERTICAL_EDGE
);
367 stack
.push(edge
.getVertexFrom());
369 edge
= n
.getEdge(EdgeDirection
.OUTGOING_HORIZONTAL_EDGE
);
371 visitor
.visit(edge
, true);
372 n
= edge
.getVertexTo();
374 // end of the horizontal list
382 * @see TmfGraph#scanLineTraverse(TmfVertex, ITmfGraphVisitor)
385 * The worker from which to start the scan
389 public void scanLineTraverse(@Nullable IGraphWorker start
, final ITmfGraphVisitor visitor
) {
393 scanLineTraverse(getHead(start
), visitor
);
397 * Return the vertex for an object at a given timestamp, or the first vertex
398 * after the timestamp
403 * The object for which to get the vertex
404 * @return Vertex at timestamp or null if no vertex at or after timestamp
406 public @Nullable TmfVertex
getVertexAt(ITmfTimestamp startTime
, IGraphWorker worker
) {
407 List
<TmfVertex
> list
= fNodeMap
.get(worker
);
409 long ts
= startTime
.getValue();
410 // Scan the list until vertex is later than time
411 for (TmfVertex vertex
: list
) {
412 if (vertex
.getTs() >= ts
) {
420 * Returns whether the graph is completed or not
422 * @return whether the graph is done building
424 public boolean isDoneBuilding() {
425 return fFinishedLatch
.getCount() == 0;
429 * Countdown the latch to show that the graph is done building
431 public void closeGraph() {
432 fFinishedLatch
.countDown();