view java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java @ 0:9418ab7b7f3f

Initial import
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Fri, 16 Dec 2011 17:35:51 +0000
parents
children 9e67171477bc
line wrap: on
line source
/*  
 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.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

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.diagrammodel.NodeProperties;
import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;

/**
 * An node in a graph. Node objects are used in a GraphPanel to render diagram nodes visually. 
 * Node objects are used in the tree representation of the diagram as well, as they're
 * subclasses of {@link DiagramNode}    
 * 
 */
@SuppressWarnings("serial")
public abstract class Node extends DiagramNode implements GraphElement{

	public Node(String type, NodeProperties properties){
		super(type,properties);
		internalNodes = new ArrayList<Node>();
		attachedEdges = new ArrayList<Edge>();
		externalNode = null;
	}

	/* --- DiagramNode abstract methods implementation --- */
	@Override
	public int getEdgesNum(){
		return attachedEdges.size();
	}

	@Override
	public Edge getEdgeAt(int index){
		return attachedEdges.get(index);
	}
	
	@Override
	public boolean addEdge(DiagramEdge e){
		return attachedEdges.add((Edge)e);
	}

	@Override
	public boolean removeEdge(DiagramEdge e){
		return attachedEdges.remove((Edge)e);
	}

	@Override
	public Node getExternalNode(){
		return externalNode; 
	}

	@Override
	public void setExternalNode(DiagramNode node){
		externalNode = (Node)node;
	}

	@Override
	public  Node getInternalNodeAt(int i){
		return internalNodes.get(i); 
	}
	
	@Override
	public int getInternalNodesNum(){
		return internalNodes.size();
	}

	@Override
	public void addInternalNode(DiagramNode node){
		internalNodes.add((Node)node);
	}

	@Override
	public void removeInternalNode(DiagramNode node){
		if (node.getExternalNode() != this) 
			return;
		internalNodes.remove(node);
		node.setExternalNode(null);
	}

	@Override
	public void stopMove(){
		notifyChange(new ElementChangedEvent(this,this,"stop_move"));
		/* edges can change as a result of nodes motion thus we call the method for all the edges
		 * of the node regardless what the mouse point is */
		for(int i = 0; i < getEdgesNum();i++){
			getEdgeAt(i).stopMove();
		}
	}

	/**
	 * 	Translates the node by a given amount
	 *	@param p the point we are translating from
	 *	@param dx the amount to translate in the x-direction
	 *	@param dy the amount to translate in the y-direction
	 */
	public void translate( Point2D p , double dx, double dy){
		translateImplementation( p, dx, dy);
		notifyChange(new ElementChangedEvent(this, this, "translate"));
	}

	protected void translateImplementation(Point2D p , double dx, double dy){
		for(int i=0; i< getInternalNodesNum();i++){
			getInternalNodeAt(i).translate(p, dx, dy);
		}
	}

	/**
	 *	Tests whether the node contains a point.
	 *	@param aPoint the point to test
	 *	@return true if this node contains aPoint
	 */
	public abstract boolean contains(Point2D aPoint);

	/**
	 * Get the bounding rectangle of the shape of this node
	 * @return the bounding rectangle
	 */
	@Override
	public abstract Rectangle2D getBounds();

	@Override
	public void startMove(Point2D p){
		/* useless, here just to comply with the GraphElement interface */
	}

	public abstract Point2D getConnectionPoint(Direction d);

	@Override
	public void draw(Graphics2D g2){
		if(!"".equals(getNotes())){
			Rectangle2D bounds = getBounds();
			Color oldColor = g2.getColor();
			g2.setColor(GraphPanel.GRABBER_COLOR);
			g2.fill(new Rectangle2D.Double(bounds.getX() - MARKER_SIZE / 2, bounds.getY() - MARKER_SIZE / 2, MARKER_SIZE, MARKER_SIZE));
			g2.setColor(oldColor);
		}
	}

	public abstract Shape getShape();

	public void encode(Document doc, Element parent){
		parent.setAttribute(PersistenceManager.NAME,getName());

		Element positionTag = doc.createElement(PersistenceManager.POSITION);
		Rectangle2D bounds = getBounds();
		positionTag.setAttribute(PersistenceManager.X, String.valueOf(bounds.getX()));
		positionTag.setAttribute(PersistenceManager.Y, String.valueOf(bounds.getY()));
		parent.appendChild(positionTag);

		if(getProperties().isEmpty())
			return;

		Element propertiesTag = doc.createElement(PersistenceManager.PROPERTIES);
		parent.appendChild(propertiesTag);
		for(String type : getProperties().getTypes()){
			List<String> values = getProperties().getValues(type);
			if(values.isEmpty())
				continue;
			Element propertyTag = doc.createElement(PersistenceManager.PROPERTY);
			propertiesTag.appendChild(propertyTag);

			Element typeTag = doc.createElement(PersistenceManager.TYPE);
			typeTag.appendChild(doc.createTextNode(type));
			propertyTag.appendChild(typeTag);

			int index = 0;
			for(String value : values){
				Element elementTag = doc.createElement(PersistenceManager.ELEMENT);
				propertyTag.appendChild(elementTag);

				Element valueTag = doc.createElement(PersistenceManager.VALUE);
				valueTag.appendChild(doc.createTextNode(value));
				elementTag.appendChild(valueTag);


				Set<Integer> modifierIndexes = getProperties().getModifiers(type).getIndexes(index);
				if(!modifierIndexes.isEmpty()){
					Element modifiersTag = doc.createElement(PersistenceManager.MODIFIERS);
					StringBuilder builder = new StringBuilder();
					for(Integer i : modifierIndexes )
						builder.append(i).append(' ');
					builder.deleteCharAt(builder.length()-1);//remove last space
					modifiersTag.appendChild(doc.createTextNode(builder.toString()));
					elementTag.appendChild(modifiersTag);
				}
				index++;
			}
		}
	}

