0b166c7fc771336ac5603965fcad2ed7ac6b6818
[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.util.EnumMap;
54 import org.scilab.modules.xcos.block.SplitBlock;
55 import org.scilab.modules.xcos.block.positionning.RoundBlock;
56 ;
57
58 /**
59  * Ease the creation of any {@link Kind} of graphical object
60  */
61 public final class XcosCellFactory {
62
63     /** Size compatibility for user defined blocks */
64     private static final double DEFAULT_SIZE_FACTOR = 20.0;
65     private static final Logger LOG = Logger.getLogger(XcosCellFactory.class.getName());
66
67     /** Default singleton constructor */
68     private XcosCellFactory() {
69         // This class is a static singleton
70     }
71
72     /**
73      * This is a notify method mapped as a Scilab gateway used to alert with the loaded UID
74      *
75      * @param uid
76      *            the loaded UID
77      * @param kind
78      *            the kind of the created object (as an int)
79      */
80     @ScilabExported(module = "xcos", filename = "XcosCellFactory.giws.xml")
81     public static synchronized void created(long uid, int kind) {
82         lastCreated = new ScicosObjectOwner(uid, Kind.values()[kind]);
83
84     }
85
86     private static ScicosObjectOwner lastCreated = null;
87
88     /**
89      * Retrieve and clear the last created object (<pre>xcosCellCreated</pre> call)
90      * @return the last created object
91      */
92     public static synchronized ScicosObjectOwner getLastCreated() {
93         ScicosObjectOwner last = lastCreated;
94         lastCreated = null;
95         return last;
96     }
97
98     /*
99      * Diagram management
100      */
101
102     /**
103      * Allocate a Java XcosDiagram from a COSF file.
104      *
105      * This method execute the file and register a
106      *
107      * @param controller
108      *            the controller
109      * @param filename
110      *            the file to execute
111      * @return an allocated XcosDiagram
112      */
113     public static XcosDiagram createDiagramFromCOSF(final JavaController controller, String filename) {
114         XcosView view = (XcosView) JavaController.lookup_view(Xcos.class.getName());
115         JavaController.unregister_view(view);
116
117         XcosDiagram diagram;
118         try {
119             synchronousScilabExec(
120                 "function f(), " + buildCall("exec", filename, -1) + "; " + buildCall("xcosCellCreated", "scs_m".toCharArray()) + "endfunction; f();");
121
122             ScicosObjectOwner last = getLastCreated();
123             if (last.getKind() == Kind.DIAGRAM) {
124                 diagram = new XcosDiagram(last.getUID(), last.getKind());
125                 insertChildren(controller, diagram);
126             } else {
127                 diagram = null;
128             }
129         } catch (InterpreterException e) {
130             diagram = null;
131         } finally {
132             JavaController.register_view(Xcos.class.getName(), view);
133         }
134
135         return diagram;
136     }
137
138     /**
139      * Insert the diagram MVC children into the JGraphX model
140      *
141      * @param controller
142      *            the shared controller
143      * @param diagram
144      *            the current diagram instance
145      */
146     public static void insertChildren(JavaController controller, XcosDiagram diagram) {
147         /*
148          * Retrieve the children
149          */
150         VectorOfScicosID children = new VectorOfScicosID();
151         controller.getObjectProperty(diagram.getUID(), diagram.getKind(), ObjectProperties.CHILDREN, children);
152         final int childrenLen = children.size();
153
154         /*
155          * Allocation some pre-sized stash data
156          */
157         final ArrayList<BasicLink> links = new ArrayList<>(childrenLen / 2);
158         final HashMap<Long, BasicPort> ports = new HashMap<>(childrenLen);
159
160         /*
161          * Create the XcosCell objects and store some of them for later use
162          */
163         XcosCell[] cells = new XcosCell[childrenLen];
164         for (int i = 0; i < childrenLen; i++) {
165             final long uid = children.get(i);
166             final Kind kind = controller.getKind(uid);
167
168             switch (kind) {
169                 case ANNOTATION:
170                 case BLOCK:
171                     BasicBlock b = createBlock(controller, uid, kind);
172                     cells[i] = b;
173                     BlockPositioning.updatePortsPosition(diagram, b);
174                     b.getTypedChildrenIndexes(BasicPort.class).stream()
175                     .map(index -> b.getChildAt(index))
176                     .filter(c -> c instanceof BasicPort)
177                     .map(c -> (BasicPort) c)
178                     .forEach(c -> ports.put(c.getUID(), c));
179                     break;
180                 case LINK:
181                     BasicLink l = createLink(controller, uid, kind);
182                     cells[i] = l;
183                     links.add(l);
184                     break;
185                 default:
186                     break;
187             }
188         }
189
190         /*
191          * Relink the links on the XcosCell part
192          */
193         for (BasicLink l : links) {
194             long[] src = new long[1];
195             controller.getObjectProperty(l.getUID(), l.getKind(), ObjectProperties.SOURCE_PORT, src);
196
197             long[] dest = new long[1];
198             controller.getObjectProperty(l.getUID(), l.getKind(), ObjectProperties.DESTINATION_PORT, dest);
199
200             BasicPort srcPort = ports.get(src[0]);
201             if (srcPort != null) {
202                 l.setSource(srcPort);
203             } else {
204                 LOG.severe("Unable to connect link " + l.getId() + " : invalid source " + src[0]);
205             }
206
207             BasicPort destPort = ports.get(dest[0]);
208             if (destPort != null) {
209                 l.setTarget(destPort);
210             } else {
211                 LOG.severe("Unable to connect link " + l.getId() + " : invalid target " + dest[0]);
212             }
213         }
214
215         // re-add the children cells without duplicating them
216         children.clear();
217         controller.setObjectProperty(diagram.getUID(), diagram.getKind(), ObjectProperties.CHILDREN, children);
218
219         // add all the children without using the diagram modification tracking features
220         diagram.getView().getStates().clear();
221         for (XcosCell c : cells) {
222             ((XcosCell) diagram.getDefaultParent()).insert(c);
223         }
224
225         // each cell has been referenced twice (CHILDREN insert and addCells), derefence them all by one
226         Arrays.stream(cells).forEach(c -> controller.deleteObject(c.getUID()));
227     }
228
229     /*
230      * Block and Annotation management
231      */
232
233     /**
234      * Instantiate a new block with the specified interface function.
235      *
236      * @param func
237      *            the interface function
238      * @return A new instance of a block.
239      */
240     public static BasicBlock createBlock(BlockInterFunction func) {
241         return createBlock(func, func.name());
242     }
243
244     /**
245      * Instantiate a new block with the specified UID value and interface function
246      *
247      * @param uid
248      *            The associated UID value
249      * @param interfaceFunction
250      *            the interface function
251      * @return A new instance of a block.
252      */
253     public static BasicBlock createBlock(String interfaceFunction) {
254         Optional<BlockInterFunction> func = EnumSet.allOf(BlockInterFunction.class).stream().filter(f -> f.name().equals(interfaceFunction)).findFirst();
255
256         final BasicBlock block;
257         if (func.isPresent()) {
258             block = createBlock(func.get());
259         } else {
260             block = createBlock(BlockInterFunction.BASIC_BLOCK, interfaceFunction);
261         }
262         block.setStyle(interfaceFunction);
263
264         return block;
265     }
266
267     private static BasicBlock createBlock(BlockInterFunction func, String interfaceFunction) {
268         return createBlock(new JavaController(), func, interfaceFunction);
269     }
270
271     private static BasicBlock createBlock(final JavaController controller, BlockInterFunction func, String interfaceFunction) {
272         BasicBlock block;
273         try {
274             ScicosObjectOwner last;
275
276             if (BlockInterFunction.BASIC_BLOCK.name().equals(interfaceFunction)) {
277                 // deliver all the MVC speed for the casual case
278                 last = new ScicosObjectOwner(controller.createObject(Kind.BLOCK), Kind.BLOCK);
279             } else {
280                 // allocate an empty block that will be filled later
281                 synchronousScilabExec("xcosCellCreated(" + interfaceFunction + "(\"define\")); ");
282                 last = getLastCreated();
283             }
284
285             // defensive programming
286             if (last == null) {
287                 System.err.println("XcosCellFactory#createBlock : unable to allocate " + interfaceFunction);
288                 return null;
289             }
290
291             if (EnumSet.of(Kind.BLOCK, Kind.ANNOTATION).contains(last.getKind())) {
292                 block = createBlock(controller, func, interfaceFunction, last.getUID(), last.getKind());
293             } else {
294                 block = null;
295             }
296         } catch (InterpreterException e) {
297             block = null;
298         }
299
300         return block;
301     }
302
303     private static BasicBlock createBlock(final JavaController controller, long uid, Kind kind) {
304         String[] interfaceFunction = new String[1];
305         if (kind == Kind.BLOCK) {
306             controller.getObjectProperty(uid, kind, ObjectProperties.INTERFACE_FUNCTION, interfaceFunction);
307         } else { // ANNOTATION
308             interfaceFunction[0] = "TEXT_f";
309         }
310
311         final BlockInterFunction func = lookForInterfunction(interfaceFunction[0]);
312
313         return createBlock(controller, func, interfaceFunction[0], uid, kind);
314     }
315
316     public static BlockInterFunction lookForInterfunction(String interfaceFunction) {
317         Optional<BlockInterFunction> optFunc = EnumSet.allOf(BlockInterFunction.class).stream().filter(f -> f.name().equals(interfaceFunction)).findFirst();
318
319         final BlockInterFunction func;
320         if (optFunc.isPresent()) {
321             func = optFunc.get();
322         } else {
323             func = BlockInterFunction.BASIC_BLOCK;
324         }
325         return func;
326     }
327
328     /**
329      * Instantiate a new block with the specified interface function and uid.
330      *
331      * @param controller
332      *            the Java controller to use
333      * @param func
334      *            the interface function as an enum
335      * @param interfaceFunction
336      *            the interface function name
337      * @param uid
338      *            the allocated uid
339      * @return A new instance of a block.
340      */
341     public static BasicBlock createBlock(final JavaController controller, BlockInterFunction func, String interfaceFunction, long uid, Kind kind) {
342         /*
343          * Retrieve the JGraphX data before cell creation
344          */
345         String[] strUID = new String[1];
346         controller.getObjectProperty(uid, kind, ObjectProperties.UID, strUID);
347
348         String[] style = new String[1];
349         controller.getObjectProperty(uid, kind, ObjectProperties.STYLE, style);
350
351         String value;
352         if (kind == Kind.ANNOTATION) {
353             String[] description = new String[1];
354             controller.getObjectProperty(uid, kind, ObjectProperties.DESCRIPTION, description);
355             value = description[0];
356         } else { // BLOCK
357             String[] label = new String[1];
358             controller.getObjectProperty(uid, kind, ObjectProperties.LABEL, label);
359             value = label[0];
360         }
361
362         VectorOfDouble geom = new VectorOfDouble(4);
363         controller.getObjectProperty(uid, kind, ObjectProperties.GEOMETRY, geom);
364
365         /*
366          * Instanciate the block
367          */
368         BasicBlock block = null;
369         try {
370             block = func.getKlass().getConstructor(Long.TYPE).newInstance(uid);
371         } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException
372                      | SecurityException e) {
373             // Something goes wrong, print it.
374             e.printStackTrace();
375             return block;
376         }
377
378         /*
379          * Synchronize model information back to the JGraphX data
380          *
381          * Annotations have no inputs/outputs
382          */
383         EnumMap<ObjectProperties, Integer> properties = new EnumMap<>(ObjectProperties.class);
384         properties.put(ObjectProperties.INPUTS, 0);
385         properties.put(ObjectProperties.OUTPUTS, 0);
386         properties.put(ObjectProperties.EVENT_INPUTS, 0);
387         properties.put(ObjectProperties.EVENT_OUTPUTS, 0);
388         if (block.getKind() == Kind.BLOCK) {
389             insertPortChildren(controller, properties, block);
390         }
391
392         block.setId(strUID[0]);
393
394         if (style[0].isEmpty()) {
395             block.setStyle(interfaceFunction);
396         } else {
397             block.setStyle(style[0]);
398         }
399
400         block.setValue(value);
401
402         double x = geom.get(0);
403         double y = geom.get(1);
404         double w = geom.get(2);
405         double h = geom.get(3);
406
407         /*
408          * Compatibility to ease user definition :
409          *   * split are not updated as they have an hard-coded fixed-size
410          *   * round blocks : ports globally layout around the block
411          *   * generic case : layout the ports per kind per block-side
412          */
413         boolean convertGeometry;
414         if ((block instanceof SplitBlock)) {
415             convertGeometry = false;
416         } else if (block instanceof RoundBlock) {
417             int numberOfPorts = properties.get(ObjectProperties.INPUTS) +
418                                 properties.get(ObjectProperties.OUTPUTS) +
419                                 properties.get(ObjectProperties.EVENT_INPUTS) +
420                                 properties.get(ObjectProperties.EVENT_OUTPUTS);
421             convertGeometry = (2 * w + 2 * h) < (numberOfPorts * BasicPort.DEFAULT_PORTSIZE);
422         } else {
423             convertGeometry = w < (properties.get(ObjectProperties.INPUTS) * BasicPort.DEFAULT_PORTSIZE) |
424                               w < (properties.get(ObjectProperties.OUTPUTS) * BasicPort.DEFAULT_PORTSIZE) |
425                               h < (properties.get(ObjectProperties.EVENT_INPUTS) * BasicPort.DEFAULT_PORTSIZE) |
426                               h < (properties.get(ObjectProperties.EVENT_OUTPUTS) * BasicPort.DEFAULT_PORTSIZE);
427         }
428
429         if (convertGeometry) {
430             w = w * DEFAULT_SIZE_FACTOR;
431             h = h * DEFAULT_SIZE_FACTOR;
432
433             /*
434              * Invert the y-axis value and translate it.
435              */
436             y = -y - h;
437         }
438
439
440         block.setGeometry(new mxGeometry(x, y, w, h));
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         switch (property) {
552             case INPUTS:
553                 controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.IMPLICIT, isImplicit);
554                 if (isImplicit[0]) {
555                     port = new ImplicitInputPort(uid);
556                 } else {
557                     port = new ExplicitInputPort(uid);
558                 }
559                 break;
560             case OUTPUTS:
561                 controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.IMPLICIT, isImplicit);
562                 if (isImplicit[0]) {
563                     port = new ImplicitOutputPort(uid);
564                 } else {
565                     port = new ExplicitOutputPort(uid);
566                 }
567                 break;
568             case EVENT_INPUTS:
569                 port = new ControlPort(uid);
570                 break;
571             case EVENT_OUTPUTS:
572                 port = new CommandPort(uid);
573                 break;
574             default:
575                 return null;
576         }
577
578         /*
579          * Setup JGraphX properties
580          */
581
582         String[] childUID = new String[1];
583         controller.getObjectProperty(port.getUID(), port.getKind(), ObjectProperties.UID, childUID);
584         if (!childUID[0].isEmpty()) {
585             port.setId(childUID[0]);
586         }
587
588         return port;
589     }
590
591     /*
592      * Link management
593      */
594
595     private static BasicLink createLink(JavaController controller, long uid, Kind kind) {
596         int[] type = new int[1];
597         controller.getObjectProperty(uid, kind, ObjectProperties.KIND, type);
598
599         BasicLink link;
600         switch (type[0]) {
601             case -1:
602                 link = new CommandControlLink(uid);
603                 break;
604             case 1:
605                 link = new ExplicitLink(uid);
606                 break;
607             case 2:
608                 link = new ImplicitLink(uid);
609                 break;
610             default:
611                 return null;
612         }
613
614         /*
615          * Synchronize model information back to the JGraphX data
616          */
617
618         String[] strUID = new String[1];
619         controller.getObjectProperty(uid, kind, ObjectProperties.UID, strUID);
620         if (!strUID[0].isEmpty()) {
621             link.setId(strUID[0]);
622         }
623
624         VectorOfDouble controlPoints = new VectorOfDouble();
625         controller.getObjectProperty(uid, kind, ObjectProperties.CONTROL_POINTS, controlPoints);
626         final int pointsLen = controlPoints.size() / 2;
627
628         mxGeometry geom = new mxGeometry();
629
630         // as the link is supposed to be connected and accordingly to the JGraphX rules : do not add the origin and destination point
631         ArrayList<mxPoint> points = new ArrayList<>();
632         int i = 0;
633         // ignore origin
634         i++;
635         // loop for points
636         for (; i < pointsLen - 1; i++) {
637             points.add(new mxPoint(controlPoints.get(2 * i), controlPoints.get(2 * i + 1)));
638         }
639         // ignore destination
640         i++;
641
642         geom.setPoints(points);
643         link.setGeometry(geom);
644         return link;
645     }
646
647 }