001/* A TypedCompositeActor that creates multiple instances of itself
002 during the preinitialize phase of model execution.
003
004 Copyright (c) 2003-2014 The Regents of the University of California and
005 Research in Motion Limited.
006 All rights reserved.
007 Permission is hereby granted, without written agreement and without
008 license or royalty fees, to use, copy, modify, and distribute this
009 software and its documentation for any purpose, provided that the above
010 copyright notice and the following two paragraphs appear in all copies
011 of this software.
012
013 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA OR RESEARCH IN MOTION
014 LIMITED BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
015 INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
016 SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA
017 OR RESEARCH IN MOTION LIMITED HAVE BEEN ADVISED OF THE POSSIBILITY OF
018 SUCH DAMAGE.
019
020 THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION LIMITED
021 SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
022 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
023 PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
024 BASIS, AND THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION
025 LIMITED HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 ENHANCEMENTS, OR MODIFICATIONS.
027
028 PT_COPYRIGHT_VERSION_2
029 COPYRIGHTENDKEY
030
031 */
032package ptolemy.actor.lib.hoc;
033
034import java.util.Iterator;
035import java.util.LinkedList;
036import java.util.List;
037
038import ptolemy.actor.TypedCompositeActor;
039import ptolemy.actor.TypedIOPort;
040import ptolemy.actor.TypedIORelation;
041import ptolemy.data.BooleanToken;
042import ptolemy.data.IntToken;
043import ptolemy.data.expr.Parameter;
044import ptolemy.data.type.BaseType;
045import ptolemy.kernel.CompositeEntity;
046import ptolemy.kernel.util.Attribute;
047import ptolemy.kernel.util.IllegalActionException;
048import ptolemy.kernel.util.InternalErrorException;
049import ptolemy.kernel.util.KernelException;
050import ptolemy.kernel.util.Location;
051import ptolemy.kernel.util.NameDuplicationException;
052import ptolemy.kernel.util.Workspace;
053
054// Note: the (at least) single-space is needed in the javadoc below to
055// protect emacs' comment text formatting from a "{@link..." appearing
056// at the start of a line and disabling paragraph reformatting and
057// line-wrap (zk)
058///////////////////////////////////////////////////////////////////
059//// MultiInstanceComposite
060
061/**
062 A {@link ptolemy.actor.TypedCompositeActor} that creates multiple
063 instances of itself during the preinitialize phase of model execution.<p>
064
065 A MultiInstanceComposite actor may be used to instantiate {@link
066 #nInstances} identical processing blocks within a model. This actor
067 (the "master") creates {@link #nInstances}&nbsp;-&nbsp;1 additional
068 instances (clones) of itself during the {@link #preinitialize()} phase
069 of model execution and destroys these additional instances during model
070 {@link #wrapup()}. MultiInstanceComposite <em>must be opaque</em> (have
071 a director), so that its Actor interface methods (preinitialize(), ...,
072 wrapup()) are invoked during model initialization. Each instance may
073 refer to its {@link #instance} [0..{@link #nInstances}-1] parameter
074 which is set automatically by the master if it needs to know its
075 instance number.<p>
076
077 MultiInstanceComposite <em>input</em> ports must not be multiports (for
078 now) and may be connected to multiports or regular ports.  During
079 preinitialize(), the master MultiInstanceComposite determines how its
080 input ports are connected, and creates additional relations in its
081 container (the model it is embedded in) to connect the input ports of
082 its clones (instances) to the same output port if that port is a
083 multiport.  If that output port is a regular port, the clone's input
084 port is linked to the already existing relation between that output
085 port and the master's input port.  MultiInstanceComposite
086 <em>output</em> ports must not be multiports (for now) and must be
087 connected to input multiports. The master MultiInstanceComposite
088 creates additional relations to connect the output ports of its clones
089 to the input port. Finally, after all these connections are made, the
090 master's preinitialize() calls preinitialize() of the clones.<p>
091
092 From here on until wrapup(), nothing special happens. Type resolution
093 occurs on all instances in the modified model, so does initialize() and
094 the computation of schedules by directors of the master and clones.<p>
095
096 During model wrapup(), the master MultiContextComposite deletes any
097 relations created, unlinks any ports if needed, and deletes the clones
098 it created. To re-synchronize vergil's model graph, an empty
099 ChangeRequest is also queued with the Manager.<p>
100
101 Actor parameters inside MultiInstanceComposite may refer to parameters
102 of the container model. This presents a problem during cloning() and
103 wrapup() where the container model's parameters are not in scope during
104 the clone's validateSettables() (unless the MultiInstanceComposite is
105 built as a moml class having its own set of parameters). This problem
106 is for now solved by providing a temporary scope copy using a
107 ScopeExtendingAttribute for the cloning() and wrapup() phases of the
108 clones.<p>
109
110 @author Zoltan Kemenczy, Sean Simmons, Research In Motion Limited
111 @version $Id$
112 @since Ptolemy II 4.0
113 @Pt.ProposedRating Red (zkemenczy)
114 @Pt.AcceptedRating Red (cxh)
115 */
116public class MultiInstanceComposite extends TypedCompositeActor {
117    /** Construct a MultiInstanceComposite actor in the specified
118     *  workspace with no container and an empty string as a name.
119     *  @param workspace The workspace of this object.
120     */
121    public MultiInstanceComposite(Workspace workspace) {
122        super(workspace);
123        _construct();
124    }
125
126    /** Construct a MultiInstanceComposite actor with the given container
127     *  and name.
128     *  @param container The container.
129     *  @param name The name of this actor.
130     *  @exception IllegalActionException If the actor cannot be contained
131     *   by the proposed container.
132     *  @exception NameDuplicationException If the container already has an
133     *   actor with this name.
134     */
135    public MultiInstanceComposite(CompositeEntity container, String name)
136            throws IllegalActionException, NameDuplicationException {
137        super(container, name);
138        _construct();
139    }
140
141    ///////////////////////////////////////////////////////////////////
142    ////                     ports and parameters                  ////
143
144    /** The total number of instances to instantiate including instance
145     * 0 (the master copy).
146     */
147    public Parameter nInstances;
148
149    /** The index of this instance. */
150    public Parameter instance;
151
152    /** If true, show the clones. */
153    public Parameter showClones;
154
155    /** Clone a "master copy" of this actor into the specified workspace
156     *  - note that this is not used for creating the additional
157     *  instances.
158     *  @param workspace The workspace for the new object.
159     *  @return A new actor.
160     *  @exception CloneNotSupportedException If a derived class contains
161     *   an attribute that cannot be cloned.
162     */
163    @Override
164    public Object clone(Workspace workspace) throws CloneNotSupportedException {
165        MultiInstanceComposite newObject = (MultiInstanceComposite) super.clone(
166                workspace);
167        newObject._isMasterCopy = _isMasterCopy;
168        return newObject;
169    }
170
171    /** Call the base class to perform standard preinitialize(), and, if
172     * this is the master copy, proceed to create {@link #nInstances}-1
173     * additional copies, and link them to the same input/output ports
174     * this master is connected to.
175     *
176     * @exception IllegalActionException If cloning the additional
177     * copies fails, or if any ports are not connected to multiports.
178     */
179    @Override
180    public void preinitialize() throws IllegalActionException {
181        if (!_isMasterCopy) {
182            //All initialization happens in the master.
183            return;
184        }
185        super.preinitialize();
186
187        // Master only from here on
188        if (getDirector() == null || getDirector().getContainer() != this) {
189            throw new IllegalActionException(this,
190                    getFullName() + "No director.");
191        }
192        // Get write permission on the workspace.
193        try {
194            _workspace.getWriteAccess();
195
196            int N = ((IntToken) nInstances.getToken()).intValue();
197
198            // Make sure instance is correct (ignore any user errors :)
199            instance.setToken(new IntToken(0));
200
201            TypedCompositeActor container = (TypedCompositeActor) getContainer();
202
203            // We first remove the superfluous clones
204            while (_clones.size() > N - 1) {
205                MultiInstanceComposite clone = _clones.get(N - 1);
206                Iterator<?> ports = clone.portList().iterator();
207                while (ports.hasNext()) {
208                    TypedIOPort port = (TypedIOPort) ports.next();
209                    Iterator<?> relations = port.linkedRelationList()
210                            .iterator();
211                    while (relations.hasNext()) {
212                        TypedIORelation relation = (TypedIORelation) relations
213                                .next();
214
215                        // Use a different criterion to delete relation
216                        // since the old one wouldn't work any more.
217                        // Added by Gang Zhou.
218                        TypedIOPort mirrorPort = (TypedIOPort) getPort(
219                                port.getName());
220
221                        if (!port.isDeeplyConnected(mirrorPort)) {
222                            //if (relation.linkedPortList().size() <= 2) {
223                            // Delete the relation that was created in
224                            // preinitialize()
225                            try {
226                                if (_debugging) {
227                                    _debug("Deleting "
228                                            + relation.getFullName());
229                                }
230                                relation.setContainer(null);
231                            } catch (NameDuplicationException ex) {
232                                throw new InternalErrorException(ex);
233                            }
234                        } else {
235                            // Unlink the clone's port from the relation
236                            if (_debugging) {
237                                _debug("Unlinking " + port.getFullName()
238                                        + " from " + relation.getFullName());
239                            }
240                            port.unlink(relation);
241                        }
242                    }
243                }
244
245                // Now delete the clone itself
246                try {
247                    if (_debugging) {
248                        _debug("Deleting " + clone.getFullName());
249                    }
250                    clone.setContainer(null);
251                } catch (NameDuplicationException ex) {
252                    throw new InternalErrorException(ex);
253                }
254                _clones.remove(N - 1);
255            }
256
257            // Initialize the clones
258            for (MultiInstanceComposite clone : _clones) {
259                clone._preinitClone();
260            }
261
262            // Now instantiate the clones and connect them to the model
263            for (int i = _clones.size() + 1; i < N; i++) {
264                MultiInstanceComposite clone = null;
265
266                try {
267                    clone = (MultiInstanceComposite) _cloneClone(
268                            container.workspace());
269                } catch (CloneNotSupportedException ex) {
270                    throw new IllegalActionException(this, ex, "Clone failed.");
271                }
272
273                try {
274                    // See if we should draw the clone.
275                    if (((BooleanToken) showClones.getToken()).booleanValue()) {
276                        // Draw the clone beneath the master's location.
277                        Location location = (Location) clone
278                                .getAttribute("_location");
279                        if (location != null) {
280                            double coords[] = location.getLocation();
281                            coords[1] += 60 * i;
282                            location.setLocation(coords);
283                        }
284                    } else {
285                        // Hide the clone.
286                        try {
287                            new Attribute(clone, "_hide");
288                        } catch (KernelException e) {
289                            // This should not occur.  Ignore if it does
290                            // since the only downside is that the actor is
291                            // rendered.
292                        }
293                    }
294
295                    clone.setName(getName() + "_" + i);
296                    clone.setContainer(container);
297                    clone.validateSettables();
298
299                    if (_debugging) {
300                        _debug("Cloned: " + clone.getFullName());
301                    }
302
303                    // Clone all attached relations and link to same
304                    // ports as the originals
305                    Iterator<?> ports = portList().iterator();
306
307                    while (ports.hasNext()) {
308                        TypedIOPort port = (TypedIOPort) ports.next();
309                        TypedIOPort newPort = (TypedIOPort) clone
310                                .getPort(port.getName());
311                        List<?> relations = port.linkedRelationList();
312                        if (relations == null || relations.size() < 1) {
313                            continue;
314                        }
315                        if (relations.size() > 1) {
316                            throw new IllegalActionException(port,
317                                    "Can be linked to one relation only");
318                        }
319
320                        TypedIORelation relation = (TypedIORelation) relations
321                                .get(0);
322                        TypedIORelation oldRelation = relation;
323
324                        // Iterate through other ports that are connected to this port.
325                        // If a connected port is a multiport, then we create
326                        // a new relation to connect the clone's newPort
327                        // to that multiport. Otherwise, we use the
328                        // relation above to link newPort.
329                        Iterator<?> otherPorts = relation.linkedPortList(port)
330                                .iterator();
331
332                        // Added by Gang Zhou. If a port is connected to
333                        // multiple other ports (through a single relation),
334                        // only one relation should be created.
335                        boolean isRelationCreated = false;
336                        boolean isPortLinked = false;
337
338                        while (otherPorts.hasNext()) {
339                            TypedIOPort otherPort = (TypedIOPort) otherPorts
340                                    .next();
341
342                            if (port.isOutput() && !otherPort.isMultiport()) {
343                                throw new IllegalActionException(this,
344                                        getFullName() + ".preinitialize(): "
345                                                + "output port "
346                                                + port.getName()
347                                                + "must be connected to a multi-port");
348                            }
349
350                            // Modified by Gang Zhou so that the port can
351                            // be connected to the otherPort either from inside
352                            // or from outside.
353                            boolean isInsideLinked = otherPort
354                                    .isInsideGroupLinked(oldRelation);
355
356                            if (port.isInput()
357                                    && (!isInsideLinked && otherPort.isOutput()
358                                            || isInsideLinked
359                                                    && otherPort.isInput())
360                                    || port.isOutput() && (!isInsideLinked
361                                            && otherPort.isInput()
362                                            || isInsideLinked
363                                                    && otherPort.isOutput())) {
364                                if (otherPort.isMultiport()) {
365                                    if (!isRelationCreated) {
366                                        relation = new TypedIORelation(
367                                                container,
368                                                "r_" + getName() + "_" + i + "_"
369                                                        + port.getName());
370                                        relation.setPersistent(false);
371                                        isRelationCreated = true;
372
373                                        if (_debugging) {
374                                            _debug(port.getFullName()
375                                                    + ": created relation "
376                                                    + relation.getFullName());
377                                        }
378                                    }
379
380                                    otherPort.link(relation);
381                                }
382
383                                if (!isPortLinked) {
384                                    newPort.link(relation);
385                                    isPortLinked = true;
386
387                                    if (_debugging) {
388                                        _debug(newPort.getFullName()
389                                                + ": linked to "
390                                                + relation.getFullName());
391                                    }
392                                }
393                            }
394                        }
395                    }
396
397                    // Let the clone know which instance it is
398                    clone.instance.setToken(new IntToken(i));
399                } catch (NameDuplicationException ex) {
400                    throw new IllegalActionException(this, ex,
401                            "couldn't clone/create");
402                }
403
404                // The clone is preinitialized only if it has just been
405                // created, otherwise the current director schedule will
406                // initialize it.
407                clone._preinitClone();
408                _clones.add(clone);
409            }
410        } finally {
411            _workspace.doneWriting();
412        }
413    }
414
415    ///////////////////////////////////////////////////////////////////
416    ////                         private methods                   ////
417
418    /** Clone to create a copy of the master copy. */
419    private Object _cloneClone(Workspace workspace)
420            throws CloneNotSupportedException {
421        MultiInstanceComposite newObject = (MultiInstanceComposite) super.clone(
422                workspace);
423        newObject._isMasterCopy = false;
424        // The following is necessary in case an exception occurs
425        // during execution because then wrapup might not properly complete.
426        newObject.setPersistent(false);
427
428        return newObject;
429    }
430
431    private void _construct() {
432        // The base class identifies the class name as TypedCompositeActor
433        // irrespective of the actual class name.  We override that here.
434        setClassName("ptolemy.actor.lib.hoc.MultiInstanceComposite");
435
436        try {
437            nInstances = new Parameter(this, "nInstances", new IntToken(1));
438            instance = new Parameter(this, "instance", new IntToken(0));
439            showClones = new Parameter(this, "showClones",
440                    new BooleanToken(false));
441            showClones.setTypeEquals(BaseType.BOOLEAN);
442        } catch (Exception ex) {
443            throw new InternalErrorException(this, ex,
444                    "Problem setting up instances or nInstances parameter");
445        }
446
447        _isMasterCopy = true;
448        _attachText("_iconDescription",
449                "<svg>\n" + "<rect x=\"-20\" y=\"-10\" width=\"60\" "
450                        + "height=\"40\" style=\"fill:red\"/>\n"
451                        + "<rect x=\"-18\" y=\"-8\" width=\"56\" "
452                        + "height=\"36\" style=\"fill:lightgrey\"/>\n"
453                        + "<rect x=\"-25\" y=\"-15\" width=\"60\" "
454                        + "height=\"40\" style=\"fill:red\"/>\n"
455                        + "<rect x=\"-23\" y=\"-13\" width=\"56\" "
456                        + "height=\"36\" style=\"fill:lightgrey\"/>\n"
457                        + "<rect x=\"-30\" y=\"-20\" width=\"60\" "
458                        + "height=\"40\" style=\"fill:red\"/>\n"
459                        + "<rect x=\"-28\" y=\"-18\" width=\"56\" "
460                        + "height=\"36\" style=\"fill:lightgrey\"/>\n"
461                        + "<rect x=\"-15\" y=\"-10\" width=\"10\" height=\"8\" "
462                        + "style=\"fill:white\"/>\n"
463                        + "<rect x=\"-15\" y=\"2\" width=\"10\" height=\"8\" "
464                        + "style=\"fill:white\"/>\n"
465                        + "<rect x=\"5\" y=\"-4\" width=\"10\" height=\"8\" "
466                        + "style=\"fill:white\"/>\n"
467                        + "<line x1=\"-5\" y1=\"-6\" x2=\"0\" y2=\"-6\"/>"
468                        + "<line x1=\"-5\" y1=\"6\" x2=\"0\" y2=\"6\"/>"
469                        + "<line x1=\"0\" y1=\"-6\" x2=\"0\" y2=\"6\"/>"
470                        + "<line x1=\"0\" y1=\"0\" x2=\"5\" y2=\"0\"/>"
471                        + "</svg>\n");
472    }
473
474    private void _preinitClone() throws IllegalActionException {
475        super.preinitialize();
476    }
477
478    ///////////////////////////////////////////////////////////////////
479    ////                         private variables                 ////
480    private List<MultiInstanceComposite> _clones = new LinkedList<MultiInstanceComposite>();
481
482    private boolean _isMasterCopy = false;
483
484    //private String _scopeExtendingAttributeName = "_micScopeExtender";
485}