1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.entityrelationship;
15  
16  import demo.view.entityrelationship.painters.ErdRealizerFactory;
17  import y.base.Edge;
18  import y.base.EdgeCursor;
19  import y.base.EdgeList;
20  import y.base.Node;
21  import y.base.NodeCursor;
22  import y.base.NodeList;
23  import y.view.Arrow;
24  import y.view.EdgeRealizer;
25  import y.view.GenericNodeRealizer;
26  import y.view.Graph2D;
27  import y.view.NodeLabel;
28  import y.view.NodeRealizer;
29  
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.Map;
33  
34  /**
35   * This is a converter that provides method to change the notation for an Entity Relationship Diagram (ERD).
36   *
37   * <p> It is possible to convert from Chen to Crow's Foot and vice versa
38   * ({@link #convertToCrowFoot(y.view.Graph2D)} and {@link #convertToChen(y.view.Graph2D)}). </p>
39   */
40  class ErdNotationConverter {
41  
42    /**
43     * Converts an ERD graph from Chen to Crow's Foot notation.
44     *
45     * If the graph is already in Crow's Foot notation, the structure remains
46     * the same.
47     * @param graph the graph to be converted
48     */
49    static void convertToCrowFoot(Graph2D graph) {
50  
51      // Back up all realizers to provide undo-functionality for the whole method
52      graph.backupRealizers();
53  
54      // Map that holds <relationship, list of neighbor entities> sets
55      Map relations = new HashMap();
56      // Map that holds <entity, list of attributes> sets
57      Map attributes = new HashMap();
58  
59      // First all neighbors of every entity nodes are stored in <code>HashMap</code>s
60      // for relationships and attributes
61      for (NodeCursor nodeCursor = graph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
62        Node node = nodeCursor.node();
63        NodeRealizer nodeRealizer = graph.getRealizer(node);
64        if (ErdRealizerFactory.isSmallEntityRealizer(nodeRealizer)) {
65          NodeList attrList = new NodeList();
66          attributes.put(node, attrList);
67          for (NodeCursor cursor = node.neighbors(); cursor.ok(); cursor.next()) {
68            Node neighbor = cursor.node();
69            NodeRealizer neighborRealizer = graph.getRealizer(neighbor);
70            if (ErdRealizerFactory.isAttributeRealizer(neighborRealizer)) {
71              attrList.add(neighbor);
72            } else {
73              if (ErdRealizerFactory.isRelationshipRealizer(neighborRealizer)) {
74                NodeList relList = new NodeList();
75                if (!relations.containsKey(neighbor)) {
76                  relations.put(neighbor, relList);
77                }
78                relList = (NodeList) relations.get(neighbor);
79                relList.add(node);
80              }
81            }
82          }
83        }
84      }
85  
86      StringBuffer buffer = new StringBuffer();
87  
88      // For every entity a list of attributes is generated and the attribute nodes
89      // are removed from the graph.
90      // Then the <code>NodeRealizer</code> is changed to a big entity realizer and
91      // the size is adapted.
92      for (Iterator it = attributes.keySet().iterator(); it.hasNext(); ) {
93        Node node = (Node) it.next();
94        NodeList attrList = (NodeList) attributes.get(node);
95        for (NodeCursor cursor = attrList.nodes(); cursor.ok(); cursor.next()) {
96          Node attrNode = cursor.node();
97          buffer = buffer.append(graph.getLabelText(attrNode)).append("\n");
98          graph.removeNode(attrNode);
99        }
100 
101       NodeRealizer bigEntity = ErdRealizerFactory.createBigEntity();
102       NodeLabel nameLabel = bigEntity.getLabel(0);
103       NodeLabel attrLabel = bigEntity.getLabel(1);
104       nameLabel.setText(graph.getLabelText(node));
105       attrLabel.setText(buffer.toString());
106       buffer.setLength(0);
107       bigEntity.setLocation(graph.getRealizer(node).getX(), graph.getRealizer(node).getY());
108       graph.setRealizer(node, bigEntity);
109 
110       double newHeight = 0;
111       double newWidth = 0;
112       for (int i = 0; i < bigEntity.labelCount(); i++) {
113         newHeight += bigEntity.getLabel(i).getBox().getHeight();
114         newWidth = Math.max(newWidth, bigEntity.getLabel(i).getBox().getWidth());
115       }
116       if (newHeight > bigEntity.getHeight() - 10) {
117         bigEntity.setHeight(newHeight + 15);
118       }
119       if (newWidth > bigEntity.getWidth() - 10) {
120         bigEntity.setWidth(newWidth + 15);
121       }
122     }
123 
124     // In place of every relation an edge is created with appropriate arrows. Then
125     // the relationship node is removed from the graph.
126     for (Iterator it = relations.keySet().iterator(); it.hasNext(); ) {
127       Node relation = (Node) it.next();
128       NodeList relList = (NodeList) relations.get(relation);
129       if (relation.degree() > relList.size()) {
130 
131         // If there are attributes for the relationship, they are added within brackets
132         // to the edge label
133         for (NodeCursor cursor = relation.neighbors(); cursor.ok(); cursor.next()) {
134           final Node node = cursor.node();
135 
136           if (!relList.contains(node)) {
137             buffer.append("\n(");
138             buffer.append(graph.getLabelText(node));
139             buffer.append(")");
140             graph.removeNode(node);
141           }
142         }
143       }
144       Node[] relNodes = relList.toNodeArray();
145       for (int i = 0; i < relNodes.length; i++) {
146         for (int j = i + 1; j < relNodes.length; j++) {
147           String labelText1 = graph.getLabelText(relation.getEdge(relNodes[i]));
148           String labelText2 = graph.getLabelText(relation.getEdge(relNodes[j]));
149           Arrow sourceArrow = getArrow(labelText1);
150           Arrow targetArrow = getArrow(labelText2);
151           Edge edge = graph.createEdge(relNodes[i], relNodes[j],
152               ErdRealizerFactory.createRelation(sourceArrow, targetArrow));
153           String edgeLabel = graph.getLabelText(relation) + buffer;
154           if (edgeLabel.length() > 0) {
155             graph.setLabelText(edge, edgeLabel);
156           }
157           buffer.setLength(0);
158         }
159       }
160       graph.removeNode(relation);
161     }
162   }
163 
164   /**
165    * Returns a Crow's Foot <code>Arrow</code> for the given multiplicity <code>String</code>.
166    * @param text a string that describes the multiplicity of the relationship
167    * @return an appropriate Crow's Foot <code>Arrow</code>
168    */
169   private static Arrow getArrow(String text) {
170     Arrow arrow = Arrow.NONE;
171     if ("1".equals(text)) {
172       arrow = Arrow.CROWS_FOOT_ONE;
173     } else {
174       if ("(0,1)".equals(text)) {
175         arrow = Arrow.CROWS_FOOT_ONE_OPTIONAL;
176       } else {
177         if ("(1,1)".equals(text)) {
178           arrow = Arrow.CROWS_FOOT_ONE_MANDATORY;
179         } else {
180           if ("M".equalsIgnoreCase(text) || "N".equalsIgnoreCase(text)) {
181             arrow = Arrow.CROWS_FOOT_MANY;
182           } else {
183             if ("(0,N)".equalsIgnoreCase(text) || "(0,M)".equalsIgnoreCase(text)) {
184               arrow = Arrow.CROWS_FOOT_MANY_OPTIONAL;
185             } else {
186               if ("(1,N)".equalsIgnoreCase(text) || "(1,M)".equalsIgnoreCase(text)) {
187                 arrow = Arrow.CROWS_FOOT_MANY_MANDATORY;
188               }
189             }
190           }
191         }
192       }
193     }
194     return arrow;
195   }
196 
197   /**
198    * Converts an ERD graph from Crow's Foot to Chen notation.
199    *
200    * If the graph is already in Chen notation, the structure remains
201    * the same.
202    * @param graph the graph to be converted
203    */
204   static void convertToChen(Graph2D graph) {
205 
206     // Back up all realizers to provide undo-functionality for the whole method
207     graph.backupRealizers();
208 
209     // Replace every relationship node with an edge and set the relationship name
210     // as edge label. Also create the appropriate Crow's Foot arrows.
211     EdgeList edgeList = new EdgeList(graph.getEdgeList());
212     for (EdgeCursor cursor = edgeList.edges(); cursor.ok(); cursor.next()) {
213       final Edge edge = cursor.edge();
214 
215       if (ErdRealizerFactory.isBigEntityRealizer(graph.getRealizer(edge.source()))
216           && ErdRealizerFactory.isBigEntityRealizer(graph.getRealizer(edge.target()))) {
217         final EdgeRealizer edgeRealizer = graph.getRealizer(edge);
218         StringBuffer buffer = new StringBuffer();
219         for (int i = 0; i < edgeRealizer.labelCount(); i++) {
220           if (i > 0) {
221             buffer.append(" / ");
222           }
223           buffer.append(edgeRealizer.getLabel(i).getText());
224         }
225 
226         String relString = buffer.toString().replaceAll("[\\)]|\n", "");
227         String[] relText = relString.split("\\(");
228         Node relation = graph.createNode(ErdRealizerFactory.createRelationship(relText[0]));
229         for (int i = 1; i < relText.length; i++) {
230           Node attribute = graph.createNode(ErdRealizerFactory.createAttribute(relText[i]));
231           graph.createEdge(relation, attribute, ErdRealizerFactory.createRelation(Arrow.NONE, Arrow.NONE));
232         }
233         buffer.setLength(0);
234 
235         Edge sourceEdge = graph.createEdge(edge.source(), relation);
236         graph.setRealizer(sourceEdge, ErdRealizerFactory.createRelation(Arrow.NONE));
237         graph.getRealizer(sourceEdge).setLabelText(getLabel(edgeRealizer.getSourceArrow()));
238         Edge targetEdge = graph.createEdge(relation, edge.target());
239         graph.setRealizer(targetEdge, ErdRealizerFactory.createRelation(Arrow.NONE));
240         graph.getRealizer(targetEdge).setLabelText(getLabel(edgeRealizer.getTargetArrow()));
241 
242         graph.removeEdge(edge);
243       }
244     }
245 
246     // Create attribute nodes for every line in the attributes label and attach them to
247     // the entity. Also change the entity's realizer to a small entity node realizer and
248     // adjust its size.
249     for (NodeCursor cursor = graph.nodes(); cursor.ok(); cursor.next()) {
250       final Node node = cursor.node();
251 
252       final NodeRealizer nodeRealizer = graph.getRealizer(node);
253       if (ErdRealizerFactory.isBigEntityRealizer(nodeRealizer)) {
254         String text = nodeRealizer.getLabel(1).getText();
255         text = text.replaceAll("<html>|<br>|</?u>", "");
256         String[] attributes = text.split("\n");
257         for (int i = 0; i < attributes.length; i++) {
258           if (attributes[i].length() > 0) {
259             Node attrNode = graph.createNode(ErdRealizerFactory.createAttribute(attributes[i]));
260             Edge attrEdge = graph.createEdge(node, attrNode);
261             graph.setRealizer(attrEdge, ErdRealizerFactory.createRelation(Arrow.NONE));
262           }
263         }
264         final GenericNodeRealizer smallEntity = ErdRealizerFactory.createSmallEntity(
265             nodeRealizer.getLabel(0).getText());
266         graph.setRealizer(node, smallEntity);
267         double newHeight = 0;
268         double newWidth = 0;
269         for (int i = 0; i < smallEntity.labelCount(); i++) {
270           newHeight += smallEntity.getLabel(i).getBox().getHeight();
271           newWidth = Math.max(newWidth, smallEntity.getLabel(i).getBox().getWidth());
272         }
273         if (newHeight > smallEntity.getHeight() - 10) {
274           smallEntity.setHeight(newHeight + 15);
275         }
276         if (newWidth > smallEntity.getWidth() - 10) {
277           smallEntity.setWidth(newWidth + 15);
278         }
279 
280       }
281     }
282   }
283 
284   /**
285    * Returns a multiplicity <code>String</code> for the specified Crow's Foot <code>Arrow</code>.
286    * @param arrow a Crow's Foot <code>Arrow</code> that shows the multiplicity of the relationship
287    * @return a multiplicity string for Chen notation
288    */
289   private static String getLabel(Arrow arrow) {
290     String label = "";
291     if (arrow.equals(Arrow.CROWS_FOOT_ONE)) {
292       label = "1";
293     } else {
294       if (arrow.equals(Arrow.CROWS_FOOT_ONE_OPTIONAL)) {
295         label = "(0,1)";
296       } else {
297         if (arrow.equals(Arrow.CROWS_FOOT_ONE_MANDATORY)) {
298           label = "(1,1)";
299         } else {
300           if (arrow.equals(Arrow.CROWS_FOOT_MANY)) {
301             label = "N";
302           } else {
303             if (arrow.equals(Arrow.CROWS_FOOT_MANY_OPTIONAL)) {
304               label = "(0,N)";
305             } else {
306               if (arrow.equals(Arrow.CROWS_FOOT_MANY_MANDATORY)) {
307                 label = "(1,N)";
308               }
309             }
310           }
311         }
312       }
313     }
314     return label;
315   }
316 }