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