/*
Violet - A program for editing UML diagrams.
Copyright (C) 2007 Cay S. Horstmann (http://horstmann.com)
Alexandre de Pellegrin (http://alexdp.free.fr);
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 com.horstmann.violet.product.diagram.abstracts.node;
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.horstmann.violet.framework.graphics.content.Content;
import com.horstmann.violet.product.diagram.abstracts.AbstractGraph;
import com.horstmann.violet.product.diagram.abstracts.Direction;
import com.horstmann.violet.product.diagram.abstracts.IGraph;
import com.horstmann.violet.product.diagram.abstracts.Id;
import com.horstmann.violet.product.diagram.abstracts.edge.IEdge;
/**
* A class that supplies convenience implementations for a number of methods in the Node interface
*
* @author Cay Horstmann
*/
public abstract class AbstractNode implements INode
{
private static class NodeGraph extends AbstractGraph
{
@Override
public List<INode> getNodePrototypes() {
return new ArrayList<INode>();
}
@Override
public List<IEdge> getEdgePrototypes() {
return new ArrayList<IEdge>();
}
}
/**
* Constructs a node_old with no parents or children at location (0, 0).
*/
public AbstractNode() {
this.id = new Id();
this.revision = new Integer(0);
this.location = new Point2D.Double(0, 0);
this.children = new ArrayList<INode>();
}
/**
* copy Constructs
*/
protected AbstractNode(AbstractNode node) throws CloneNotSupportedException
{
if (null == node)
{
throw new CloneNotSupportedException("node can't be null");
}
this.id = node.getId().clone();
this.revision = new Integer(0);
this.children = new ArrayList<INode>();
this.location = (Point2D.Double) node.getLocation().clone();
for (INode child : node.getChildren()) {
INode clonedChild = child.clone();
clonedChild.setParent(this);
this.children.add(clonedChild);
}
}
@Override
public final void reconstruction()
{
beforeReconstruction();
createContentStructure();
afterReconstruction();
}
protected void beforeReconstruction()
{
}
protected void afterReconstruction()
{
getContent().refresh();
}
@Override
// public AbstractNode clone(){
public final AbstractNode clone()
{
try {
return (AbstractNode) copy();
} catch (CloneNotSupportedException e) {
return null;
}
}
protected INode copy() throws CloneNotSupportedException
{
throw new CloneNotSupportedException("You can't clone abstract class");
}
@Override
public String getToolTip()
{
return "";
}
protected abstract void createContentStructure();
@Override
public void draw(Graphics2D graphics)
{
getContent().draw(graphics, getLocationOnGraph());
}
@Override
public Rectangle2D getBounds()
{
Point2D location = getLocation();
Rectangle2D contentBounds = getContent().getBounds();
return new Rectangle2D.Double(location.getX(), location.getY(), contentBounds.getWidth(), contentBounds.getHeight());
}
public boolean contains(Point2D p) {
return getContent().contains(p);
}
/**
* @return currently connected edges
*/
protected List<IEdge> getConnectedEdges()
{
List<IEdge> connectedEdges = new ArrayList<IEdge>();
IGraph currentGraph = getGraph();
for (IEdge anEdge : currentGraph.getAllEdges()) {
INode start = anEdge.getStartNode();
INode end = anEdge.getEndNode();
if (this.equals(start) || this.equals(end)) {
connectedEdges.add(anEdge);
}
}
return connectedEdges;
}
@Override
public Point2D getLocation() {
return this.location;
}
@Override
public Point2D getLocationOnGraph()
{
INode parentNode = getParent();
if (parentNode == null) {
return getLocation();
}
Point2D parentLocationOnGraph = parentNode.getLocationOnGraph();
Point2D relativeLocation = getLocation();
Point2D result = new Point2D.Double(parentLocationOnGraph.getX() + relativeLocation.getX(), parentLocationOnGraph.getY()
+ relativeLocation.getY());
return result;
}
@Override
public void setLocation(Point2D point)
{
if (null == point) {
throw new NullPointerException("Location can't be null");
}
this.location = point;
if (null != parent) {
if (parent instanceof AbstractNode) {
((AbstractNode) parent).onChildChangeLocation(this);
}
}
}
protected void onChildChangeLocation(INode child) {
}
@Override
public Id getId() {
return this.id;
}
@Override
public void setId(Id id) {
if (null == id) {
throw new NullPointerException("Id can't be null");
}
this.id = id;
}
/**
* List edges connected to the same side
*
* @param edge
* @return ordered list of edges
*/
private List<IEdge> getEdgesOnSameSide(IEdge edge)
{
// Step 1 : look for edges
List<IEdge> result = new ArrayList<IEdge>();
Direction d = edge.getDirection(this);
if (d == null) return result;
Direction cardinalDirectionToSearch = d.getNearestCardinalDirection();
for (IEdge anEdge : getConnectedEdges()) {
Direction edgeDirection = anEdge.getDirection(this);
Direction nearestCardinalDirection = edgeDirection.getNearestCardinalDirection();
if (cardinalDirectionToSearch.equals(nearestCardinalDirection)) {
result.add(anEdge);
}
if (anEdge.getStartNode().equals(anEdge.getEndNode()) && anEdge.getStartNode().equals(this)) {
// self loop
result.add(anEdge);
}
}
// Step 2: sort them
if (Direction.NORTH.equals(cardinalDirectionToSearch) || Direction.SOUTH.equals(cardinalDirectionToSearch)) {
Collections.sort(result, new Comparator<IEdge>() {
@Override
public int compare(IEdge e1, IEdge e2) {
Direction d1 = e1.getDirection(AbstractNode.this);
Direction d2 = e2.getDirection(AbstractNode.this);
double x1 = d1.getX();
double x2 = d2.getX();
return Double.compare(x1, x2);
}
});
}
if (Direction.EAST.equals(cardinalDirectionToSearch) || Direction.WEST.equals(cardinalDirectionToSearch)) {
Collections.sort(result, new Comparator<IEdge>() {
@Override
public int compare(IEdge e1, IEdge e2) {
Direction d1 = e1.getDirection(AbstractNode.this);
Direction d2 = e2.getDirection(AbstractNode.this);
double y1 = d1.getY();
double y2 = d2.getY();
return Double.compare(y1, y2);
}
});
}
return result;
}
public Point2D getConnectionPoint(IEdge edge)
{
List<IEdge> edgesOnSameSide = getEdgesOnSameSide(edge);
int position = edgesOnSameSide.indexOf(edge);
int size = edgesOnSameSide.size();
Direction edgeDirection = edge.getDirection(this);
Point2D startingNodeLocation = getLocation();
double x = startingNodeLocation.getX();
double y = startingNodeLocation.getY();
Direction nearestCardinalDirection = edgeDirection.getNearestCardinalDirection();
if (Direction.NORTH.equals(nearestCardinalDirection))
{
x += getContent().getWidth() - (getContent().getWidth() / (size + 1)) * (position + 1);
y += getContent().getHeight();
}
else if (Direction.SOUTH.equals(nearestCardinalDirection))
{
x += getContent().getWidth() - (getContent().getWidth() / (size + 1)) * (position + 1);
}
else if (Direction.EAST.equals(nearestCardinalDirection))
{
y += getContent().getHeight() - (getContent().getHeight() / (size + 1)) * (position + 1);
}
else if (Direction.WEST.equals(nearestCardinalDirection))
{
x += getContent().getWidth();
y += getContent().getHeight() - (getContent().getHeight() / (size + 1)) * (position + 1);
}
Point2D p = new Point2D.Double(x, y);
return p;
}
@Override
public Integer getRevision() {
return this.revision;
}
@Override
public void setRevision(Integer newRevisionNumber) {
if (null == newRevisionNumber) {
throw new NullPointerException("Integer can't be null");
}
this.revision = newRevisionNumber;
}
@Override
public void incrementRevision() {
int i = getRevision().intValue() + 1;
this.revision = new Integer(i);
}
@Override
public void translate(double dx, double dy) {
Point2D newLocation = new Point2D.Double(getLocation().getX() + dx, getLocation().getY() + dy);
setLocation(newLocation);
}
@Override
public boolean addConnection(IEdge edge)
{
return edge.getEndNode() != null;
}
@Override
public void removeConnection(IEdge edge) {
}
@Override
public void removeChild(INode node)
{
if (node.getParent() != this) return;
getChildren().remove(node);
}
@Override
public boolean addChild(INode node, Point2D point)
{
return false;
}
@Override
public INode getParent() {
return parent;
}
@Override
public List<INode> getParents()
{
List<INode> parents = new ArrayList<INode>();
if(null != getParent())
{
parents.add(getParent());
while (null != parents.get(0).getParent())
{
parents.add(0, parents.get(0).getParent());
}
}
return parents;
}
@Override
public void setParent(INode node)
{
parent = node;
}
@Override
public List<INode> getChildren()
{
return children;
}
@Override
public boolean addChild(INode node, int index)
{
INode oldParent = node.getParent();
if (oldParent != null) oldParent.removeChild(node);
getChildren().add(index, node);
node.setParent(this);
node.setGraph(getGraph());
return true;
}
public void onConnectedEdge(IEdge connectedEdge)
{}
/**
* @return the shape to be used for computing the drop shadow
*/
public Shape getShape()
{
return new Rectangle2D.Double(0, 0, 0, 0);
}
@Override
public int getZ()
{
return z;
}
@Override
public void setZ(int z)
{
this.z = z;
}
@Override
public void setGraph(IGraph graph)
{
if(null == graph)
{
throw new NullPointerException("Graph can't be null");
}
this.graph = graph;
for (INode aChild : getChildren()) {
aChild.setGraph(graph);
}
}
@Override
public IGraph getGraph()
{
if(null == graph)
{
return new NodeGraph();
}
return this.graph;
}
public final Content getContent()
{
if(null == content)
{
reconstruction();
}
return content;
}
protected final void setContent(Content content)
{
this.content = content;
}
private transient Content content;
private transient IGraph graph;
private transient int z;
/** Node's current id (unique in all the graph) */
private Id id;
/** Node's current revision */
private Integer revision;
private List<INode> children;
private INode parent;
private Point2D location;
}