001/* A state in an FSMActor.
002
003 Copyright (c) 1999-2018 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.domains.modal.kernel;
028
029import java.io.IOException;
030import java.io.StringReader;
031import java.io.Writer;
032import java.net.URL;
033import java.util.Iterator;
034import java.util.LinkedList;
035import java.util.List;
036import java.util.StringTokenizer;
037
038import ptolemy.actor.Actor;
039import ptolemy.actor.DesignPatternGetMoMLAction;
040import ptolemy.actor.TypedActor;
041import ptolemy.actor.TypedCompositeActor;
042import ptolemy.data.BooleanToken;
043import ptolemy.data.expr.Parameter;
044import ptolemy.data.expr.SingletonParameter;
045import ptolemy.data.type.BaseType;
046import ptolemy.domains.modal.modal.ModalModel;
047import ptolemy.domains.modal.modal.ModalRefinement;
048import ptolemy.kernel.ComponentEntity;
049import ptolemy.kernel.ComponentPort;
050import ptolemy.kernel.CompositeEntity;
051import ptolemy.kernel.Entity;
052import ptolemy.kernel.Port;
053import ptolemy.kernel.Relation;
054import ptolemy.kernel.util.Attribute;
055import ptolemy.kernel.util.ChangeRequest;
056import ptolemy.kernel.util.DropTargetHandler;
057import ptolemy.kernel.util.Flowable;
058import ptolemy.kernel.util.IllegalActionException;
059import ptolemy.kernel.util.InternalErrorException;
060import ptolemy.kernel.util.NameDuplicationException;
061import ptolemy.kernel.util.Nameable;
062import ptolemy.kernel.util.NamedObj;
063import ptolemy.kernel.util.Settable;
064import ptolemy.kernel.util.SingletonAttribute;
065import ptolemy.kernel.util.StringAttribute;
066import ptolemy.kernel.util.Workspace;
067import ptolemy.moml.MoMLChangeRequest;
068import ptolemy.moml.MoMLParser;
069
070///////////////////////////////////////////////////////////////////
071//// State
072
073/**
074 A State has two ports: one for linking incoming transitions, the other for
075 outgoing transitions. When the FSMActor containing a state is the mode
076 controller of a modal model, the state can be refined by one or more
077 instances of TypedActor. The refinements must have the same container
078 as the FSMActor. During execution of a modal model, only the mode
079 controller and the refinements of the current state of the mode
080 controller react to input to the modal model and produce
081 output. The outgoing transitions from a state are either preemptive or
082 non-preemptive. When a modal model is fired, if a preemptive transition
083 from the current state of the mode controller is chosen, the refinements of
084 the current state are not fired. Otherwise the refinements are fired before
085 choosing a non-preemptive transition.
086
087 @author Xiaojun Liu
088 @version $Id$
089 @since Ptolemy II 8.0
090 @Pt.ProposedRating Yellow (liuxj)
091 @Pt.AcceptedRating Yellow (kienhuis)
092 @see Transition
093 @see FSMActor
094 @see FSMDirector
095 */
096public class State extends ComponentEntity
097        implements ConfigurableEntity, DropTargetHandler, Flowable {
098
099    /** Construct a state with the given name contained by the specified
100     *  composite entity. The container argument must not be null, or a
101     *  NullPointerException will be thrown. This state will use the
102     *  workspace of the container for synchronization and version counts.
103     *  If the name argument is null, then the name is set to the empty
104     *  string.
105     *  Increment the version of the workspace.
106     *  This constructor write-synchronizes on the workspace.
107     *  @param container The container.
108     *  @param name The name of the state.
109     *  @exception IllegalActionException If the state cannot be contained
110     *   by the proposed container.
111     *  @exception NameDuplicationException If the name coincides with
112     *   that of an entity already in the container.
113     */
114    public State(CompositeEntity container, String name)
115            throws IllegalActionException, NameDuplicationException {
116        super(container, name);
117        incomingPort = new ComponentPort(this, "incomingPort");
118        outgoingPort = new ComponentPort(this, "outgoingPort");
119        refinementName = new StringAttribute(this, "refinementName");
120
121        _attachText("_iconDescription", "<svg>\n"
122                + "<circle cx=\"0\" cy=\"0\" r=\"20\" style=\"fill:white\"/>\n"
123                + "</svg>\n");
124
125        // Specify that the name should be centered in graphical displays.
126        SingletonParameter center = new SingletonParameter(this, "_centerName");
127        center.setExpression("true");
128        center.setVisibility(Settable.EXPERT);
129
130        isInitialState = new Parameter(this, "isInitialState");
131        isInitialState.setTypeEquals(BaseType.BOOLEAN);
132        isInitialState.setExpression("false");
133        // If this is the only state in the container, then make
134        // it the initial state.  For backward compatibility,
135        // we do not do this if the container has a non-empty
136        // value for initialStateName. In that case, we
137        // make this the initial state if its name matches that
138        // name.
139        String initialStateName = "";
140        if (container instanceof FSMActor) {
141            initialStateName = ((FSMActor) container).initialStateName
142                    .getExpression().trim();
143            // If the container is an FSMActor, and this is the only
144            // state in it, then make the state an initial state.
145            // Also, for backward compatibility, if the container
146            // has an initialStateName value, and that value matches
147            // the name of this state, set the isInitialState parameter.
148            if (initialStateName.equals("")) {
149                if (container.entityList(State.class).size() == 1) {
150                    isInitialState.setExpression("true");
151                    // Have to force this to export to MoML, since
152                    // the true value will otherwise be seen as the default.
153                    isInitialState.setPersistent(true);
154                }
155            } else {
156                // Backward compatibility scenario. The initial state
157                // was given by a name in the container.
158                if (initialStateName.equals(name)) {
159                    isInitialState.setExpression("true");
160                    // Have to force this to export to MoML, since
161                    // the true value will otherwise be seen as the default.
162                    isInitialState.setPersistent(true);
163                }
164            }
165        }
166        isFinalState = new Parameter(this, "isFinalState");
167        isFinalState.setTypeEquals(BaseType.BOOLEAN);
168        isFinalState.setExpression("false");
169
170        saveRefinementsInConfigurer = new Parameter(this,
171                "saveRefinementsInConfigurer");
172        saveRefinementsInConfigurer.setTypeEquals(BaseType.BOOLEAN);
173        saveRefinementsInConfigurer.setVisibility(Settable.EXPERT);
174        saveRefinementsInConfigurer.setExpression("false");
175        saveRefinementsInConfigurer.setPersistent(false);
176
177        ContainmentExtender containmentExtender = new ContainmentExtender(this,
178                "_containmentExtender");
179        containmentExtender.setPersistent(false);
180
181        _configurer = new Configurer(workspace());
182    }
183
184    ///////////////////////////////////////////////////////////////////
185    ////                         ports and parameters              ////
186
187    /** The port linking incoming transitions.
188     */
189    public ComponentPort incomingPort = null;
190
191    /** An indicator of whether this state is a final state.
192     *  This is a boolean that defaults to false. Setting it to true
193     *  will cause the containing FSMActor to return false from its
194     *  postfire() method, which indicates to the director that the
195     *  FSMActor should not be fired again.
196     */
197    public Parameter isFinalState;
198
199    /** An indicator of whether this state is the initial state.
200     *  This is a boolean that defaults to false, unless this state
201     *  is the only one in the container, in which case it defaults
202     *  to true. Setting it to true
203     *  will cause this parameter to become false for whatever
204     *  other state is currently the initial state in the same
205     *  container.
206     */
207    public Parameter isInitialState;
208
209    /** The port linking outgoing transitions.
210     */
211    public ComponentPort outgoingPort = null;
212
213    /** Attribute specifying one or more names of refinements. The
214     *  refinements must be instances of TypedActor and have the same
215     *  container as the FSMActor containing this state, otherwise
216     *  an exception will be thrown when getRefinement() is called.
217     *  Usually, the refinement is a single name. However, if a
218     *  comma-separated list of names is provided, then all the specified
219     *  refinements will be executed.
220     *  This attribute has a null expression or a null string as
221     *  expression when the state is not refined.
222     */
223    public StringAttribute refinementName = null;
224
225    /** A boolean attribute to decide refinements of this state should be
226     *  exported as configurations of this state or not.
227     */
228    public Parameter saveRefinementsInConfigurer;
229
230    ///////////////////////////////////////////////////////////////////
231    ////                         public methods                    ////
232
233    /** React to a change in an attribute. If the changed attribute is
234     *  the <i>refinementName</i> attribute, record the change but do
235     *  not check whether there is a TypedActor with the specified name
236     *  and having the same container as the FSMActor containing this
237     *  state.
238     *  @param attribute The attribute that changed.
239     *  @exception IllegalActionException If thrown by the superclass
240     *   attributeChanged() method.
241     */
242    @Override
243    public void attributeChanged(Attribute attribute)
244            throws IllegalActionException {
245        super.attributeChanged(attribute);
246
247        if (attribute == refinementName) {
248            _refinementVersion = -1;
249        } else if (attribute == isInitialState) {
250            NamedObj container = getContainer();
251            // Container might not be an FSMActor if, for example,
252            // the state is in a library.
253            if (container instanceof FSMActor) {
254                if (((BooleanToken) isInitialState.getToken()).booleanValue()) {
255                    // If there is a previous initial state, unset its
256                    // isInitialState parameter.
257                    if (((FSMActor) container)._initialState != null
258                            && ((FSMActor) container)._initialState != this) {
259                        ((FSMActor) container)._initialState.isInitialState
260                                .setToken("false");
261                    }
262                    ((FSMActor) container)._initialState = this;
263                    // If the initial state name of the container is set,
264                    // unset it.
265                    String name = ((FSMActor) container).initialStateName
266                            .getExpression();
267                    if (!name.equals("")) {
268                        ((FSMActor) container).initialStateName
269                                .setExpression("");
270                    }
271                }
272            }
273        }
274    }
275
276    /** Clone the state into the specified workspace. This calls the
277     *  base class and then sets the attribute and port public members
278     *  to refer to the attributes and ports of the new state.
279     *  @param workspace The workspace for the new state.
280     *  @return A new state.
281     *  @exception CloneNotSupportedException If a derived class contains
282     *   an attribute that cannot be cloned.
283     */
284    @Override
285    public Object clone(Workspace workspace) throws CloneNotSupportedException {
286        State newObject = (State) super.clone(workspace);
287        newObject.incomingPort = (ComponentPort) newObject
288                .getPort("incomingPort");
289        newObject.outgoingPort = (ComponentPort) newObject
290                .getPort("outgoingPort");
291        newObject.refinementName = (StringAttribute) newObject
292                .getAttribute("refinementName");
293        newObject._configurer = new Configurer(newObject.workspace());
294        newObject._nonpreemptiveTransitionList = new LinkedList();
295        newObject._preemptiveTransitionList = new LinkedList();
296        newObject._refinementVersion = -1;
297        newObject._transitionListVersion = -1;
298        newObject._nonErrorNonTerminationTransitionList = new LinkedList();
299        return newObject;
300    }
301
302    /** Configure the object with data from the specified input source
303     *  (a URL) and/or textual data.  The object should interpret the
304     *  source first, if it is specified, followed by the literal text,
305     *  if that is specified.  The new configuration should usually
306     *  override any old configuration wherever possible, in order to
307     *  ensure that the current state can be successfully retrieved.
308     *  <p>
309     *  This method is defined to throw a very general exception to allow
310     *  classes that implement the interface to use whatever exceptions
311     *  are appropriate.
312     *  @param base The base relative to which references within the input
313     *   are found, or null if this is not known, or there is none.
314     *  @param source The input source, which specifies a URL, or null
315     *   if none.
316     *  @param text Configuration information given as text, or null if
317     *   none.
318     *  @exception Exception If something goes wrong.
319     */
320    @Override
321    public void configure(URL base, String source, String text)
322            throws Exception {
323        refinementName.setExpression("");
324        _configureSource = source;
325        // Coverity: Avoid a call to configure() in MoMLParser
326        // throwing a NPE if text is null.
327        if (text != null) {
328            text = text.trim();
329            if (!text.equals("")) {
330                MoMLParser parser = new MoMLParser(workspace());
331                _configurer.removeAllEntities();
332                parser.setContext(_configurer);
333                parser.parse(base, source, new StringReader(text));
334                _populateRefinements();
335            }
336        }
337    }
338
339    /** React to a list of objects being dropped onto a target.
340     *
341     *  @param target The target on which the objects are dropped.
342     *  @param dropObjects The list of objects dropped onto the target.
343     *  @param moml The moml string generated for the dropped objects.
344     *  @exception IllegalActionException If the handling is unsuccessful.
345     */
346    @Override
347    public void dropObject(NamedObj target, List dropObjects, String moml)
348            throws IllegalActionException {
349        NamedObj container = getContainer();
350        if (container instanceof DropTargetHandler) {
351            ((DropTargetHandler) container).dropObject(target, dropObjects,
352                    moml);
353        }
354    }
355
356    /** Return the list of outgoing error transitions from
357     *  this state.
358     *  @return The list of outgoing error transitions from
359     *   this state.
360     *  @exception IllegalActionException If the parameters giving transition
361     *   properties cannot be evaluated.
362     */
363    public List errorTransitionList() throws IllegalActionException {
364        if (_transitionListVersion != workspace().getVersion()) {
365            _updateTransitionLists();
366        }
367        return _errorTransitionList;
368    }
369
370    /** Return the input source that was specified the last time the configure
371     *  method was called.
372     *  @return The string representation of the input URL, or null if the
373     *  no source has been used to configure this object, or null if no
374     *  external source need be used to configure this object.
375     */
376    @Override
377    public String getConfigureSource() {
378        return _configureSource;
379    }
380
381    /** Return the text string that represents the current configuration of
382     *  this object.  Note that any configuration that was previously
383     *  specified using the source attribute need not be represented here
384     *  as well.
385     *  @return A configuration string, or null if no configuration
386     *  has been used to configure this object, or null if no
387     *  configuration string need be used to configure this object.
388     */
389    @Override
390    public String getConfigureText() {
391        return null;
392    }
393
394    /** Get the {@link Configurer} object for this entity.
395     */
396    @Override
397    public Configurer getConfigurer() {
398        return _configurer;
399    }
400
401    /** Return the incoming port.
402     *  @return The incoming port.
403     */
404    @Override
405    public ComponentPort getIncomingPort() {
406        return incomingPort;
407    }
408
409    /** Get a NamedObj with the given name in the refinement of this state, if
410     *  any.
411     *
412     *  @param name The name of the NamedObj.
413     *  @return The NamedObj in the refinement, or null if not found.
414     *  @exception IllegalActionException If the refinement cannot be found, or
415     *   if a comma-separated list is malformed.
416     */
417    public NamedObj getObjectInRefinement(String name)
418            throws IllegalActionException {
419        TypedActor[] refinements = getRefinement();
420        if (refinements == null) {
421            return null;
422        }
423
424        for (TypedActor refinement : refinements) {
425            if (refinement instanceof NamedObj) {
426                Attribute attribute = ((NamedObj) refinement)
427                        .getAttribute(name);
428                if (attribute != null) {
429                    return attribute;
430                } else if (refinement instanceof Entity) {
431                    Port port = ((Entity) refinement).getPort(name);
432                    if (port != null) {
433                        return port;
434                    } else if (refinement instanceof CompositeEntity) {
435                        ComponentEntity entity = ((CompositeEntity) refinement)
436                                .getEntity(name);
437                        if (entity != null) {
438                            return entity;
439                        }
440                        Relation relation = ((CompositeEntity) refinement)
441                                .getRelation(name);
442                        if (relation != null) {
443                            return relation;
444                        }
445                    }
446                }
447            }
448        }
449        return null;
450    }
451
452    /** Return the outgoing port.
453     *  @return The outgoing port.
454     */
455    @Override
456    public ComponentPort getOutgoingPort() {
457        return outgoingPort;
458    }
459
460    /** Return the refinements of this state. The names of the refinements
461     *  are specified by the <i>refinementName</i> attribute. The refinements
462     *  must be instances of TypedActor and have the same container as
463     *  the FSMActor containing this state, otherwise an exception is thrown.
464     *  This method can also return null if there is no refinement.
465     *  This method is read-synchronized on the workspace.
466     *  @return The refinements of this state, or null if there are none.
467     *  @exception IllegalActionException If the specified refinement
468     *   cannot be found, or if a comma-separated list is malformed.
469     */
470    public TypedActor[] getRefinement() throws IllegalActionException {
471        if (_refinementVersion == workspace().getVersion()) {
472            return _refinement;
473        }
474
475        try {
476            workspace().getReadAccess();
477
478            String names = refinementName.getExpression();
479
480            if (names == null || names.trim().equals("")) {
481                _refinementVersion = workspace().getVersion();
482                _refinement = null;
483                return null;
484            }
485
486            StringTokenizer tokenizer = new StringTokenizer(names, ",");
487            int size = tokenizer.countTokens();
488
489            if (size <= 0) {
490                _refinementVersion = workspace().getVersion();
491                _refinement = null;
492                return null;
493            }
494
495            _refinement = new TypedActor[size];
496
497            Nameable container = getContainer();
498            TypedCompositeActor containerContainer = (TypedCompositeActor) container
499                    .getContainer();
500            int index = 0;
501
502            while (tokenizer.hasMoreTokens()) {
503                String name = tokenizer.nextToken().trim();
504
505                if (name.equals("")) {
506                    throw new IllegalActionException(this,
507                            "Malformed list of refinements: " + names);
508                }
509
510                if (containerContainer == null) {
511                    // If we are doing saveAs of ModalBSC and select
512                    // submodel only, then some of the refinements might
513                    // not yet have a container (containercontainer == null).
514                    // ptolemy.vergil.modal.StateIcon._getFill() will call
515                    // this and properly handles an IllegalActionException
516                    throw new IllegalActionException(this, "Container of \""
517                            + getFullName()
518                            + "\" is null?  This is not always a problem.");
519                }
520
521                TypedActor element = (TypedActor) containerContainer
522                        .getEntity(name);
523
524                if (element == null) {
525                    throw new IllegalActionException(this,
526                            "Cannot find " + "refinement with name \"" + name
527                                    + "\" in "
528                                    + containerContainer.getFullName());
529                }
530
531                _refinement[index++] = element;
532            }
533
534            _refinementVersion = workspace().getVersion();
535            return _refinement;
536        } finally {
537            workspace().doneReading();
538        }
539    }
540
541    /** Return the list of outgoing transitions from
542     *  this state that are neither error nor termination transitions.
543     *  @return A list of outgoing transitions from this state.
544     *  @exception IllegalActionException If the parameters giving transition
545     *   properties cannot be evaluated.
546     */
547    public List nonErrorNonTerminationTransitionList()
548            throws IllegalActionException {
549        if (_transitionListVersion != workspace().getVersion()) {
550            _updateTransitionLists();
551        }
552        return _nonErrorNonTerminationTransitionList;
553    }
554
555    /** Return the list of non-preemptive outgoing transitions from
556     *  this state. This list does not include error transitions
557     *  and does include termination transitions.
558     *  @return The list of non-preemptive outgoing transitions from
559     *   this state.
560     *  @exception IllegalActionException If the parameters giving transition
561     *   properties cannot be evaluated.
562     */
563    public List nonpreemptiveTransitionList() throws IllegalActionException {
564        if (_transitionListVersion != workspace().getVersion()) {
565            _updateTransitionLists();
566        }
567
568        return _nonpreemptiveTransitionList;
569    }
570
571    /** Return the list of preemptive outgoing transitions from
572     *  this state.
573     *  @return The list of preemptive outgoing transitions from
574     *   this state. This will be an empty list if there aren't any.
575     *  @exception IllegalActionException If the parameters giving transition
576     *   properties cannot be evaluated.
577     */
578    public List preemptiveTransitionList() throws IllegalActionException {
579        if (_transitionListVersion != workspace().getVersion()) {
580            _updateTransitionLists();
581        }
582        return _preemptiveTransitionList;
583    }
584
585    /** Return the list of termination transitions from
586     *  this state.
587     *  @return The list of termination transitions from
588     *   this state. This will be an empty list if there aren't any.
589     *  @exception IllegalActionException If the parameters giving transition
590     *   properties cannot be evaluated.
591     */
592    public List terminationTransitionList() throws IllegalActionException {
593        if (_transitionListVersion != workspace().getVersion()) {
594            _updateTransitionLists();
595        }
596        return _terminationTransitionList;
597    }
598
599    ///////////////////////////////////////////////////////////////////
600    ////                         protected methods                 ////
601
602    /** Write a MoML description of the contents of this object, which
603     *  in this class are the attributes plus the ports.  This method is called
604     *  by exportMoML().  Each description is indented according to the
605     *  specified depth and terminated with a newline character.
606     *  @param output The output to write to.
607     *  @param depth The depth in the hierarchy, to determine indenting.
608     *  @exception IOException If an I/O error occurs.
609     */
610    @Override
611    protected void _exportMoMLContents(Writer output, int depth)
612            throws IOException {
613        super._exportMoMLContents(output, depth);
614        boolean createConfigurer = false;
615        try {
616            createConfigurer = ((BooleanToken) saveRefinementsInConfigurer
617                    .getToken()).booleanValue();
618        } catch (IllegalActionException e) {
619            // Ignore. Use false.
620        }
621        boolean configurePrinted = false;
622        if (createConfigurer) {
623            try {
624                TypedActor[] actors = getRefinement();
625                if (actors != null) {
626                    for (TypedActor actor : actors) {
627                        if (!configurePrinted) {
628                            output.write(
629                                    _getIndentPrefix(depth) + "<configure>\n");
630                            configurePrinted = true;
631                        }
632                        if (actor instanceof FSMActor) {
633                            ((FSMActor) actor).exportSubmodel(output, depth + 1,
634                                    actor.getName());
635                        } else {
636                            ((NamedObj) actor).exportMoML(output, depth + 1);
637                        }
638                    }
639                }
640            } catch (IllegalActionException e) {
641                throw new InternalErrorException(this, e,
642                        "Unable to save refinements.");
643            }
644        }
645        List<ComponentEntity> actors = _configurer.entityList();
646        for (ComponentEntity actor : actors) {
647            if (!configurePrinted) {
648                output.write(_getIndentPrefix(depth) + "<configure>\n");
649                configurePrinted = true;
650            }
651            if (actor instanceof FSMActor) {
652                ((FSMActor) actor).exportSubmodel(output, depth + 1,
653                        actor.getName());
654            } else {
655                ((NamedObj) actor).exportMoML(output, depth + 1);
656            }
657        }
658        if (configurePrinted) {
659            output.write(_getIndentPrefix(depth) + "</configure>\n");
660        }
661    }
662
663    ///////////////////////////////////////////////////////////////////
664    ////                         private methods                   ////
665
666    /** Move the refinements in the configurer of this state to the closest
667     *  modal model above this state in the model hierarchy.
668     */
669    private void _populateRefinements() throws IllegalActionException {
670        CompositeEntity container = (CompositeEntity) getContainer();
671        CompositeEntity modalModel = (CompositeEntity) container.getContainer();
672        boolean isModalModelInvisible = modalModel != null && !modalModel
673                .attributeList(InvisibleModalModel.class).isEmpty();
674        if (!(modalModel instanceof TypedCompositeActor)
675                || isModalModelInvisible) {
676            if (modalModel == null || isModalModelInvisible) {
677                try {
678                    if (modalModel == null) {
679                        modalModel = new ModalModel(workspace());
680                        new InvisibleModalModel(modalModel,
681                                modalModel.uniqueName("_invisibleModalModel"));
682                        container.setContainer(modalModel);
683                    }
684                } catch (NameDuplicationException e) {
685                    // This should not happen.
686                }
687                saveRefinementsInConfigurer.setToken(BooleanToken.TRUE);
688            } else {
689                return;
690            }
691        }
692        List<ComponentEntity> entities = new LinkedList<ComponentEntity>(
693                _configurer.entityList());
694        if (container instanceof RefinementActor) {
695            RefinementActor actor = (RefinementActor) container;
696            for (ComponentEntity entity : entities) {
697                String oldName = entity.getName();
698                String newName = modalModel.uniqueName(oldName);
699
700                String refinements = refinementName.getExpression();
701                String[] names = refinements.split("\\s*,\\s*");
702                boolean changed = false;
703                StringBuffer newRefinements = new StringBuffer();
704                for (String part : names) {
705                    if (newRefinements.length() > 0) {
706                        newRefinements.append(", ");
707                    }
708                    if (part.equals(oldName)) {
709                        changed = true;
710                    } else {
711                        newRefinements.append(part);
712                    }
713                }
714                if (changed) {
715                    refinementName.setExpression(newRefinements.toString());
716                }
717
718                actor.addRefinement(this, newName, null, entity.getClassName(),
719                        null);
720
721                String moml = new DesignPatternGetMoMLAction().getMoml(entity,
722                        newName);
723                try {
724                    entity.setContainer(null);
725                } catch (NameDuplicationException e) {
726                    // Ignore.
727                }
728
729                UpdateContentsRequest request = new UpdateContentsRequest(this,
730                        modalModel, newName, moml);
731                modalModel.requestChange(request);
732            }
733        }
734    }
735
736    /** Update the cached transition lists. This method is read-synchronized on
737     *  the workspace.
738     *  @exception IllegalActionException If the parameters giving transition
739     *   properties cannot be evaluated.
740     */
741    private void _updateTransitionLists() throws IllegalActionException {
742        try {
743            workspace().getReadAccess();
744            _nonpreemptiveTransitionList.clear();
745            _preemptiveTransitionList.clear();
746            _errorTransitionList.clear();
747            _terminationTransitionList.clear();
748            _nonErrorNonTerminationTransitionList.clear();
749
750            // If this state is final, it should not have any outgoing
751            // transitions.
752            if (((BooleanToken) isFinalState.getToken()).booleanValue()) {
753                if (outgoingPort.linkedRelationList().size() > 0) {
754                    throw new IllegalActionException(this,
755                            "Final state cannot have outgoing transitions");
756                }
757            }
758
759            Iterator transitions = outgoingPort.linkedRelationList().iterator();
760
761            while (transitions.hasNext()) {
762                Transition transition = (Transition) transitions.next();
763
764                if (transition.isErrorTransition()) {
765                    // An error transition is required to not be preemptive
766                    // or termination. Check that here.
767                    if (transition.isPreemptive()) {
768                        throw new IllegalActionException(transition,
769                                "An error transition cannot also be preemptive.");
770                    }
771                    if (transition.isTermination()) {
772                        throw new IllegalActionException(transition,
773                                "An error transition cannot also be a termination transition.");
774                    }
775                    _errorTransitionList.add(transition);
776                } else if (transition.isPreemptive()) {
777                    // A preemptive transition is not allowed to be a termination transition.
778                    if (transition.isTermination()) {
779                        throw new IllegalActionException(transition,
780                                "A preemptive transition cannot also be a termination transition.");
781                    }
782                    _preemptiveTransitionList.add(transition);
783                    _nonErrorNonTerminationTransitionList.add(transition);
784                } else if (transition.isTermination()) {
785                    // Termination transitions are allowed to have output actions only
786                    // all refinements of this state are state machine refinements.
787                    TypedActor[] refinements = getRefinement();
788                    if (refinements == null || refinements.length == 0) {
789                        throw new IllegalActionException(transition,
790                                "Termination transitions must come from states with refinements");
791                    }
792                    // There are refinements.
793                    // Check that if there are output actions on the termination transition
794                    // then all refinements are FSM refinements. This is because non-FSM
795                    // refinements can only be known to have terminated in postfire, and that
796                    // is too late to produce outputs for some domains (SR and Continuous, at least).
797                    if (!transition.outputActions.getExpression().trim()
798                            .equals("")) {
799                        for (Actor refinementActor : refinements) {
800                            if (!(refinementActor instanceof ModalRefinement)) {
801                                throw new IllegalActionException(transition,
802                                        "Termination transition cannot have output actions because "
803                                                + "such a transition is taken in the postfire phase of execution.");
804                            }
805                        }
806                    }
807                    // Note that a transition does not appear on this list unless it is
808                    // NOT a preemptive or error transition.
809                    _terminationTransitionList.add(transition);
810                    _nonpreemptiveTransitionList.add(transition);
811                } else {
812                    _nonpreemptiveTransitionList.add(transition);
813                    _nonErrorNonTerminationTransitionList.add(transition);
814                }
815            }
816
817            _transitionListVersion = workspace().getVersion();
818        } finally {
819            workspace().doneReading();
820        }
821    }
822
823    // The source of the configuration, which is not used.
824    private String _configureSource;
825
826    // The Configurer object for this state.
827    private Configurer _configurer;
828
829    // Cached list of error transitions from this state
830    private List _errorTransitionList = new LinkedList();
831
832    // Cached list of outgoing transitions from this state that are
833    // neither error nor termination transitions.
834    private List _nonErrorNonTerminationTransitionList = new LinkedList();
835
836    // Cached list of non-preemptive outgoing transitions from this state.
837    private List _nonpreemptiveTransitionList = new LinkedList();
838
839    // Cached list of preemptive outgoing transitions from this state.
840    private List _preemptiveTransitionList = new LinkedList();
841
842    // Cached reference to the refinement of this state.
843    private TypedActor[] _refinement = null;
844
845    // Version of the cached reference to the refinement.
846    private long _refinementVersion = -1;
847
848    // Cached list of termination transitions from this state.
849    private List _terminationTransitionList = new LinkedList();
850
851    // Version of cached transition lists.
852    private long _transitionListVersion = -1;
853
854    ///////////////////////////////////////////////////////////////////
855    //// InvisibleModalModel
856
857    /**
858     An attribute that marks a modal model is created because the designer opens
859     a design pattern whose top-level is an FSMActor. In that case, a modal
860     model is automatically created to contain the FSMActor, and this attribute
861     is associated with the created (invisible) modal model.
862
863     @author Thomas Huining Feng
864     @version $Id$
865     @since Ptolemy II 8.0
866     @Pt.ProposedRating Red (tfeng)
867     @Pt.AcceptedRating Red (tfeng)
868     */
869    private static class InvisibleModalModel extends SingletonAttribute {
870
871        /** Construct an attribute with the given container and name.
872         *  If an attribute already exists with the same name as the one
873         *  specified here, that is an instance of class
874         *  SingletonAttribute (or a derived class), then that
875         *  attribute is removed before this one is inserted in the container.
876         *  @param container The container.
877         *  @param name The name of this attribute.
878         *  @exception IllegalActionException If the attribute cannot be contained
879         *   by the proposed container.
880         *  @exception NameDuplicationException If the container already has an
881         *   attribute with this name, and the class of that container is not
882         *   SingletonAttribute.
883         */
884        InvisibleModalModel(CompositeEntity container, String name)
885                throws NameDuplicationException, IllegalActionException {
886            super(container, name);
887        }
888    }
889
890    ///////////////////////////////////////////////////////////////////
891    //// UpdateContentsRequest
892
893    /**
894     A change request the updates the refinements of a state if it contains a
895     configure element.
896
897     @author Thomas Huining Feng
898     @version $Id$
899     @since Ptolemy II 8.0
900     @Pt.ProposedRating Red (tfeng)
901     @Pt.AcceptedRating Red (tfeng)
902     */
903    private static class UpdateContentsRequest extends ChangeRequest {
904
905        /** Construct a request.
906         *
907         *  @param source The state that originates the request.
908         *  @param modalModel The closest modal model that the source state is
909         *   contained in.
910         *  @param name The name of the refinement.
911         *  @param moml The moml of the refinement.
912         */
913        public UpdateContentsRequest(State source, CompositeEntity modalModel,
914                String name, String moml) {
915            super(source, "Update contents of refinement " + name + ".");
916            _modalModel = modalModel;
917            _name = name;
918            _moml = moml;
919        }
920
921        /** Execute the change.
922         *  @exception Exception If the change fails.
923         */
924        @Override
925        protected void _execute() throws Exception {
926            ComponentEntity entity = _modalModel.getEntity(_name);
927            MoMLChangeRequest request = new MoMLChangeRequest(this, entity,
928                    _moml);
929            request.execute();
930        }
931
932        // The name of the refinement.
933        private String _name;
934
935        // The closest modal model that the source state is ontained in.
936        private CompositeEntity _modalModel;
937
938        // The moml of the refinement.
939        private String _moml;
940    }
941}