/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.tuscany.sca.databinding.impl; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; /** * Directed, weighted graph * * @param <V> The type of vertex object * @param <E> The type of edge object * * @version $Rev$ $Date$ */ public class DirectedGraph<V, E> implements Cloneable { private final static Logger logger = Logger.getLogger(DirectedGraph.class.getName()); private final Map<V, Vertex> vertices = new HashMap<V, Vertex>(); /** * Key for the shortest path cache */ private final class VertexPair { private Vertex source; private Vertex target; /** * @param source * @param target */ private VertexPair(Vertex source, Vertex target) { super(); this.source = source; this.target = target; } @Override public boolean equals(Object object) { if (!VertexPair.class.isInstance(object)) { return false; } VertexPair pair = (VertexPair)object; return source == pair.source && target == pair.target; } @Override public int hashCode() { int x = source == null ? 0 : source.hashCode(); int y = target == null ? 0 : target.hashCode(); return x ^ y; } } // Fix for TUSCANY-2069, making the map concurrent private final Map<VertexPair, Path> paths = new ConcurrentHashMap<VertexPair, Path>(); private final Path NULL_PATH = new Path(); /** * Vertex of a graph */ public final class Vertex { private V value; // TODO: Do we want to support multiple edges for a vertex pair? If so, // we should use a List instead of Map private Map<Vertex, Edge> outEdges = new HashMap<Vertex, Edge>(); private Map<Vertex, Edge> inEdges = new HashMap<Vertex, Edge>(); private Vertex(V value) { this.value = value; } @Override public String toString() { return "(" + value + ")"; } public V getValue() { return value; } public Map<Vertex, Edge> getOutEdges() { return outEdges; } public Map<Vertex, Edge> getInEdges() { return inEdges; } } /** * An Edge connects two vertices in one direction */ public final class Edge { private Vertex sourceVertex; private Vertex targetVertex; private E value; private int weight; private boolean pub = true; public Edge(Vertex source, Vertex target, E value, int weight, boolean pub) { this.sourceVertex = source; this.targetVertex = target; this.value = value; this.weight = weight; this.pub = pub; } @Override public String toString() { return sourceVertex + "->" + targetVertex + "[" + value + "," + weight + "]"; } public E getValue() { return value; } public void setValue(E value) { this.value = value; } public Vertex getTargetVertex() { return targetVertex; } public void setTargetVertex(Vertex vertex) { this.targetVertex = vertex; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public Vertex getSourceVertex() { return sourceVertex; } public void setSourceVertex(Vertex sourceVertex) { this.sourceVertex = sourceVertex; } public boolean isPublic() { return pub; } public void setPublic(boolean pub) { this.pub = pub; } } private final class Node implements Comparable<Node> { private long distance = Integer.MAX_VALUE; private Node previous; // NOPMD by rfeng on 9/26/06 9:17 PM private Vertex vertex; // NOPMD by rfeng on 9/26/06 9:17 PM private Node(Vertex vertex) { this.vertex = vertex; } public int compareTo(Node o) { return (distance > o.distance) ? 1 : ((distance == o.distance) ? 0 : -1); } } public void addEdge(V source, V target, E edgeValue, int weight, boolean publicEdge) { // Fix for TUSCANY-3456 // First check if we already has an edge Edge edge = getEdge(source, target); if (edge != null) { // An existing edge has higher weight, let's replace it if (edge.weight > weight) { logger.fine("An edge exists with higher weight: " + edge); removeEdge(edge); } else { // Don't add this edge logger.fine("An edge exists with lower weight: " + edge); return; } } Vertex s = getVertex(source); if (s == null) { s = new Vertex(source); vertices.put(source, s); } Vertex t = getVertex(target); if (t == null) { t = new Vertex(target); vertices.put(target, t); } edge = new Edge(s, t, edgeValue, weight, publicEdge); s.outEdges.put(t, edge); t.inEdges.put(s, edge); } public void addEdge(V soure, V target) { addEdge(soure, target, null, 0, true); } public Vertex getVertex(V source) { Vertex s = vertices.get(source); return s; } public boolean removeEdge(V source, V target) { Vertex s = getVertex(source); if (s == null) { return false; } Vertex t = getVertex(target); if (t == null) { return false; } return s.outEdges.remove(t) != null && t.inEdges.remove(s) != null; } public void removeEdge(Edge edge) { edge.sourceVertex.outEdges.remove(edge.targetVertex); edge.targetVertex.inEdges.remove(edge.sourceVertex); } public void removeVertex(Vertex vertex) { vertices.remove(vertex.getValue()); for (Edge e : new ArrayList<Edge>(vertex.outEdges.values())) { removeEdge(e); } for (Edge e : new ArrayList<Edge>(vertex.inEdges.values())) { removeEdge(e); } } public Edge getEdge(Vertex source, Vertex target) { return source.outEdges.get(target); } public Edge getEdge(V source, V target) { Vertex sv = getVertex(source); if (sv == null) { return null; } Vertex tv = getVertex(target); if (tv == null) { return null; } return getEdge(getVertex(source), getVertex(target)); } /** * Get the shortest path from the source vertex to the target vertex using * Dijkstra's algorithm. If there's no path, null will be returned. If the * source is the same as the target, it returns a path with empty edges with * weight 0. * * @param sourceValue The value identifies the source * @param targetValue The value identifies the target * @return The shortest path */ public Path getShortestPath(V sourceValue, V targetValue) { Vertex source = getVertex(sourceValue); if (source == null) { return null; } Vertex target = getVertex(targetValue); if (target == null) { return null; } VertexPair pair = new VertexPair(source, target); Path path = null; if (paths.containsKey(pair)) { path = paths.get(pair); return path == NULL_PATH? null: path; } // Check if there is a direct link, if yes, use it instead Edge direct = getEdge(source, target); path = new Path(); if (direct != null) { path.addEdge(direct); paths.put(pair, path); return path; } Map<Vertex, Node> nodes = new HashMap<Vertex, Node>(); for (Vertex v : vertices.values()) { Node node = new Node(v); if (v == source) { node.distance = 0; } nodes.put(v, node); } Set<Node> otherNodes = new HashSet<Node>(nodes.values()); Set<Node> nodesOnPath = new HashSet<Node>(); Node nextNode = null; while (!otherNodes.isEmpty()) { nextNode = extractMin(otherNodes); if (nextNode.vertex == target) { path = getPath(nextNode); paths.put(pair, path); // Cache it return path == NULL_PATH? null: path; } nodesOnPath.add(nextNode); for (Edge edge : nextNode.vertex.outEdges.values()) { Node adjacentNode = nodes.get(edge.targetVertex); // The private edge can only be used if the edge connects to the target directly if (edge.isPublic() || edge.getTargetVertex() == target) { if (nextNode.distance + edge.weight < adjacentNode.distance) { adjacentNode.distance = nextNode.distance + edge.weight; adjacentNode.previous = nextNode; } } } } paths.put(pair, NULL_PATH); // Cache it return null; } /** * Searches for the vertex u in the vertex set Q that has the least d[u] * value. That vertex is removed from the set Q and returned to the user. * * @param nodes * @return */ private Node extractMin(Set<Node> nodes) { Node node = Collections.min(nodes); nodes.remove(node); return node; } /** * The path between two vertices */ public final class Path { private List<Edge> edges = new LinkedList<Edge>(); private int weight; public int getWeight() { return weight; } public List<Edge> getEdges() { return edges; } public void addEdge(Edge edge) { edges.add(0, edge); weight += edge.weight; } @Override public String toString() { return edges + ", " + weight; } } private Path getPath(Node t) { if (t.distance == Integer.MAX_VALUE) { return NULL_PATH; } Path path = new Path(); Node u = t; while (u.previous != null) { Edge edge = getEdge(u.previous.vertex, u.vertex); path.addEdge(edge); u = u.previous; } return path; } @Override public String toString() { StringBuffer sb = new StringBuffer(); for (Vertex v : vertices.values()) { sb.append(v.outEdges.values()).append("\n"); } return sb.toString(); } public Map<V, Vertex> getVertices() { return vertices; } public void addGraph(DirectedGraph<V, E> otherGraph) { for (Vertex v : otherGraph.vertices.values()) { for (Edge e : v.outEdges.values()) { addEdge(e.sourceVertex.value, e.targetVertex.value, e.value, e.weight, true); } } } private Vertex getFirst() { for (Vertex v : vertices.values()) { if (v.inEdges.isEmpty()) { return v; } } if (!vertices.isEmpty()) { throw new IllegalArgumentException("Circular ordering has been detected: " + toString()); } else { return null; } } public List<V> topologicalSort(boolean readOnly) { DirectedGraph<V, E> graph = (!readOnly) ? this : (DirectedGraph<V, E>)clone(); List<V> list = new ArrayList<V>(); while (true) { Vertex v = graph.getFirst(); if (v == null) { break; } list.add(v.getValue()); graph.removeVertex(v); } return list; } @Override public Object clone() { DirectedGraph<V, E> copy = new DirectedGraph<V, E>(); copy.addGraph(this); return copy; } }