* Bug #15072 fixed: The context was stored as a root diagram attribute instead of...
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / io / codec / XcosDiagramCodec.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - DIGITEO - Clement DAVID
4  * Copyright (C) 2011-2015 - Scilab Enterprises - Clement DAVID
5  *
6  * Copyright (C) 2012 - 2016 - Scilab Enterprises
7  *
8  * This file is hereby licensed under the terms of the GNU GPL v2.0,
9  * pursuant to article 5.3.4 of the CeCILL v.2.1.
10  * This file was originally licensed under the terms of the CeCILL v2.1,
11  * and continues to be available under such terms.
12  * For more information, see the COPYING file which you should have received
13  * along with this program.
14  *
15  */
16
17 package org.scilab.modules.xcos.io.codec;
18
19 import java.lang.reflect.Field;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.logging.Logger;
26
27 import org.scilab.modules.graph.io.ScilabGraphCodec;
28 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
29 import org.scilab.modules.gui.messagebox.ScilabModalDialog.IconType;
30 import org.scilab.modules.localization.Messages;
31 import org.scilab.modules.xcos.JavaController;
32 import org.scilab.modules.xcos.Kind;
33 import org.scilab.modules.xcos.Xcos;
34 import org.scilab.modules.xcos.graph.ScicosParameters;
35 import org.scilab.modules.xcos.graph.XcosDiagram;
36 import org.scilab.modules.xcos.graph.model.XcosCell;
37 import org.w3c.dom.Element;
38 import org.w3c.dom.NamedNodeMap;
39 import org.w3c.dom.Node;
40 import org.w3c.dom.NodeList;
41
42 import com.mxgraph.io.mxCodec;
43 import com.mxgraph.io.mxCodecRegistry;
44 import com.mxgraph.model.mxGraphModel;
45 import com.mxgraph.model.mxICell;
46 import org.scilab.modules.xcos.graph.model.ScicosObjectOwner;
47
48 /**
49  * Codec for an {@link org.scilab.modules.xcos.graph.XcosDiagram} instance.
50  */
51 // CSOFF: ClassFanOutComplexity
52 public class XcosDiagramCodec extends ScilabGraphCodec {
53     private static final String SCICOS_PARAMETERS = "scicosParameters";
54     private static final String AS_ATTRIBUTE = "as";
55     private static final String SEP = " - ";
56
57     private static final String INCOMPATIBILITY_DETECTED = Messages.gettext("Incompatibility detected");
58     private static final String PLEASE_CHECK_THE_DIAGRAM = Messages.gettext("Please check the diagram, before trying to simulate it.");
59     private static final String SOME_BLOCKS_HAVE_BEEN_REMOVED = Messages.gettext("Some blocks have been removed to ensure compatibility.");
60
61     // The non saved fields are hardcoded and can have the same name.
62     // CSOFF: MultipleStringLiterals
63     private static final String[] DIAGRAM_IGNORED_FIELDS = { "stylesheet", "parentTab", "viewPort", "viewPortMenu", "view", "selectionModel", "savedFile",
64                                                              "multiplicities", "opened", "modified", "undoManager", "background"
65                                                            };
66     private static final String[] SUPERBLOCKDIAGRAM_IGNORED_FIELDS = { "stylesheet", "parentTab", "viewPort", "viewPortMenu", "view", "selectionModel",
67                                                                        "multiplicities", "opened", "modified", "undoManager", "savedFile", "container", "integratorAbsoluteTolerance", "integratorRelativeTolerance",
68                                                                        "maxIntegrationTimeInterval", "toleranceOnTime", "background"
69                                                                      };
70
71     // CSON: MultipleStringLiterals
72
73     /**
74      * Default constructor
75      *
76      * @param template
77      *            the instance template
78      */
79     public XcosDiagramCodec(Object template) {
80         super(template);
81     }
82
83     /**
84      * The constructor used for configuration
85      *
86      * @param template
87      *            Prototypical instance of the object to be encoded/decoded.
88      * @param exclude
89      *            Optional array of fieldnames to be ignored.
90      * @param idrefs
91      *            Optional array of fieldnames to be converted to/from references.
92      * @param mapping
93      *            Optional mapping from field- to attributenames.
94      */
95     public XcosDiagramCodec(Object template, String[] exclude, String[] idrefs, Map<String, String> mapping) {
96         super(template, exclude, idrefs, mapping);
97     }
98
99     /**
100      * Register this codec into the {@link mxCodecRegistry}.
101      */
102     public static void register() {
103         JavaController controller = new JavaController();
104
105         ScilabGraphCodec diagramCodec = new XcosDiagramCodec(new XcosDiagram(controller, controller.createObject(Kind.DIAGRAM), Kind.DIAGRAM, ""), DIAGRAM_IGNORED_FIELDS, null,
106                 null);
107         mxCodecRegistry.register(diagramCodec);
108         ScilabGraphCodec superBlockDiagramCodec = new XcosDiagramCodec(new XcosDiagram(controller, controller.createObject(Kind.BLOCK), Kind.BLOCK, ""),
109                 SUPERBLOCKDIAGRAM_IGNORED_FIELDS, null, null);
110         mxCodecRegistry.register(superBlockDiagramCodec);
111     }
112
113     /**
114      * Encode the fieldname value.
115      *
116      * This method encode the 'scicosParameters' variable to the parent node.
117      *
118      * @param enc
119      *            Codec that controls the encoding process.
120      * @param obj
121      *            Object whose field is going to be encoded.
122      * @param fieldname
123      *            Name if the field to be encoded.
124      * @param value
125      *            Value of the property to be encoded.
126      * @param node
127      *            XML node that contains the encoded object.
128      * @see com.mxgraph.io.mxObjectCodec#encodeValue(com.mxgraph.io.mxCodec, java.lang.Object, java.lang.String, java.lang.Object, org.w3c.dom.Node)
129      */
130     @Override
131     protected void encodeValue(mxCodec enc, Object obj, String fieldname, Object value, Node node) {
132         super.encodeValue(enc, obj, fieldname, value, node);
133
134         /*
135          * Put child parameters on the parent when child is 'scicosParameters'.
136          */
137         if (fieldname.equals(SCICOS_PARAMETERS)) {
138             Node params = node.getLastChild();
139
140             /*
141              * Remove the "as" attribute
142              */
143             NamedNodeMap childAttributes = params.getAttributes();
144             childAttributes.removeNamedItem(AS_ATTRIBUTE);
145
146             /*
147              * Move each attribute from child to parent
148              */
149             NamedNodeMap parentAttributes = node.getAttributes();
150             for (int length = childAttributes.getLength() - 1; length >= 0; length--) {
151                 Node element = childAttributes.item(length);
152
153                 childAttributes.removeNamedItem(element.getNodeName());
154                 parentAttributes.setNamedItem(element);
155             }
156
157             /*
158              * Move each childNode from child to parent
159              */
160             NodeList children = params.getChildNodes();
161             for (int length = children.getLength() - 1; length >= 0; length--) {
162                 Node element = children.item(length);
163
164                 params.removeChild(element);
165                 node.appendChild(element);
166             }
167
168             /*
169              * Remove the ScicosParameter instance
170              */
171             node.removeChild(params);
172         }
173     }
174
175     /**
176      * Load the ScicosParameters fields from the current object
177      *
178      * @param obj
179      *            the {@link XcosDiagram} instance
180      * @param fieldname
181      *            the {@link Current} field name
182      * @param value
183      *            the current field value
184      * @see com.mxgraph.io.mxObjectCodec#setFieldValue(java.lang.Object, java.lang.String, java.lang.Object)
185      */
186     @Override
187     protected void setFieldValue(Object obj, String fieldname, Object value) {
188         Field field;
189         try {
190             field = ScicosParameters.class.getDeclaredField(fieldname);
191             XcosDiagram d = (XcosDiagram) obj;
192             ScicosParameters params = new ScicosParameters(Xcos.findRoot(d), new ScicosObjectOwner(d.getUID(), d.getKind()));
193             super.setFieldValue(params, fieldname, value);
194         } catch (SecurityException e) {
195             field = null;
196         } catch (NoSuchFieldException e) {
197             field = null;
198         }
199
200         // pre 6.0.0 Xcos have an mxCell defaultParent whereas it should be an XcosCell
201         if (field == null && "defaultParent".equals(fieldname) && !(value instanceof XcosCell)) {
202             XcosDiagram diag = (XcosDiagram) obj;
203             XcosCell defaultParent = (XcosCell) diag.getDefaultParent();
204             mxICell root = (mxICell) diag.getModel().getRoot();
205
206             /*
207              * Restore the initial hierarchy mxCell -> root XcosCell -> default parent with diagram uid / kind BasicBlock BasicLink . . .
208              */
209
210             Object[] cells = diag.getChildCells(value);
211             root.remove(0);
212             diag.addCells(cells, defaultParent);
213
214             return;
215         }
216
217         if (field == null) {
218             super.setFieldValue(obj, fieldname, value);
219         }
220     }
221
222     /**
223      * {@inheritDoc} <BR>
224      * <BR>
225      * <B>UPDATED TO COVER</B>
226      *
227      * <UL>
228      * <LI>Strip out any node with an invalid parent id. (5.3.1 diagrams may contains invalid default parents, remove them.)</LI>
229      * <LI>Remove cell where id end with "#identifier#identifier"</LI>
230      * </UL>
231      */
232     @Override
233     public Node beforeDecode(mxCodec dec, Node node, Object obj) {
234         final Set<String> ids = new HashSet<String>();
235         final Collection<Node> trash = new ArrayList<Node>();
236
237         if (node instanceof Element) {
238             final Node model = ((Element) node).getElementsByTagName("mxGraphModel").item(0);
239             if (model instanceof Element) {
240                 final Node root = ((Element) model).getElementsByTagName("root").item(0);
241                 if (root != null) {
242                     for (Node cell = root.getFirstChild(); cell != null; cell = cell.getNextSibling()) {
243
244                         if (cell instanceof Element && cell.getLocalName().contentEquals("mxCell")) {
245                             cleanUpNode(ids, trash, cell);
246                         }
247                     }
248
249                     for (Node cell : trash) {
250                         root.removeChild(cell);
251                     }
252                 }
253             }
254         }
255
256         return super.beforeDecode(dec, node, obj);
257     }
258
259     /**
260      * Add the cell to the ids or trash set
261      *
262      * @param ids
263      *            the valid id set
264      * @param trash
265      *            the invalid id list
266      * @param cell
267      *            the cell to clean or not
268      */
269     private void cleanUpNode(final Set<String> ids, final Collection<Node> trash, Node cell) {
270         final Node id = cell.getAttributes().getNamedItem("id");
271         final String idValue = id.getNodeValue();
272         final Node parent = cell.getAttributes().getNamedItem("parent");
273
274         if (id instanceof Element) {
275             ids.add(idValue);
276         }
277         if (parent instanceof Element && !ids.contains(parent.getNodeValue())) {
278             trash.add(parent);
279         }
280
281         // remove dual identifier cells
282         if (idValue.endsWith(XcosDiagram.HASH_IDENTIFIER + XcosDiagram.HASH_IDENTIFIER)) {
283             trash.add(cell);
284         }
285     }
286
287     /**
288      * Put a comment with versions.
289      *
290      * @param enc
291      *            the encoder
292      * @param obj
293      *            the object to encode
294      * @param node
295      *            the cureent node
296      * @return the updated object
297      * @see org.scilab.modules.graph.io.ScilabGraphCodec#beforeEncode(com.mxgraph.io.mxCodec, java.lang.Object, org.w3c.dom.Node)
298      */
299     @Override
300     public Object beforeEncode(mxCodec enc, Object obj, Node node) {
301         final Package p = Package.getPackage("org.scilab.modules.xcos");
302
303         trace(enc, node, new StringBuilder().append(Xcos.TRADENAME).append(SEP).append(Xcos.VERSION).append(SEP).append(p.getSpecificationVersion()).append(SEP)
304               .append(p.getImplementationVersion()).toString());
305
306         return super.beforeEncode(enc, obj, node);
307     }
308
309     /**
310      * Apply compatibility pattern to the decoded object
311      *
312      * @param dec
313      *            Codec that controls the decoding process.
314      * @param node
315      *            XML node to decode the object from.
316      * @param obj
317      *            Object decoded.
318      * @return The Object transformed
319      * @see org.scilab.modules.graph.io.ScilabGraphCodec#afterDecode(com.mxgraph.io.mxCodec, org.w3c.dom.Node, java.lang.Object)
320      */
321     @Override
322     public Object afterDecode(mxCodec dec, Node node, Object obj) {
323         final XcosDiagram diag = (XcosDiagram) obj;
324
325         final mxGraphModel model = (mxGraphModel) diag.getModel();
326         final Object parent = diag.getDefaultParent();
327
328         // pre-5.3 diagram may be saved in a locked state
329         // unlock it
330         diag.setReadOnly(false);
331
332         // 5.3.1 diagrams may contains invalid default parents, remove them.
333         final Object root = model.getParent(parent);
334         if (root != model.getRoot() && root != null) {
335             Logger.getLogger(XcosDiagramCodec.class.getName()).warning("Removing misplaced cells");
336             model.setRoot(root);
337         }
338
339         return super.afterDecode(dec, node, obj);
340     }
341
342     /**
343      * Pop up an update dialog to alert the user.
344      */
345     public void showUpdateDialog() {
346         ScilabModalDialog.show(null, new String[] { SOME_BLOCKS_HAVE_BEEN_REMOVED, "", PLEASE_CHECK_THE_DIAGRAM }, INCOMPATIBILITY_DETECTED,
347                                IconType.WARNING_ICON);
348     }
349 }
350 // CSON: ClassFanOutComplexity