/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package org.opentripplanner.routing.graph;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.XmlTransient;
import lombok.Getter;
import org.onebusaway.gtfs.model.Trip;
import org.opentripplanner.common.MavenVersion;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.edgetype.PlainStreetEdge;
import org.opentripplanner.routing.patch.Patch;
import org.opentripplanner.routing.util.IncrementingIdGenerator;
import org.opentripplanner.routing.util.UniqueIdGenerator;
import com.vividsolutions.jts.geom.LineString;
/**
* This is the standard implementation of an edge with fixed from and to Vertex instances; all standard OTP edges are subclasses of this.
*/
public abstract class Edge implements Serializable {
private static final long serialVersionUID = MavenVersion.VERSION.getUID();
/**
* Generates globally unique edge IDs.
*/
private static final UniqueIdGenerator<Edge> idGenerator = new IncrementingIdGenerator<Edge>();
/**
* Identifier of the edge. Negative means not set.
*/
@Getter
private int id;
protected Vertex fromv;
protected Vertex tov;
private List<Patch> patches;
protected Edge(Vertex v1, Vertex v2) {
if (v1 == null || v2 == null) {
String err = String.format("%s constructed with null vertex : %s %s", this.getClass(),
v1, v2);
throw new IllegalStateException(err);
}
this.fromv = v1;
this.tov = v2;
this.id = idGenerator.getId(this);
// if (! vertexTypesValid()) {
// throw new IllegalStateException(this.getClass() +
// " constructed with bad vertex types");
// }
fromv.addOutgoing(this);
tov.addIncoming(this);
}
public Vertex getFromVertex() {
return fromv;
}
public Vertex getToVertex() {
return tov;
}
/**
* Returns true if this edge is partial - overriden by subclasses.
*/
public boolean isPartial() {
return false;
}
/**
* Checks equivalency to another edge. Default implementation is trivial equality, but subclasses may want to do something more tricky.
*/
public boolean isEquivalentTo(Edge e) {
return this == e;
}
/**
* Returns true if this edge is the reverse of another.
*/
public boolean isReverseOf(Edge e) {
return (this.getFromVertex() == e.getToVertex() &&
this.getToVertex() == e.getFromVertex());
}
public void attachFrom(Vertex fromv) {
detachFrom();
if (fromv == null)
throw new IllegalStateException("attaching to fromv null");
this.fromv = fromv;
fromv.addOutgoing(this);
}
public void attachTo(Vertex tov) {
detachTo();
if (tov == null)
throw new IllegalStateException("attaching to tov null");
this.tov = tov;
tov.addIncoming(this);
}
/** Attach this edge to new endpoint vertices, keeping edgelists coherent */
public void attach(Vertex fromv, Vertex tov) {
attachFrom(fromv);
attachTo(tov);
}
/**
* Get a direction on paths where it matters, or null
*
* @return
*/
public String getDirection() {
return null;
}
protected boolean detachFrom() {
boolean detached = false;
if (fromv != null) {
detached = fromv.removeOutgoing(this);
fromv = null;
}
return detached;
}
protected boolean detachTo() {
boolean detached = false;
if (tov != null) {
detached = tov.removeIncoming(this);
tov = null;
}
return detached;
}
/**
* Disconnect this edge from its endpoint vertices, keeping edgelists coherent
*
* @return
*/
public int detach() {
int nDetached = 0;
if (detachFrom()) {
++nDetached;
}
if (detachTo()) {
++nDetached;
}
return nDetached;
}
/**
* This should only be called inside State; other methods should call {@link org.opentripplanner.routing.core.State.getBackTrip()}.
*
* @author mattwigway
*/
public Trip getTrip() {
return null;
}
// Notes are now handled by State
@Override
public int hashCode() {
return fromv.hashCode() * 31 + tov.hashCode();
}
/**
* Edges are not roundabouts by default.
*/
public boolean isRoundabout() {
return false;
}
/**
* Traverse this edge.
*
* @param s0 The State coming into the edge.
* @return The State upon exiting the edge.
*/
public abstract State traverse(State s0);
public State optimisticTraverse(State s0) {
return this.traverse(s0);
}
/**
* Returns a lower bound on edge weight given the routing options.
*
* @param options
* @return edge weight as a double.
*/
public double weightLowerBound(RoutingRequest options) {
// Edge weights are non-negative. Zero is an admissible default lower
// bound.
return 0;
}
/**
* Returns a lower bound on traversal time given the routing options.
*
* @param options
* @return edge weight as a double.
*/
public double timeLowerBound(RoutingRequest options) {
// No edge should take less than zero time to traverse.
return 0;
}
public void addPatch(Patch patch) {
if (patches == null) {
patches = new ArrayList<Patch>();
}
if (!patches.contains(patch)) {
patches.add(patch);
}
}
public List<Patch> getPatches() {
if (patches == null) {
return Collections.emptyList();
}
return patches;
}
public void removePatch(Patch patch) {
if (patches == null || patches.size() == 1) {
patches = null;
} else {
patches.remove(patch);
}
}
public abstract String getName();
public boolean hasBogusName() {
return false;
}
public String toString() {
if (id >= 0) {
return String.format("%s:%s (%s -> %s)", getClass().getName(), id, fromv, tov);
}
return String.format("%s (%s -> %s)", getClass().getName(), fromv, tov);
}
// The next few functions used to live in EdgeNarrative, which has now been
// removed
// @author mattwigway
public LineString getGeometry() {
return null;
}
/**
* Returns the azimuth of this edge from head to tail.
*
* @return
*/
public double getAzimuth() {
// TODO(flamholz): cache?
return getFromVertex().azimuthTo(getToVertex());
}
public double getDistance() {
return 0;
}
/* SERIALIZATION */
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// edge lists are transient, reconstruct them
fromv.addOutgoing(this);
tov.addIncoming(this);
}
private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException {
if (fromv == null) {
if (this instanceof PlainStreetEdge)
System.out.println(((PlainStreetEdge) this).getGeometry());
System.out.printf("fromv null %s \n", this);
}
if (tov == null) {
if (this instanceof PlainStreetEdge)
System.out.println(((PlainStreetEdge) this).getGeometry());
System.out.printf("tov null %s \n", this);
}
out.defaultWriteObject();
}
/* GRAPH COHERENCY AND TYPE CHECKING */
@SuppressWarnings("unchecked")
private static final ValidVertexTypes VALID_VERTEX_TYPES = new ValidVertexTypes(Vertex.class,
Vertex.class);
@XmlTransient
public ValidVertexTypes getValidVertexTypes() {
return VALID_VERTEX_TYPES;
}
/*
* This may not be necessary if edge constructor types are strictly specified
*/
public final boolean vertexTypesValid() {
return getValidVertexTypes().isValid(fromv, tov);
}
public static final class ValidVertexTypes {
private final Class<? extends Vertex>[] classes;
// varargs constructor:
// a loophole in the law against arrays/collections of parameterized
// generics
public ValidVertexTypes(Class<? extends Vertex>... classes) {
if (classes.length % 2 != 0) {
throw new IllegalStateException("from/to/from/to...");
} else {
this.classes = classes;
}
}
public boolean isValid(Vertex from, Vertex to) {
for (int i = 0; i < classes.length; i += 2) {
if (classes[i].isInstance(from) && classes[i + 1].isInstance(to))
return true;
}
return false;
}
}
}