diff java/src/uk/ac/qmul/eecs/ccmi/gui/Edge.java @ 0:78b7fc5391a2

first import, outcome of NIME 2014 hackaton
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 16:28:59 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Edge.java	Tue Jul 08 16:28:59 2014 +0100
@@ -0,0 +1,864 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+
+ Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ 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 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 uk.ac.qmul.eecs.ccmi.gui;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramEdge;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
+import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
+
+/**
+ * An edge in a graph. Edge objects are used in a GraphPanel to render a diagram edge visually. 
+ * Edge objects are used in the tree representation of the diagram as well, as they're
+ * subclasses of {@link DiagramEdge}    
+ * 
+ */
+@SuppressWarnings("serial")
+public abstract class Edge extends DiagramEdge implements GraphElement{
+
+	/**
+	 * Creates a new Edge 
+	 * 
+	 * @param type the type of the edge. The type is just a string that the user assign to the edge.  
+	 * All the edges created via clonation from this edge will have the same type.  
+	 * @param availableEndDescriptions all the possible end description ends of this edge can be
+	 * associated to. An end description is a text associated to a possible arrow head of an edge end. 
+	 * It's used to give awareness of arrow heads via speech.     
+	 * @param minAttachedNodes the minimum number of nodes that can be attached to this edge 
+	 * @param maxAttachedNodes the minimum number of nodes that can be attached to this edge 
+	 * @param style the style of the edge: whether it's solid, dotted or dashed 
+	 */
+	public Edge(String type, String[] availableEndDescriptions, int minAttachedNodes, int maxAttachedNodes,LineStyle style){
+		super(type,availableEndDescriptions);
+		this.minAttachedNodes = minAttachedNodes;
+		this.maxAttachedNodes = maxAttachedNodes;
+		this.style = style;
+		nodes = new ArrayList<Node>();
+	}
+
+	/* --- Methods inherited from DiagramEdge --- */
+	@Override
+	public Node getNodeAt(int index){
+		return nodes.get(index);
+	}
+
+	@Override
+	public int getNodesNum(){
+		return nodes.size();
+	}
+
+	@Override
+	public boolean removeNode(DiagramNode diagramNode, Object source){
+		Node n = (Node)diagramNode;
+		if(nodes.size() == 2)
+			throw new RuntimeException("Cannot remove a node from a two ends edge");
+		else{
+			for(InnerPoint p : points)
+				if(p.hasNeighbour(n)){
+					p.neighbours.remove(n);
+					if(p.neighbours.size() == 1)
+						removePoint(p);
+					break;
+				} 
+			boolean nodeRemoved = nodes.remove(n);
+			/* for update in the haptic device */
+			notifyChange(new ElementChangedEvent(this,n,"remove_node",source));
+			return nodeRemoved;
+		}
+	}
+
+	@Override
+	public void connect(List<DiagramNode> nodes) throws ConnectNodesException{
+		assert(getNodesNum() == 0);
+		/* this is to eliminate duplicates */
+		LinkedHashSet<Node> nodeSet = new LinkedHashSet<Node>();
+		for(DiagramNode n : nodes)
+			nodeSet.add((Node)n);
+	
+		/* checks on connection consistency */
+		if((nodeSet == null)||(nodeSet.size() < minAttachedNodes))
+			throw new ConnectNodesException("You must select at least "+ minAttachedNodes + "nodes");
+		if((nodeSet.size() > maxAttachedNodes))
+			throw new ConnectNodesException("You must select at most " + maxAttachedNodes +" nodes");
+	
+		points = new ArrayList<InnerPoint>();
+		if(nodeSet.size() > 2){
+			/* there are more than three nodes. compute the central inner point         *
+			 * which will connect all the nodes, as the middle points of the edge bound */
+			Rectangle2D bounds = new Rectangle2D.Double();
+			for(Node n : nodeSet){
+				bounds.add(n.getBounds());
+			}
+			InnerPoint p = new InnerPoint();
+			p.translate(new Point2D.Double(0,0), bounds.getCenterX(), bounds.getCenterY(),DiagramEventSource.NONE);
+			p.neighbours.addAll(nodeSet);
+			points.add(p);
+		}
+		this.nodes.addAll(nodeSet);
+	
+		if(!points.isEmpty())// points is empty when the edge has two nodes only 
+			masterInnerPoint = points.get(0);
+	}
+
+	@Override
+	public abstract void draw(Graphics2D g2);
+
+	@Override
+	public void translate(Point2D p, double dx, double dy, Object source){
+		for(InnerPoint ip : points)
+			ip.translate(p, dx, dy,source);
+	}
+
+	/**
+	 * To be called before {@code bend}, determines from {@code downPoint} whether 
+	 * a line is going to be break into two lines (with the creation of a new inner point) 
+	 * or if the bending is determined by an already existing inner point being translated 
+	 */
+	@Override
+	public void startMove(Point2D downPoint,Object source){
+		this.downPoint = downPoint;
+		newInnerPoint = null;
+		for(InnerPoint itrPoint : points)
+			if(itrPoint.contains(downPoint)){
+				/* clicked on an already existing EdgePoint */
+				newInnerPoint = itrPoint;
+				newPointCreated = false;
+			}
+		if(newInnerPoint == null){
+			/* no point under the click, create a new one */
+			newInnerPoint = new InnerPoint();
+			newInnerPoint.translate(downPoint, downPoint.getX() - newInnerPoint.getBounds().getCenterX(),
+					downPoint.getY() - newInnerPoint.getBounds().getCenterY(),DiagramEventSource.NONE);
+			newPointCreated = true;
+			/* this methods checks for segments of the edge which are aligned and makes a unique edge out of them */
+		}
+	}
+	
+	/**
+	 * If this edge is made out of several lines and two or more of them becomes
+	 * aligned then they are blended in one single line and the inner point that was
+	 * at the joint is removed. 
+	 * 
+	 * @param source the source of the {@code stopMove} action
+	 */
+	@Override
+	public void stopMove(Object source){
+		for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext(); ){
+			InnerPoint ePoint = pItr.next();
+			if(ePoint.neighbours.size() > 2)
+				continue;
+			Rectangle2D startBounds = ePoint.getBounds();
+			Rectangle2D endBounds = ePoint.neighbours.get(0).getBounds();
+			Direction d1 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
+			endBounds = ePoint.neighbours.get(1).getBounds();
+			Direction d2 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
+			if(d1.isParallel(d2)){
+				InnerPoint p = null;
+				GraphElement q = null;
+				if(ePoint.neighbours.get(0) instanceof InnerPoint){
+					p = (InnerPoint)ePoint.neighbours.get(0);
+					q = ePoint.neighbours.get(1);
+					p.neighbours.add(q);
+					p.neighbours.remove(ePoint);
+				}
+				if(ePoint.neighbours.get(1) instanceof InnerPoint){
+					p = (InnerPoint)ePoint.neighbours.get(1);
+					q = ePoint.neighbours.get(0);
+					p.neighbours.add(q);
+					p.neighbours.remove(ePoint);
+				}
+				pItr.remove();
+			}
+		}
+		notifyChange(new ElementChangedEvent(this,this,"stop_move",source));
+	}
+
+	@Override
+	public abstract Rectangle2D getBounds();
+
+	@Override
+	public Point2D getConnectionPoint(Direction d){return null;}
+
+	@Override
+	public boolean contains(Point2D aPoint){
+		if(points.isEmpty()){ 
+			return  fatStrokeContains (nodes.get(0), nodes.get(1), aPoint);
+		}
+		for(InnerPoint p : points){
+			for(GraphElement ge : p.neighbours){
+				if(fatStrokeContains(p,ge,aPoint))
+					return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Look for the node attached to this edge which lays at the minimum distance 
+	 * from the point passed as argument. The distance cannot be lower than the    
+	 * value passed as argument. 
+	 * 
+	 * @param aPoint the point the distance is measured from
+	 * @param distanceLimit the limit from the distance between the nodes and the point
+	 * @return the closest node or null if the node lays at an higher distance than distanceLimit
+	 */
+	public Node getClosestNode(Point2D aPoint, double distanceLimit){
+		Node closestNode = null;
+		double minDist = distanceLimit;
+
+		if(points.isEmpty()){
+			Line2D line = getSegment(nodes.get(0),nodes.get(1)); 
+			if(line.getP1().distance(aPoint) < minDist){
+				minDist = line.getP1().distance(aPoint);
+				closestNode = nodes.get(0);
+			}
+			if(line.getP2().distance(aPoint) < minDist){
+				minDist = line.getP2().distance(aPoint);
+				closestNode = nodes.get(1);
+			}
+			return closestNode;
+		}else{
+			for(InnerPoint p : points){
+				for(GraphElement ge : p.getNeighbours())
+					if(ge instanceof Node){
+						Node n = (Node)ge;
+						Direction d = new Direction(p.getBounds().getCenterX() - n.getBounds().getCenterX(),p.getBounds().getCenterY() - n.getBounds().getCenterY());
+						if(n.getConnectionPoint(d).distance(aPoint) < minDist){
+							minDist = n.getConnectionPoint(d).distance(aPoint);
+							closestNode = n; 
+						}
+					}
+			}
+			return closestNode;
+		}
+	}
+
+	private void removePoint(InnerPoint p){
+		/* we assume at this moment p has one neighbour only */
+		InnerPoint neighbour = (InnerPoint)p.neighbours.get(0);
+		points.remove(p);
+		neighbour.neighbours.remove(p);
+		if(neighbour.neighbours.size() == 1)
+			removePoint(neighbour);
+	}
+
+	/* checks if a point belongs to a shape with a margin of MAX_DIST*/
+	private boolean fatStrokeContains(GraphElement ge1, GraphElement ge2, Point2D p){
+		BasicStroke fatStroke = new BasicStroke((float) (2 * MAX_DIST));
+		Line2D line = new Line2D.Double(
+				ge1.getBounds().getCenterX(),
+				ge1.getBounds().getCenterY(),
+				ge2.getBounds().getCenterX(),
+				ge2.getBounds().getCenterY());
+		Shape fatPath = fatStroke.createStrokedShape(line);
+		return fatPath.contains(p);
+	}
+
+	/**
+	 * Returns a line connecting the centre of the graph elements passed as argument.
+	 * 
+	 * @param start the first graph element 
+	 * @param end the second graph element 
+	 * 
+	 * @return a line connecting {@code start} and {@code end}
+	 */
+	protected Line2D.Double getSegment(GraphElement start, GraphElement end){
+		Rectangle2D startBounds = start.getBounds();
+		Rectangle2D endBounds = end.getBounds();
+		Direction d = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY());
+		return new Line2D.Double(start.getConnectionPoint(d), end.getConnectionPoint(d.turn(180)));
+	}
+
+	/**
+	 * Returns a list of the points where this edge and the nodes it connects come to a contact
+	 * 
+	 * @return a list of points
+	 */
+	public List<Point2D> getConnectionPoints(){
+		List<Point2D> list = new LinkedList<Point2D>();
+		if(points.isEmpty()){
+			Line2D line = getSegment(nodes.get(0),nodes.get(1)); 
+			list.add(line.getP1());
+			list.add(line.getP2());
+		}else{
+			for(InnerPoint p : points){
+				for(GraphElement ge : p.neighbours)
+					if(ge instanceof Node){
+						Direction d = new Direction(p.getBounds().getCenterX() - ge.getBounds().getCenterX(),p.getBounds().getCenterY() - ge.getBounds().getCenterY());
+						list.add(((Node)ge).getConnectionPoint(d));
+					}
+			}
+		}
+		return list;
+	}
+
+	/**
+	 * Gets the stipple pattern of this edge line style 
+	 * 
+	 * @see LineStyle#getStipplePattern() 
+	 * 
+	 * @return an int representing the stipple pattern of this edge
+	 */
+	public int getStipplePattern(){
+		return getStyle().getStipplePattern();
+	}
+
+	/**
+	 * Bends one of the lines forming this edge. 
+	 * 
+	 * When an line is bent, if the location where the bending happens is a line then a new 
+	 * inner point is created and the line is broken into two sub lines. If the location is an 
+	 * already existing inner point, then the point is translated.
+	 * 
+	 * @param p the starting point of the bending
+	 * @param source the source of the bending action
+	 */
+	public void bend(Point2D p,Object source) {
+		boolean found = false;
+		if(points.isEmpty()){
+			newInnerPoint.neighbours.addAll(nodes); 
+			points.add(newInnerPoint);
+			newPointCreated = false;
+		}else if(newPointCreated){
+			/* find the segment closest to where the new point lays */
+			InnerPoint closestP1 = null;
+			GraphElement closestP2 = null;
+			double minDist = 0;
+			for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext(); ){
+				InnerPoint ePoint = pItr.next();
+				for(ListIterator<GraphElement> geItr = ePoint.neighbours.listIterator(); geItr.hasNext() && !found;){
+					/* find the neighbour of the current edge point whose line the new point lays on */
+					GraphElement next = geItr.next();
+					double dist = Line2D.ptSegDist(
+							ePoint.getBounds().getCenterX(),
+							ePoint.getBounds().getCenterY(),
+							next.getBounds().getCenterX(),
+							next.getBounds().getCenterY(),
+							downPoint.getX(),
+							downPoint.getY()
+							);
+					
+					if(closestP1 == null || dist < minDist){
+						closestP1 = ePoint;
+						closestP2 = next;
+						minDist = dist;
+						continue;
+					}
+				}
+			}
+				
+			if(closestP2 instanceof InnerPoint ){
+				/* remove current edge point from the neighbour's neighbours */
+				((InnerPoint)closestP2).neighbours.remove(closestP1);
+				/* add the new inner point */
+				((InnerPoint)closestP2).neighbours.add(newInnerPoint);
+			}
+			/*remove old neighbour from edge inner point neighbours */
+			closestP1.neighbours.remove(closestP2);
+			newInnerPoint.neighbours.add(closestP1);
+			newInnerPoint.neighbours.add(closestP2);
+			/* add the new node to the list of EdgeNodes of this edge */
+			points.add(newInnerPoint);
+			closestP1.neighbours.add(newInnerPoint);
+			found = true;
+			newPointCreated = false;
+		}
+		newInnerPoint.translate(p, p.getX() - newInnerPoint.getBounds().getCenterX(),
+				p.getY() - newInnerPoint.getBounds().getCenterY(),DiagramEventSource.NONE);
+		notifyChange(new ElementChangedEvent(this,this,"bend",source));
+	}
+
+	/**
+	 * Returns the line where the edge name will be painted. If the edge is only made out
+	 * of a straight line then this will be returned. 
+	 * 
+	 * If the edge has been broken into several segments (by bending it) then the central 
+	 * line is returned. If the edge connects more than two nodes then a line (not necessarily
+	 * matching the edge) that has the central point in its centre is returned. Note that a 
+	 * edge connecting more than two nodes is painted as a central point connected to all the nodes.  
+	 * 
+	 * @return the line where the name will be painted
+	 */
+	public Line2D getNameLine(){
+		if(points.isEmpty()){ // straight line 
+			return getSegment(nodes.get(0),nodes.get(1));
+		}else{
+			if(masterInnerPoint != null){/* multiended edge  */
+				Rectangle2D bounds = masterInnerPoint.getBounds();
+				Point2D p = new Point2D.Double(bounds.getCenterX() - 1,bounds.getCenterY());
+				Point2D q = new Point2D.Double(bounds.getCenterX() + 1,bounds.getCenterY());
+				return new Line2D.Double(p, q);
+			}else{
+				GraphElement ge1 = nodes.get(0);
+				GraphElement ge2 = nodes.get(1);
+				InnerPoint c1 = null;
+				InnerPoint c2 = null;
+
+				for(InnerPoint innp : points){
+					if(innp.getNeighbours().contains(ge1)){
+						c1 = innp;
+					}
+					if(innp.getNeighbours().contains(ge2)){
+						c2 = innp;
+					}
+				}
+
+				/* we only have two nodes but the edge has been bended */
+				while((c1 != c2)&&(!c2.getNeighbours().contains(c1))){
+					if(c1.getNeighbours().get(0) == ge1){
+						ge1 = c1;
+						c1 = (InnerPoint)c1.getNeighbours().get(1);
+					}
+					else{
+						ge1 = c1;
+						c1 = (InnerPoint)c1.getNeighbours().get(0);
+					}
+					if(c2.getNeighbours().get(0) == ge2){
+						ge2 = c2;
+						c2 = (InnerPoint)c2.getNeighbours().get(1);
+					}
+					else{
+						ge2 = c2;
+						c2 = (InnerPoint)c2.getNeighbours().get(0);
+					}
+				}
+
+				Point2D p = new Point2D.Double();
+				Point2D q = new Point2D.Double();
+				if(c1 == c2){
+					Rectangle2D bounds = c1.getBounds();
+					p.setLocation( bounds.getCenterX() - 1,bounds.getCenterY());
+					q.setLocation(  bounds.getCenterX() + 1,bounds.getCenterY());
+				}else{
+					Rectangle2D bounds = c1.getBounds();
+					p.setLocation( bounds.getCenterX(),bounds.getCenterY());
+					bounds = c2.getBounds();
+					q.setLocation(bounds.getCenterX(),bounds.getCenterY());
+
+				}
+				return new Line2D.Double(p,q);
+			}
+		}
+	}
+
+	/**
+	 * Encodes all the relevant data of this object in XML format. 
+	 * 
+	 * @param doc an XML document 
+	 * @param parent the parent XML element, where tag about this edge will be inserted 
+	 * @param nodes a list of all nodes of the diagram
+	 */
+	public void encode(Document doc, Element parent, List<Node> nodes){
+		parent.setAttribute(PersistenceManager.TYPE,getType());
+		parent.setAttribute(PersistenceManager.NAME, getName());
+		parent.setAttribute(PersistenceManager.ID, String.valueOf(getId()));
+
+		int numNodes = getNodesNum(); 
+		if(numNodes > 0){
+			Element nodesTag = doc.createElement(PersistenceManager.NODES);
+			parent.appendChild(nodesTag);
+			for(int i=0; i<numNodes;i++){
+				Element nodeTag = doc.createElement(PersistenceManager.NODE);
+				nodeTag.setAttribute(PersistenceManager.ID, String.valueOf(getNodeAt(i).getId()));
+				nodeTag.setAttribute(PersistenceManager.LABEL, getEndLabel(getNodeAt(i)));
+				nodesTag.appendChild(nodeTag);
+			}
+		}
+
+		if(!points.isEmpty()){
+			Element pointsTag = doc.createElement(PersistenceManager.POINTS);
+			parent.appendChild(pointsTag);
+			for(InnerPoint point : points){
+				Element pointTag = doc.createElement(PersistenceManager.POINT);
+				pointsTag.appendChild(pointTag);
+				pointTag.setAttribute(PersistenceManager.ID, String.valueOf(-(points.indexOf(point)+1)));
+
+				Element positionTag = doc.createElement(PersistenceManager.POSITION);
+				pointTag.appendChild(positionTag);
+				Rectangle2D bounds = point.getBounds();
+				positionTag.setAttribute(PersistenceManager.X,String.valueOf(bounds.getX()));
+				positionTag.setAttribute(PersistenceManager.Y,String.valueOf(bounds.getY()));
+
+				Element neighboursTag = doc.createElement(PersistenceManager.NEIGHBOURS);
+				pointTag.appendChild(neighboursTag);
+				StringBuilder builder = new StringBuilder();
+				for(GraphElement ge : point.getNeighbours()){
+					if(ge instanceof Node){
+						builder.append(((Node)ge).getId());
+					}else{
+						builder.append(-(points.indexOf(ge)+1));
+					}
+					builder.append(" ");
+				}
+				builder.deleteCharAt(builder.length()-1);
+				neighboursTag.setTextContent(builder.toString());
+			}
+		}
+	}
+
+
+	/**
+	 * Decodes an edge from the XML representation. 
+	 * 
+	 * @see #encode(Document, Element, List)
+	 * 
+	 * @param doc an XML document 
+	 * @param edgeTag the tag in the XML file related to this edge
+	 * @param nodesId a map linking node ids in the XML file to the {@code Node} objects they represent
+	 * @throws IOException if something goes wrong while reading the XML file
+ 	 */
+	public void decode(Document doc, Element edgeTag, Map<String,Node> nodesId) throws IOException{
+		setName(edgeTag.getAttribute(PersistenceManager.NAME),DiagramEventSource.PERS);
+		if(getName().isEmpty())
+			throw new IOException();
+		try{
+			setId(Integer.parseInt(edgeTag.getAttribute(PersistenceManager.ID)));
+		}catch(NumberFormatException nfe){
+			throw new IOException(nfe);
+		}
+
+		NodeList nodeList = edgeTag.getElementsByTagName(PersistenceManager.NODE);
+		List<DiagramNode> attachedNodes = new ArrayList<DiagramNode>(nodeList.getLength());
+		List<String> labels = new ArrayList<String>(nodeList.getLength());
+		for(int i=0; i<nodeList.getLength();i++){
+			String id = ((Element)nodeList.item(i)).getAttribute(PersistenceManager.ID);
+			if(!nodesId.containsKey(id))
+				throw new IOException();
+			attachedNodes.add(nodesId.get(id));
+			labels.add(((Element)nodeList.item(i)).getAttribute(PersistenceManager.LABEL));
+		}
+
+		try {
+			connect(attachedNodes);
+		} catch (ConnectNodesException e) {
+			throw new IOException(e);
+		}
+
+		for(int i=0; i < labels.size(); i++){
+			setEndLabel(attachedNodes.get(i), labels.get(i),DiagramEventSource.PERS);
+		}
+
+		Map<String, InnerPoint> pointsId = new LinkedHashMap<String, InnerPoint>();
+		NodeList pointTagList = edgeTag.getElementsByTagName(PersistenceManager.POINT);
+
+		for(int i=0; i<pointTagList.getLength(); i++){
+			InnerPoint point = new InnerPoint();
+			Element pointTag = (Element)pointTagList.item(i);
+			String id = pointTag.getAttribute(PersistenceManager.ID);
+			/* id of property nodes must be a negative value */
+			try{
+				if(Integer.parseInt(id) >= 0)
+					throw new IOException();
+			}catch(NumberFormatException nfe){
+				throw new IOException(nfe);
+			}
+
+			pointsId.put(id, point);
+
+			if(pointTag.getElementsByTagName(PersistenceManager.POSITION).item(0) == null)
+				throw new IOException();
+			Element pointPositionTag = (Element)pointTag.getElementsByTagName(PersistenceManager.POSITION).item(0);
+			double dx = 0,dy = 0;
+			try{
+				dx = Double.parseDouble(pointPositionTag.getAttribute(PersistenceManager.X));
+				dy = Double.parseDouble(pointPositionTag.getAttribute(PersistenceManager.Y));
+			}catch(NumberFormatException nfe){
+				throw new IOException();
+			}
+			point.translate(new Point2D.Double(), dx, dy,DiagramEventSource.PERS);
+		}
+
+		/* remove the master inner point eventually created by connect */
+		/* we're going to replace it with the one in the XML file      */
+		points.clear();
+		/* re do the cycle when all the points id have been Map-ped */
+		for(int i=0; i<pointTagList.getLength(); i++){
+			Element pointTag = (Element)pointTagList.item(i);
+			InnerPoint point = pointsId.get(pointTag.getAttribute(PersistenceManager.ID));
+
+			if(pointTag.getElementsByTagName(PersistenceManager.NEIGHBOURS).item(0) == null)
+				throw new IOException();
+			Element pointNeighboursTag = (Element)pointTag.getElementsByTagName(PersistenceManager.NEIGHBOURS).item(0);
+			String pointNeighboursTagContent = pointNeighboursTag.getTextContent();
+			String[] neighboursId = pointNeighboursTagContent.split(" ");
+
+			for(String neighbourId : neighboursId){
+				GraphElement ge = nodesId.get(neighbourId);
+				if(ge == null) // it ain't a node
+					ge = pointsId.get(neighbourId);
+				if(ge == null)
+					throw new IOException();
+				point.neighbours.add(ge);
+			}
+			points.add(point);
+			if(i==0)
+				masterInnerPoint = point;
+		}
+	}
+
+	/**
+	 * Returns the minimum number of nodes that edge of this type can connect 
+	 * 
+	 * @return the minimum nodes for edges of this type 
+	 */
+	public int getMinAttachedNodes(){
+		return minAttachedNodes;
+	}
+
+	/**
+	 * Returns the maximum number of nodes that edge of this type can connect 
+	 * 
+	 * @return the maximum nodes for edges of this type 
+	 */
+	public int getMaxAttachedNodes(){
+		return maxAttachedNodes;
+	}
+
+	/**
+	 * Return the line style of this edge 
+	 * 
+	 * @return the line style of this edge 
+	 */
+	public LineStyle getStyle(){
+		return style;
+	}
+
+	protected Point2D downPoint;
+	private List<Node> nodes;
+
+	/* list containing the vertex of the edge which are not nodes */
+	/**
+	 * The list of the inner points of this edge 
+	 */
+	protected List<InnerPoint> points;
+	/**
+	 * For edges connecting more than two nodes, this is the central inner point where 
+	 * all the lines from the nodes join 
+	 */
+	protected InnerPoint masterInnerPoint;
+	
+	private boolean newPointCreated;
+	private InnerPoint newInnerPoint;
+	private int minAttachedNodes;
+	private int maxAttachedNodes;
+
+	private LineStyle style;
+
+
+	private static final double MAX_DIST = 5;
+	private static final Color POINT_COLOR = Color.GRAY;
+	/**
+	 * The end description for an end that has no hand description set by the user
+	 */
+	public static final String NO_ENDDESCRIPTION_STRING = ResourceBundle.getBundle(EditorFrame.class.getName()).getString("no_arrow_string");
+
+
+	/**
+	 * When an edge's (straight) line is bent it breaks into two sub lines. At the point where this two 
+	 * sub lines join a square shaped point is painted. This class represent that point. Objects of this class
+	 * are graph elements and the user can click on them and translate them along the graph. 
+	 *  
+	 */
+	protected static class InnerPoint implements GraphElement{
+		/**
+		 * Creates a new inner point
+		 */
+		public InnerPoint(){
+			bounds = new Rectangle2D.Double(0,0,DIM,DIM);
+			neighbours = new LinkedList<GraphElement>();
+		}
+
+		@Override
+		public void startMove(Point2D p, Object source){}
+
+		@Override
+		public void stopMove(Object source){}
+
+		@Override
+		public void draw(Graphics2D g2){
+			Color oldColor = g2.getColor();
+			g2.setColor(POINT_COLOR);
+			g2.fill(bounds);
+			g2.setColor(oldColor);
+		}
+
+		@Override
+		public boolean contains(Point2D p){
+			return bounds.contains(p);
+		}
+
+		@Override
+		public Point2D getConnectionPoint(Direction d){
+			return new Point2D.Double(bounds.getCenterX(),bounds.getCenterY());
+		}
+
+		@Override
+		public void translate(Point2D p, double dx, double dy, Object source){
+			bounds.setFrame(bounds.getX() + dx,
+					bounds.getY() + dy, 
+					bounds.getWidth(), 
+					bounds.getHeight());
+		}
+
+		@Override
+		public Rectangle2D getBounds(){
+			return (Rectangle2D)bounds.clone();
+		}
+
+		/**
+		 * Neighbours are the graph elements (either nodes or other inner points) this inner point is 
+		 * directly connected to. Directly connected means there is a straight line from this node
+		 * and the neighbour.  
+		 * 
+		 * @return a list of neighbours of this inner node
+		 */
+		public List<GraphElement> getNeighbours(){
+			return neighbours;
+		}
+
+		/**
+		 * Returns true if this inner node and {@code ge} are neighbours. 
+		 * 
+		 *  @see #getNeighbours()
+		 * 
+		 * @param ge the graph element to be tested
+		 * @return {@code true} if {@code ge} is a neighbour of this graph element, {@code false} otherwise 
+		 * 
+		 */
+		public boolean hasNeighbour(GraphElement ge){
+			return neighbours.contains(ge);
+		}
+
+		@Override
+		public String toString(){
+			return "EdgePoint: "+bounds.getCenterX()+"-"+bounds.getCenterY();
+		}
+
+		private Rectangle2D bounds;
+		private List<GraphElement> neighbours; 
+		private static final int DIM = 7;
+	}
+
+	/**
+	 * A representation of this edge as a set of 2D points.
+	 * Every node the edge connects and every inner point are represented as a pair with coordinates 
+	 * of their centre. Furthermore an adjacency matrix holds the information 
+	 * about which point is connected to which is. This representation of the edge is
+	 * used in the haptic space, being more suitable for haptic devices.  	   
+	 *
+	 */
+	public static class PointRepresentation {
+		public PointRepresentation(int size){
+			xs = new double[size];
+			ys = new double[size];
+			adjMatrix = new BitSet[size];
+			for(int i=0; i<size; i++){
+				adjMatrix[i] = new BitSet(size); 
+			}
+		}
+		/**
+		 * An array with all the x coordinate of the edge's points (nodes and inner points)
+		 */
+		public double xs[];
+		/**
+		 * An array with all the y coordinate of the edge's points (nodes and inner points)
+		 */
+		public double ys[];
+		/**
+		 * The adjacency matrix. If the i-th bit of {@code adjMatrix[j]} is set to {@code true}
+		 * it means there is a direct line connecting the i-th point (coordinates {@code (xs[i],ys[i])} 
+		 * to the j-th point (coordinates {@code (xs[j],ys[j])}. Note that connection are represented only
+		 * once to avoid double paintings by the haptic engine. So if the i-th bit of {@code adjMatrix[j]} 
+		 * is set to {@code true}, the j-th bit of {@code adjMatrix[i]} and information redundancy is avoided. 
+		 */
+		public BitSet adjMatrix[];
+		/** 
+		 * The index of the beginning of the nodes (after inner points) in  {@code adjMatrix}.
+		 * So if the edge has three nodes and two inner points. The inner points
+		 * will be at {@code adjMatrix[0]} and {@code adjMatrix[1]} and the nodes at the following indexes. 
+		 * In this case {@code nodeStart} is equal to 2.
+		 */
+		public int nodeStart; 
+	}
+
+	/**
+	 * Returns a new {@code PointRepresentation} of this edge 
+	 * 
+	 * @return a new {@code PointRepresentation} of this edge
+	 */
+	public PointRepresentation getPointRepresentation(){
+		PointRepresentation pr = new PointRepresentation(points.size()+nodes.size());
+		if(points.isEmpty()){ // two ended edge 
+			pr.xs[0] = nodes.get(0).getBounds().getCenterX();
+			pr.ys[0] = nodes.get(0).getBounds().getCenterY();
+			pr.xs[1] = nodes.get(1).getBounds().getCenterX();
+			pr.ys[1] = nodes.get(1).getBounds().getCenterY();
+			// we only need one edge, else it would be painted twice 
+			pr.adjMatrix[0].set(1);
+			pr.nodeStart = 0;
+		}else{
+			//[ point 1, point 2, point 3, ... , point n, node, 1 node 2, ... ,  node n ]
+			int pSize = points.size();
+			pr.nodeStart = pSize; // the first node starts after the points
+			for(int i=0; i<pSize;i++){
+				pr.xs[i] = points.get(i).getBounds().getCenterX();
+				pr.ys[i] = points.get(i).getBounds().getCenterY();
+				for(GraphElement ge : points.get(i).neighbours){
+					if(ge instanceof InnerPoint)
+						pr.adjMatrix[i].set(points.indexOf(ge));
+					else //Node
+						pr.adjMatrix[i].set(pSize+nodes.indexOf(ge));
+				}
+			}
+			/* set the coordinates of the nodes, no adj matrix needed as the inner points are enough */
+			for(int i=0 ; i<nodes.size(); i++){
+				pr.xs[pSize+i] = nodes.get(i).getBounds().getCenterX();
+				pr.ys[pSize+i] = nodes.get(i).getBounds().getCenterY();
+			}
+		}
+		return pr;
+	}
+
+}
+