001/* A class that contains a number of decorated attributes.
002
003 Copyright (c) 2009-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 */
028
029package ptolemy.kernel.util;
030
031import java.io.IOException;
032import java.io.Writer;
033import java.util.List;
034
035import ptolemy.kernel.CompositeEntity;
036
037///////////////////////////////////////////////////////////////////
038//// DecoratorAttributes
039
040/**
041A container for attributes created by a decorator.
042This is an attribute that will be contained by a target object that is
043decorated by a decorator. The parameters that the decorator creates
044will be contained by an instance of this object.
045These attributes can be retrieved by using
046{@link NamedObj#getDecoratorAttribute(Decorator,String)} or
047{@link NamedObj#getDecoratorAttributes(Decorator)}.
048
049@author Bert Rodiers
050@author Edward A. Lee
051@version $Id$
052@since Ptolemy II 10.0
053@Pt.ProposedRating Yellow (eal)
054@Pt.AcceptedRating Red (rodiers)
055 */
056public class DecoratorAttributes extends Attribute {
057
058    // FIXME: The decoratorName mechanism is very fragile.
059    // If the decorator changes name, the connection will be lost.
060    // If the container of the decorator changes name, it will again be lost.
061    // Probably the first constructor should not set the name, and this should
062    // be set only when MoML is to be exported.
063    // The second constructor should immediately look for the decorator by name.
064    // But it may not have been constructed yet. Hence, when a decorator is
065    // constructed, it should look for decorated objects and establish the link.
066    // This way, it doesn't matter which gets constructed first.
067
068    /** Construct a DecoratorAttributes instance to contain the
069     *  decorator parameter for the specified container provided
070     *  by the specified decorator. This constructor is used
071     *  when retrieving decorator parameters from a target
072     *  NamedObj that does not yet have the decorator parameters.
073     *  @param container The target for the decorator.
074     *  @param decorator The decorator.
075     *  @exception IllegalActionException If the attribute is not of an
076     *   acceptable class for the container, or if the name contains a period.
077     *  @exception NameDuplicationException If the name coincides with
078     *   an attribute already in the container. This should not occur.
079     */
080    public DecoratorAttributes(NamedObj container, Decorator decorator)
081            throws IllegalActionException, NameDuplicationException {
082        super(container, container
083                .uniqueName("DecoratorAttributesFor_" + decorator.getName()));
084        _decorator = decorator;
085
086        decoratorName = new StringAttribute(this, "decoratorName");
087        decoratorName.setVisibility(Settable.NONE);
088    }
089
090    /** Construct a DecoratorAttributes instance with the given name
091     *  and container.  This constructor is used when parsing MoML files,
092     *  where it is assumed that the decorator is specified by name as
093     *  the value of the {@link #decoratorName} parameter.
094     *  @param container The container of this object.
095     *  @param name The name of this attribute.
096     *  @exception IllegalActionException If the attribute is not of an
097     *   acceptable class for the container, or if the name contains a period.
098     *  @exception NameDuplicationException If the name coincides with
099     *   an attribute already in the container.
100     */
101    public DecoratorAttributes(NamedObj container, String name)
102            throws IllegalActionException, NameDuplicationException {
103        super(container, name);
104
105        decoratorName = new StringAttribute(this, "decoratorName");
106        decoratorName.setVisibility(Settable.NONE);
107    }
108
109    ///////////////////////////////////////////////////////////////////
110    ////                         parameters                        ////
111
112    /** The name of the decorator relative to the top-level of
113     *  the model, to be stored in a MoML file
114     *  to re-establish the connection with a decorator after saving
115     *  and re-parsing the file. The name is relative to the top-level,
116     *  rather than the full name, so that SaveAs works (where the name
117     *  of the toplevel changes).
118     *  FIXME: However, if you save a submodel, then this will not work!
119     *  This is a string that is not visible to the user.
120     */
121    public StringAttribute decoratorName;
122
123    ///////////////////////////////////////////////////////////////////
124    ////                         public methods                    ////
125
126    /** Override the base class to establish a link to the decorator
127     *  if the argument is decoratorName.
128     *  @exception IllegalActionException If the change is not acceptable
129     *   to this container (not thrown in this base class).
130     */
131    @Override
132    public void attributeChanged(Attribute attribute)
133            throws IllegalActionException {
134        if (attribute == decoratorName) {
135            // The decorator name is being set.
136            // Attempt to set the _decorator now, because its name
137            // or the name of one of its containers may change later.
138            // This will remain null if the decorator has not yet been
139            // constructed.
140            _decorator = getDecorator();
141        } else {
142            super.attributeChanged(attribute);
143        }
144    }
145
146    /** Clone the object into the specified workspace.
147     *  @param workspace The workspace for the cloned object.
148     *  @exception CloneNotSupportedException Not thrown in this base class
149     *  @return The new Attribute.
150     */
151    @Override
152    public Object clone(Workspace workspace) throws CloneNotSupportedException {
153        DecoratorAttributes newObject = (DecoratorAttributes) super.clone(
154                workspace);
155        newObject._decorator = null;
156        return newObject;
157    }
158
159    /** Override the base class to first set the decoratorName attribute
160     *  to the current name of the associated decorator, and then export
161     *  using the superclass.
162     *
163     *  @param output The output stream to write to.
164     *  @param depth The depth in the hierarchy, to determine indenting.
165     *  @param name The name of we use when exporting the description.
166     *  @exception IOException If an I/O error occurs.
167     *  @see #exportMoML(Writer, int)
168     */
169    @Override
170    public void exportMoML(Writer output, int depth, String name)
171            throws IOException {
172        try {
173            _decorator = getDecorator();
174        } catch (IllegalActionException e1) {
175            throw new IOException("Export failed.", e1);
176        }
177        if (_decorator == null) {
178            // No matching decorator is found. Discard the decorator attributes.
179            return;
180        }
181        // Record the name relative to the toplevel entity so that even if you
182        // do SaveAs (which changes the name of the toplevel) the decorator
183        // can still be found.
184        // FIXME: If you save a submodel, this will break the connection
185        // to the decorator.
186        try {
187            decoratorName.setExpression(_decorator.getName(toplevel()));
188        } catch (IllegalActionException e) {
189            throw new InternalErrorException(e);
190        }
191
192        // Now invoke the superclass to export MoML.
193        super.exportMoML(output, depth, name);
194    }
195
196    /** Return the decorator that is responsible for this DecoratorAttributes instance.
197     *  @return The decorator, or null if there is none.
198     *  @exception IllegalActionException If the decorator cannot be determined
199     *   (e.g., a parameter cannot be evaluated).
200     */
201    public Decorator getDecorator() throws IllegalActionException {
202        if (_decorator != null) {
203            // There is a decorator associated associated with this DecoratorAttributes.
204            // Check to see whether the decorator is still in scope. If it is not,
205            // then see whether there is a decorator in scope whose name matches,
206            // and establish a link with that one. This makes undo work. Specifically,
207            // if you delete a decorator, then undo, the undo will actually create a
208            // new decorator instance. This code re-establishes the connection.
209            NamedObj decoratorContainer = _decorator.getContainer();
210            if (decoratorContainer == null
211                    || !decoratorContainer.deepContains(this)) {
212                // Decorator is no longer in scope.  Fall into code below to try to find it by name.
213                _decorator = null;
214            }
215        }
216        if (_decorator == null) {
217            // Retrieve the decorator using the decoratorName parameter.
218            String name = decoratorName.getExpression();
219            if (name != null && !name.equals("")) {
220                // Find all the decorators in scope, and return the first one whose name matches.
221                NamedObj container = getContainer().getContainer();
222                boolean crossedOpaqueBoundary = false;
223                while (container != null) {
224                    List<Decorator> localDecorators = container
225                            ._containedDecorators();
226                    for (Decorator decorator : localDecorators) {
227                        if (!crossedOpaqueBoundary
228                                || decorator.isGlobalDecorator()) {
229                            if (decorator.getName(toplevel()).equals(name)) {
230                                // We have a match.
231                                _decorator = decorator;
232                                return _decorator;
233                            }
234                        }
235                    }
236                    // FIXME: kernel.util should not have a dependence on kernel classes.
237                    if (container instanceof CompositeEntity
238                            && ((CompositeEntity) container).isOpaque()) {
239                        crossedOpaqueBoundary = true;
240                    }
241                    container = container.getContainer();
242                }
243            }
244        }
245        return _decorator;
246    }
247
248    ///////////////////////////////////////////////////////////////////
249    ////                         protected variables               ////
250
251    /** The decorator.*/
252    protected Decorator _decorator = null;
253}