code: ///////////////////////////////////////////////////////////////////////////////
  // For information as to what this class does, see the Javadoc, below.       //
  // Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,       //
  // 2007, 2008, 2009, 2010, 2014, 2015 by Peter Spirtes, Richard Scheines, Joseph   //
  // Ramsey, and Clark Glymour.                                                //
  //                                                                           //
  // This program is 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 2 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, write to the Free Software               //
  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA //
  ///////////////////////////////////////////////////////////////////////////////
  
  package edu.cmu.tetrad.graph;
  
  import edu.cmu.tetrad.graph.EdgeTypeProbability.EdgeType;
  import edu.cmu.tetrad.util.TetradSerializable;
  
  import java.awt.*;
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * Represents an edge node1 *-# node2 where * and # are endpoints of type
   * Endpoint--that is, Endpoint.TAIL, Endpoint.ARROW, or Endpoint.CIRCLE.
   * <p>
   * Note that because speed is of the essence, and Edge cannot be compared to an
   * object of any other type; this will throw an exception.
   *
   * @author Joseph Ramsey
   */
  public class Edge implements TetradSerializable, Comparable {
  	static final long serialVersionUID = 23L;
  
  	public enum Property {
  		dd, nl, pd, pl
  	}
  
  	private Node node1;
  	private Node node2;
  	private Endpoint endpoint1;
  	private Endpoint endpoint2;
  
  	// Usual coloring--set to something else for a special line color.
  	private transient Color lineColor = null;
  
  	private boolean bold = false;
  
  	private List<Property> properties = new ArrayList<>();
  
      private List<EdgeTypeProbability> edgeTypeProbabilities = new ArrayList<>();
  
  	// =========================CONSTRUCTORS============================//
  
  	/**
  	 * Constructs a new edge by specifying the nodes it connects and the
  	 * endpoint types.
  	 *
  	 * @param node1
  	 *            the first node
  	 * @param node2
  	 *            the second node _
  	 * @param endpoint1
  	 *            the endpoint at the first node
  	 * @param endpoint2
  	 *            the endpoint at the second node
  	 */
  	public Edge(Node node1, Node node2, Endpoint endpoint1, Endpoint endpoint2) {
  		if (node1 == null || node2 == null) {
  			throw new NullPointerException("Nodes must not be null. node1 = " + node1 + " node2 = " + node2);
  		}
  
  		if (endpoint1 == null || endpoint2 == null) {
  			throw new NullPointerException("Endpoints must not be null.");
  		}
  
  		// Flip edges pointing left the other way.
  		if (pointingLeft(endpoint1, endpoint2)) {
  			this.node1 = node2;
  			this.node2 = node1;
  			this.endpoint1 = endpoint2;
  			this.endpoint2 = endpoint1;
  		} else {
  			this.node1 = node1;
  			this.node2 = node2;
  			this.endpoint1 = endpoint1;
  			this.endpoint2 = endpoint2;
  		}
  	}
  
  	public Edge(Edge edge) {
  		this(edge.node1, edge.node2, edge.endpoint1, edge.endpoint2);
  		this.lineColor = edge.getLineColor();
  	}
  
  	/**
  	 * Generates a simple exemplar of this class to test serialization.
  	 */
  	public static Edge serializableInstance() {
  		return new Edge(GraphNode.serializableInstance(), GraphNode.serializableInstance(), Endpoint.ARROW,
  				Endpoint.ARROW);
  	}
  
  	// ==========================PUBLIC METHODS===========================//
  
  	/**
  	 * @return the A node.
  	 */
  	public final Node getNode1() {
  		return this.node1;
  	}
  
  	/**
  	 * @return the B node.
  	 */
  	public final Node getNode2() {
  		return this.node2;
  	}
  
  	/**
  	 * @return the endpoint of the edge at the A node.
  	 */
  	public final Endpoint getEndpoint1() {
  		return this.endpoint1;
  	}
  
  	/**
  	 * @return the endpoint of the edge at the B node.
  	 */
  	public final Endpoint getEndpoint2() {
  		return this.endpoint2;
  	}
  
  	public final void setEndpoint1(Endpoint e) {
  		this.endpoint1 = e;
  	}
  
  	public final void setEndpoint2(Endpoint e) {
  		this.endpoint2 = e;
  	}
  
  	/**
  	 * @return the endpoint nearest to the given node.
  	 * @throws IllegalArgumentException
  	 *             if the given node is not along the edge.
  	 */
  	public final Endpoint getProximalEndpoint(Node node) {
  		if (this.node1 == node) {
  			return getEndpoint1();
  		} else if (this.node2 == node) {
  			return getEndpoint2();
  		}
  
  		return null;
  	}
  
  	/**
  	 * @return the endpoint furthest from the given node.
  	 * @throws IllegalArgumentException
  	 *             if the given node is not along the edge.
  	 */
  	public final Endpoint getDistalEndpoint(Node node) {
  		if (this.node1 == node) {
  			return getEndpoint2();
  		} else if (this.node2 == node) {
  			return getEndpoint1();
  		}
  
  		return null;
  	}
  
  	/**
  	 * Traverses the edge in an undirected fashion--given one node along the
  	 * edge, returns the node at the opposite end of the edge.
  	 */
  	public final Node getDistalNode(Node node) {
  		if (this.node1 == node) {
  			return this.node2;
  		}
  
  		if (this.node2 == node) {
  			return this.node1;
  		}
  
  		return null;
  	}
  
  	/**
  	 * @return true just in case this edge is directed.
  	 */
  	public boolean isDirected() {
  		return Edges.isDirectedEdge(this);
  	}
  
  	/**
  	 * @return true just in case the edge is pointing toward the given node--
  	 *         that is, x --> node or x o--> node.
  	 */
  	public boolean pointsTowards(Node node) {
  		Endpoint proximal = getProximalEndpoint(node);
  		Endpoint distal = getDistalEndpoint(node);
  		return (proximal == Endpoint.ARROW && (distal == Endpoint.TAIL || distal == Endpoint.CIRCLE));
  	}
  
  	/**
  	 * @return the edge with endpoints reversed.
  	 */
  	public Edge reverse() {
  		return new Edge(getNode2(), getNode1(), getEndpoint1(), getEndpoint2());
  	}
  
  	/**
  	 * Produces a string representation of the edge.
  	 */
  	public final String toString() {
  		StringBuilder buf = new StringBuilder();
  
  		Endpoint endptTypeA = getEndpoint1();
  		Endpoint endptTypeB = getEndpoint2();
  
  		buf.append(getNode1());
  		buf.append(" ");
  
  		if (endptTypeA == Endpoint.TAIL) {
  			buf.append("-");
  		} else if (endptTypeA == Endpoint.ARROW) {
  			buf.append("<");
  		} else if (endptTypeA == Endpoint.CIRCLE) {
  			buf.append("o");
  		}
  
  		buf.append("-");
  
  		if (endptTypeB == Endpoint.TAIL) {
  			buf.append("-");
  		} else if (endptTypeB == Endpoint.ARROW) {
  			buf.append(">");
  		} else if (endptTypeB == Endpoint.CIRCLE) {
  			buf.append("o");
  		}
  
  		buf.append(" ");
  		buf.append(getNode2());
  
          // Bootstrapping edge type distribution
          List<EdgeTypeProbability> edgeTypeDist = getEdgeTypeProbabilities();
          if(edgeTypeDist.size() > 0){
              buf.append(" ");
  
              String n1 = getNode1().getName();
  			String n2 = getNode2().getName();
  			if(n1.compareTo(n2) > 0) {// Sort node's names
  				n1 = getNode2().getName();
  				n2 = getNode1().getName();
  			}
  			
              for (int i = 0; i < edgeTypeDist.size(); i++) {
              	EdgeTypeProbability etp = edgeTypeDist.get(i);
              	double prob = etp.getProbability();
              	if(prob > 0) {
          			String _type = "" + etp.getEdgeType();
          			switch (etp.getEdgeType()) {
          			case nil:
          				_type = "no edge";
          				break;
          			case ta:
          				_type = "-->";
          				break;
          			case at:
          				_type = "<--";
          				break;
          			case ca:
          				_type = "o->";
          				break;
          			case ac:
          				_type = "<-o";
          				break;
          			case cc:
          				_type = "o-o";
          				break;
          			case aa:
          				_type = "<->";
          				break;
          			case tt:
          				_type = "---";
          				break;
          			default:
          				break;
          			}
          			
          			if(etp.getEdgeType() != EdgeType.nil) {
          				_type = n1 + " " + _type + " " + n2;
          			}
          			List<Property> properties = etp.getProperties();
          			if(properties != null && properties.size() > 0) {
          	        	for(Property property : properties) {
          	        		_type = _type + " " + property.toString();
          	        	}
          	        }
          			buf.append("[" + _type + "]:" + String.format("%.4f", etp.getProbability()) + ";");
          			
              	}
              }
          }
          
          List<Property> properties = getProperties();
          if(properties != null && properties.size() > 0) {
          	for(Property property : properties) {
          		buf.append(" ");
          		buf.append(property.toString());
          	}
          }
          
  		return buf.toString();
  	}
  
  	public final int hashCode() {
  		return node1.hashCode() + node2.hashCode();
  	}
  
  	/**
  	 * Two edges are equal just in case they connect the same nodes and have the
  	 * same endpoints proximal to each node.
  	 */
  	public final boolean equals(Object o) {
  		if (o == this)
  			return true;
  		if (!(o instanceof Edge))
  			return false;
  
  		Edge edge = (Edge) o;
  
  		if (NodeEqualityMode.getEqualityType() == NodeEqualityMode.Type.OBJECT) {
  			Node node1 = getNode1();
  			Node node2 = getNode2();
  			Node node1b = edge.getNode1();
  			Node node2b = edge.getNode2();
  
  			Endpoint end1 = getEndpoint1();
  			Endpoint end2 = getEndpoint2();
  			Endpoint end1b = edge.getEndpoint1();
  			Endpoint end2b = edge.getEndpoint2();
  
  			boolean equal;
  
  			// if ((node1 == node1b && node2 == node2b) || (node1 == node2b &&
  			// node2 == node1b)) {
  			// System.out.println();
  			// }
  
  			if (node1 == node1b && node2 == node2b) {
  				equal = end1 == end1b && end2 == end2b;
  			} else
  				equal = node1 == node2b && node2 == node1b && end1 == end2b && end2 == end1b;
  
  			return equal;
  		} else if (NodeEqualityMode.getEqualityType() == NodeEqualityMode.Type.NAME) {
  			String name1 = getNode1().getName();
  			String name2 = getNode2().getName();
  			String name1b = edge.getNode1().getName();
  			String name2b = edge.getNode2().getName();
  
  			Endpoint end1 = getEndpoint1();
  			Endpoint end2 = getEndpoint2();
  			Endpoint end1b = edge.getEndpoint1();
  			Endpoint end2b = edge.getEndpoint2();
  
  			if (name1.equals(name1b) && name2.equals(name2b)) {
  				return end1 == end1b && end2 == end2b;
  			} else
  				return name1.equals(name2b) && name2.equals(name1b) && end1 == end2b && end2 == end1b;
  		} else {
  			throw new IllegalStateException();
  		}
  	}
  
  	public int compareTo(Object o) {
  		Edge _edge = (Edge) o;
  
  		int comp1 = getNode1().compareTo(_edge.getNode1());
  
  		if (comp1 != 0) {
  			return comp1;
  		}
  
  		return getNode2().compareTo(_edge.getNode2());
  	}
  
  	// ===========================PRIVATE METHODS===========================//
  
  	private boolean pointingLeft(Endpoint endpoint1, Endpoint endpoint2) {
  		return (endpoint1 == Endpoint.ARROW && (endpoint2 == Endpoint.TAIL || endpoint2 == Endpoint.CIRCLE));
  	}
  
  	/**
  	 * Adds semantic checks to the default deserialization method. This method
  	 * must have the standard signature for a readObject method, and the body of
  	 * the method must begin with "s.defaultReadObject();". Other than that, any
  	 * semantic checks can be specified and do not need to stay the same from
  	 * version to version. A readObject method of this form may be added to any
  	 * class, even if Tetrad sessions were previously saved out using a version
  	 * of the class that didn't include it. (That's what the
  	 * "s.defaultReadObject();" is for. See J. Bloch, Effective Java, for help.
  	 *
  	 * @throws java.io.IOException
  	 * @throws ClassNotFoundException
  	 */
  	private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
  		s.defaultReadObject();
  
  		if (node1 == null) {
  			throw new NullPointerException();
  		}
  
  		if (node2 == null) {
  			throw new NullPointerException();
  		}
  
  		if (endpoint1 == null) {
  			throw new NullPointerException();
  		}
  
  		if (endpoint2 == null) {
  			throw new NullPointerException();
  		}
  	}
  
  	public boolean isNull() {
  		return endpoint1 == Endpoint.NULL && endpoint2 == Endpoint.NULL;
  	}
  
  	public Color getLineColor() {
  		return this.lineColor;
  	}
  
  	public void setLineColor(Color lineColor) {
  		if (lineColor != null) {
  			this.lineColor = lineColor;
  		}
  	}
  
  	public boolean isBold() {
  		return bold;
  	}
  
  	public void setBold(boolean bold) {
  		this.bold = bold;
  	}
  
  	public void addProperty(Property property) {
  		if (!properties.contains(property)) {
  			this.properties.add(property);
  		}
  	}
  
  	public void removeProperty(Property property) {
  		this.properties.remove(property);
  	}
  
  	public ArrayList<Property> getProperties() {
  		return new ArrayList<>(this.properties);
  	}
  
  	public void addEdgeTypeProbability(EdgeTypeProbability prob) {
  		if (!edgeTypeProbabilities.contains(prob)) {
  			this.edgeTypeProbabilities.add(prob);
  		}
  	}
  
  	public void removeEdgeTypeProbability(EdgeTypeProbability prob) {
  		this.edgeTypeProbabilities.remove(prob);
  	}
  
  	public List<EdgeTypeProbability> getEdgeTypeProbabilities() {
  		return edgeTypeProbabilities;
  	}
  }
  