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