* 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 / scicos / DiagramElement.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-2017 - 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.scicos;
18
19 import static java.util.Arrays.asList;
20
21 import java.util.Arrays;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25
26 import org.scilab.modules.types.ScilabBoolean;
27 import org.scilab.modules.types.ScilabDouble;
28 import org.scilab.modules.types.ScilabList;
29 import org.scilab.modules.types.ScilabMList;
30 import org.scilab.modules.types.ScilabString;
31 import org.scilab.modules.types.ScilabTList;
32 import org.scilab.modules.types.ScilabType;
33 import org.scilab.modules.xcos.JavaController;
34 import org.scilab.modules.xcos.block.BasicBlock;
35 import org.scilab.modules.xcos.block.SuperBlock;
36 import org.scilab.modules.xcos.block.io.ContextUpdate.IOBlocks;
37 import org.scilab.modules.xcos.graph.model.ScicosObjectOwner;
38 import org.scilab.modules.xcos.graph.ScicosParameters;
39 import org.scilab.modules.xcos.graph.XcosDiagram;
40 import org.scilab.modules.xcos.io.scicos.ScicosFormatException.VersionMismatchException;
41 import org.scilab.modules.xcos.io.scicos.ScicosFormatException.WrongStructureException;
42 import org.scilab.modules.xcos.io.scicos.ScicosFormatException.WrongTypeException;
43 import org.scilab.modules.xcos.link.BasicLink;
44 import org.scilab.modules.xcos.port.BasicPort;
45 import org.scilab.modules.xcos.utils.BlockPositioning;
46
47 import com.mxgraph.model.mxCell;
48 import com.mxgraph.model.mxGeometry;
49 import com.mxgraph.model.mxGraphModel;
50 import com.mxgraph.model.mxICell;
51 import com.mxgraph.model.mxIGraphModel;
52 import com.mxgraph.util.mxPoint;
53 import org.scilab.modules.xcos.Xcos;
54
55 /**
56  * Perform a diagram transformation between Scicos and Xcos.
57  */
58 // CSOFF: ClassDataAbstractionCoupling
59 // CSOFF: ClassFanOutComplexity
60 public final class DiagramElement extends AbstractElement<XcosDiagram> {
61     protected static final List<String> DATA_FIELD_NAMES = asList("diagram", "props", "objs");
62     protected static final List<String> DATA_FIELD_NAMES_FULL = asList("diagram", "props", "objs", "version", "contrib");
63     private static final List<String> VERSIONS = asList("", "scicos4.2", "scicos4.3", "scicos4.4");
64
65     private static final int OBJS_INDEX = DATA_FIELD_NAMES_FULL.indexOf("objs");
66     private static final int VERSION_INDEX = DATA_FIELD_NAMES_FULL.indexOf("version");
67
68     private static final double H_MARGIN = 40.0;
69     private static final double V_MARGIN = 40.0;
70
71     /** Diagram properties MList header (scs_m.props) */
72     private static final String[] PROPS_FIELDS = { "params", "wpar", "title", "tol", "tf", "context", "void1", "options", "void2", "void3", "doc" };
73
74     /** Index of the title in the props field */
75     private static final int TITLE_INDEX = 2;
76
77     /** Diagram options MList header (scs_m.props.options) */
78     private static final String[] OPTS_FIELDS = { "scsopt", "3D", "Background", "Link", "ID", "Cmap" };
79     /**
80      * Window properties (scs_m.props.wpar).
81      *
82      * This property has no impact among simulation
83      */
84     private static final double[][] WPAR = { { 600, 450, 0, 0, 600, 450 } };
85
86     // The window parameters and diagram options are not used in the simulation
87     // thus we set it to default values.
88     // As the values are scicos dependent we avoid using constant references.
89     // CSOFF: MagicNumber
90     private static final ScilabTList DIAGRAM_OPTIONS = new ScilabTList(OPTS_FIELDS,
91             Arrays.asList(
92                 new ScilabList(// 3D
93                     Arrays.asList(new ScilabBoolean(true), new ScilabDouble(33))),
94     new ScilabDouble(new double[][] { { 8, 1 } }), // Background
95     new ScilabDouble(new double[][] { { 1, 5 } }), // Link
96     new ScilabList(// ID
97     Arrays.asList(new ScilabDouble(new double[][] { { 5, 1 } }), new ScilabDouble(new double[][] { { 4, 1 } }))),
98     new ScilabDouble(new double[][] { { 0.8, 0.8, 0.8 } }) // Cmap
99             ));
100     // CSON: MagicNumber
101
102     private ScilabMList base;
103
104     private double minimalYaxisValue = Double.POSITIVE_INFINITY;
105     private double minimalXaxisValue = Double.POSITIVE_INFINITY;
106
107     /**
108      * Default constructor
109      */
110     public DiagramElement(final JavaController controller) {
111         super(controller);
112     }
113
114     /**
115      * Decode the diagram with version validation.
116      *
117      * @param element
118      *            the diagram Scicos element
119      * @param into
120      *            the Xcos instance, if null, a new instance is returned.
121      * @return the modified into parameters
122      * @throws ScicosFormatException
123      *             when a decoding error occurs
124      * @see org.scilab.modules.xcos.io.scicos.Element#decode(org.scilab.modules.types.ScilabType, java.lang.Object)
125      */
126     @Override
127     public XcosDiagram decode(ScilabType element, XcosDiagram into) throws ScicosFormatException {
128         return decode(element, into, true);
129     }
130
131     /**
132      * Decode the diagram
133      *
134      * @param element
135      *            the diagram Scicos element
136      * @param into
137      *            the Xcos instance, if null, a new instance is returned.
138      * @param validate
139      *            true, if the diagram version will be checked. false otherwise.
140      * @return the modified into parameters
141      * @throws ScicosFormatException
142      *             when a decoding error occurs
143      * @see org.scilab.modules.xcos.io.scicos.Element#decode(org.scilab.modules.types.ScilabType, java.lang.Object)
144      */
145     public XcosDiagram decode(ScilabType element, XcosDiagram into, boolean validate) throws ScicosFormatException {
146         base = (ScilabMList) element;
147
148         XcosDiagram diag = into;
149         if (diag == null) {
150             throw new IllegalArgumentException();
151         }
152
153         diag = beforeDecode(element, diag);
154         diag.getModel().beginUpdate();
155
156         // Validate the base field
157         String wrongVersion = null;
158         try {
159             validate(validate);
160         } catch (VersionMismatchException e) {
161             wrongVersion = e.getWrongVersion();
162         }
163
164         // Fill the diag
165         decodeDiagram(diag, validate);
166
167         diag.getModel().endUpdate();
168         diag = afterDecode(element, diag);
169
170         // Rethrow the version exception after a decode.
171         if (wrongVersion != null) {
172             throw new VersionMismatchException(wrongVersion);
173         }
174
175         return diag;
176     }
177
178     /**
179      * Decode the diagram
180      *
181      * @param diag
182      *            the current diagram
183      * @param validate
184      *            if true, enable graphic updates ; false disable them
185      * @throws ScicosFormatException
186      *             on decoding error
187      */
188     private void decodeDiagram(XcosDiagram diag, boolean validate) throws ScicosFormatException {
189         // Fill the local parameters
190         // NOTE: the title field is checked on the ScicosParametersElement
191         final String title = ((ScilabString) ((ScilabTList) base.get(1)).get(2)).getData()[0][0];
192         diag.setTitle(title);
193
194         // Fill the diagram attributes
195         ScicosParametersElement params = new ScicosParametersElement(controller);
196         params.decode(base.get(1), new ScicosParameters(Xcos.findRoot(diag), new ScicosObjectOwner(diag.getUID(), diag.getKind())));
197
198         // Decode the objs attributes
199         decodeObjs(diag);
200         // Update the objs properties if applicable
201         updateObjs(diag, validate);
202     }
203
204     /**
205      * Decode the objs list into cells
206      *
207      * @param diag
208      *            the target instance
209      * @throws ScicosFormatException
210      *             on error
211      */
212     private void decodeObjs(final XcosDiagram diag) throws ScicosFormatException {
213         final int nbOfObjs = ((ScilabList) base.get(OBJS_INDEX)).size();
214         final HashMap<Integer, BasicBlock> blocks = new HashMap<Integer, BasicBlock>(nbOfObjs, 1.0f);
215
216         final BlockElement blockElement = new BlockElement(controller, diag);
217         final LinkElement linkElement = new LinkElement(controller, blocks);
218         final LabelElement labelElement = new LabelElement(controller);
219
220         /*
221          * Decode blocks
222          */
223         for (int i = 0; i < nbOfObjs; i++) {
224             final ScilabMList data = (ScilabMList) ((ScilabList) base.get(OBJS_INDEX)).get(i);
225             Object cell = null;
226
227             if (blockElement.canDecode(data)) {
228                 BasicBlock block = blockElement.decode(data, null);
229                 blocks.put(i, block);
230                 cell = block;
231
232                 BlockPositioning.updateBlockView(diag, block);
233
234                 minimalYaxisValue = Math.min(minimalYaxisValue, ((mxCell) cell).getGeometry().getY());
235                 minimalXaxisValue = Math.min(minimalXaxisValue, ((mxCell) cell).getGeometry().getX());
236             } else if (labelElement.canDecode(data)) {
237                 cell = labelElement.decode(data, null);
238
239                 minimalYaxisValue = Math.min(minimalYaxisValue, ((mxCell) cell).getGeometry().getY());
240                 minimalXaxisValue = Math.min(minimalXaxisValue, ((mxCell) cell).getGeometry().getX());
241             }
242
243             if (cell != null) {
244                 diag.addCell(cell);
245             }
246         }
247
248         /*
249          * Decode links
250          */
251         for (int i = 0; i < nbOfObjs; i++) {
252             final ScilabMList data = (ScilabMList) ((ScilabList) base.get(OBJS_INDEX)).get(i);
253             Object cell = null;
254
255             if (linkElement.canDecode(data)) {
256                 BasicLink link = linkElement.decode(data, null);
257                 cell = link;
258
259                 final List<mxPoint> points = ((mxCell) cell).getGeometry().getPoints();
260                 for (final mxPoint p : points) {
261                     minimalYaxisValue = Math.min(minimalYaxisValue, p.getY());
262                     minimalXaxisValue = Math.min(minimalXaxisValue, p.getX());
263                 }
264             }
265
266             if (cell != null) {
267                 diag.addCell(cell);
268             }
269         }
270     }
271
272     /**
273      * Update the diagram object after decode
274      *
275      * @param diag
276      *            the diagram to update
277      * @param validate
278      *            perform graphic updates, or not
279      */
280     private void updateObjs(final XcosDiagram diag, boolean validate) {
281         final mxGraphModel model = (mxGraphModel) diag.getModel();
282
283         final double minY = -minimalYaxisValue + V_MARGIN;
284         final double minX = -minimalXaxisValue + H_MARGIN;
285         for (final Object cell : model.getCells().values()) {
286             updateMinimalSize(cell, model);
287             translate(cell, model, minX, minY);
288             if (validate) {
289                 updateLabels(cell, model);
290             }
291         }
292     }
293
294     // update the cell size to be at least selectable
295     private static final void updateMinimalSize(final Object cell, final mxIGraphModel model) {
296         if (!(cell instanceof BasicBlock)) {
297             return;
298         }
299
300         final double min = 7.0;
301
302         final mxGeometry geom = model.getGeometry(cell);
303         if (geom == null) {
304             return;
305         }
306
307         final double dx;
308         if (geom.getWidth() < min) {
309             dx = (geom.getWidth() - min) / 2;
310             geom.setWidth(min);
311         } else {
312             dx = 0.0;
313         }
314         final double dy;
315         if (geom.getHeight() < min) {
316             dy = (geom.getHeight() - min) / 2;
317             geom.setHeight(min);
318         } else {
319             dy = 0.0;
320         }
321
322         geom.translate(dx, dy);
323     }
324
325     // Translate the y axis for blocks and links
326     private static final void translate(final Object cell, final mxIGraphModel model, final double minX, final double minY) {
327         if (cell instanceof BasicPort) {
328             return;
329         }
330
331         final mxGeometry geom = model.getGeometry(cell);
332         if (geom != null) {
333             geom.translate(minX, minY);
334         }
335     }
336
337     // update the labels of ports for SuperBlock
338     private static final void updateLabels(final Object cell, final mxIGraphModel model) {
339         if (!(cell instanceof SuperBlock)) {
340             return;
341         }
342         final SuperBlock parent = (SuperBlock) cell;
343
344         // Assume that the children are sorted after decode
345         final Map<IOBlocks, List<mxICell>> ports = IOBlocks.getAllPorts(parent);
346         // FIXME is it really needed
347         // final Map<IOBlocks, List<BasicBlock>> blocks = IOBlocks.getAllBlocks(parent);
348         //
349         // for (final IOBlocks io : IOBlocks.values()) {
350         // final List<mxICell> port = ports.get(io);
351         // final List<BasicBlock> block = blocks.get(io);
352         //
353         // final int len = Math.min(port.size(), block.size());
354         // for (int i = 0; i < len; i++) {
355         // final mxICell p = port.get(i);
356         // final mxICell b = block.get(i);
357         //
358         // // if the I/O block has a port child and a label child,
359         // // update
360         // if (b.getChildCount() > 1) {
361         // final Object value = b.getChildAt(b.getChildCount() - 1).getValue();
362         // p.setValue(value);
363         // }
364         // }
365         // }
366     }
367
368     /**
369      * Check that the current ScilabType is a valid Diagram.
370      *
371      * This method doesn't pass the metrics because it perform many test. Therefore all these tests are trivial and the conditioned action only throw an exception.
372      *
373      * @param checkVersion
374      *            true, when the check validate the version
375      * @throws ScicosFormatException
376      *             When the diagram is not valid
377      */
378     // CSOFF: CyclomaticComplexity
379     // CSOFF: NPathComplexity
380     private void validate(boolean checkVersion) throws ScicosFormatException {
381
382         // Have we enough fields ?
383         if (base.size() < DATA_FIELD_NAMES.size()) {
384             throw new WrongStructureException(DATA_FIELD_NAMES);
385         }
386
387         int field = 0;
388
389         /*
390          * Checking the MList header
391          */
392
393         // Check the first field
394         if (!(base.get(field) instanceof ScilabString)) {
395             throw new WrongTypeException(DATA_FIELD_NAMES, field);
396         }
397         String[] header = ((ScilabString) base.get(field)).getData()[0];
398
399         // Check the number of fields
400         if (header.length < DATA_FIELD_NAMES.size()) {
401             throw new WrongStructureException(DATA_FIELD_NAMES);
402         }
403
404         // Check the first fields values
405         for (int i = 0; i < DATA_FIELD_NAMES.size(); i++) {
406             if (!header[i].equals(DATA_FIELD_NAMES.get(i))) {
407                 throw new WrongStructureException(DATA_FIELD_NAMES);
408             }
409         }
410
411         /*
412          * Checking the data types
413          */
414
415         // the second field must contain list of props
416         field++;
417         if (!(base.get(field) instanceof ScilabTList)) {
418             throw new WrongTypeException(DATA_FIELD_NAMES, field);
419         }
420
421         // the third field must contains lists of blocks and links
422         field++;
423         if (!(base.get(field) instanceof ScilabList)) {
424             throw new WrongTypeException(DATA_FIELD_NAMES, field);
425         }
426
427         // the last field must contain the scicos version used
428         field++;
429
430         // doesn't check version if not present (optional field)
431         if (base.size() <= field) {
432             return;
433         }
434
435         if (!(base.get(field) instanceof ScilabString)) {
436             throw new WrongTypeException(DATA_FIELD_NAMES, field);
437         }
438
439         /*
440          * Check the version if applicable
441          */
442         if (checkVersion) {
443             String scicosVersion = ((ScilabString) base.get(field)).getData()[0][0];
444             if (!VERSIONS.contains(scicosVersion)) {
445                 throw new VersionMismatchException(scicosVersion);
446             }
447         }
448     }
449
450     // CSON: CyclomaticComplexity
451     // CSON: NPathComplexity
452
453     /**
454      * @param element
455      *            the base element
456      * @return true if the header is valid, false otherwise
457      * @see org.scilab.modules.xcos.io.scicos.Element#canDecode(org.scilab.modules.types.ScilabType)
458      */
459     @Override
460     public boolean canDecode(ScilabType element) {
461         if (!(element instanceof ScilabMList)) {
462             return false;
463         }
464
465         base = (ScilabMList) element;
466
467         /*
468          * Checking header
469          */
470         final String type = ((ScilabString) base.get(0)).getData()[0][0];
471         final boolean typeIsValid = type.equals(DATA_FIELD_NAMES.get(0));
472
473         /*
474          * Check the version if applicable
475          */
476         final String scicosVersion;
477         if (base.size() > VERSION_INDEX) {
478             scicosVersion = ((ScilabString) base.get(VERSION_INDEX)).getData()[0][0];
479         } else {
480             scicosVersion = "";
481         }
482         final boolean versionIsValid = VERSIONS.contains(scicosVersion);
483         return typeIsValid && versionIsValid;
484     }
485 }
486 // CSON: ClassDataAbstractionCoupling
487 // CSON: ClassFanOutComplexity