Fix typos in messages and comments.
[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  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
10  *
11  */
12
13 package org.scilab.modules.xcos.io.scicos;
14
15 import static java.util.Arrays.asList;
16
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.Collections;
20 import java.util.Comparator;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.logging.Level;
25 import java.util.logging.Logger;
26
27 import org.scilab.modules.graph.ScilabGraphUniqueObject;
28 import org.scilab.modules.types.ScilabBoolean;
29 import org.scilab.modules.types.ScilabDouble;
30 import org.scilab.modules.types.ScilabList;
31 import org.scilab.modules.types.ScilabMList;
32 import org.scilab.modules.types.ScilabString;
33 import org.scilab.modules.types.ScilabTList;
34 import org.scilab.modules.types.ScilabType;
35 import org.scilab.modules.xcos.block.BasicBlock;
36 import org.scilab.modules.xcos.block.SuperBlock;
37 import org.scilab.modules.xcos.block.TextBlock;
38 import org.scilab.modules.xcos.block.io.ContextUpdate.IOBlocks;
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 import org.scilab.modules.xcos.utils.FileUtils;
47 import org.scilab.modules.xcos.utils.XcosMessages;
48
49 import com.mxgraph.model.mxCell;
50 import com.mxgraph.model.mxGeometry;
51 import com.mxgraph.model.mxGraphModel;
52 import com.mxgraph.model.mxGraphModel.Filter;
53 import com.mxgraph.model.mxICell;
54 import com.mxgraph.model.mxIGraphModel;
55 import com.mxgraph.util.mxPoint;
56
57 /**
58  * Perform a diagram transformation between Scicos and Xcos.
59  */
60 // CSOFF: ClassDataAbstractionCoupling
61 // CSOFF: ClassFanOutComplexity
62 public final class DiagramElement extends AbstractElement<XcosDiagram> {
63     protected static final List<String> DATA_FIELD_NAMES = asList("diagram", "props", "objs");
64     protected static final List<String> DATA_FIELD_NAMES_FULL = asList("diagram", "props", "objs", "version", "contrib");
65     private static final List<String> VERSIONS = asList("", "scicos4.2", "scicos4.3", "scicos4.4");
66
67     private static final int OBJS_INDEX = DATA_FIELD_NAMES_FULL.indexOf("objs");
68     private static final int VERSION_INDEX = DATA_FIELD_NAMES_FULL.indexOf("version");
69
70     private static final double H_MARGIN = 40.0;
71     private static final double V_MARGIN = 40.0;
72
73     /** Diagram properties MList header (scs_m.props) */
74     private static final String[] PROPS_FIELDS = { "params", "wpar", "title", "tol", "tf", "context", "void1", "options", "void2", "void3", "doc" };
75
76     /** Index of the title in the props field */
77     private static final int TITLE_INDEX = 2;
78
79     /** Diagram options MList header (scs_m.props.options) */
80     private static final String[] OPTS_FIELDS = { "scsopt", "3D", "Background", "Link", "ID", "Cmap" };
81     /**
82      * Window properties (scs_m.props.wpar).
83      *
84      * This property has no impact among simulation
85      */
86     private static final double[][] WPAR = { { 600, 450, 0, 0, 600, 450 } };
87
88     // The window parameters and diagram options are not used in the simulation
89     // thus we set it to default values.
90     // As the values are scicos dependent we avoid using constant references.
91     // CSOFF: MagicNumber
92     private static final ScilabTList DIAGRAM_OPTIONS = new ScilabTList(OPTS_FIELDS, Arrays.asList(new ScilabList(// 3D
93     Arrays.asList(new ScilabBoolean(true), new ScilabDouble(33))), new ScilabDouble(new double[][] { { 8, 1 } }), // Background
94     new ScilabDouble(new double[][] { { 1, 5 } }), // Link
95             new ScilabList(// ID
96     Arrays.asList(new ScilabDouble(new double[][] { { 5, 1 } }), new ScilabDouble(new double[][] { { 4, 1 } }))), new ScilabDouble(
97     new double[][] { { 0.8, 0.8, 0.8 } }) // Cmap
98                                                                                                  ));
99     // CSON: MagicNumber
100
101     private ScilabMList base;
102
103     private double minimalYaxisValue = Double.POSITIVE_INFINITY;
104     private double minimalXaxisValue = Double.POSITIVE_INFINITY;
105
106     /**
107      * Default constructor
108      */
109     public DiagramElement() {
110     }
111
112     /**
113      * Decode the diagram with version validation.
114      *
115      * @param element
116      *            the diagram Scicos element
117      * @param into
118      *            the Xcos instance, if null, a new instance is returned.
119      * @return the modified into parameters
120      * @throws ScicosFormatException
121      *             when a decoding error occurs
122      * @see org.scilab.modules.xcos.io.scicos.Element#decode(org.scilab.modules.types.ScilabType,
123      *      java.lang.Object)
124      */
125     @Override
126     public XcosDiagram decode(ScilabType element, XcosDiagram into) throws ScicosFormatException {
127         return decode(element, into, true);
128     }
129
130     /**
131      * Decode the diagram
132      *
133      * @param element
134      *            the diagram Scicos element
135      * @param into
136      *            the Xcos instance, if null, a new instance is returned.
137      * @param validate
138      *            true, if the diagram version will be checked. false otherwise.
139      * @return the modified into parameters
140      * @throws ScicosFormatException
141      *             when a decoding error occurs
142      * @see org.scilab.modules.xcos.io.scicos.Element#decode(org.scilab.modules.types.ScilabType,
143      *      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             diag = new XcosDiagram();
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);
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      * Reassociate the children with the current diagram.
180      *
181      * @param element
182      *            the encoded element
183      * @param into
184      *            the target instance
185      * @return the modified target instance
186      * @see org.scilab.modules.xcos.io.scicos.AbstractElement#afterDecode(org.scilab.modules.types.ScilabType,
187      *      java.lang.Object)
188      */
189     @Override
190     public XcosDiagram afterDecode(ScilabType element, XcosDiagram into) {
191         into.setChildrenParentDiagram();
192
193         return super.afterDecode(element, into);
194     }
195
196     /**
197      * Decode the diagram
198      *
199      * @param diag
200      *            the current diagram
201      * @throws ScicosFormatException
202      *             on decoding error
203      */
204     private void decodeDiagram(XcosDiagram diag) throws ScicosFormatException {
205         // Fill the local parameters
206         // NOTE: the title field is checked on the ScicosParametersElement
207         final String title = ((ScilabString) ((ScilabTList) base.get(1)).get(2)).getData()[0][0];
208         diag.setTitle(title);
209
210         // Fill the diagram attributes
211         ScicosParametersElement params = new ScicosParametersElement();
212         params.decode(base.get(1), diag.getScicosParameters());
213
214         // Decode the objs attributes
215         decodeObjs(diag);
216         // Update the objs properties if applicable
217         updateObjs(diag);
218     }
219
220     /**
221      * Decode the objs list into cells
222      *
223      * @param diag
224      *            the target instance
225      * @throws ScicosFormatException
226      *             on error
227      */
228     private void decodeObjs(final XcosDiagram diag) throws ScicosFormatException {
229         final int nbOfObjs = ((ScilabList) base.get(OBJS_INDEX)).size();
230         final HashMap<Integer, BasicBlock> blocks = new HashMap<Integer, BasicBlock>(nbOfObjs, 1.0f);
231
232         final BlockElement blockElement = new BlockElement(diag);
233         final LinkElement linkElement = new LinkElement(blocks);
234         final LabelElement labelElement = new LabelElement();
235
236         /*
237          * Decode blocks
238          */
239         for (int i = 0; i < nbOfObjs; i++) {
240             final ScilabMList data = (ScilabMList) ((ScilabList) base.get(OBJS_INDEX)).get(i);
241             Object cell = null;
242
243             if (blockElement.canDecode(data)) {
244                 BasicBlock block = blockElement.decode(data, null);
245                 blocks.put(i, block);
246                 cell = block;
247
248                 BlockPositioning.updateBlockView(block);
249
250                 minimalYaxisValue = Math.min(minimalYaxisValue, ((mxCell) cell).getGeometry().getY());
251                 minimalXaxisValue = Math.min(minimalXaxisValue, ((mxCell) cell).getGeometry().getX());
252             } else if (labelElement.canDecode(data)) {
253                 cell = labelElement.decode(data, null);
254
255                 minimalYaxisValue = Math.min(minimalYaxisValue, ((mxCell) cell).getGeometry().getY());
256                 minimalXaxisValue = Math.min(minimalXaxisValue, ((mxCell) cell).getGeometry().getX());
257             }
258
259             if (cell != null) {
260                 diag.addCell(cell);
261             }
262         }
263
264         /*
265          * Decode links
266          */
267         for (int i = 0; i < nbOfObjs; i++) {
268             final ScilabMList data = (ScilabMList) ((ScilabList) base.get(OBJS_INDEX)).get(i);
269             Object cell = null;
270
271             if (linkElement.canDecode(data)) {
272                 BasicLink link = linkElement.decode(data, null);
273                 cell = link;
274
275                 final List<mxPoint> points = ((mxCell) cell).getGeometry().getPoints();
276                 for (final mxPoint p : points) {
277                     minimalYaxisValue = Math.min(minimalYaxisValue, p.getY());
278                     minimalXaxisValue = Math.min(minimalXaxisValue, p.getX());
279                 }
280             }
281
282             if (cell != null) {
283                 diag.addCell(cell);
284             }
285         }
286     }
287
288     /**
289      * Update the diagram object after decode
290      *
291      * @param diag
292      *            the diagram to update
293      */
294     private void updateObjs(final XcosDiagram diag) {
295         final mxGraphModel model = (mxGraphModel) diag.getModel();
296
297         final double minY = -minimalYaxisValue + V_MARGIN;
298         final double minX = -minimalXaxisValue + H_MARGIN;
299         for (final Object cell : model.getCells().values()) {
300             updateMinimalSize(cell, model);
301             translate(cell, model, minX, minY);
302             updateLabels(cell, model);
303         }
304     }
305
306     // update the cell size to be at least selectable
307     private static final void updateMinimalSize(final Object cell, final mxIGraphModel model) {
308         if (!(cell instanceof BasicBlock)) {
309             return;
310         }
311
312         final double min = 7.0;
313
314         final mxGeometry geom = model.getGeometry(cell);
315         if (geom == null) {
316             return;
317         }
318
319         final double dx;
320         if (geom.getWidth() < min) {
321             dx = (geom.getWidth() - min) / 2;
322             geom.setWidth(min);
323         } else {
324             dx = 0.0;
325         }
326         final double dy;
327         if (geom.getHeight() < min) {
328             dy = (geom.getHeight() - min) / 2;
329             geom.setHeight(min);
330         } else {
331             dy = 0.0;
332         }
333
334         geom.translate(dx, dy);
335     }
336
337     // Translate the y axis for blocks and links
338     private static final void translate(final Object cell, final mxIGraphModel model, final double minX, final double minY) {
339         if (cell instanceof BasicPort) {
340             return;
341         }
342
343         final mxGeometry geom = model.getGeometry(cell);
344         if (geom != null) {
345             geom.translate(minX, minY);
346         }
347     }
348
349     // update the labels of ports for SuperBlock
350     private static final void updateLabels(final Object cell, final mxIGraphModel model) {
351         if (!(cell instanceof SuperBlock)) {
352             return;
353         }
354         final SuperBlock parent = (SuperBlock) cell;
355
356         // Assume that the children are sorted after decode
357         // blk.sortChildren();
358         final Map<IOBlocks, List<mxICell>> ports = IOBlocks.getAllPorts(parent);
359         final Map<IOBlocks, List<mxICell>> blocks = IOBlocks.getAllBlocks(parent);
360
361         for (final IOBlocks io : IOBlocks.values()) {
362             final List<mxICell> port = ports.get(io);
363             final List<mxICell> block = blocks.get(io);
364
365             final int len = Math.min(port.size(), block.size());
366             for (int i = 0; i < len; i++) {
367                 final mxICell p = port.get(i);
368                 final mxICell b = block.get(i);
369
370                 // if the I/O block has a port child and a label child,
371                 // update
372                 if (b.getChildCount() > 1) {
373                     final Object value = b.getChildAt(b.getChildCount() - 1).getValue();
374                     p.setValue(value);
375                 }
376             }
377         }
378     }
379
380     /**
381      * Check that the current ScilabType is a valid Diagram.
382      *
383      * This method doesn't pass the metrics because it perform many test.
384      * Therefore all these tests are trivial and the conditioned action only
385      * throw an exception.
386      *
387      * @param checkVersion
388      *            true, when the check validate the version
389      * @throws ScicosFormatException
390      *             When the diagram is not valid
391      */
392     // CSOFF: CyclomaticComplexity
393     // CSOFF: NPathComplexity
394     private void validate(boolean checkVersion) throws ScicosFormatException {
395
396         // Have we enough fields ?
397         if (base.size() < DATA_FIELD_NAMES.size()) {
398             throw new WrongStructureException(DATA_FIELD_NAMES);
399         }
400
401         int field = 0;
402
403         /*
404          * Checking the MList header
405          */
406
407         // Check the first field
408         if (!(base.get(field) instanceof ScilabString)) {
409             throw new WrongTypeException(DATA_FIELD_NAMES, field);
410         }
411         String[] header = ((ScilabString) base.get(field)).getData()[0];
412
413         // Check the number of fields
414         if (header.length < DATA_FIELD_NAMES.size()) {
415             throw new WrongStructureException(DATA_FIELD_NAMES);
416         }
417
418         // Check the first fields values
419         for (int i = 0; i < DATA_FIELD_NAMES.size(); i++) {
420             if (!header[i].equals(DATA_FIELD_NAMES.get(i))) {
421                 throw new WrongStructureException(DATA_FIELD_NAMES);
422             }
423         }
424
425         /*
426          * Checking the data types
427          */
428
429         // the second field must contain list of props
430         field++;
431         if (!(base.get(field) instanceof ScilabTList)) {
432             throw new WrongTypeException(DATA_FIELD_NAMES, field);
433         }
434
435         // the third field must contains lists of blocks and links
436         field++;
437         if (!(base.get(field) instanceof ScilabList)) {
438             throw new WrongTypeException(DATA_FIELD_NAMES, field);
439         }
440
441         // the last field must contain the scicos version used
442         field++;
443
444         // doesn't check version if not present (optional field)
445         if (base.size() <= field) {
446             return;
447         }
448
449         if (!(base.get(field) instanceof ScilabString)) {
450             throw new WrongTypeException(DATA_FIELD_NAMES, field);
451         }
452
453         /*
454          * Check the version if applicable
455          */
456         if (checkVersion) {
457             String scicosVersion = ((ScilabString) base.get(field)).getData()[0][0];
458             if (!VERSIONS.contains(scicosVersion)) {
459                 throw new VersionMismatchException(scicosVersion);
460             }
461         }
462     }
463
464     // CSON: CyclomaticComplexity
465     // CSON: NPathComplexity
466
467     /**
468      * @param element
469      *            the base element
470      * @return true if the header is valid, false otherwise
471      * @see org.scilab.modules.xcos.io.scicos.Element#canDecode(org.scilab.modules.types.ScilabType)
472      */
473     @Override
474     public boolean canDecode(ScilabType element) {
475         if (!(element instanceof ScilabMList)) {
476             return false;
477         }
478
479         base = (ScilabMList) element;
480
481         /*
482          * Checking header
483          */
484         final String type = ((ScilabString) base.get(0)).getData()[0][0];
485         final boolean typeIsValid = type.equals(DATA_FIELD_NAMES.get(0));
486
487         /*
488          * Check the version if applicable
489          */
490         final String scicosVersion;
491         if (base.size() > VERSION_INDEX) {
492             scicosVersion = ((ScilabString) base.get(VERSION_INDEX)).getData()[0][0];
493         } else {
494             scicosVersion = "";
495         }
496         final boolean versionIsValid = VERSIONS.contains(scicosVersion);
497         return typeIsValid && versionIsValid;
498     }
499
500     /**
501      * {@inheritDoc}
502      *
503      * Clear cell warnings before encoding
504      */
505     @Override
506     public ScilabType beforeEncode(XcosDiagram from, ScilabType element) {
507         if (from.getAsComponent() != null) {
508             from.getAsComponent().clearCellOverlays();
509         }
510         return super.beforeEncode(from, element);
511     }
512
513     /**
514      * Encode the instance into the element
515      *
516      * @param from
517      *            the source instance
518      * @param element
519      *            the previously allocated element.
520      * @return the element parameter
521      * @see org.scilab.modules.xcos.io.scicos.Element#encode(java.lang.Object,
522      *      org.scilab.modules.types.ScilabType)
523      */
524     @Override
525     public ScilabType encode(XcosDiagram from, ScilabType element) {
526         base = (ScilabMList) element;
527
528         int field = 0;
529
530         if (base == null) {
531             base = allocateElement();
532         }
533
534         base = (ScilabMList) beforeEncode(from, base);
535
536         field++;
537         fillParams(from, field);
538
539         field++;
540         fillObjs(from, field);
541
542         base = (ScilabMList) afterEncode(from, base);
543
544         return base;
545     }
546
547     /**
548      * Allocate a new element
549      *
550      * @return the new element
551      */
552     private ScilabMList allocateElement() {
553         ScilabMList data = new ScilabMList(DATA_FIELD_NAMES_FULL.toArray(new String[0]));
554         data.add(allocatePropsField()); // props
555         data.add(new ScilabList()); // objs
556         data.add(new ScilabString(VERSIONS.get(0))); // official version
557         data.add(new ScilabList()); // contrib
558         return data;
559     }
560
561     /**
562      * Allocate the props field
563      *
564      * @return the new props field
565      */
566     private ScilabTList allocatePropsField() {
567         ScilabTList data = new ScilabTList(PROPS_FIELDS);
568         data.add(new ScilabDouble(WPAR)); // wpar
569         data.add(new ScilabString("")); // title
570         data.add(new ScilabDouble()); // tol
571         data.add(new ScilabDouble()); // tf
572         data.add(new ScilabDouble()); // context
573         data.add(new ScilabDouble()); // void1
574         data.add(DIAGRAM_OPTIONS); // options
575         data.add(new ScilabDouble()); // void2
576         data.add(new ScilabDouble()); // void3
577         data.add(new ScilabList()); // doc
578
579         return data;
580     }
581
582     /**
583      * Fill the params field
584      *
585      * @param from
586      *            the source instance
587      * @param field
588      *            the params field number
589      */
590     private void fillParams(XcosDiagram from, int field) {
591         ScilabType data;
592         final ScicosParametersElement paramsElement = new ScicosParametersElement();
593         data = base.get(field);
594         data = paramsElement.encode(from.getScicosParameters(), data);
595
596         // set the title as it is need for generating files
597         ((ScilabTList) data).set(TITLE_INDEX, new ScilabString(FileUtils.toValidCIdentifier(from.getTitle())));
598
599         base.set(field, data);
600     }
601
602     /**
603      * Fill the objs field
604      *
605      * @param from
606      *            the source instance
607      * @param field
608      *            the objs field number
609      */
610     private void fillObjs(XcosDiagram from, int field) {
611         final BlockElement blockElement = new BlockElement(from);
612         final LinkElement linkElement = new LinkElement(null);
613         final ScilabList data = (ScilabList) base.get(field);
614
615         final List<BasicBlock> blockList = new ArrayList<BasicBlock>();
616         final List<BasicLink> linkList = new ArrayList<BasicLink>();
617
618         /*
619          * Fill the block and link lists
620          */
621         final Filter filter = new Filter() {
622             @Override
623             public boolean filter(Object current) {
624                 if (current instanceof BasicBlock && !(current instanceof TextBlock)) {
625                     filterBlocks(blockList, linkList, (BasicBlock) current);
626                 } else if (current instanceof BasicLink) {
627                     filterLink(linkList, (BasicLink) current);
628                 }
629
630                 return false;
631             }
632
633             /**
634              * Filter blocks
635              *
636              * @param blockList
637              *            the current block list
638              * @param linkList
639              *            the current link list
640              * @param block
641              *            the block to filter
642              */
643             private void filterBlocks(final List<BasicBlock> blockList, final List<BasicLink> linkList, final BasicBlock block) {
644                 blockList.add(block);
645
646                 //
647                 // Look inside a Block to see if there is no "AutoLink"
648                 // Jgraphx will store this link as block's child
649                 //
650                 for (int j = 0; j < block.getChildCount(); ++j) {
651                     if (block.getChildAt(j) instanceof BasicLink) {
652                         final BasicLink link = (BasicLink) block.getChildAt(j);
653
654                         // do not add the link if not connected
655                         if (link.getSource() != null && link.getTarget() != null) {
656                             linkList.add(link);
657                         }
658                     }
659                 }
660
661             }
662
663             /**
664              * Filter links
665              *
666              * @param linkList
667              *            the current link list
668              * @param link
669              *            the link to filter
670              */
671             private void filterLink(final List<BasicLink> linkList, final BasicLink link) {
672                 // Only add connected links
673                 final mxICell source = link.getSource();
674                 final mxICell target = link.getTarget();
675                 if (source != null && target != null) {
676                     linkList.add(link);
677                 }
678             }
679         };
680         mxGraphModel.filterDescendants(from.getModel(), filter);
681
682         /*
683          * Use a predictable block and links order when debug is enable
684          */
685         if (Logger.getLogger(BlockElement.class.getName()).isLoggable(Level.FINE)) {
686             Collections.sort(blockList);
687             Collections.sort(linkList, new Comparator<BasicLink>() {
688                 @Override
689                 public int compare(BasicLink o1, BasicLink o2) {
690                     return ((ScilabGraphUniqueObject) o1.getSource()).compareTo((ScilabGraphUniqueObject) o2.getSource());
691                 }
692             });
693         }
694
695         /*
696          * Reorder links
697          */
698         for (int i = 0; i < linkList.size(); ++i) {
699             linkList.get(i).setOrdering(i + blockList.size() + 1);
700         }
701
702         /*
703          * Reorder and encode blocks
704          */
705         for (int i = 0; i < blockList.size(); ++i) {
706             blockList.get(i).setOrdering(i + 1);
707
708             data.add(blockElement.encode(blockList.get(i), null));
709         }
710
711         /*
712          * Encode links
713          */
714         for (int i = 0; i < linkList.size(); ++i) {
715             final ScilabType link = linkElement.encode(linkList.get(i), null);
716             if (link != null) {
717                 data.add(link);
718             } else {
719                 data.add(new ScilabMList(new String[] { "Deleted" }));
720                 from.warnCellByUID(linkList.get(i).getId(), XcosMessages.LINK_NOT_CONNECTED);
721             }
722         }
723     }
724 }
725 // CSON: ClassDataAbstractionCoupling
726 // CSON: ClassFanOutComplexity