001/* A composite actor that applies models dynamically.
002
003 Copyright (c) 2003-2014 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 */
027package ptolemy.actor.lib.hoc;
028
029import java.io.IOException;
030import java.io.StringWriter;
031import java.io.Writer;
032import java.lang.reflect.Constructor;
033import java.util.Iterator;
034
035import ptolemy.actor.CompositeActor;
036import ptolemy.actor.Director;
037import ptolemy.actor.IORelation;
038import ptolemy.actor.TypedCompositeActor;
039import ptolemy.actor.TypedIOPort;
040import ptolemy.actor.lib.Const;
041import ptolemy.data.BooleanToken;
042import ptolemy.data.IntToken;
043import ptolemy.data.StringToken;
044import ptolemy.data.expr.Parameter;
045import ptolemy.data.type.BaseType;
046import ptolemy.kernel.CompositeEntity;
047import ptolemy.kernel.Entity;
048import ptolemy.kernel.Port;
049import ptolemy.kernel.util.Attribute;
050import ptolemy.kernel.util.IllegalActionException;
051import ptolemy.kernel.util.InternalErrorException;
052import ptolemy.kernel.util.NameDuplicationException;
053import ptolemy.kernel.util.Workspace;
054import ptolemy.moml.MoMLChangeRequest;
055import ptolemy.moml.MoMLParser;
056import ptolemy.moml.filter.BackwardCompatibility;
057
058///////////////////////////////////////////////////////////////////
059//// MobileModel
060
061/**
062 This is a composite actor with an input port that accepts MoML descriptions
063 of changes that are applied to the contents. Rather than
064 specified before executing, the inside model can be dynamically changed
065 either locally or remotely. Currently, the model that dynamically applied
066 to this actor is specified by a moml string from the <i>modelString</i>
067 input.
068
069 Currently, it only accepts models with one input and one output, and
070 requires the model name its input port as "input", output port as "output".
071
072 @author Yang Zhao
073 @version $Id$
074 @since Ptolemy II 4.0
075 @Pt.ProposedRating Red (eal)
076 @Pt.AcceptedRating Red (reviewmoderator)
077 */
078public class MobileModel extends TypedCompositeActor {
079    /** Construct an actor in the specified workspace with
080     *  no container and an empty string as a name. You can then change
081     *  the name with setName(). If the workspace argument is null, then
082     *  use the default workspace.
083     *  @param workspace The workspace that will list the actor.
084     *  @exception IllegalActionException If populating the actor with
085     *   ports and parameters fails.
086     */
087    public MobileModel(Workspace workspace) throws IllegalActionException {
088        super(workspace);
089        _init();
090    }
091
092    /** Construct an actor with a name and a container.
093     *  The container argument must not be null, or a
094     *  NullPointerException will be thrown.
095     *  @param container The container.
096     *  @param name The name of this actor.
097     *  @exception IllegalActionException If the container is incompatible
098     *   with this actor or if populating the actor with
099     *   ports and parameters fails.
100     *  @exception NameDuplicationException If the name coincides with
101     *   an actor already in the container.
102     */
103    public MobileModel(CompositeEntity container, String name)
104            throws IllegalActionException, NameDuplicationException {
105        super(container, name);
106        _init();
107    }
108
109    ///////////////////////////////////////////////////////////////////
110    ////                         public variables                  ////
111
112    /** The input port for incoming data to the inside model.
113     */
114    public TypedIOPort input;
115
116    /** The input port for model changing request of the inside model.
117     *  The type is string.
118     */
119    public TypedIOPort modelString;
120
121    /** The output port for the result after firing the inside model
122     * upon the incoming data. Notice that the type is determined by
123     * the type of the <i>defaultValue</i> parameter.
124     */
125    public TypedIOPort output;
126
127    /** The inside Director for executing the inside model.
128     */
129    public Parameter director;
130
131    /** This Parameter specifies whether to replace the previous model
132     *  when there is model changing request or not. The type of this
133     *  parameter is boolean. Select this parameter if it does replace.
134     */
135    public Parameter refresh;
136
137    /** the Parameter specifies whether to connect the input and output
138     * to the inside model. The type of this parameter is boolean.
139     */
140    public Parameter connectPorts;
141
142    /** The default output token when there is no inside model
143     *  defined. The default value is 0, and the default type is
144     *  int. Notice that the type of the output port
145     *  is determined by the type of this parameter.
146     */
147    public Parameter defaultValue;
148
149    ///////////////////////////////////////////////////////////////////
150    ////                         public methods                    ////
151
152    /** Clone the actor into the specified workspace. This calls the
153     *  base class and then sets the value public variable in the new
154     *  object to equal the cloned parameter in that new object.
155     *  @param workspace The workspace for the new object.
156     *  @return A new actor.
157     *  @exception CloneNotSupportedException If a derived class contains
158     *   an attribute that cannot be cloned.
159     */
160    @Override
161    public Object clone(Workspace workspace) throws CloneNotSupportedException {
162        MobileModel newObject = (MobileModel) super.clone(workspace);
163        return newObject;
164    }
165
166    /** Save the model here if there is a new model to apply. and then call
167     *  super.fire().
168     *  @exception IllegalActionException If there is no director, or if
169     *  the director's fire() method throws it, or if the actor is not
170     *  opaque.
171     */
172    @Override
173    public void fire() throws IllegalActionException {
174        if (_debugging) {
175            _debug("Invoking fire");
176        }
177
178        if (modelString.getWidth() < 1) {
179            throw new IllegalActionException(getName() + "need to have"
180                    + "the modelString port be connected");
181        } else if (modelString.hasToken(0)) {
182            StringToken str = null;
183
184            try {
185                str = (StringToken) modelString.get(0);
186
187                _parser.reset();
188
189                CompositeActor model = (CompositeActor) _parser
190                        .parse(str.stringValue());
191                StringWriter writer = new StringWriter();
192
193                try {
194                    model.exportMoML(writer, 1);
195                } catch (Exception ex) {
196                    throw new IllegalActionException(this, ex,
197                            "Failed to export MoML for " + model);
198                }
199
200                String modelMoML = writer.toString();
201
202                if (((BooleanToken) connectPorts.getToken()).booleanValue()) {
203                    _moml = "<group>\n" + modelMoML
204                            + "<relation name=\"newR1\" "
205                            + "class=\"ptolemy.actor.TypedIORelation\">\n"
206                            + "</relation>\n" + "<relation name=\"newR2\" "
207                            + "class=\"ptolemy.actor.TypedIORelation\">\n"
208                            + "</relation>\n"
209                            + "<link port=\"input\" relation=\"newR1\"/>\n"
210                            + "<link port=\"" + model.getName()
211                            + ".input\" relation=\"newR1\"/>\n"
212                            + "<link port=\"" + model.getName()
213                            + ".output\" relation=\"newR2\"/>\n"
214                            + "<link port=\"output\" relation=\"newR2\"/>\n"
215                            + "</group>";
216                } else {
217                    _moml = "<group>\n" + modelMoML + "</group>";
218                }
219            } catch (Exception ex) {
220                if (_debugging) {
221                    _debug("Problem parsing "
222                            + (str == null ? "null" : str.stringValue()));
223                }
224
225                throw new IllegalActionException(this, ex, "Problem parsing "
226                        + (str == null ? "null" : str.stringValue()));
227            }
228        }
229
230        super.fire();
231    }
232
233    /** Return true.
234     */
235    @Override
236    public boolean isOpaque() {
237        return true;
238    }
239
240    /** Update the model here to achieve consistency.
241     *  @exception IllegalActionException If there is no director,
242     *  or if the director's postfire() method throws it, or if this actor
243     *  is not opaque.
244     */
245    @Override
246    public boolean postfire() throws IllegalActionException {
247        if (!_stopRequested && _moml != null) {
248            //remove the old model inside first, if there is one.
249            if (((BooleanToken) refresh.getToken()).booleanValue()) {
250                String delete = _requestToRemoveAll(this);
251                MoMLChangeRequest removeRequest = new MoMLChangeRequest(this, // originator
252                        this, // context
253                        delete, // MoML code
254                        null);
255                requestChange(removeRequest);
256            }
257
258            //update the inside model change.
259            MoMLChangeRequest request2 = new MoMLChangeRequest(this, // originator
260                    this, // context
261                    _moml, // MoML code
262                    null);
263            requestChange(request2);
264
265            if (_debugging) {
266                _debug("issues change request to modify the model");
267            }
268
269            _moml = null;
270
271            // the model topology changes,
272            _modelChanged = true;
273        }
274
275        return super.postfire();
276    }
277
278    /** Return true if the actor either of its input port has token.
279     *  @exception IllegalActionException Not thrown in this base class.
280     */
281    @Override
282    public boolean prefire() throws IllegalActionException {
283        if (_debugging) {
284            _debug("Invoking prefire");
285        }
286
287        // if model just changed, there is a pure event registered
288        // for this model. this model needs firing immediately.
289        // FIXME:
290        // a correct solution is to register a pure event to local event queue
291        // and another pure event to upper level.
292        // Again, this involves hierarchical execution ....
293        if (_modelChanged) {
294            _modelChanged = false;
295            return true;
296        } else {
297            return super.prefire();
298        }
299
300        //if (input.hasToken(0) || modelString.hasToken(0)) {
301        //    return super.prefire();
302        //}
303        //return false;
304    }
305
306    /** preinitialize this actor. create the director as specified
307     *  by the <i>director</i> parameter.
308     *  @exception IllegalActionException If can't create the director, or
309     *  if the director's preinitialize() method throws it.
310     */
311    @Override
312    public void preinitialize() throws IllegalActionException {
313        _director = null;
314        _createDirector();
315
316        _moml = null;
317
318        try {
319            _parser = new MoMLParser();
320            MoMLParser.setMoMLFilters(BackwardCompatibility.allFilters());
321
322            // When no model applied, output the default value.
323            if (((BooleanToken) connectPorts.getToken()).booleanValue()) {
324                Const constActor = new Const(this, "Const");
325                constActor.value
326                        .setExpression(defaultValue.getToken().toString());
327                connect(input, constActor.trigger);
328                connect(constActor.output, output);
329            } //otherwise, do nothing.
330        } catch (Exception ex) {
331            throw new IllegalActionException(this, ex,
332                    "preinitialize() failed");
333        }
334
335        //connect(input, output);
336        super.preinitialize();
337    }
338
339    /** Clean up tha changes that have been made.
340     *  @exception IllegalActionException If there is no director,
341     *  or if the director's wrapup() method throws it, or if this
342     *  actor is not opaque.
343     */
344    @Override
345    public void wrapup() throws IllegalActionException {
346        // clean the inside content.
347        String delete = _requestToRemoveAll(this);
348        MoMLChangeRequest removeRequest = new MoMLChangeRequest(this, // originator
349                this, // context
350                delete, // MoML code
351                null);
352        requestChange(removeRequest);
353        super.wrapup();
354
355        if (_director != null) {
356            try {
357                _director.setContainer(null);
358            } catch (NameDuplicationException ex) {
359                throw new InternalErrorException(ex);
360            }
361        }
362    }
363
364    ///////////////////////////////////////////////////////////////////
365    ////                         protected methods                 ////
366
367    /** Export the moml description of this.
368     */
369    @Override
370    protected void _exportMoMLContents(Writer output, int depth)
371            throws IOException {
372        Iterator attributes = attributeList().iterator();
373
374        while (attributes.hasNext()) {
375            Attribute attribute = (Attribute) attributes.next();
376            attribute.exportMoML(output, depth);
377        }
378
379        Iterator ports = portList().iterator();
380
381        while (ports.hasNext()) {
382            Port port = (Port) ports.next();
383            port.exportMoML(output, depth);
384        }
385
386        output.write(exportLinks(depth, null));
387    }
388
389    ///////////////////////////////////////////////////////////////////
390    ////                         private methods                   ////
391
392    /** create the inside director of this composite actor according
393     * to the <i>director</i> parameter.
394     * @exception IllegalActionException If cannot find the director
395     * class with the specified name by the <i>director</i> parameter,
396     *  or if there is name duplication for the director.
397     */
398    private void _createDirector() throws IllegalActionException {
399        try {
400            String directorName = ((StringToken) director.getToken())
401                    .stringValue();
402            Class directorClass = Class.forName(directorName);
403            Class[] argClasses = new Class[2];
404            argClasses[0] = CompositeEntity.class;
405            argClasses[1] = String.class;
406
407            Constructor constructor = directorClass.getConstructor(argClasses);
408
409            if (constructor != null) {
410                if (_debugging) {
411                    _debug("find constructor for the specified director");
412                }
413
414                Object[] args = new Object[2];
415                args[0] = this;
416                args[1] = "new director";
417                _director = (Director) constructor.newInstance(args);
418
419                if (_debugging) {
420                    _debug("create a instance of the specified director");
421                }
422            }
423        } catch (Exception ex) {
424            throw new IllegalActionException("get an illegal action exception"
425                    + "when create director" + ex);
426        }
427    }
428
429    /** Create the parameters and ports. This method is called by the
430     *  constructors.
431     *  @exception IllegalActionException If creating the parameters
432     *  and ports throws it.
433     */
434    private void _init() throws IllegalActionException {
435        try {
436            input = new TypedIOPort(this, "input", true, false);
437            modelString = new TypedIOPort(this, "modelString", true, false);
438            modelString.setTypeEquals(BaseType.STRING);
439            defaultValue = new Parameter(this, "defaultValue", new IntToken(0));
440            output = new TypedIOPort(this, "output", false, true);
441            output.setTypeAtLeast(defaultValue);
442            refresh = new Parameter(this, "refresh", new BooleanToken(true));
443            refresh.setTypeEquals(BaseType.BOOLEAN);
444            connectPorts = new Parameter(this, "connectPorts",
445                    new BooleanToken(true));
446            connectPorts.setTypeEquals(BaseType.BOOLEAN);
447
448            // create a defaultDirector. Without this director, it may get
449            // an infinite loop when preinitialize, etc. is called in case the
450            // specified director is not successfully constructed. Even when the
451            // specified director is construced successfully, it cannot be removed
452            // in wrapup() without this default director.
453            //The default director may not work when we need multi tokens to fire
454            //the inside model because the receiver it creates is an instance of
455            //Mailbox, which can only hold one token. In this case, specify a proper
456            //director using the <i>director<i> parameter.
457            new Director(this, "defaultDirector");
458            director = new Parameter(this, "director",
459                    new StringToken("ptolemy.actor.Director"));
460            setClassName("ptolemy.actor.lib.hoc.MobileModel");
461        } catch (NameDuplicationException e) {
462            // This should not be thrown.
463            throw new InternalErrorException(e);
464        }
465    }
466
467    /** Construct a MoML string for the composite actor to delete
468     *  of its entities and relations.
469     *  @param actor The composite actor.
470     */
471    private String _requestToRemoveAll(CompositeActor actor) {
472        if (_debugging) {
473            _debug("create request to remove old model");
474        }
475
476        StringBuffer delete = new StringBuffer("<group>");
477
478        // FIXME: Should this also remove class definitions?
479        // To do that, use classDefinitionList().
480        Iterator entities = actor.entityList().iterator();
481
482        while (entities.hasNext()) {
483            Entity entity = (Entity) entities.next();
484            delete.append("<deleteEntity name=\"" + entity.getName()
485                    + "\" class=\"" + entity.getClass().getName() + "\"/>");
486        }
487
488        Iterator relations = actor.relationList().iterator();
489
490        while (relations.hasNext()) {
491            IORelation relation = (IORelation) relations.next();
492            delete.append("<deleteRelation name=\"" + relation.getName()
493                    + "\" class=\"ptolemy.actor.TypedIORelation\"/>");
494        }
495
496        delete.append("</group>");
497        return delete.toString();
498    }
499
500    ///////////////////////////////////////////////////////////////////
501    ////                         private variables                 ////
502
503    /** The inside director.
504     */
505    private Director _director;
506
507    /** The moml string for the inside model that contained by this actor.
508     *
509     */
510    private String _moml;
511
512    /** The moml parser for parsing the moml string of the inside model.
513     *
514     */
515    private MoMLParser _parser;
516
517    private boolean _modelChanged = false;
518}