001/* An output port that publishes its data on a named channel.
002
003 Copyright (c) 1997-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;
028
029import ptolemy.data.expr.Parameter;
030import ptolemy.data.expr.StringParameter;
031import ptolemy.data.type.ArrayType;
032import ptolemy.data.type.BaseType;
033import ptolemy.kernel.ComponentEntity;
034import ptolemy.kernel.Entity;
035import ptolemy.kernel.InstantiableNamedObj;
036import ptolemy.kernel.util.HierarchyListener;
037import ptolemy.kernel.util.IllegalActionException;
038import ptolemy.kernel.util.InternalErrorException;
039import ptolemy.kernel.util.NameDuplicationException;
040import ptolemy.kernel.util.NamedObj;
041import ptolemy.kernel.util.Workspace;
042
043///////////////////////////////////////////////////////////////////
044//// PubSubPort
045
046/**
047 An abstract base class for publisher and subscriber ports.
048
049 @author Edward A. Lee
050 @version $Id$
051 @since Ptolemy II 10.0
052 @Pt.ProposedRating Yellow (eal)
053 @Pt.AcceptedRating Red (eal)
054 */
055public abstract class PubSubPort extends TypedIOPort
056        implements HierarchyListener, Initializable {
057
058    /** Construct a subscriber port with a containing actor and a name.
059     *  This is always an input port.
060     *  @param container The container actor.
061     *  @param name The name of the port.
062     *  @exception IllegalActionException If the port is not of an acceptable
063     *   class for the container, or if the container does not implement the
064     *   Actor interface.
065     *  @exception NameDuplicationException If the name coincides with
066     *   a port already in the container.
067     */
068    public PubSubPort(ComponentEntity container, String name)
069            throws IllegalActionException, NameDuplicationException {
070        super(container, name);
071        channel = new StringParameter(this, "channel");
072
073        global = new Parameter(this, "global");
074        global.setTypeEquals(BaseType.BOOLEAN);
075        global.setExpression("false");
076
077        initialTokens = new Parameter(this, "initialTokens") {
078            /** Override the base class to to allow the type to be unknown.
079             *  @return True if the current type is acceptable.
080             */
081            @Override
082            public boolean isTypeAcceptable() {
083                return super.isTypeAcceptable()
084                        || getType().equals(BaseType.UNKNOWN);
085            }
086        };
087        setTypeAtLeast(ArrayType.elementType(initialTokens));
088    }
089
090    ///////////////////////////////////////////////////////////////////
091    ////                         parameters                        ////
092
093    /** If set, then this port is used to communicate over a named
094     *  publish and subscribe channel, rather than over manually
095     *  established connections.
096     */
097    public StringParameter channel;
098
099    /** Specification of whether the published data is global.
100     *  This is ignored if {@link #channel} is empty.
101     *  If this is set to true, then a subscriber anywhere in the model that
102     *  references the same channel by name will see values published by
103     *  this port. If this is set to false (the default), then only
104     *  those subscribers that are controlled by the same director will see
105     *  values published on this channel.
106     */
107    public Parameter global;
108
109    /** The values that will be made available in the initialize method.
110     *  By default, this is empty, indicating that no initial tokens are
111     *  available. If you wish for this port to have initial tokens,
112     *  then give this parameter an array value specifying
113     *  the sequence of initial values. If this is an output port,
114     *  these initial values will be sent in the initialize() phase.
115     *  If this is an input port, then these initial values will be
116     *  available for reading after the initialize() phase.
117     *  Changes to this parameter after initialize() has been invoked
118     *  are ignored until the next execution of the model.
119     */
120    public Parameter initialTokens;
121
122    ///////////////////////////////////////////////////////////////////
123    ////                         public methods                    ////
124
125    /** Throw an exception.
126     * Adding initializables to the container is not supported.
127     */
128    @Override
129    public void addInitializable(Initializable initializable) {
130        throw new InternalErrorException(
131                "Cannot add Initializables to publisher and subscriber ports.");
132    }
133
134    /** Clone the actor into the specified workspace. This calls the
135     *  base class and then resets the type constraints.
136     *  @param workspace The workspace for the new object.
137     *  @return A new actor.
138     *  @exception CloneNotSupportedException If a derived class contains
139     *   an attribute that cannot be cloned.
140     */
141    @Override
142    public Object clone(Workspace workspace) throws CloneNotSupportedException {
143        PubSubPort newObject = (PubSubPort) super.clone(workspace);
144
145        // Set the type constraints.
146        try {
147            newObject.setTypeAtLeast(
148                    ArrayType.elementType(newObject.initialTokens));
149        } catch (IllegalActionException e) {
150            throw new InternalErrorException(e);
151        }
152
153        return newObject;
154    }
155
156    /** Notify this object that the containment hierarchy above it has
157     *  changed. This method does nothing because instead we use
158     *  {@link #preinitialize()} to handle re-establishing the connections.
159     *  @exception IllegalActionException If the change is not
160     *   acceptable.
161     */
162    @Override
163    public void hierarchyChanged() throws IllegalActionException {
164        // Make sure we are registered as to be initialized
165        // with the container.
166        Initializable container = _getInitializableContainer();
167        if (container != null) {
168            container.addInitializable(this);
169        }
170    }
171
172    /** Notify this object that the containment hierarchy above it will be
173     *  changed, which results in this port being removed from the set
174     *  of initializables of the container.
175     *  @exception IllegalActionException If unlinking to a published port fails.
176     */
177    @Override
178    public void hierarchyWillChange() throws IllegalActionException {
179        // Unregister to be initialized with the initializable container.
180        // We will be re-registered when hierarchyChanged() is called.
181        Initializable container = _getInitializableContainer();
182        if (container != null) {
183            container.removeInitializable(this);
184        }
185    }
186
187    /** Do nothing. */
188    @Override
189    public void initialize() throws IllegalActionException {
190    }
191
192    /** Do nothing.  Subclasses should check to see if the port
193     *  is in the top level and throw an exception that suggests
194     *  using a Publisher or Subscriber.
195     */
196    @Override
197    public void preinitialize() throws IllegalActionException {
198    }
199
200    /** Do nothing. */
201    @Override
202    public void removeInitializable(Initializable initializable) {
203    }
204
205    /** Override the base class to register as an
206     *  {@link Initializable}
207     *  so that preinitialize() is invoked, and as a
208     *  {@link HierarchyListener}, so that we are notified of
209     *  changes in the hierarchy above.
210     *  @param container The proposed container.
211     *  @exception IllegalActionException If the action would result in a
212     *   recursive containment structure, or if
213     *   this entity and container are not in the same workspace.
214     *  @exception NameDuplicationException If the container already has
215     *   an entity with the name of this entity.
216     */
217    @Override
218    public void setContainer(Entity container)
219            throws IllegalActionException, NameDuplicationException {
220        Initializable previousInitializableContainer = _getInitializableContainer();
221        NamedObj previousContainer = getContainer();
222        if (previousContainer != container) {
223            hierarchyWillChange();
224            try {
225                super.setContainer(container);
226                Initializable newInitializableContainer = _getInitializableContainer();
227                if (previousInitializableContainer != newInitializableContainer) {
228                    if (previousInitializableContainer != null) {
229                        previousInitializableContainer
230                                .removeInitializable(this);
231                    }
232                    if (newInitializableContainer != null) {
233                        newInitializableContainer.addInitializable(this);
234                    }
235                }
236                if (previousContainer != container) {
237                    if (previousContainer != null) {
238                        previousContainer.removeHierarchyListener(this);
239                    }
240                    if (container != null) {
241                        container.addHierarchyListener(this);
242                    }
243                }
244            } finally {
245                hierarchyChanged();
246            }
247        }
248    }
249
250    /** Do nothing. */
251    @Override
252    public void wrapup() throws IllegalActionException {
253    }
254
255    ///////////////////////////////////////////////////////////////////
256    ////                         protected variables               ////
257
258    /** Cached channel name, for publish and subscribe. */
259    protected String _channel;
260
261    /** Cached variable indicating whether publishing or subscribing is global. */
262    protected boolean _global;
263
264    ///////////////////////////////////////////////////////////////////
265    ////                         private methods                   ////
266
267    /** Return the first Initializable encountered above this
268     *  in the hierarchy that will be initialized (i.e., it is either
269     *  an atomic actor or an opaque composite actor).
270     *  @return The first Initializable above this in the hierarchy,
271     *   or null if there is none.
272     */
273    private Initializable _getInitializableContainer() {
274        NamedObj container = getContainer();
275        if (container instanceof InstantiableNamedObj) {
276            if (((InstantiableNamedObj) container).isWithinClassDefinition()) {
277                return null;
278            }
279        }
280        while (container != null) {
281            if (container instanceof Initializable) {
282                if (container instanceof CompositeActor) {
283                    if (((CompositeActor) container).isOpaque()) {
284                        return (Initializable) container;
285                    }
286                } else {
287                    return (Initializable) container;
288                }
289            }
290            container = container.getContainer();
291        }
292        return null;
293    }
294}