/*
* This file is part of the Trickl Open Source Libraries.
*
* Trickl Open Source Libraries - http://open.trickl.com/
*
* Copyright (C) 2011 Tim Gee.
*
* Trickl Open Source Libraries are free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Trickl Open Source Libraries are 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this project. If not, see <http://www.gnu.org/licenses/>.
*/
package com.trickl.graph.planar;
import com.trickl.graph.GraphArgumentException;
import com.trickl.graph.edges.DirectedEdge;
import com.trickl.graph.planar.xml.XmlDoublyConnectedEdgeListAdapter;
import java.io.Serializable;
import java.util.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.jgrapht.EdgeFactory;
import org.jgrapht.UndirectedGraph;
import org.jgrapht.graph.ClassBasedEdgeFactory;
@XmlJavaTypeAdapter(value = XmlDoublyConnectedEdgeListAdapter.class)
public class DoublyConnectedEdgeList<V, E, F>
implements PlanarFaceGraph<V, E, F>,
UndirectedGraph<V, E>,
Serializable {
private EdgeFactory<V, E> edgeFactory;
private FaceFactory<V, F> faceFactory;
private F boundaryFace;
private Map<E, DcelHalfEdge<V, E, F>> edgeMap;
private Map<V, DcelVertex<V, E, F>> vertexMap;
private Map<F, DcelFace<V, E, F>> faceMap;
private DoublyConnectedEdgeList() {
}
public DoublyConnectedEdgeList(PlanarGraph<V, E> graph, Class<? extends F> faceClass) {
this(graph, new ClassBasedFaceFactory<V, F>(faceClass));
}
public DoublyConnectedEdgeList(PlanarGraph<V, E> graph, FaceFactory<V, F> faceFactory) {
this(graph.getEdgeFactory(), faceFactory);
PlanarGraphs.copy(graph, this, null, null);
}
public DoublyConnectedEdgeList(Class<? extends E> edgeClass, Class<? extends F> faceClass) {
this(new ClassBasedEdgeFactory<V, E>(edgeClass), faceClass);
}
public DoublyConnectedEdgeList(EdgeFactory<V, E> edgeFactory, Class<? extends F> faceClass) {
this(edgeFactory, new ClassBasedFaceFactory<V, F>(faceClass));
}
public DoublyConnectedEdgeList(EdgeFactory<V, E> edgeFactory, FaceFactory<V, F> faceFactory) {
this.edgeFactory = edgeFactory;
this.faceFactory = faceFactory;
edgeMap = new LinkedHashMap<E, DcelHalfEdge<V, E, F>>();
vertexMap = new LinkedHashMap<V, DcelVertex<V, E, F>>();
faceMap = new LinkedHashMap<F, DcelFace<V, E, F>>();
if (faceFactory != null) {
boundaryFace = faceFactory.createFace(null, null, true);
DcelFace<V, E, F> dcelFace = new DcelFace<V, E, F>(boundaryFace, true);
faceMap.put(boundaryFace, dcelFace);
}
}
@Override
public EdgeFactory<V, E> getEdgeFactory() {
return edgeFactory;
}
@Override
public boolean containsEdge(final V source, final V target) {
return getHalfEdge(source, target) != null;
}
@Override
public boolean containsVertex(V vertex) {
return vertexMap.containsKey(vertex);
}
@Override
public Set<E> edgesOf(V vertex) {
DcelVertex<V, E, F> dcelVertex = vertexMap.get(vertex);
return (dcelVertex == null ? new HashSet<E>() : dcelVertex.getEdges());
}
protected DcelHalfEdge<V, E, F> getHalfEdge(final V source, final V target) {
DcelVertex<V, E, F> sourceVertex = vertexMap.get(source);
if (sourceVertex == null) {
return null;
}
return sourceVertex.getHalfEdge(target);
}
@Override
public int degreeOf(V vertex) {
DcelVertex<V, E, F> dcelVertex = vertexMap.get(vertex);
if (dcelVertex == null) {
throw new GraphArgumentException(this,
new NoSuchElementException("Vertex not found."));
}
return dcelVertex.getEdgeCount();
}
@Override
public Set<E> getAllEdges(V source, V target) {
HashSet<E> edges = new HashSet<E>();
E edge = getEdge(source, target);
if (edge != null) {
edges.add(getEdge(source, target));
}
return edges;
}
@Override
public E getEdge(V source, V target) {
DcelHalfEdge<V, E, F> dcelHalfEdge = getHalfEdge(source, target);
if (dcelHalfEdge == null) {
return null;
}
return dcelHalfEdge.getEdge();
}
@Override
public boolean containsEdge(E edge) {
return edgeMap.containsKey(edge);
}
@Override
public Set<E> edgeSet() {
return edgeMap.keySet();
}
@Override
public Set<V> vertexSet() {
return vertexMap.keySet();
}
@Override
public Set<F> faceSet() {
return faceMap.keySet();
}
@Override
public V getEdgeSource(E edge) {
DcelHalfEdge<V, E, F> dcelHalfEdge = edgeMap.get(edge);
if (dcelHalfEdge == null) {
throw new GraphArgumentException(this,
new NoSuchElementException("Edge not found."));
}
return dcelHalfEdge.getOrigin().getVertex();
}
@Override
public V getEdgeTarget(E edge) {
DcelHalfEdge<V, E, F> dcelHalfEdge = edgeMap.get(edge);
if (dcelHalfEdge == null) {
throw new GraphArgumentException(this,
new NoSuchElementException("Edge not found."));
}
return dcelHalfEdge.getNext().getOrigin().getVertex();
}
@Override
public double getEdgeWeight(E e) {
return 1.0;
}
@Override
public DirectedEdge<V> getBoundary() {
DcelFace<V, E, F> dcelFace = faceMap.get(boundaryFace);
if (dcelFace.getAdjacent() == null) {
// Graph contains zero edges
if (vertexMap.isEmpty()) {
return new DirectedEdge<V>(null, null);
}
else {
return new DirectedEdge<V>(vertexMap.keySet().iterator().next(), null);
}
}
return new DirectedEdge<V>(dcelFace.getAdjacent().getOrigin().getVertex(),
dcelFace.getAdjacent().getNext().getOrigin().getVertex());
}
public V getNextVertexOnBoundary(V vertex) {
DcelFace<V, E, F> dcelFace = faceMap.get(boundaryFace);
DcelHalfEdge<V, E, F> dcelHalfEdge = dcelFace.getHalfEdge(vertex);
if (dcelHalfEdge == null) {
throw new GraphArgumentException(this,
new NoSuchElementException("Vertex not found on boundary."));
}
return dcelHalfEdge.getNext().getOrigin().getVertex();
}
public V getPrevVertexOnBoundary(V vertex) {
DcelFace<V, E, F> dcelFace = faceMap.get(boundaryFace);
DcelHalfEdge<V, E, F> dcelHalfEdge = dcelFace.getHalfEdge(vertex);
if (dcelHalfEdge == null) {
throw new GraphArgumentException(this,
new NoSuchElementException("Vertex not found on boundary."));
}
return dcelHalfEdge.getPrev().getOrigin().getVertex();
}
@Override
public V getNextVertex(V source, V target) {
DcelHalfEdge<V, E, F> dcelHalfEdge = getHalfEdge(source, target);
if (dcelHalfEdge == null) {
throw new GraphArgumentException(this,
new NoSuchElementException("Edge not found."));
}
return dcelHalfEdge.getNext().getNext().getOrigin().getVertex();
}
@Override
public V getPrevVertex(V source, V target) {
DcelHalfEdge<V, E, F> dcelHalfEdge = getHalfEdge(source, target);
if (dcelHalfEdge == null) {
throw new GraphArgumentException(this,
new NoSuchElementException("Edge not found."));
}
return dcelHalfEdge.getPrev().getOrigin().getVertex();
}
@Override
public boolean isBoundary(V source, V target) {
DcelHalfEdge<V, E, F> dcelHalfEdge = getHalfEdge(source, target);
if (dcelHalfEdge == null) {
throw new GraphArgumentException(this,
new NoSuchElementException("Edge not found."));
}
return dcelHalfEdge.isBoundary();
}
public boolean isVertexBoundary(V vertex) {
DcelVertex<V, E, F> dcelVertex = vertexMap.get(vertex);
if (dcelVertex == null) {
throw new GraphArgumentException(this,
new NoSuchElementException("Vertex not found."));
}
return dcelVertex.isBoundary();
}
@Override
public F getFace(V source, V target) {
DcelHalfEdge<V, E, F> halfEdge = getHalfEdge(source, target);
if (halfEdge == null) {
return null;
}
return halfEdge.getFace().getFace();
}
@Override
public E addEdge(V source, V target) {
return addEdge(source, target, (V) null, (V) null);
}
@Override
public boolean addEdge(V source, V target, E edge) {
return addEdge(source, target, null, null, edge);
}
@Override
public E addEdge(V sourceVertex, V targetVertex, V beforeVertex, V afterVertex) {
E edge = edgeFactory.createEdge(sourceVertex, targetVertex);
addEdge(sourceVertex, targetVertex, beforeVertex, afterVertex, edge);
return edge;
}
private E getEdge(E e) {
if (!edgeMap.containsKey(e)) return null;
for (E edge : edgeMap.keySet()) {
if (edge.equals(e)) {
return edge;
}
}
return null;
}
/**
* An an edge to the DCEL.
* Note that the DCEL is undirected, so this will add two half-edges and
* attempt to do so in a way that leaves the DCEL in a safe-traversable state.
* @param sourceVertex
* @param targetVertex
* @param beforeVertex
* @param afterVertex
* @param e
* @return
*/
@Override
public boolean addEdge(V sourceVertex, V targetVertex, V beforeVertex, V afterVertex, E e) {
if (containsEdge(sourceVertex, targetVertex)) {
return false;
}
if (edgeMap.containsKey(e)) {
throw new GraphArgumentException(this,
new IllegalArgumentException("Cannot redefine an existing edge" + getEdge(e) + " as " + e.toString()));
}
addVertex(sourceVertex);
addVertex(targetVertex);
DcelVertex<V, E, F> source = vertexMap.get(sourceVertex);
DcelVertex<V, E, F> target = vertexMap.get(targetVertex);
DcelHalfEdge<V, E, F> beforeEdge = null;
if (beforeVertex != null) {
DcelVertex<V, E, F> before = vertexMap.get(beforeVertex);
if (before != null) {
beforeEdge = before.getHalfEdge(sourceVertex);
}
if (beforeEdge == null) {
throw new GraphArgumentException(this,
new NoSuchElementException("Before ("
+ beforeVertex.toString()
+ ") to ("
+ sourceVertex.toString()
+ ") source edge not found"));
}
}
DcelHalfEdge<V, E, F> afterEdge = null;
if (afterVertex != null) {
afterEdge = target.getHalfEdge(afterVertex);
if (afterEdge == null) {
throw new GraphArgumentException(this,
new NoSuchElementException("Target ("
+ targetVertex.toString()
+ ") to ("
+ afterVertex.toString()
+ ") after edge not found"));
}
if (beforeEdge != null
&&!beforeEdge.getFace().equals(afterEdge.getFace())) {
throw new GraphArgumentException(this,
new NoSuchElementException("Before ("
+ beforeEdge.toString()
+ ") and ("
+ afterEdge.toString()
+ ") after edges must share a face"));
}
}
DcelFace<V, E, F> boundary = faceMap.get(boundaryFace);
DcelHalfEdge<V, E, F> createdEdge =
source.addEdge(target, beforeEdge, afterEdge, boundary, getFaceFactory(), e);
edgeMap.put(e, createdEdge);
faceMap.put(createdEdge.getFace().getFace(), createdEdge.getFace());
faceMap.put(createdEdge.getTwin().getFace().getFace(), createdEdge.getTwin().getFace());
assert(isFaceConsistent(createdEdge)) : "Face mismatch, edge: " + createdEdge;
assert(isFaceConsistent(createdEdge.getTwin())) : "Face mismatch, edge: " + createdEdge.getTwin();
return true;
}
private boolean isFaceConsistent(DcelHalfEdge<V, E, F> start) {
DcelFace<V, E, F> face = null;
for (DcelHalfEdge<V, E, F> halfEdge : start.edges()) {
if (face == null) face = halfEdge.getFace();
else if (!face.equals(halfEdge.getFace())) {
return false;
}
}
return true;
}
@Override
public boolean addVertex(V vertex) {
if (!vertexMap.containsKey(vertex)) {
vertexMap.put(vertex, new DcelVertex<V, E, F>(vertex));
return true;
}
return false;
}
@Override
public boolean removeAllEdges(Collection<? extends E> edges) {
boolean changed = false;
for (E edge : edges) {
changed = changed || removeEdge(edge);
}
return changed;
}
@Override
public Set<E> removeAllEdges(V source, V target) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean removeAllVertices(Collection<? extends V> vertices) {
boolean changed = false;
for (V vertex : vertices) {
changed = changed || removeVertex(vertex);
}
return changed;
}
@Override
public E removeEdge(V source, V target) {
E edge = getEdge(source, target);
if (edge != null) {
removeEdge(getEdge(source, target));
}
return edge;
}
@Override
public boolean removeEdge(E edge) {
DcelHalfEdge<V, E, F> halfEdge = edgeMap.get(edge);
if (halfEdge == null) {
return false;
}
DcelVertex<V, E, F> source = halfEdge.getOrigin();
DcelVertex<V, E, F> target = halfEdge.getNext().getOrigin();
DcelFace<V, E, F> face = halfEdge.getFace();
DcelFace<V, E, F> opposingFace = halfEdge.getTwin().getFace();
DcelHalfEdge<V, E, F> nextEdge = halfEdge.getNext();
if (face.isBoundary()) {
// Maintain the same boundary face
halfEdge.getTwin().remove();
}
else {
halfEdge.remove();
}
edgeMap.remove(edge);
// Remove any disconnected vertices
if (source.getLeaving() == null) {
vertexMap.remove(source.getVertex());
}
if (target.getLeaving() == null) {
vertexMap.remove(target.getVertex());
}
// Remove any disconnected faces
if (face.getAdjacent() == null
&& !face.isBoundary()) {
faceMap.remove(face.getFace());
}
if (opposingFace.getAdjacent() == null
&& !opposingFace.isBoundary()) {
faceMap.remove(opposingFace.getFace());
}
assert(isFaceConsistent(nextEdge)) : "Face mismatch, edge: " + nextEdge;
return true;
}
@Override
public boolean removeVertex(V vertex) {
DcelVertex<V, E, F> dcelVertex = vertexMap.get(vertex);
if (dcelVertex == null) {
return false;
}
Set<E> edges = new LinkedHashSet<E>();
// Get a copy of the edges as the iterator will be invalidated
for (E edge : edgesOf(vertex)) {
edges.add(edge);
}
for (E edge : edges) {
removeEdge(edge);
edgeMap.remove(edge);
}
dcelVertex.remove();
vertexMap.remove(vertex);
return true;
}
@Override
public void setBoundary(V source, V target) {
DcelHalfEdge<V, E, F> dcelHalfEdge = getHalfEdge(source, target);
if (dcelHalfEdge == null) {
throw new NoSuchElementException("Edge not found.");
}
DcelFace<V, E, F> oldBoundaryFace = faceMap.get(boundaryFace);
oldBoundaryFace.setBoundary(false);
DcelFace<V, E, F> newBoundaryFace = dcelHalfEdge.getFace();
newBoundaryFace.setBoundary(true);
boundaryFace = newBoundaryFace.getFace();
}
@Override
public DirectedEdge<V> getAdjacentEdge(F face) {
DcelFace<V, E, F> dcelFace = faceMap.get(face);
if (dcelFace == null) {
throw new NoSuchElementException("Face not found.");
}
V source = dcelFace.getAdjacent().getOrigin().getVertex();
V target = dcelFace.getAdjacent().getNext().getOrigin().getVertex();
return new DirectedEdge<V>(source, target);
}
@Override
public boolean replaceFace(F oldFace, F newFace) {
DcelFace<V, E, F> dcelFace = faceMap.get(oldFace);
if (dcelFace == null) {
return false;
}
faceMap.remove(oldFace);
faceMap.put(newFace, dcelFace);
dcelFace.setFace(newFace);
if (oldFace.equals(boundaryFace)) {
boundaryFace = newFace;
}
return true;
}
/**
* A bit uncomfortable about this. Not sure if we should allow people to alter the
* face factory after construction. The reason this was added was due to the conceptual
* problem of copying a PlanarFaceGraph. Using the PlanarGraph algorithm (which
* knows nothing about faces), a way is required to map the faces from the original to the copy.
* This is achieved with the CopyFaceFactory. However, using that, necessitates
* that we can change the factory to something else after it is used.
* I *think* it should be okay to allow the factories to be changed after construction.
* @param faceFactory
*/
public void setFaceFactory(FaceFactory<V, F> faceFactory) {
this.faceFactory = faceFactory;
}
@Override
public FaceFactory<V, F> getFaceFactory() {
return faceFactory;
}
public Map<E, DcelHalfEdge<V, E, F>> getEdgeMap() {
return edgeMap;
}
public Map<V, DcelVertex<V, E, F>> getVertexMap() {
return vertexMap;
}
public Map<F, DcelFace<V, E, F>> getFaceMap() {
return faceMap;
}
public F getBoundaryFace() {
return boundaryFace;
}
public void setBoundaryFace(F boundaryFace) {
this.boundaryFace = boundaryFace;
}
}