001/* A publisher that transparently tunnels messages to subscribers.
002
003 Copyright (c) 2006-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
027 */
028package ptolemy.actor.lib;
029
030import java.util.Set;
031
032import ptolemy.actor.AtomicActor;
033import ptolemy.actor.PublisherPort;
034import ptolemy.actor.TypedAtomicActor;
035import ptolemy.actor.TypedIOPort;
036import ptolemy.actor.util.ActorDependencies;
037import ptolemy.data.BooleanToken;
038import ptolemy.data.Token;
039import ptolemy.data.expr.Parameter;
040import ptolemy.data.expr.SingletonParameter;
041import ptolemy.data.expr.StringParameter;
042import ptolemy.data.type.BaseType;
043import ptolemy.kernel.CompositeEntity;
044import ptolemy.kernel.util.IllegalActionException;
045import ptolemy.kernel.util.KernelException;
046import ptolemy.kernel.util.NameDuplicationException;
047import ptolemy.kernel.util.Workspace;
048
049/**
050 This actor publishes input tokens on a named channel. The tokens are
051 "tunneled" to any instance of {@link Subscriber} that names the same channel.
052 If {@link #global} is false (the default), then this publisher
053 will only send to instances of Subscriber that are under the
054 control of the same director. That is, it can
055 be at a different level of the hierarchy, or in an entirely different
056 composite actor, as long as the relevant composite actors are
057 transparent (have no director). If {@link #global} is true,
058 then the subscriber may be anywhere in the model, as long as its
059 <i>global</i> parameter is also true.
060 <p>
061 It is an error to have two instances of Publisher using the same
062 channel under the control of the same director. When you create a
063 new Publisher, by default, it has no channel name. You have to
064 specify a channel name to use it.
065 <p>
066 <b>How it works:</b>
067 This actor has a hidden output port. When the channel name
068 is specified, typically during model construction, this actor
069 causes a relation to be created in the least opaque composite
070 actor above it in the hierarchy and links to that relation.
071 In addition, if {@link #global} is set to true, it causes
072 a port to be created in that composite, and also links that
073 port to the relation on the inside.  The relation is recorded by the opaque
074 composite.  When a Subscriber is preinitialized that refers
075 to the same channel, that Subscriber finds the relation (by
076 finding the least opaque composite actor above it) and links
077 to the relation. Some of these links are "liberal links" in that
078 they cross levels of the hierarchy.
079 <p>
080 Since publishers are linked to subscribers,
081 any data dependencies that the director might assume on a regular
082 "wired" connection will also be assumed across Publisher-Subscriber
083 pairs. Similarly, type constraints will propagate across
084 Publisher-Subscriber pairs. That is, the type of the Subscriber
085 output will match the type of the Publisher input.
086
087 @author Edward A. Lee, Raymond A. Cardillo, Bert Rodiers
088 @version $Id$
089 @since Ptolemy II 5.2
090 @Pt.ProposedRating Green (cxh)
091 @Pt.AcceptedRating Red (cxh)
092 */
093public class Publisher extends TypedAtomicActor {
094
095    /** Construct a publisher with the specified container and name.
096     *  @param container The container actor.
097     *  @param name The name of the actor.
098     *  @exception IllegalActionException If the actor is not of an acceptable
099     *   class for the container.
100     *  @exception NameDuplicationException If the name coincides with
101     *   an actor already in the container.
102     */
103    public Publisher(CompositeEntity container, String name)
104            throws IllegalActionException, NameDuplicationException {
105        super(container, name);
106
107        channel = new StringParameter(this, "channel");
108
109        input = new TypedIOPort(this, "input", true, false);
110        input.setMultiport(true);
111
112        output = new PublisherPort(this, "output");
113        output.setMultiport(true);
114        output.setTypeAtLeast(input);
115
116        // We only have constraints from the publisher on the subscriber
117        // and the output of the subscriber and not the other way around
118        // to not break any existing models.
119        output.setWidthEquals(input, false);
120
121        Parameter hide = new SingletonParameter(output, "_hide");
122        hide.setToken(BooleanToken.TRUE);
123        // hide = new SingletonParameter(this, "_hideName");
124        // hide.setToken(BooleanToken.TRUE);
125
126        global = new Parameter(this, "global");
127        global.setExpression("false");
128        global.setTypeEquals(BaseType.BOOLEAN);
129
130        propagateNameChanges = new Parameter(this, "propagateNameChanges");
131        propagateNameChanges.setExpression("false");
132        propagateNameChanges.setTypeEquals(BaseType.BOOLEAN);
133
134        // Refer the parameters of the output port to those of
135        // this actor.
136        output.channel.setExpression("$channel");
137        output.global.setExpression("global");
138        output.propagateNameChanges.setExpression("propagateNameChanges");
139    }
140
141    ///////////////////////////////////////////////////////////////////
142    ////                   ports and parameters                    ////
143
144    /** The name of the channel.  Subscribers that reference this same
145     *  channel will receive any transmissions to this port.
146     *  This is a string that defaults to empty, indicating that
147     *  no channel is specified. A channel must be set before
148     *  the actor executes or an exception will occur.
149     */
150    public StringParameter channel;
151
152    /** Specification of whether the published data is global.
153     *  If this is set to true, then a subscriber anywhere in the model that
154     *  references the same channel by name will see values published by
155     *  this publisher. If this is set to false (the default), then only
156     *  those subscribers that are fired by the same director will see
157     *  values published on this channel.
158     */
159    public Parameter global;
160
161    /** The input port.  This is a multiport, allowing multiple
162     *  signals to be be transmitted through the publisher channel.
163     *  This base class imposes no type constraints except
164     *  that the type of the input cannot be greater than the type of the
165     *  output.
166     */
167    public TypedIOPort input;
168
169    /** The output port. This port is hidden and should not be
170     *  directly used. By default, the type of this output is constrained
171     *  to be at least that of the input. This port is hidden by default
172     *  and the actor handles creating connections to it.
173     */
174    public PublisherPort output;
175
176    /** If true, then propagate channel name changes to any
177     *  Subscribers.  The default value is a BooleanToken with the
178     *  value false, indicating that if the channel name is changed,
179     *  then the channel names of the Subscribers are not changed.  If
180     *  the value is true, then if the channel name is changed, the
181     *  channel names of the connected Subscribers are updated.
182     *
183     *  <p>If the value is true, then SubscriptionAggregators that
184     *  have the same regular expression as the channel name of the
185     *  Publisher will be updated.  However, SubscriptionAggregators
186     *  usually have regular expressions as channel names, so usually
187     *  the channel name of the SubscriptionAggregator will <b>not</b>
188     *  be updated.</p>
189     *
190     *  <p>Note that if a Publisher is within an Actor Oriented Class
191     *  definition, then any Subscribers with the same channel name in
192     *  Actor Oriented Class definitions will <b>not</b> be updated.
193     *  This is because there is no connection between the Publisher
194     *  in the Actor Oriented Class definition and the Subscriber.
195     *  However, if the channel name in a Publisher in an instance of
196     *  an Actor Oriented Class is updated, then the
197     *  corresponding Subscribers in instances of Actor Oriented Class
198     *  will be updated.</p>
199     */
200    public Parameter propagateNameChanges;
201
202    ///////////////////////////////////////////////////////////////////
203    ////                         public methods                    ////
204
205    /** Clone the actor into the specified workspace.
206     *  @param workspace The workspace for the new object.
207     *  @return A new actor.
208     *  @exception CloneNotSupportedException If a derived class contains
209     *   an attribute that cannot be cloned.
210     */
211    @Override
212    public Object clone(Workspace workspace) throws CloneNotSupportedException {
213        Publisher newObject = (Publisher) super.clone(workspace);
214
215        // We only have constraints from the publisher on the subscriber
216        // and the output of the subscriber and not the other way around
217        // to not break any existing models.
218        newObject.output.setWidthEquals(newObject.input, false);
219
220        newObject.output.setTypeAtLeast(newObject.input);
221
222        return newObject;
223    }
224
225    /** Read at most one input token from each
226     *  input channel and send it to the subscribers,
227     *  if any.
228     *  @exception IllegalActionException If there is no director.
229     */
230    @Override
231    public void fire() throws IllegalActionException {
232        super.fire();
233        for (int i = 0; i < input.getWidth(); i++) {
234            if (input.hasToken(i)) {
235                Token token = input.get(i);
236                output.send(i, token);
237            }
238        }
239    }
240
241    /** Override the base class to ensure that links to subscribers
242     *  have been updated.
243     *  @exception IllegalActionException If there is already a publisher
244     *   publishing on the same channel, or if the channel name has not
245     *   been specified.
246     */
247    @Override
248    public void preinitialize() throws IllegalActionException {
249        String channelValue = channel.stringValue();
250        if (channelValue == null || channelValue.trim().equals("")) {
251            throw new IllegalActionException(this,
252                    "No channel name has been specified.");
253        }
254
255        // Call super.preinitialize() after updating links so that
256        // we have connections made before possibly inferring widths.
257        super.preinitialize();
258    }
259
260    /** Return a Set of Subscribers that are connected to this Publisher.
261     *  @return A Set of Subscribers that are connected to this Publisher
262     *  @exception KernelException If thrown when a Manager is added to
263     *  the top level or if preinitialize() fails.
264     */
265    public Set<AtomicActor> subscribers() throws KernelException {
266        return ActorDependencies.dependents(this, Subscriber.class);
267    }
268}