Xcos decoding: manage mixed xcos files
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / io / scicos / BlockModelElement.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.1-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.List;
18
19 import org.flexdock.util.UUID;
20 import org.scilab.modules.types.ScilabBoolean;
21 import org.scilab.modules.types.ScilabDouble;
22 import org.scilab.modules.types.ScilabList;
23 import org.scilab.modules.types.ScilabMList;
24 import org.scilab.modules.types.ScilabString;
25 import org.scilab.modules.types.ScilabTList;
26 import org.scilab.modules.types.ScilabType;
27 import org.scilab.modules.xcos.JavaController;
28 import org.scilab.modules.xcos.Kind;
29 import org.scilab.modules.xcos.ObjectProperties;
30 import org.scilab.modules.xcos.VectorOfDouble;
31 import org.scilab.modules.xcos.VectorOfInt;
32 import org.scilab.modules.xcos.block.BasicBlock;
33 import org.scilab.modules.xcos.graph.XcosDiagram;
34 import org.scilab.modules.xcos.io.ScilabTypeCoder;
35 import org.scilab.modules.xcos.io.scicos.ScicosFormatException.WrongElementException;
36 import org.scilab.modules.xcos.io.scicos.ScicosFormatException.WrongStructureException;
37 import org.scilab.modules.xcos.io.scicos.ScicosFormatException.WrongTypeException;
38 import org.scilab.modules.xcos.port.BasicPort;
39 import org.scilab.modules.xcos.port.command.CommandPort;
40 import org.scilab.modules.xcos.port.control.ControlPort;
41
42 /**
43  * Protected class which decode model fields of a block.
44  *
45  * This class is intentionally package-protected to prevent external use.
46  */
47 // CSOFF: ClassDataAbstractionCoupling
48 final class BlockModelElement extends BlockPartsElement {
49     /*
50      * "uid" have been added on the 5.5.0 cycle. It is not checked to be compatible with older versions.
51      */
52     protected static final List<String> DATA_FIELD_NAMES = asList("model", "sim", "in", "in2", "intyp", "out", "out2", "outtyp", "evtin", "evtout", "state",
53             "dstate", "odstate", "rpar", "ipar", "opar", "blocktype", "firing", "dep_ut", "label", "nzcross", "nmode", "equations");
54     protected static final List<String> DATA_FIELD_NAMES_FULL = asList("model", "sim", "in", "in2", "intyp", "out", "out2", "outtyp", "evtin", "evtout",
55             "state", "dstate", "odstate", "rpar", "ipar", "opar", "blocktype", "firing", "dep_ut", "label", "nzcross", "nmode", "equations", "uid");
56
57     private static final int CTRL_PORT_INDEX = DATA_FIELD_NAMES.indexOf("evtin");
58     private static final int CMD_PORT_INDEX = DATA_FIELD_NAMES.indexOf("evtout");
59     private static final int STATE_INDEX = DATA_FIELD_NAMES.indexOf("state");
60     private static final int FIRING_INDEX = DATA_FIELD_NAMES.indexOf("firing");
61     private static final int DEPENDU_INDEX = DATA_FIELD_NAMES.indexOf("dep_ut");
62
63     /** Mutable field to easily get the data through methods */
64     private ScilabMList data;
65
66     /** In-progress decoded diagram */
67     private final XcosDiagram diag;
68
69     /**
70      * Default constructor
71      */
72     public BlockModelElement(final JavaController controller, final XcosDiagram diag) {
73         super(controller);
74
75         this.diag = diag;
76     }
77
78     /**
79      * Decode Scicos element into the block.
80      *
81      * This decode method doesn't coverage Port management because we need graphics information to handle it.
82      *
83      * @param element
84      *            the scicos element
85      * @param into
86      *            the previously instantiated block.
87      * @return the modified into block.
88      * @throws ScicosFormatException
89      *             on error.
90      * @see org.scilab.modules.xcos.io.scicos.Element#decode(org.scilab.modules.types.ScilabType, java.lang.Object)
91      */
92     @Override
93     public BasicBlock decode(ScilabType element, BasicBlock into) throws ScicosFormatException {
94
95         if (into == null) {
96             throw new IllegalArgumentException();
97         }
98
99         data = (ScilabMList) element;
100         BasicBlock local = into;
101
102         validate();
103
104         local = beforeDecode(element, local);
105
106         /*
107          * fill the data
108          */
109         fillSimulationFunction(local);
110         fillControlCommandPorts(local);
111         fillFirstRawParameters(local);
112         fillFiringParameters(local);
113         fillSecondRawParameters(local);
114
115         local = afterDecode(element, local);
116
117         return local;
118     }
119
120     /**
121      * Fill the simulation data into the block
122      *
123      * @param into
124      *            the target instance
125      */
126     private void fillSimulationFunction(BasicBlock into) {
127         String[] functionName = new String[1];
128         controller.getObjectProperty(into.getUID(), into.getKind(), ObjectProperties.SIM_FUNCTION_NAME, functionName);
129
130         int[] functionType = new int[1];
131         controller.getObjectProperty(into.getUID(), into.getKind(), ObjectProperties.SIM_FUNCTION_API, functionType);
132
133         if (data.get(1) instanceof ScilabString) {
134             functionName[0] = ((ScilabString) data.get(1)).getData()[0][0];
135         } else if ((data.get(1) instanceof ScilabList)) {
136             functionName[0] = ((ScilabString) ((ScilabList) data.get(1)).get(0)).getData()[0][0];
137             functionType[0] = (int) ((ScilabDouble) ((ScilabList) data.get(1)).get(1)).getRealPart()[0][0];
138         }
139
140         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.SIM_FUNCTION_NAME, functionName[0]);
141         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.SIM_FUNCTION_API, functionType[0]);
142     }
143
144     /**
145      * Fill the block with the control and command ports
146      *
147      * @param into
148      *            the target instance
149      */
150     private void fillControlCommandPorts(BasicBlock into) {
151         ScilabDouble dataNbControlPort = (ScilabDouble) data.get(CTRL_PORT_INDEX);
152         ScilabDouble dataNbCommandPort = (ScilabDouble) data.get(CMD_PORT_INDEX);
153
154         if (dataNbControlPort.getRealPart() != null) {
155             final int baseIndex = into.getChildCount();
156
157             int nbControlPort = dataNbControlPort.getHeight();
158             for (int i = 0; i < nbControlPort; i++) {
159                 final BasicPort port = new ControlPort(controller.createObject(Kind.PORT));
160                 port.setId(UUID.randomUUID().toString());
161
162                 // do not use BasicPort#addPort() to avoid the view update
163                 into.insert(port, baseIndex + i);
164             }
165         }
166
167         if (dataNbCommandPort.getRealPart() != null) {
168             final int baseIndex = into.getChildCount();
169
170             int nbCommandPort = dataNbCommandPort.getHeight();
171             for (int i = 0; i < nbCommandPort; i++) {
172                 final BasicPort port = new CommandPort(controller.createObject(Kind.PORT));
173                 port.setId(UUID.randomUUID().toString());
174
175                 // do not use BasicPort#addPort() to avoid the view update
176                 into.insert(port, baseIndex + i);
177             }
178         }
179     }
180
181     /**
182      * Fill the block with the first raw parameters
183      *
184      * @param into
185      *            the target instance
186      */
187     private void fillFirstRawParameters(BasicBlock into) {
188         // state
189         int field = STATE_INDEX;
190         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.STATE, toVectorOfDouble((ScilabDouble) data.get(field)));
191
192         // dstate
193         field++;
194         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.DSTATE, toVectorOfDouble((ScilabDouble) data.get(field)));
195
196         // odstate
197         field++;
198         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.ODSTATE, new ScilabTypeCoder().var2vec(data.get(field)));
199
200         // rpar
201         field++;
202         if (data.get(field) instanceof ScilabMList) {
203             try {
204                 new DiagramElement(new JavaController()).decode((ScilabMList) data.get(field), new XcosDiagram(into.getUID(), into.getKind()));
205             } catch (ScicosFormatException e) {}
206         } else if (data.get(field) instanceof ScilabDouble ) {
207             controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.RPAR, toVectorOfDouble((ScilabDouble) data.get(field)));
208         }
209
210         // ipar
211         field++;
212         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.IPAR, toVectorOfInt((ScilabDouble) data.get(field)));
213
214         // opar
215         field++;
216         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.OPAR, new ScilabTypeCoder().var2vec(data.get(field)));
217
218         // blocktype
219         field++;
220         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.SIM_BLOCKTYPE, ((ScilabString) data.get(field)).getData()[0][0]);
221     }
222
223     /**
224      * Fill the block with the firing parameters
225      *
226      * @param into
227      *            the target instance
228      */
229     private void fillFiringParameters(BasicBlock into) {
230         /*
231          * A boolean can be used to indicate that no initial event will be emitted.
232          */
233         if (data.get(FIRING_INDEX) instanceof ScilabBoolean) {
234             return;
235         }
236
237         /*
238          * Normal behavior
239          */
240         final ScilabDouble firing = (ScilabDouble) data.get(FIRING_INDEX);
241
242         if (!isEmptyField(firing)) {
243             final List<CommandPort> allCommandPorts = BasicBlockInfo.getAllTypedPorts(into, false, CommandPort.class);
244
245             final boolean isColumnDominant = firing.getHeight() >= firing.getWidth();
246             final double[][] values = firing.getRealPart();
247             final int[] indexes = { 0, 0 };
248
249             for (int i = 0; i < allCommandPorts.size(); i++) {
250                 CommandPort port = allCommandPorts.get(i);
251                 controller.setObjectProperty(port.getUID(), port.getKind(), ObjectProperties.FIRING, values[indexes[0]][indexes[1]]);
252
253                 incrementIndexes(indexes, isColumnDominant);
254             }
255         }
256     }
257
258     private VectorOfDouble toVectorOfDouble(ScilabDouble value) {
259         VectorOfDouble ret = new VectorOfDouble(value.getHeight());
260         for (int i = 0; i < value.getWidth(); i++) {
261             ret.set(i, value.getRealElement(i, 0));
262         }
263         return ret;
264     }
265
266     private VectorOfInt toVectorOfInt(ScilabDouble value) {
267         VectorOfInt ret = new VectorOfInt(value.getHeight());
268         for (int i = 0; i < value.getWidth(); i++) {
269             ret.set(i, (int) value.getRealElement(i, 0));
270         }
271         return ret;
272     }
273
274     /**
275      * Fill the block with the second raw parameters
276      *
277      * @param into
278      *            the target instance
279      * @throws WrongStructureException
280      *             on wrong value
281      */
282     private void fillSecondRawParameters(BasicBlock into) throws WrongStructureException {
283         // dep-ut
284         int field = DEPENDU_INDEX;
285         final boolean[][] dependsOn = ((ScilabBoolean) data.get(field)).getData();
286         VectorOfInt v = new VectorOfInt(2);
287
288         if (dependsOn.length == 1 && dependsOn[0].length == 2) {
289             v.set(0, dependsOn[0][0] ? 1 : 0);
290             v.set(1, dependsOn[0][1] ? 1 : 0);
291         } else if (dependsOn.length == 2 && dependsOn[0].length == 1 && dependsOn[1].length == 1) {
292             v.set(0, dependsOn[0][0] ? 1 : 0);
293             v.set(1, dependsOn[1][0] ? 1 : 0);
294         } else {
295             throw new WrongStructureException(((ScilabString) data.get(0)).getData()[0][DEPENDU_INDEX]);
296         }
297         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.SIM_DEP_UT, v);
298
299         // label
300         // do nothing
301         field++;
302
303         // nzcross
304         field++;
305         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.NZCROSS, toVectorOfInt((ScilabDouble) data.get(field)));
306
307         // nmode
308         field++;
309         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.NMODE, toVectorOfInt((ScilabDouble) data.get(field)));
310
311         // equation
312         field++;
313         controller.setObjectProperty(into.getUID(), into.getKind(), ObjectProperties.EQUATIONS, new ScilabTypeCoder().var2vec(data.get(field)));
314
315         // uid
316         // compatibility check, for pre-5.5.0 diagrams
317         field++;
318         if (field >= data.size()) {
319             return;
320         }
321         final ScilabType uid = data.get(field);
322         if (uid instanceof ScilabString) {
323             final String id = ((ScilabString) uid).getData()[0][0];
324             if (id != null && !id.isEmpty()) {
325                 into.setId(id);
326             }
327         }
328     }
329
330     /**
331      * Validate the current data.
332      *
333      * 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.
334      *
335      * @throws ScicosFormatException
336      *             when there is a validation error.
337      */
338     // CSOFF: CyclomaticComplexity
339     // CSOFF: NPathComplexity
340     // CSOFF: JavaNCSS
341     // CSOFF: MethodLength
342     private void validate() throws ScicosFormatException {
343         if (!canDecode(data)) {
344             throw new WrongElementException();
345         }
346
347         int field = 0;
348
349         // we test if the structure as enough field
350         if (data.size() < DATA_FIELD_NAMES.size()) {
351             throw new WrongStructureException(DATA_FIELD_NAMES);
352         }
353
354         /*
355          * Checking the MList header
356          */
357
358         // Check the first field
359         if (!(data.get(field) instanceof ScilabString)) {
360             throw new WrongTypeException(DATA_FIELD_NAMES, field);
361         }
362         final String[] header = ((ScilabString) data.get(field)).getData()[0];
363
364         // Checking for the field names
365         if (header.length < DATA_FIELD_NAMES.size()) {
366             throw new WrongStructureException(DATA_FIELD_NAMES);
367         }
368         for (int i = 0; i < DATA_FIELD_NAMES.size(); i++) {
369             if (!header[i].equals(DATA_FIELD_NAMES.get(i))) {
370                 throw new WrongStructureException(DATA_FIELD_NAMES);
371             }
372         }
373
374         /*
375          * Checking the data
376          */
377
378         // sim : String or list(String, int)
379         field++;
380         if (!(data.get(field) instanceof ScilabString) && !(data.get(field) instanceof ScilabList)) {
381             throw new WrongTypeException(DATA_FIELD_NAMES, field);
382         }
383
384         // in
385         field++;
386         if (!(data.get(field) instanceof ScilabDouble)) {
387             throw new WrongTypeException(DATA_FIELD_NAMES, field);
388         }
389
390         // in2
391         field++;
392         if (!(data.get(field) instanceof ScilabDouble)) {
393             throw new WrongTypeException(DATA_FIELD_NAMES, field);
394         }
395
396         // intyp
397         field++;
398         if (!(data.get(field) instanceof ScilabDouble)) {
399             throw new WrongTypeException(DATA_FIELD_NAMES, field);
400         }
401
402         // out
403         field++;
404         if (!(data.get(field) instanceof ScilabDouble)) {
405             throw new WrongTypeException(DATA_FIELD_NAMES, field);
406         }
407
408         // out2
409         field++;
410         if (!(data.get(field) instanceof ScilabDouble)) {
411             throw new WrongTypeException(DATA_FIELD_NAMES, field);
412         }
413
414         // outtyp
415         field++;
416         if (!(data.get(field) instanceof ScilabDouble)) {
417             throw new WrongTypeException(DATA_FIELD_NAMES, field);
418         }
419
420         // evtin
421         field++;
422         if (!(data.get(field) instanceof ScilabDouble)) {
423             throw new WrongTypeException(DATA_FIELD_NAMES, field);
424         }
425
426         // evtout
427         field++;
428         if (!(data.get(field) instanceof ScilabDouble)) {
429             throw new WrongTypeException(DATA_FIELD_NAMES, field);
430         }
431
432         // state
433         field++;
434         if (!(data.get(field) instanceof ScilabDouble)) {
435             throw new WrongTypeException(DATA_FIELD_NAMES, field);
436         }
437
438         // dstate
439         // TODO: the ScilabString value is undocumented
440         field++;
441         if (!(data.get(field) instanceof ScilabDouble) && !(data.get(field) instanceof ScilabString)) {
442             throw new WrongTypeException(DATA_FIELD_NAMES, field);
443         }
444
445         // odstate
446         field++;
447         if (!(data.get(field) instanceof ScilabDouble) && !(data.get(field) instanceof ScilabList)) {
448             throw new WrongTypeException(DATA_FIELD_NAMES, field);
449         }
450
451         // rpar
452         // SuperBlocks store all "included" data in rpar field.
453         field++;
454         if (!(data.get(field) instanceof ScilabDouble) && !(data.get(field) instanceof ScilabMList) && !(data.get(field) instanceof ScilabString)) {
455             throw new WrongTypeException(DATA_FIELD_NAMES, field);
456         }
457
458         // ipar
459         // !! WARNING !! scifunc_block_m ipar = list(...)
460         field++;
461         if (!(data.get(field) instanceof ScilabDouble) && !(data.get(field) instanceof ScilabList)) {
462             throw new WrongTypeException(DATA_FIELD_NAMES, field);
463         }
464
465         // opar
466         field++;
467         if (!(data.get(field) instanceof ScilabDouble) && !(data.get(field) instanceof ScilabList)) {
468             throw new WrongTypeException(DATA_FIELD_NAMES, field);
469         }
470
471         // blocktype
472         field++;
473         if (!(data.get(field) instanceof ScilabString)) {
474             throw new WrongTypeException(DATA_FIELD_NAMES, field);
475         }
476
477         // firing
478         field++;
479         if (!(data.get(field) instanceof ScilabDouble) && !(data.get(field) instanceof ScilabBoolean)) {
480             throw new WrongTypeException(DATA_FIELD_NAMES, field);
481         }
482
483         // dep-ut
484         field++;
485         if (!(data.get(field) instanceof ScilabBoolean)) {
486             throw new WrongTypeException(DATA_FIELD_NAMES, field);
487         }
488
489         // label
490         field++;
491         if (!(data.get(field) instanceof ScilabString)) {
492             throw new WrongTypeException(DATA_FIELD_NAMES, field);
493         }
494
495         // nzcross
496         field++;
497         if (!(data.get(field) instanceof ScilabDouble)) {
498             throw new WrongTypeException(DATA_FIELD_NAMES, field);
499         }
500
501         // nmode
502         field++;
503         if (!(data.get(field) instanceof ScilabDouble)) {
504             throw new WrongTypeException(DATA_FIELD_NAMES, field);
505         }
506
507         // equations
508         field++;
509         if (!(data.get(field) instanceof ScilabTList) && !isEmptyField(data.get(field))) {
510             throw new WrongTypeException(DATA_FIELD_NAMES, field);
511         }
512
513         // uid not checked, introduced in Scilab 5.5.0
514     }
515
516     // CSON: CyclomaticComplexity
517     // CSON: NPathComplexity
518     // CSON: JavaNCSS
519     // CSON: MethodLength
520
521     /**
522      * Check if the element can be decoded.
523      *
524      * @param element
525      *            the Scicos element
526      * @return true, if the Scicos types match.
527      * @see org.scilab.modules.xcos.io.scicos.Element#canDecode(org.scilab.modules.types.ScilabType)
528      */
529     @Override
530     public boolean canDecode(ScilabType element) {
531         data = (ScilabMList) element;
532
533         final String type = ((ScilabString) data.get(0)).getData()[0][0];
534         return type.equals(DATA_FIELD_NAMES.get(0));
535     }
536 }
537 // CSON: ClassDataAbstractionCoupling