	public void decode(Document doc, Element nodeTag) throws IOException{
		setName(nodeTag.getAttribute(PersistenceManager.NAME));
		try{
			setId(Integer.parseInt(nodeTag.getAttribute(PersistenceManager.ID)));
		}catch(NumberFormatException nfe){
			throw new IOException();
		}

		if(nodeTag.getElementsByTagName(PersistenceManager.POSITION).item(0) == null)
			throw new IOException();
		Element positionTag = (Element)nodeTag.getElementsByTagName(PersistenceManager.POSITION).item(0);
		double dx,dy;
		try{
			dx = Double.parseDouble(positionTag.getAttribute(PersistenceManager.X));
			dy = Double.parseDouble(positionTag.getAttribute(PersistenceManager.Y));
		}catch(NumberFormatException nfe){
			throw new IOException();
		}
		Rectangle2D bounds = getBounds();
		translate(new Point2D.Double(0,0), dx - bounds.getX(), dy - bounds.getY());

		NodeList propList = nodeTag.getElementsByTagName(PersistenceManager.PROPERTY);
		NodeProperties properties = getProperties();
		for(int j=0; j<propList.getLength();j++){
			Element propertyTag = (Element)propList.item(j);

			if(propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0) == null)
				throw new IOException();
			Element pTypeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
			String propertyType = pTypeTag.getTextContent();

			/* scan all the <Element> of the current <Property>*/
			NodeList elemValueList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT);
			for(int h=0; h<elemValueList.getLength(); h++){

				Element elemTag = (Element)elemValueList.item(h);
				/* get the <value> */
				if(elemTag.getElementsByTagName(PersistenceManager.VALUE).item(0) == null)
					throw new IOException();
				Element valueTag = (Element)elemTag.getElementsByTagName(PersistenceManager.VALUE).item(0);
				String value = valueTag.getTextContent();

				/* <modifiers>. need to go back on the prototypes because the content of <modifier> is a list */
				/* of int index pointing to the modifiers type, defined just in the prototypes                */
				Element prototypesTag = (Element)doc.getElementsByTagName(PersistenceManager.PROTOTYPES).item(0);
				Modifiers modifiers = null;
				try {
					modifiers = properties.getModifiers(propertyType);
				}catch(IllegalArgumentException iae){
					throw new IOException(iae);
				}
				if(!modifiers.isNull()){
					Element modifiersTag = (Element)((Element)elemValueList.item(h)).getElementsByTagName(PersistenceManager.MODIFIERS).item(0);
					if(modifiersTag != null){ //else there are no modifiers specified for this property value
						Set<Integer> indexesToAdd = new LinkedHashSet<Integer>();
						String indexesString = modifiersTag.getTextContent();
						String[] indexes = indexesString.split(" ");
						for(String s : indexes){
							try{
								int index = Integer.parseInt(s);
								NodeList templatePropList = prototypesTag.getElementsByTagName(PersistenceManager.PROPERTY);
								String modifiersType = null;
								/* look at the property prototypes to see which modifier the index is referring to.      *  
								 * The index is in fact the id attribute of the <Modifier> tag in the prototypes section */
								for(int k=0; k<templatePropList.getLength();k++){
									Element prototypePropTag = (Element)templatePropList.item(k);
									Element prototypePropTypeTag = (Element)prototypePropTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
	
									if(propertyType.equals(prototypePropTypeTag.getTextContent())){
										NodeList proptotypeModifierList = prototypePropTag.getElementsByTagName(PersistenceManager.MODIFIER);
										for(int m = 0 ; m<proptotypeModifierList.getLength();m++){
											if(index == Integer.parseInt(((Element)proptotypeModifierList.item(m)).getAttribute(PersistenceManager.ID))){
												Element modifierTypeTag = (Element)((Element)proptotypeModifierList.item(m)).getElementsByTagName(PersistenceManager.TYPE).item(0);
												modifiersType = modifierTypeTag.getTextContent();
											}
										}
									}
								}
								if(modifiersType == null) // the index must point to a valid modifier's id
									throw new IOException();
								indexesToAdd.add(Integer.valueOf(modifiers.getTypes().indexOf(modifiersType)));
							}catch(NumberFormatException nfe){
								throw new IOException(nfe);
							}
						}
						addProperty(propertyType, value);//whether propertyType actually exist in the prototypes has been already checked  
						setModifierIndexes(propertyType, h, indexesToAdd);
					}else
						addProperty(propertyType, value);
				}else
					addProperty(propertyType, value);
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Object clone(){
		Node clone = (Node)super.clone();
		clone.internalNodes = (ArrayList<Node>) internalNodes.clone();
		clone.externalNode = null;
		clone.attachedEdges = (ArrayList<Edge>) attachedEdges.clone(); 
		return clone;
	}

	protected ArrayList<Node> internalNodes;
	protected Node externalNode;
	protected ArrayList<Edge> attachedEdges;

	private final int MARKER_SIZE = 7;
	protected static final Color SHADOW_COLOR = Color.LIGHT_GRAY;
	public static final int SHADOW_GAP = 2;

}