/******************************************************************************* * Copyright (c) 2004, 2006 Sybase, Inc. and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Sybase, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.jst.jsf.facesconfig.ui.pageflow.layout; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.draw2d.graph.DirectedGraph; import org.eclipse.draw2d.graph.DirectedGraphLayout; import org.eclipse.draw2d.graph.Edge; import org.eclipse.draw2d.graph.EdgeList; import org.eclipse.draw2d.graph.Node; import org.eclipse.draw2d.graph.NodeList; import org.eclipse.emf.common.util.EList; import org.eclipse.jst.jsf.facesconfig.ui.pageflow.model.Pageflow; import org.eclipse.jst.jsf.facesconfig.ui.pageflow.model.PageflowFactory; import org.eclipse.jst.jsf.facesconfig.ui.pageflow.model.PageflowLink; import org.eclipse.jst.jsf.facesconfig.ui.pageflow.model.PageflowLinkBendpoint; import org.eclipse.jst.jsf.facesconfig.ui.pageflow.model.PageflowNode; import org.eclipse.jst.jsf.facesconfig.ui.pageflow.model.PageflowPage; import org.eclipse.jst.jsf.facesconfig.ui.pageflow.util.PageflowModelManager; /** * This class is a singleton adpater to create the directed graph for the * specified pageflow model or update the location information of pageflow model * according to directed graph layout algorithms. * * @author Xiao-guang Zhang */ public class PageflowLayoutManager { /** node's default size */ private static final int NODE_DEFAULT_WIDTH = 50; private static final int NODE_DEFAULT_HEIGHT = 50; /** * the margin of the top and left , and margin between each connnected * directed graph. */ private static final int X_SPACE = 50; private static final int Y_SPACE = 50; /** node's default padding */ private static final int DEFAULT_PADDING = 30; private static PageflowLayoutManager manager; private Map copiedLinks = null; /** * constructor of PageflowDirectedGraphAdapter * */ private PageflowLayoutManager() { // no external instantiation } /** * return the singleton instance of PageflowDirectedGraphAdapter * * @return - the singleton instance of PageflowDirectedGraphAdapter */ static public PageflowLayoutManager getInstance() { if (manager == null) { manager = new PageflowLayoutManager(); } return manager; } /** * layout pageflow using directed graph layout algorithms * * @param pageflow - * pageflow model */ public void layoutPageflow(Pageflow pageflow) { List selfLoopLinks = removeSelfLoopLinks(pageflow); List lstGraph = createGraphs(pageflow); for (Iterator iter = lstGraph.iterator(); iter.hasNext();) { DirectedGraph graph = (DirectedGraph) iter.next(); new DirectedGraphLayout().visit(graph);// .visit(graph); } updatePageflow(lstGraph); if (selfLoopLinks != null) { addSelfLoopLinks(pageflow, selfLoopLinks); } } /** * remove the self loop links from pageflow, because the layout algorithm * does not allow it. * * @param pageflow * @return */ private List removeSelfLoopLinks(Pageflow pageflow) { List selfLoopLinks = new ArrayList(); if (pageflow.getLinks() != null) { for (Iterator iter = pageflow.getLinks().iterator(); iter.hasNext();) { PageflowLink link = (PageflowLink) iter.next(); if (link.getSource() == link.getTarget()) { iter.remove(); link.getBendPoints().clear(); selfLoopLinks.add(link); } } } return selfLoopLinks; } /** * add back the self loop links with updated bendpoints. * * @param pageflow * @param selfLoopLinks */ private void addSelfLoopLinks(Pageflow pageflow, List selfLoopLinks) { if (pageflow.getLinks() != null && selfLoopLinks != null && selfLoopLinks.size() > 0) { EList links = pageflow.getLinks(); for (Iterator iter = selfLoopLinks.iterator(); iter.hasNext();) { PageflowLink link = (PageflowLink) iter.next(); updateSelfLoopLink(link); links.add(link); } } } /** * Update the bendpoints of the self loop link Follow the following style: * * LeftTop-Top | | Left----Node * * @param selfLoopLink */ public static void updateSelfLoopLink(PageflowLink selfLoopLink) { PageflowNode sourceNode = selfLoopLink.getSource(); Rectangle rectNode = getPageflowNodeBounds(sourceNode); EList outLinks = sourceNode.getOutlinks(); if (outLinks != null && outLinks.size() > 0) { for (Iterator iter = outLinks.iterator(); iter.hasNext();) { PageflowLink anotherSelfLoopLink = (PageflowLink) iter.next(); if (anotherSelfLoopLink != selfLoopLink && anotherSelfLoopLink.getTarget() == sourceNode) { rectNode = getFitnessRectangle(rectNode, anotherSelfLoopLink); } } } PageflowFactory factory = PageflowModelManager.getFactory(); PageflowLinkBendpoint bpTop = factory.createPFLinkBendpoint(); PageflowLinkBendpoint bpLeftTop = factory.createPFLinkBendpoint(); PageflowLinkBendpoint bpLeft = factory.createPFLinkBendpoint(); bpTop.setD1Height(-rectNode.height); bpTop.setD2Height(-rectNode.height); bpLeftTop.setD1Width(-rectNode.width); bpLeftTop.setD1Height(-rectNode.height); bpLeftTop.setD2Width(-rectNode.width); bpLeftTop.setD2Height(-rectNode.height); bpLeft.setD1Width(-rectNode.width); bpLeft.setD2Width(-rectNode.width); selfLoopLink.getBendPoints().add(bpTop); selfLoopLink.getBendPoints().add(bpLeftTop); selfLoopLink.getBendPoints().add(bpLeft); } /** * Calculate the fitness rectangle without conflict with the existing self * loop's rectangle. * * @param rectDefault * @param anotherSelfLoopLink */ private static Rectangle getFitnessRectangle(Rectangle rectDefault, PageflowLink anotherSelfLoopLink) { EList bendPoints = anotherSelfLoopLink.getBendPoints(); if (bendPoints != null && bendPoints.size() > 0) { for (Iterator iterBendPoint = bendPoints.iterator(); iterBendPoint .hasNext();) { PageflowLinkBendpoint bendPoint = (PageflowLinkBendpoint) iterBendPoint .next(); if (bendPoint.getD1Width() == -rectDefault.width && bendPoint.getD1Height() == -rectDefault.height) { rectDefault = new Rectangle(0, 0, rectDefault.width + DEFAULT_PADDING, rectDefault.height + DEFAULT_PADDING); break; } } } return rectDefault; } /** * get the pageflow node's bounds, the orginal point is (0,0) * * @param pfNode * @return */ private static Rectangle getPageflowNodeBounds(PageflowNode pfNode) { return new Rectangle(0, 0, 64, 36); } /** * get the pageflow node's border rectangle * * @param pfNode * @return */ private static Rectangle getPageflowNodeRectangle(PageflowNode pfNode) { Rectangle rectNode = null; Rectangle bounds = getPageflowNodeBounds(pfNode); rectNode = new Rectangle(pfNode.getX(), pfNode.getY(), bounds.width, bounds.height); return rectNode; } /** * create the connected subgraphs for the pageflow model, because there * maybe more than one connected graph in one pageflow definition. * * @param pageflow - * Pageflow model * @return - the connected subgraphs */ private List createGraphs(Pageflow pageflow) { /** the connected subgraphs */ List lstGraph = null; if (pageflow != null) { // Graph is not connected totally. DirectedGraph graph = null; HashMap nodesMap = new HashMap(); NodeList nodes = new NodeList(); EdgeList edges = new EdgeList(); // get all nodes in the pageflow List pfNodes = pageflow.getNodes(); if (pfNodes != null) { for (Iterator iter = pfNodes.iterator(); iter.hasNext();) { PageflowNode pfNode = (PageflowNode) iter.next(); Node node = new Node(pfNode); Rectangle rectNode = null; rectNode = getPageflowNodeBounds(pfNode); if (rectNode != null) { node.width = rectNode.width * 2; node.height = rectNode.height * 2; node.setPadding(new Insets(node.height, node.width, node.height, node.width)); } else { node.width = NODE_DEFAULT_WIDTH; node.height = NODE_DEFAULT_HEIGHT; node.setPadding(new Insets(DEFAULT_PADDING)); } nodesMap.put(pfNode, node); nodes.add(node); } } // get all edges in the pageflow List pfLinks = pageflow.getLinks(); for (Iterator iter = pfLinks.iterator(); iter.hasNext();) { PageflowLink link = (PageflowLink) iter.next(); PageflowNode source = link.getSource(); PageflowNode target = link.getTarget(); Node sourceNode = (Node) nodesMap.get(source); Node targetNode = (Node) nodesMap.get(target); if (sourceNode != null && targetNode != null) { Edge edge = new Edge(sourceNode, targetNode); edges.add(edge); } } graph = new DirectedGraph(); graph.nodes = nodes; graph.edges = edges; // get the connected subgraphs. lstGraph = new ArrayList(); lstGraph.add(graph);// ..getConnectedSubGraphs(); } return lstGraph; } /** * update the pageflow according to layout results which are stored in the * connected subgraphs * * @param lstGraph - * connected subgraphs * */ private void updatePageflow(List lstGraph) { // y coordiantion of the subgraph's start point int topSubGraph = 0; for (Iterator iter = lstGraph.iterator(); iter.hasNext();) { // sub graph's bottom int bottomSubGraph = 0; DirectedGraph graph = (DirectedGraph) iter.next(); for (int i = 0; i < graph.nodes.size(); i++) { Node node = graph.nodes.getNode(i); if (!(node.data instanceof PageflowNode)) { continue; } PageflowNode pfNode = (PageflowNode) node.data; pfNode.setX(X_SPACE + node.y); pfNode.setY(Y_SPACE + node.x + topSubGraph); if ((Y_SPACE + node.x + topSubGraph) > bottomSubGraph) { bottomSubGraph = Y_SPACE + node.x + topSubGraph; } } topSubGraph = bottomSubGraph + Y_SPACE; } } /** * update new pageflow's layout using the existing one. * @param newPageflow * @param oldPageflow */ public void updatePageflowLayout(Pageflow newPageflow, Pageflow oldPageflow) { List notUpdatedNodes = new ArrayList(); copiedLinks = new HashMap(); if (oldPageflow.getNodes().size() > 0) { for (Iterator iter = newPageflow.getNodes().iterator(); iter .hasNext();) { PageflowNode newNode = (PageflowNode) iter.next(); if (!updatePageflowNode(newNode, oldPageflow)) { notUpdatedNodes.add(newNode); } } } else { notUpdatedNodes.addAll(newPageflow.getNodes()); } if (oldPageflow.getLinks().size() > 0) { for (Iterator iter = newPageflow.getLinks().iterator(); iter .hasNext();) { PageflowLink newLink = (PageflowLink) iter.next(); updatePFLink(newLink, oldPageflow); } } // if there are still some nodes which are not updated, // they should be check whether there are some layout conflict. if (notUpdatedNodes.size() > 0) { resolveConflict(newPageflow, notUpdatedNodes); } } /** * Update pageflow node using the same node * * @param object * @return - the old pageflow node. */ private boolean updatePageflowNode(PageflowNode newNode, Pageflow oldPageflow) { for (Iterator iter = oldPageflow.getNodes().iterator(); iter.hasNext();) { PageflowNode oldNode = (PageflowNode) iter.next(); if (oldNode instanceof PageflowPage && newNode instanceof PageflowPage) { if (((PageflowPage) oldNode).getPath().trim().equals( ((PageflowPage) newNode).getPath().trim())) { updatePageflowNode(newNode, oldNode); return true; } } } return false; } /** * Update pageflow node using the same node * * @param object */ private void updatePageflowNode(PageflowNode newNode, PageflowNode node) { newNode.setX(node.getX()); newNode.setY(node.getY()); } /** * If there are conflict for the nodes, it will resolve it. * * @param newPageflow * @param notUpdatedNodes */ private void resolveConflict(Pageflow newPageflow, List notUpdatedNodes) { for (Iterator iter = notUpdatedNodes.iterator(); iter.hasNext();) { resolveConflict(newPageflow, (PageflowNode) iter.next()); } } /** * Resolve the layout conflict * * @param newPageflow * @param node */ private void resolveConflict(Pageflow newPageflow, PageflowNode node) { Rectangle nodeRect = getPageflowNodeRectangle(node); boolean bModified = false; for (int i = 0, size = newPageflow.getNodes().size(); i < size; i++) { PageflowNode fixedNode = (PageflowNode) newPageflow.getNodes().get( i); if (node == fixedNode) { continue; } Rectangle fixedNodeRect = getPageflowNodeRectangle(fixedNode); if (fixedNodeRect.intersects(nodeRect)) { nodeRect.x += fixedNodeRect.width + X_SPACE; nodeRect.y += fixedNodeRect.height + Y_SPACE; bModified = true; } } if (bModified) { node.setX(nodeRect.x); node.setY(nodeRect.y); } } /** * Update the link using the same link in the old pageflow * * @param newLink * @param oldPageflow */ private void updatePFLink(PageflowLink newLink, Pageflow oldPageflow) { for (Iterator iter = oldPageflow.getLinks().iterator(); iter.hasNext();) { PageflowLink oldLink = (PageflowLink) iter.next(); if (copiedLinks.get(oldLink) != null) { continue; } if (isSameLink(newLink, oldLink)) { updatePFLink(newLink, oldLink); break; } } } /** * Check whether this two links in differnet pageflow are same or not. * * @param newLink * @param oldLink * @return */ private boolean isSameLink(PageflowLink newLink, PageflowLink oldLink) { PageflowNode newSource = newLink.getSource(); PageflowNode newTarget = newLink.getTarget(); PageflowNode oldSource = oldLink.getSource(); PageflowNode oldTarget = oldLink.getTarget(); // Page-Page if (newSource instanceof PageflowPage && oldSource instanceof PageflowPage && newTarget instanceof PageflowPage && oldTarget instanceof PageflowPage) { if (((PageflowPage) newSource).getPath().trim().equalsIgnoreCase( ((PageflowPage) oldSource).getPath().trim()) && ((PageflowPage) newTarget) .getPath() .trim() .equalsIgnoreCase( ((PageflowPage) oldTarget).getPath().trim())) { if ((newLink.getOutcome() == null && oldLink.getOutcome() == null) || (newLink.getOutcome() != null && oldLink.getOutcome() != null && newLink .getOutcome().trim().equals( oldLink.getOutcome().trim()))) { return true; } } } return false; } /** * Update the link using the same link * * @param nodesMap * @param object * @return */ private PageflowLink updatePFLink(PageflowLink newLink, PageflowLink link) { PageflowFactory factory = PageflowModelManager.getFactory(); newLink.getBendPoints().clear(); for (Iterator iter = link.getBendPoints().iterator(); iter.hasNext();) { PageflowLinkBendpoint bendPoint = (PageflowLinkBendpoint) iter .next(); PageflowLinkBendpoint newBendPoint = factory .createPFLinkBendpoint(); newBendPoint.setD1Height(bendPoint.getD1Height()); newBendPoint.setD1Width(bendPoint.getD1Width()); newBendPoint.setD2Height(bendPoint.getD2Height()); newBendPoint.setD2Width(bendPoint.getD2Width()); newLink.getBendPoints().add(newBendPoint); } copiedLinks.put(link, link); return newLink; } }