Xcos: refactoring of constructor-chain
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / graph / model / XcosCellFactory.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2015-2015 - Scilab Enterprises - 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.graph.model;
14
15 import static org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.buildCall;
16 import static org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.synchronousScilabExec;
17
18 import java.lang.reflect.InvocationTargetException;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.EnumSet;
22 import java.util.HashMap;
23 import java.util.Optional;
24 import java.util.logging.Logger;
25
26 import org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.InterpreterException;
27 import org.scilab.modules.graph.utils.ScilabExported;
28 import org.scilab.modules.xcos.JavaController;
29 import org.scilab.modules.xcos.Kind;
30 import org.scilab.modules.xcos.ObjectProperties;
31 import org.scilab.modules.xcos.VectorOfDouble;
32 import org.scilab.modules.xcos.VectorOfScicosID;
33 import org.scilab.modules.xcos.Xcos;
34 import org.scilab.modules.xcos.XcosView;
35 import org.scilab.modules.xcos.block.BasicBlock;
36 import org.scilab.modules.xcos.graph.XcosDiagram;
37 import org.scilab.modules.xcos.link.BasicLink;
38 import org.scilab.modules.xcos.link.CommandControlLink;
39 import org.scilab.modules.xcos.link.ExplicitLink;
40 import org.scilab.modules.xcos.link.ImplicitLink;
41 import org.scilab.modules.xcos.port.BasicPort;
42 import org.scilab.modules.xcos.port.command.CommandPort;
43 import org.scilab.modules.xcos.port.control.ControlPort;
44 import org.scilab.modules.xcos.port.input.ExplicitInputPort;
45 import org.scilab.modules.xcos.port.input.ImplicitInputPort;
46 import org.scilab.modules.xcos.port.output.ExplicitOutputPort;
47 import org.scilab.modules.xcos.port.output.ImplicitOutputPort;
48 import org.scilab.modules.xcos.utils.BlockPositioning;
49
50 import com.mxgraph.model.mxCell;
51 import com.mxgraph.model.mxGeometry;
52 import com.mxgraph.util.mxPoint;
53 import java.lang.reflect.Constructor;
54 import java.util.EnumMap;
55 import org.scilab.modules.xcos.block.SplitBlock;
56 import org.scilab.modules.xcos.block.positionning.RoundBlock;
57 ;
58
59 /**
60  * Ease the creation of any {@link Kind} of graphical object
61  */
62 public final class XcosCellFactory {
63
64     /** Size compatibility for user defined blocks */
65     private static final double DEFAULT_SIZE_FACTOR = 20.0;
66     private static final Logger LOG = Logger.getLogger(XcosCellFactory.class.getName());
67
68     /** Default singleton constructor */
69     private XcosCellFactory() {
70         // This class is a static singleton
71     }
72
73     /**
74      * This is a notify method mapped as a Scilab gateway used to alert with the loaded UID
75      *
76      * @param uid
77      *            the loaded UID
78      * @param kind
79      *            the kind of the created object (as an int)
80      */
81     @ScilabExported(module = "xcos", filename = "XcosCellFactory.giws.xml")
82     public static synchronized void created(long uid, int kind) {
83         lastCreated = new ScicosObjectOwner(uid, Kind.values()[kind]);
84
85     }
86
87     private static ScicosObjectOwner lastCreated = null;
88
89     /**
90      * Retrieve and clear the last created object (<pre>xcosCellCreated</pre> call)
91      * @return the last created object
92      */
93     public static synchronized ScicosObjectOwner getLastCreated() {
94         ScicosObjectOwner last = lastCreated;
95         lastCreated = null;
96         return last;
97     }
98
99     /*
100      * Diagram management
101      */
102
103     /**
104      * Allocate a Java XcosDiagram from a COSF file.
105      *
106      * This method execute the file and register a
107      *
108      * @param controller
109      *            the controller
110      * @param filename
111      *            the file to execute
112      * @return an allocated XcosDiagram
113      */
114     public static XcosDiagram createDiagramFromCOSF(final JavaController controller, String filename) {
115         XcosView view = (XcosView) JavaController.lookup_view(Xcos.class.getName());
116         JavaController.unregister_view(view);
117
118         XcosDiagram diagram;
119         try {
120             synchronousScilabExec(
121                 "function f(), " + buildCall("exec", filename, -1) + "; " + buildCall("xcosCellCreated", "scs_m".toCharArray()) + "endfunction; f();");
122
123             ScicosObjectOwner last = getLastCreated();
124             if (last.getKind() == Kind.DIAGRAM) {
125                 String[] strUID = new String[1];
126                 controller.getObjectProperty(last.getUID(), last.getKind(), ObjectProperties.UID, strUID);
127
128                 diagram = new XcosDiagram(controller, last.getUID(), last.getKind(), strUID[0]);
129                 insertChildren(controller, diagram);
130             } else {
131                 diagram = null;
132             }
133         } catch (InterpreterException e) {
134             diagram = null;
135         } finally {
136             JavaController.register_view(Xcos.class.getName(), view);
137         }
138
139         return diagram;
140     }
141
142     /**
143      * Insert the diagram MVC children into the JGraphX model
144      *
145      * @param controller
146      *            the shared controller
147      * @param diagram
148      *            the current diagram instance
149      */
150     public static void insertChildren(JavaController controller, XcosDiagram diagram) {
151         /*
152          * Retrieve the children
153          */
154         VectorOfScicosID children = new VectorOfScicosID();
155         controller.getObjectProperty(diagram.getUID(), diagram.getKind(), ObjectProperties.CHILDREN, children);
156         final int childrenLen = children.size();
157
158         /*
159          * Allocation some pre-sized stash data
160          */
161         final ArrayList<BasicLink> links = new ArrayList<>(childrenLen / 2);
162         final HashMap<Long, BasicPort> ports = new HashMap<>(childrenLen);
163
164         /*
165          * Create the XcosCell objects and store some of them for later use
166          */
167         XcosCell[] cells = new XcosCell[childrenLen];
168         for (int i = 0; i < childrenLen; i++) {
169             final long uid = children.get(i);
170             final Kind kind = controller.getKind(uid);
171
172             switch (kind) {
173                 case ANNOTATION:
174                 case BLOCK:
175                     BasicBlock b = createBlock(controller, uid, kind);
176                     cells[i] = b;
177                     BlockPositioning.updatePortsPosition(diagram, b);
178                     b.getTypedChildrenIndexes(BasicPort.class).stream()
179                     .map(index -> b.getChildAt(index))
180                     .filter(c -> c instanceof BasicPort)
181                     .map(c -> (BasicPort) c)
182                     .forEach(c -> ports.put(c.getUID(), c));
183                     break;
184                 case LINK:
185                     BasicLink l = createLink(controller, uid, kind);
186                     cells[i] = l;
187                     links.add(l);
188                     break;
189                 default:
190                     break;
191             }
192         }
193
194         /*
195          * Relink the links on the XcosCell part
196          */
197         for (BasicLink l : links) {
198             long[] src = new long[1];
199             controller.getObjectProperty(l.getUID(), l.getKind(), ObjectProperties.SOURCE_PORT, src);
200
201             long[] dest = new long[1];
202             controller.getObjectProperty(l.getUID(), l.getKind(), ObjectProperties.DESTINATION_PORT, dest);
203
204             BasicPort srcPort = ports.get(src[0]);
205             if (srcPort != null) {
206                 l.setSource(srcPort);
207             } else {
208                 LOG.severe("Unable to connect link " + l.getId() + " : invalid source " + src[0]);
209             }
210
211             BasicPort destPort = ports.get(dest[0]);
212             if (destPort != null) {
213                 l.setTarget(destPort);
214             } else {
215                 LOG.severe("Unable to connect link " + l.getId() + " : invalid target " + dest[0]);
216             }
217         }
218
219         // re-add the children cells without duplicating them
220         children.clear();
221         controller.setObjectProperty(diagram.getUID(), diagram.getKind(), ObjectProperties.CHILDREN, children);
222
223         // add all the children using the diagram modification tracking features
224         diagram.addCells(cells);
225
226         // each cell has been referenced twice (CHILDREN insert and addCells), derefence them all by one
227         Arrays.stream(cells).forEach(c -> controller.deleteObject(c.getUID()));
228     }
229
230     /*
231      * Block and Annotation management
232      */
233
234     /**
235      * Instantiate a new block with the specified interface function.
236      *
237      * @param func
238      *            the interface function
239      * @return A new instance of a block.
240      */
241     public static BasicBlock createBlock(BlockInterFunction func) {
242         return createBlock(func, func.name());
243     }
244
245     /**
246      * Instantiate a new block with the specified UID value and interface function
247      *
248      * @param uid
249      *            The associated UID value
250      * @param interfaceFunction
251      *            the interface function
252      * @return A new instance of a block.
253      */
254     public static BasicBlock createBlock(String interfaceFunction) {
255         Optional<BlockInterFunction> func = EnumSet.allOf(BlockInterFunction.class).stream().filter(f -> f.name().equals(interfaceFunction)).findFirst();
256
257         final BasicBlock block;
258         if (func.isPresent()) {
259             block = createBlock(func.get());
260         } else {
261             block = createBlock(BlockInterFunction.BASIC_BLOCK, interfaceFunction);
262         }
263         block.setStyle(interfaceFunction);
264
265         return block;
266     }
267
268     private static BasicBlock createBlock(BlockInterFunction func, String interfaceFunction) {
269         return createBlock(new JavaController(), func, interfaceFunction);
270     }
271
272     private static BasicBlock createBlock(final JavaController controller, BlockInterFunction func, String interfaceFunction) {
273         BasicBlock block;
274         try {
275             ScicosObjectOwner last;
276
277             if (BlockInterFunction.BASIC_BLOCK.name().equals(interfaceFunction)) {
278                 // deliver all the MVC speed for the casual case
279                 last = new ScicosObjectOwner(controller.createObject(Kind.BLOCK), Kind.BLOCK);
280             } else {
281                 // allocate an empty block that will be filled later
282                 synchronousScilabExec("xcosCellCreated(" + interfaceFunction + "(\"define\")); ");
283                 last = getLastCreated();
284             }
285
286             // defensive programming
287             if (last == null) {
288                 System.err.println("XcosCellFactory#createBlock : unable to allocate " + interfaceFunction);
289                 return null;
290             }
291
292             if (EnumSet.of(Kind.BLOCK, Kind.ANNOTATION).contains(last.getKind())) {
293                 block = createBlock(controller, func, interfaceFunction, last.getUID(), last.getKind());
294             } else {
295                 block = null;
296             }
297         } catch (InterpreterException e) {
298             block = null;
299         }
300
301         return block;
302     }
303
304     private static BasicBlock createBlock(final JavaController controller, long uid, Kind kind) {
305         String[] interfaceFunction = new String[1];
306         if (kind == Kind.BLOCK) {
307             controller.getObjectProperty(uid, kind, ObjectProperties.INTERFACE_FUNCTION, interfaceFunction);
308         } else { // ANNOTATION
309             interfaceFunction[0] = "TEXT_f";
310         }
311
312         final BlockInterFunction func = lookForInterfunction(interfaceFunction[0]);
313
314         return createBlock(controller, func, interfaceFunction[0], uid, kind);
315     }
316
317     public static BlockInterFunction lookForInterfunction(String interfaceFunction) {
318         Optional<BlockInterFunction> optFunc = EnumSet.allOf(BlockInterFunction.class).stream().filter(f -> f.name().equals(interfaceFunction)).findFirst();
319
320         final BlockInterFunction func;
321         if (optFunc.isPresent()) {
322             func = optFunc.get();
323         } else {
324             func = BlockInterFunction.BASIC_BLOCK;
325         }
326         return func;
327     }
328
329     /**
330      * Instantiate a new block with the specified interface function and uid.
331      *
332      * @param controller
333      *            the Java controller to use
334      * @param func
335      *            the interface function as an enum
336      * @param interfaceFunction
337      *            the interface function name
338      * @param uid
339      *            the allocated uid
340      * @return A new instance of a block.
341      */
342     public static BasicBlock createBlock(final JavaController controller, BlockInterFunction func, String interfaceFunction, long uid, Kind kind) {
343
344         final EnumMap<ObjectProperties, Integer> properties = new EnumMap<>(ObjectProperties.class);
345         properties.put(ObjectProperties.INPUTS, 0);
346         properties.put(ObjectProperties.OUTPUTS, 0);
347         properties.put(ObjectProperties.EVENT_INPUTS, 0);
348         properties.put(ObjectProperties.EVENT_OUTPUTS, 0);
349
350
351         /*
352          * Retrieve the JGraphX data before cell creation
353          */
354         String[] strUID = new String[1];
355         controller.getObjectProperty(uid, kind, ObjectProperties.UID, strUID);
356
357         String[] style = new String[1];
358         controller.getObjectProperty(uid, kind, ObjectProperties.STYLE, style);
359         if (style[0].isEmpty()) {
360             style[0] = interfaceFunction;
361         }
362
363         String value;
364         if (kind == Kind.ANNOTATION) {
365             String[] description = new String[1];
366             controller.getObjectProperty(uid, kind, ObjectProperties.DESCRIPTION, description);
367             value = description[0];
368         } else { // BLOCK
369             String[] label = new String[1];
370             controller.getObjectProperty(uid, kind, ObjectProperties.LABEL, label);
371             value = label[0];
372         }
373
374         VectorOfDouble geom = new VectorOfDouble(4);
375         controller.getObjectProperty(uid, kind, ObjectProperties.GEOMETRY, geom);
376
377         double x = geom.get(0);
378         double y = geom.get(1);
379         double w = geom.get(2);
380         double h = geom.get(3);
381         mxGeometry geometry = new mxGeometry(x, y, w, h);
382
383         /*
384          * Instanciate the block
385          */
386         BasicBlock block = null;
387         try {
388             Constructor<? extends BasicBlock> cstr = func.getKlass().getConstructor(JavaController.class, Long.TYPE, Kind.class, Object.class, mxGeometry.class, String.class, String.class);
389             block = cstr.newInstance(controller, uid, kind, value, geometry, style[0], strUID[0]);
390         } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException
391                      | SecurityException e) {
392             // Something goes wrong, print it.
393             e.printStackTrace();
394             return block;
395         }
396
397         /*
398          * Synchronize model information back to the JGraphX data
399          *
400          * Annotations have no inputs/outputs
401          */
402
403         if (block.getKind() == Kind.BLOCK) {
404             insertPortChildren(controller, properties, block);
405         }
406
407
408         /*
409          * Compatibility to ease user definition :
410          *   * split are not updated as they have an hard-coded fixed-size
411          *   * round blocks : ports globally layout around the block
412          *   * generic case : layout the ports per kind per block-side
413          */
414         boolean convertGeometry;
415         if (block instanceof SplitBlock) {
416             convertGeometry = false;
417         } else if (block instanceof RoundBlock) {
418             int numberOfPorts = properties.get(ObjectProperties.INPUTS) +
419                                 properties.get(ObjectProperties.OUTPUTS) +
420                                 properties.get(ObjectProperties.EVENT_INPUTS) +
421                                 properties.get(ObjectProperties.EVENT_OUTPUTS);
422             convertGeometry = (2 * w + 2 * h) < (numberOfPorts * BasicPort.DEFAULT_PORTSIZE);
423         } else {
424             convertGeometry = w < (properties.get(ObjectProperties.INPUTS) * BasicPort.DEFAULT_PORTSIZE) |
425                               w < (properties.get(ObjectProperties.OUTPUTS) * BasicPort.DEFAULT_PORTSIZE) |
426                               h < (properties.get(ObjectProperties.EVENT_INPUTS) * BasicPort.DEFAULT_PORTSIZE) |
427                               h < (properties.get(ObjectProperties.EVENT_OUTPUTS) * BasicPort.DEFAULT_PORTSIZE);
428         }
429
430         if (convertGeometry) {
431             w = w * DEFAULT_SIZE_FACTOR;
432             h = h * DEFAULT_SIZE_FACTOR;
433
434             /*
435              * Invert the y-axis value and translate it.
436              */
437             y = -y - h;
438
439             block.setGeometry(new mxGeometry(x, y, w, h));
440         }
441
442         return block;
443     }
444
445     /**
446      * Instantiate a new block for an already created MVC object
447      *
448      * @param lastCreated the owned MVC object
449      * @return a block or null
450      */
451     public static BasicBlock createBlock(final JavaController controller, ScicosObjectOwner lastCreated) {
452         // pre-condition
453         if (lastCreated.getKind() != Kind.ANNOTATION && lastCreated.getKind() != Kind.BLOCK) {
454             return null;
455         }
456
457         String[] interfaceFunction = new String[1];
458         BlockInterFunction func = lookForInterfunction(interfaceFunction[0]);
459
460         return createBlock(controller, func, interfaceFunction[0], lastCreated.getUID(), lastCreated.getKind());
461     }
462
463     /*
464      * Port management
465      */
466
467     /**
468      * Helper used to create port children on a parent block.
469      *
470      * This method does not manage the model transaction and should be used to preset the children of a block out of an {@link XcosDiagram}.
471      *
472      * @param controller
473      *            is the shared controller instance
474      * @param properties
475      *            specify the kind of port to insert and should be some of :
476      *            <UL>
477      *            <LI>{@link ObjectProperties#INPUTS}
478      *            <LI>{@link ObjectProperties#OUTPUTS}
479      *            <LI>{@link ObjectProperties#EVENT_INPUTS}
480      *            <LI>{@link ObjectProperties#EVENT_OUTPUTS}
481      *            </UL>
482      *            This method will fill the value with the number of added ports
483      * @param parent
484      *            is the parent {@link mxCell} to modify
485      */
486     private static void insertPortChildren(final JavaController controller, final EnumMap<ObjectProperties, Integer> properties, final XcosCell parent) {
487         for (ObjectProperties property : properties.keySet()) {
488             properties.put(property, insertPortChildren(controller, property, parent));
489         }
490     }
491
492     /**
493      * Helper used to create port children on a parent block.
494      *
495      * This method does not manage the model transaction and should be used to preset the children of a block out of an {@link XcosDiagram}.
496      *
497      * @param controller
498      *            is the shared controller instance
499      * @param property
500      *            specify the kind of port to insert and should be one of :
501      *            <UL>
502      *            <LI>{@link ObjectProperties#INPUTS}
503      *            <LI>{@link ObjectProperties#OUTPUTS}
504      *            <LI>{@link ObjectProperties#EVENT_INPUTS}
505      *            <LI>{@link ObjectProperties#EVENT_OUTPUTS}
506      * @param parent
507      *            is the parent {@link mxCell} to modify
508      * @return the number of inserted children
509      */
510     private static int insertPortChildren(final JavaController controller, final ObjectProperties property, final XcosCell parent) {
511         VectorOfScicosID modelChildren = new VectorOfScicosID();
512         controller.getObjectProperty(parent.getUID(), parent.getKind(), property, modelChildren);
513
514         XcosCell[] children = new XcosCell[modelChildren.size()];
515         for (int i = 0; i < children.length; i++) {
516             XcosCell child = createPort(controller, modelChildren.get(i), property);
517             children[i] = child;
518         }
519
520         modelChildren.clear();
521         controller.setObjectProperty(parent.getUID(), parent.getKind(), property, modelChildren);
522
523         Arrays.stream(children).forEach(c -> {
524             parent.insert(c);
525             controller.deleteObject(c.getUID());
526         });
527
528         return children.length;
529     }
530
531     /**
532      * Create a port for a specific uid
533      *
534      * @param controller
535      *            is the shared controller instance
536      * @param uid
537      *            represent the allocated UID on the MVC
538      * @param property
539      *            specify the kind of port to create and should be one of :
540      *            <UL>
541      *            <LI>{@link ObjectProperties#INPUTS}
542      *            <LI>{@link ObjectProperties#OUTPUTS}
543      *            <LI>{@link ObjectProperties#EVENT_INPUTS}
544      *            <LI>{@link ObjectProperties#EVENT_OUTPUTS}
545      * @return a newly allocated port
546      */
547     private static final BasicPort createPort(final JavaController controller, long uid, final ObjectProperties property) {
548         BasicPort port;
549         boolean[] isImplicit = { false };
550
551         String[] strUID = new String[] { "" };
552         controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.UID, strUID);
553
554         String[] style = new String[] { "" };
555         controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.STYLE, style);
556
557         String[] value = new String[] { "" };
558         controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.LABEL, value);
559
560         switch (property) {
561             case INPUTS:
562                 controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.IMPLICIT, isImplicit);
563                 if (isImplicit[0]) {
564                     port = new ImplicitInputPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
565                 } else {
566                     port = new ExplicitInputPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
567                 }
568                 break;
569             case OUTPUTS:
570                 controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.IMPLICIT, isImplicit);
571                 if (isImplicit[0]) {
572                     port = new ImplicitOutputPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
573                 } else {
574                     port = new ExplicitOutputPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
575                 }
576                 break;
577             case EVENT_INPUTS:
578                 port = new ControlPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
579                 break;
580             case EVENT_OUTPUTS:
581                 port = new CommandPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
582                 break;
583             default:
584                 return null;
585         }
586
587         /*
588          * Setup JGraphX properties
589          */
590
591         return port;
592     }
593
594     /*
595      * Link management
596      */
597
598     private static BasicLink createLink(JavaController controller, long uid, Kind kind) {
599         int[] type = new int[1];
600         controller.getObjectProperty(uid, kind, ObjectProperties.KIND, type);
601
602         /*
603          * Synchronize model information back to the JGraphX data
604          */
605
606         String[] value = new String[] { "" };
607         controller.getObjectProperty(uid, kind, ObjectProperties.LABEL, value);
608
609         String[] style = new String[] { "" };
610         controller.getObjectProperty(uid, kind, ObjectProperties.STYLE, style);
611
612         String[] strUID = new String[] { "" };
613         controller.getObjectProperty(uid, kind, ObjectProperties.UID, strUID);
614
615         VectorOfDouble controlPoints = new VectorOfDouble();
616         controller.getObjectProperty(uid, kind, ObjectProperties.CONTROL_POINTS, controlPoints);
617         final int pointsLen = controlPoints.size() / 2;
618
619         mxGeometry geom = new mxGeometry();
620
621         // as the link is supposed to be connected and accordingly to the JGraphX rules : do not add the origin and destination point
622         ArrayList<mxPoint> points = new ArrayList<>();
623         int i = 0;
624         // ignore origin
625         i++;
626         // loop for points
627         for (; i < pointsLen - 1; i++) {
628             points.add(new mxPoint(controlPoints.get(2 * i), controlPoints.get(2 * i + 1)));
629         }
630         // ignore destination
631         i++;
632
633         geom.setPoints(points);
634
635         /*
636          * Allocate the link
637          */
638         BasicLink link;
639         switch (type[0]) {
640             case -1:
641                 link = new CommandControlLink(controller, uid, kind, value[0], geom, style[0], strUID[0]);
642                 break;
643             case 1:
644                 link = new ExplicitLink(controller, uid, kind, value[0], geom, style[0], strUID[0]);
645                 break;
646             case 2:
647                 link = new ImplicitLink(controller, uid, kind, value[0], geom, style[0], strUID[0]);
648                 break;
649             default:
650                 return null;
651         }
652
653         return link;
654     }
655 }