001/* An actor that clones a composite actor containing itself into itself.
002
003 Copyright (c) 2003-2015 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.domains.ddf.lib;
029
030import java.io.IOException;
031import java.io.Writer;
032import java.util.Arrays;
033import java.util.HashMap;
034import java.util.Iterator;
035
036import ptolemy.actor.CompositeActor;
037import ptolemy.actor.IOPort;
038import ptolemy.actor.IORelation;
039import ptolemy.actor.QueueReceiver;
040import ptolemy.actor.Receiver;
041import ptolemy.actor.TypedCompositeActor;
042import ptolemy.actor.TypedIOPort;
043import ptolemy.actor.util.DFUtilities;
044import ptolemy.data.ArrayToken;
045import ptolemy.data.IntToken;
046import ptolemy.data.Token;
047import ptolemy.data.expr.Parameter;
048import ptolemy.data.expr.StringParameter;
049import ptolemy.data.expr.Variable;
050import ptolemy.data.type.BaseType;
051import ptolemy.data.type.Type;
052import ptolemy.domains.ddf.kernel.DDFDirector;
053import ptolemy.kernel.ComponentEntity;
054import ptolemy.kernel.CompositeEntity;
055import ptolemy.kernel.Port;
056import ptolemy.kernel.util.Attribute;
057import ptolemy.kernel.util.IllegalActionException;
058import ptolemy.kernel.util.InternalErrorException;
059import ptolemy.kernel.util.NameDuplicationException;
060
061/**
062 This actor performs actor recursion dynamically during execution.
063 Upon firing, it clones the composite actor which contains itself and
064 is referred to by the StringParameter <i>recursionActor</i>. It then
065 places the clone inside itself and connects the corresponding ports of
066 both actors. It uses a local DDFDirector to preinitialize the clone and
067 then transfers all tokens contained by input ports of this actor to the
068 connected opaque ports inside. It again uses the local DDFDirector to
069 initialize all actors contained by this actor and classifies each of them
070 such as their enabling and deferrable status. It then transfers all
071 tokens contained by output ports of this actor to the connected opaque
072 ports outside. It finally merges the local DDFDirector with its executive
073 DDFDirector and then removes the local DDFDirector. Thus during execution
074 this actor is fired at most once, after which the executive director
075 directly controls all actors inside. Since there is no type constraint
076 between input ports and output ports of this actor, users have to
077 manually configure types for all outputs of this actor.
078
079 @author Gang Zhou
080 @version $Id$
081 @since Ptolemy II 4.1
082 @Pt.ProposedRating Yellow (zgang)
083 @Pt.AcceptedRating Yellow (cxh)
084 */
085public class ActorRecursion extends TypedCompositeActor {
086    /** Create an ActorRecursion with a name and a container.
087     *  The container argument must not be null, or a NullPointerException
088     *  will be thrown. This actor will use the workspace of the container
089     *  for synchronization and version counts. If the name argument is
090     *  null, then the name is set to the empty string. Increment the
091     *  version of the workspace.
092     *  The actor creates a DDFDirector initially, which will be removed
093     *  toward the end of firing this actor, when the director completes
094     *  its responsibility of preinitializing and initializing the cloned
095     *  composite actor and merging with the outside DDFDirector.
096     *  @param container The container actor.
097     *  @param name The name of this actor.
098     *  @exception IllegalActionException If the container is incompatible
099     *   with this actor.
100     *  @exception NameDuplicationException If the name coincides with
101     *   an actor already in the container.
102     */
103    public ActorRecursion(CompositeEntity container, String name)
104            throws IllegalActionException, NameDuplicationException {
105        super(container, name);
106        new DDFDirector(this, uniqueName("DDFDirector"));
107        recursionActor = new StringParameter(this, "recursionActor");
108    }
109
110    ///////////////////////////////////////////////////////////////////
111    ////                         parameters                        ////
112
113    /** A StringParameter representing the name of the composite actor
114     *  to clone from. The composite actor contains this actor in some
115     *  hierarchy.
116     */
117    public StringParameter recursionActor;
118
119    ///////////////////////////////////////////////////////////////////
120    ////                         public methods                    ////
121
122    /** Clone the composite actor referred to by the StringParameter
123     *  recursionActor into itself. Use a local DDFDirector to
124     *  preinitialize all (just cloned) actors contained by this actor.
125     *  Transfer all tokens contained by input ports of this actor to
126     *  the connected opaque ports inside. Read rate parameters of
127     *  input ports of all actors receiving tokens from this actor and
128     *  propagate these parameters back to the connected output ports
129     *  of this actor. Use the local DDFDirector to initialize all actors
130     *  contained by this actor and classify each of them according to
131     *  their enabling and deferrable status. Transfer all tokens contained
132     *  by output ports of this actor to the connected opaque ports
133     *  outside. Merge the local DDFDirector with the outside DDFDirector
134     *  and finally remove local DDFDirector.
135     *  @exception IllegalActionException If any called method throws
136     *   IllegalActionException.
137     */
138    @Override
139    public void fire() throws IllegalActionException {
140        // Don't call super.fire() here. It does not follow what a regular
141        // composite actor would do.
142        try {
143            // Disable redoing type resolution because type compatibility
144            // has been guaranteed during initialization.
145            ((DDFDirector) getExecutiveDirector()).disableTypeResolution(true);
146            ((DDFDirector) getDirector()).disableTypeResolution(true);
147
148            try {
149                _cloneRecursionActor();
150            } catch (CloneNotSupportedException ex) {
151                throw new IllegalActionException(this, ex, "The actor "
152                        + recursionActor.stringValue() + " cannot be cloned.");
153            }
154
155            getDirector().preinitialize();
156            _transferInputs();
157            _setOutputPortRate();
158            getDirector().initialize();
159            _transferOutputs();
160            ((DDFDirector) getExecutiveDirector())
161                    .merge((DDFDirector) getDirector());
162
163            try {
164                // get rid of the local director.
165                getDirector().setContainer(null);
166            } catch (NameDuplicationException ex) {
167                // should not happen.
168                throw new InternalErrorException(this, ex, null);
169            }
170        } finally {
171            ((DDFDirector) getExecutiveDirector()).disableTypeResolution(false);
172        }
173    }
174
175    /** Initialize this actor. First find the composite actor to be
176     *  cloned, which is the first containing actor up in the hierarchy
177     *  with the name referred to by the StringParameter recursionActor.
178     *  Then check the compatibility of the found composite actor with
179     *  this actor. It is only done once due to the recursive
180     *  nature of this actor.
181     *  @exception IllegalActionException If no actor is found with
182     *   the given name or the found actor is not compatible.
183     */
184    @Override
185    public void initialize() throws IllegalActionException {
186        _searchRecursionActor();
187
188        if (!_isCompatibilityChecked) {
189            _checkCompatibility();
190        }
191    }
192
193    /** Override the base class to return false. Upon seeing the return
194     *  value, its executive director disables this actor and only fires
195     *  all inside actors next time.
196     *  @return false.
197     *  @exception IllegalActionException Not thrown in this base class.
198     */
199    @Override
200    public boolean postfire() throws IllegalActionException {
201        return false;
202    }
203
204    /** Write a MoML description of the contents of this object.
205     *  Override the base class to describe contained ports and
206     *  attributes, but not inside entities, links and relations
207     *  created during execution.
208     *  @param output The output to write to.
209     *  @param depth The depth in the hierarchy, to determine indenting.
210     *  @exception IOException If an I/O error occurs.
211     */
212    @Override
213    protected void _exportMoMLContents(Writer output, int depth)
214            throws IOException {
215        Iterator attributes = attributeList().iterator();
216
217        while (attributes.hasNext()) {
218            Attribute attribute = (Attribute) attributes.next();
219            attribute.exportMoML(output, depth);
220        }
221
222        Iterator ports = portList().iterator();
223
224        while (ports.hasNext()) {
225            Port port = (Port) ports.next();
226            port.exportMoML(output, depth);
227        }
228    }
229
230    /** Notify this actor that the given entity has been added inside it.
231     *  Override the base class to do nothing. This will prevent it from
232     *  calling requestInitialization(Actor) to the cloned composite actor.
233     *  The preinitialization and initialization have already been done in
234     *  the fire() method.
235     */
236    @Override
237    protected void _finishedAddEntity(ComponentEntity entity) {
238    }
239
240    ///////////////////////////////////////////////////////////////////
241    ////                         private methods                   ////
242
243    /** Check the compatibility of the to-be-cloned composite actor with
244     *  this actor. The compatibility criteria are self-evident in the code.
245     *  Basically two actors should look the same from outside and it should
246     *  not violate type constraints by placing the cloned composite actor
247     *  inside this actor and connecting the corresponding ports.
248     *  @exception IllegalActionException If the composite actor to be
249     *   cloned is not compatible with this actor.
250     */
251    private void _checkCompatibility() throws IllegalActionException {
252        if (!(getExecutiveDirector() instanceof DDFDirector)) {
253            throw new IllegalActionException(this,
254                    "The executive Director must be a DDFDirector.");
255        }
256
257        if (_recursionActor.inputPortList().size() != inputPortList().size()
258                || _recursionActor.outputPortList().size() != outputPortList()
259                        .size()) {
260            throw new IllegalActionException(this,
261                    "The recursionActor " + recursionActor.stringValue()
262                            + " must have the same number of input ports and "
263                            + "same number of output ports as this actor.");
264        }
265
266        Iterator ports = portList().iterator();
267
268        while (ports.hasNext()) {
269            TypedIOPort port = (TypedIOPort) ports.next();
270            Object matching = _recursionActor.getPort(port.getName());
271
272            if (matching == null) {
273                throw new IllegalActionException(this,
274                        "Each port of this actor must have the same name as "
275                                + "the matching port of the recursionActor "
276                                + recursionActor.stringValue()
277                                + ". However, the port " + port.getFullName()
278                                + " does not have a matching "
279                                + "port with the same name.");
280            }
281
282            TypedIOPort matchingPort = (TypedIOPort) matching;
283
284            if (port.getWidth() != matchingPort.getWidth()) {
285                throw new IllegalActionException(this,
286                        "The matching ports: " + port.getFullName() + " and "
287                                + matchingPort.getFullName()
288                                + " must have the same width. Port "
289                                + port.getFullName() + "'s width "
290                                + port.getWidth() + " is not equal to "
291                                + matchingPort.getFullName() + "'s width "
292                                + matchingPort.getWidth() + ".");
293            }
294
295            if (port.isInput() && !matchingPort.isInput()
296                    || port.isOutput() && !matchingPort.isOutput()) {
297                throw new IllegalActionException(this,
298                        "The matching ports: " + port.getFullName() + " and "
299                                + matchingPort.getFullName()
300                                + " must be both input ports or output ports.");
301            }
302
303            Type portType = port.getType();
304            Type matchingPortType = matchingPort.getType();
305
306            if (port.isInput() && !matchingPortType.isCompatible(portType)) {
307                throw new IllegalActionException(this,
308                        "The type of the port " + port.getName()
309                                + " of the actor " + getName()
310                                + " must be equal to or less than "
311                                + "that of the matching port.");
312            }
313
314            if (port.isOutput() && !portType.isCompatible(matchingPortType)) {
315                throw new IllegalActionException(this,
316                        "The type of the port " + port.getName()
317                                + " of the actor " + getName()
318                                + " must be equal to or greater than "
319                                + "that of the matching port.");
320            }
321        }
322
323        _isCompatibilityChecked = true;
324    }
325
326    /** Clone the composite actor into the same workspace as this actor.
327     *  Set its container to this actor. Store all tokens contained by
328     *  input ports of this actor. Connect the corresponding ports of
329     *  this actor and the cloned composite actor.
330     *  @exception IllegalActionException If any called method throws
331     *   IllegalActionException, or NameDuplicationException is caught
332     *   in this method.
333     *  @exception CloneNotSupportedException If the CompositeActor cannot
334     *   be cloned.
335     */
336    private void _cloneRecursionActor()
337            throws IllegalActionException, CloneNotSupportedException {
338        try {
339            // Clone the composite actor.
340            CompositeActor clone = (CompositeActor) _recursionActor
341                    .clone(workspace());
342
343            // Place the clone inside this actor.
344            clone.setContainer(this);
345
346            // i is used to generate different names for new relations.
347            int i = 0;
348            Iterator ports = portList().iterator();
349
350            _inputTokensHolder.clear();
351
352            while (ports.hasNext()) {
353                IOPort port = (IOPort) ports.next();
354
355                // Store all tokens contained by input ports of this actor
356                // because connecting ports will result in creating receivers
357                // again and all tokens in the original receivers will be lost.
358                if (port.isInput()) {
359                    int width = port.getWidth();
360                    Receiver[][] receivers = port.getReceivers();
361                    Token[][] tokens = new Token[width][0];
362
363                    for (int channel = 0; channel < width; channel++) {
364                        int size = ((QueueReceiver) receivers[channel][0])
365                                .size();
366                        tokens[channel] = new Token[size];
367
368                        for (int count = 0; count < size; count++) {
369                            tokens[channel][count] = port.get(channel);
370                        }
371                    }
372
373                    _inputTokensHolder.put(port, tokens);
374                }
375
376                // Connect the corresponding ports of both actors.
377                IOPort matchingPort = (IOPort) clone.getPort(port.getName());
378                IORelation relation = (IORelation) newRelation("r_" + i++);
379                port.link(relation);
380                matchingPort.link(relation);
381
382                if (port.isMultiport()) {
383                    relation.setWidth(port.getWidth());
384                }
385            }
386        } catch (NameDuplicationException ex) {
387            throw new IllegalActionException(this, "name duplication.");
388        }
389    }
390
391    /** Get token consumption rate for the given receiver. If the port
392     *  containing the receiver is an input port, return the consumption
393     *  rate for that receiver. If the port containing the receiver is
394     *  an opaque output port, return the production rate for that receiver.
395     *  @param receiver The receiver to get token consumption rate.
396     *  @return The token consumption rate of the given receiver.
397     *  @exception IllegalActionException If any called method throws
398     *   IllegalActionException.
399     */
400    private int _getTokenConsumptionRate(Receiver receiver)
401            throws IllegalActionException {
402        int tokenConsumptionRate;
403
404        IOPort port = receiver.getContainer();
405        Variable rateVariable = null;
406        Token token = null;
407        Receiver[][] portReceivers = null;
408
409        // If DDF domain is inside another domain and the
410        // receiver is contained by an opaque output port...
411        // The default production rate is -1 which means all
412        // tokens in the receiver are transferred to the outside.
413        if (port.isOutput()) {
414            rateVariable = DFUtilities.getRateVariable(port,
415                    "tokenProductionRate");
416            portReceivers = port.getInsideReceivers();
417
418            if (rateVariable == null) {
419                return -1;
420            } else {
421                token = rateVariable.getToken();
422
423                if (token == null) {
424                    return -1;
425                }
426            }
427        }
428
429        if (port.isInput()) {
430            rateVariable = DFUtilities.getRateVariable(port,
431                    "tokenConsumptionRate");
432            portReceivers = port.getReceivers();
433
434            if (rateVariable == null) {
435                return 1;
436            } else {
437                token = rateVariable.getToken();
438
439                if (token == null) {
440                    return 1;
441                }
442            }
443        }
444
445        if (token instanceof ArrayToken) {
446            Token[] tokens = ((ArrayToken) token).arrayValue();
447
448            // Scan the contained receivers of the port to find
449            // out channel index.
450            int channelIndex = 0;
451            foundChannelIndex: for (int m = 0; m < portReceivers.length; m++) {
452                for (int n = 0; n < portReceivers[m].length; n++) {
453                    if (receiver == portReceivers[m][n]) {
454                        channelIndex = m;
455                        break foundChannelIndex;
456                    }
457                }
458            }
459
460            tokenConsumptionRate = ((IntToken) tokens[channelIndex]).intValue();
461        } else {
462            tokenConsumptionRate = ((IntToken) token).intValue();
463        }
464
465        return tokenConsumptionRate;
466    }
467
468    /** Get the to-be-cloned composite actor's name from StringParameter
469     *  recursionActor. Go up in hierarchy and find the first container
470     *  with matching name.
471     *  @exception IllegalActionException If no actor is found with
472     *   the given name.
473     */
474    private void _searchRecursionActor() throws IllegalActionException {
475        String recursionActorValue = recursionActor.stringValue();
476        CompositeActor container = (CompositeActor) getContainer();
477
478        while (container != null) {
479            if (recursionActorValue.equals(container.getName())) {
480                _recursionActor = container;
481                return;
482            } else {
483                container = (CompositeActor) container.getContainer();
484            }
485        }
486
487        throw new IllegalActionException(this,
488                "Can not find a container with name " + recursionActorValue);
489    }
490
491    /** Read the rate parameters of the input ports of all actors receiving
492     *  tokens from this actor and propagate these parameters back to the
493     *  connected output ports of this actor. This is needed because during
494     *  the initialization of the local director, the contained actors only
495     *  see the opaque output ports of this actor instead of the connected
496     *  opaque ports on the outside after the local director is removed.
497     *  To determine the deferrability (see DDFDirector for its definition)
498     *  of the contained actors, which happens during the initialization of
499     *  the local director, we need to propagate these rate parameters back
500     *  to the connected output ports of this actor.
501     *  @exception IllegalActionException If any called method throws
502     *   IllegalActionException.
503     */
504    private void _setOutputPortRate() throws IllegalActionException {
505        Iterator outputPorts = outputPortList().iterator();
506
507        while (outputPorts.hasNext()) {
508            IOPort outputPort = (IOPort) outputPorts.next();
509            int[] productionRate = new int[outputPort.getWidthInside()];
510
511            // If there are more inside channels than outside channels,
512            // it sets default rates of these extra inside channels to
513            // be -1 which then won't cause an upstream actor to be
514            // deferrable because any tokens on these extra channels
515            // are discarded.
516            Arrays.fill(productionRate, -1);
517
518            Receiver[][] farReceivers = outputPort.getRemoteReceivers();
519
520            for (int i = 0; i < farReceivers.length; i++) {
521                if (i < outputPort.getWidthInside()) {
522                    for (int j = 0; j < farReceivers[i].length; j++) {
523                        QueueReceiver farReceiver = (QueueReceiver) farReceivers[i][j];
524                        int rate = _getTokenConsumptionRate(farReceiver);
525
526                        // According to the definition of deferrability,
527                        // we need to find the minimum rate associated with
528                        // this channel. -1 is actually the largest rate in
529                        // some sense.
530                        if (productionRate[i] < 0) {
531                            productionRate[i] = rate;
532                        } else if (rate >= 0 && rate < productionRate[i]) {
533                            productionRate[i] = rate;
534                        }
535                    }
536                }
537            }
538
539            IntToken[] productionRateToken = new IntToken[outputPort
540                    .getWidthInside()];
541
542            for (int i = 0; i < outputPort.getWidthInside(); i++) {
543                productionRateToken[i] = new IntToken(productionRate[i]);
544            }
545
546            // Since this is output port, we look for token production rate
547            // instead of token consumption rate.
548            Variable rateVariable = DFUtilities.getRateVariable(outputPort,
549                    "tokenProductionRate");
550
551            if (rateVariable == null) {
552                try {
553                    rateVariable = new Parameter(outputPort,
554                            "tokenProductionRate");
555                } catch (NameDuplicationException ex) {
556                    //should not happen.
557                    throw new InternalErrorException(this, ex, null);
558                }
559            }
560
561            rateVariable.setToken(
562                    new ArrayToken(BaseType.INT, productionRateToken));
563        }
564    }
565
566    /** Transfer all tokens contained by input ports of this actor and
567     *  stored by an internal variable to the connected opaque ports inside.
568     *  We cannot use transferInputs(IOPort) of the local director for two
569     *  reason. One is that tokens are now stored by an internal variable.
570     *  The other is that we have to transfer <i>all</i> tokens instead of
571     *  those specified by rate parameters because all input ports will
572     *  become transparent after this firing.
573     *  @exception IllegalActionException If conversion to the type of
574     *   the destination port cannot be done.
575     */
576    private void _transferInputs() throws IllegalActionException {
577        Iterator inputPorts = inputPortList().iterator();
578
579        while (inputPorts.hasNext()) {
580            IOPort inputPort = (IOPort) inputPorts.next();
581            Token[][] tokens = (Token[][]) _inputTokensHolder.get(inputPort);
582
583            for (int channel = 0; channel < inputPort.getWidth(); channel++) {
584                for (int j = 0; j < tokens[channel].length; j++) {
585                    inputPort.sendInside(channel, tokens[channel][j]);
586                }
587            }
588        }
589    }
590
591    /** Transfer all tokens contained by output ports of this actor to the
592     *  connected opaque ports outside.We cannot use transferOutputs(IOPort)
593     *  of the local director because we have to transfer <i>all</i> tokens
594     *  instead of those specified by rate parameters because all output
595     *  ports will become transparent after this firing.
596     *  @exception IllegalActionException If conversion to the type of
597     *   the destination port cannot be done.
598     */
599    private void _transferOutputs() throws IllegalActionException {
600        Iterator outputPorts = outputPortList().iterator();
601
602        while (outputPorts.hasNext()) {
603            IOPort outputPort = (IOPort) outputPorts.next();
604
605            for (int i = 0; i < outputPort.getWidthInside(); i++) {
606                while (outputPort.hasNewTokenInside(i)) {
607                    outputPort.send(i, outputPort.getInside(i));
608                }
609            }
610        }
611    }
612
613    ///////////////////////////////////////////////////////////////////
614    ////                         private variables                 ////
615
616    /** The composite actor to be cloned.
617     */
618    private CompositeActor _recursionActor = null;
619
620    /** A flag indicating if the compatibility of the to-be-cloned composite
621     *  actor has been checked. It is set to true after checking so that
622     *  checking is performed only once during the execution of the model.
623     */
624    private boolean _isCompatibilityChecked = false;
625
626    /** A HashMap to store tokens of the input ports.
627     */
628    private HashMap _inputTokensHolder = new HashMap();
629}