001/* A base class for attributes to be attached to instances of NamedObj.
002
003 Copyright (c) 1998-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.kernel.util;
029
030import java.util.Iterator;
031
032///////////////////////////////////////////////////////////////////
033//// Attribute
034
035/**
036 Attribute is a base class for attributes to be attached to instances
037 of NamedObj.  This base class is itself a NamedObj, with the only
038 extension being that it can have a container.  The setContainer()
039 method puts this object on the list of attributes of the container.
040
041 @author Edward A. Lee, Neil Smyth
042 @version $Id$
043 @since Ptolemy II 0.2
044 @Pt.ProposedRating Green (eal)
045 @Pt.AcceptedRating Green (johnr)
046 */
047public class Attribute extends NamedObj {
048    /** Construct an attribute in the default workspace with an empty string
049     *  as its name.
050     *  The object is added to the directory of the workspace.
051     *  Increment the version number of the workspace.
052     */
053    public Attribute() {
054        super();
055        _elementName = "property";
056    }
057
058    /** Construct an attribute in the specified workspace with an empty
059     *  string as a name. You can then change the name with setName().
060     *  If the workspace argument
061     *  is null, then use the default workspace.
062     *  The object is added to the directory of the workspace.
063     *  Increment the version number of the workspace.
064     *  @param workspace The workspace that will list the attribute.
065     */
066    public Attribute(Workspace workspace) {
067        super(workspace);
068        _elementName = "property";
069    }
070
071    /** Construct an attribute with the given name contained by the specified
072     *  entity. The container argument must not be null, or a
073     *  NullPointerException will be thrown.  This attribute will use the
074     *  workspace of the container for synchronization and version counts.
075     *  If the name argument is null, then the name is set to the empty string.
076     *  Increment the version of the workspace.
077     *  @param container The container.
078     *  @param name The name of this attribute.
079     *  @exception IllegalActionException If the attribute is not of an
080     *   acceptable class for the container, or if the name contains a period.
081     *  @exception NameDuplicationException If the name coincides with
082     *   an attribute already in the container.
083     */
084    public Attribute(NamedObj container, String name)
085            throws IllegalActionException, NameDuplicationException {
086        this(container, name, true);
087    }
088
089    /** Construct an attribute with the given name contained by the specified
090     *  entity. The container argument must not be null, or a
091     *  NullPointerException will be thrown.  This attribute will use the
092     *  workspace of the container for synchronization and version counts.
093     *  If the name argument is null, then the name is set to the empty string.
094     *  Increment the version of the workspace.
095     *  @param container The container.
096     *  @param name The name of this attribute.
097     *  @param incrementWorkspaceVersion False to not add this to the workspace
098     *   or do anything else that might change the workspace version number.
099     *  @exception IllegalActionException If the attribute is not of an
100     *   acceptable class for the container, or if the name contains a period.
101     *  @exception NameDuplicationException If the name coincides with
102     *   an attribute already in the container.
103     */
104    protected Attribute(NamedObj container, String name,
105            boolean incrementWorkspaceVersion)
106            throws IllegalActionException, NameDuplicationException {
107        super(container.workspace(), name, incrementWorkspaceVersion);
108        if (incrementWorkspaceVersion) {
109            setContainer(container);
110        } else {
111            // Get writeAccess to the workspace because we are updating
112            // the _attributes field of the container.
113            // See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=3255
114            try {
115                _workspace.getWriteAccess();
116
117                // Avoid methods that increment the workspace version.
118                if (container._attributes == null) {
119                    container._attributes = new NamedList();
120                }
121                container._attributes.append(this);
122
123                _container = container;
124                // Make sure even the debugging messages are unchanged.
125                if (container._debugging) {
126                    container._debug("Added attribute", getName(), "to",
127                            container.getFullName());
128                }
129            } finally {
130                // Note that _doneTemporaryWriting
131                // does not increment the workspace version.
132                _workspace.doneTemporaryWriting();
133            }
134        }
135        _elementName = "property";
136    }
137
138    ///////////////////////////////////////////////////////////////////
139    ////                         public methods                    ////
140
141    /** Clone the object into the specified workspace. The new object is
142     *  <i>not</i> added to the directory of that workspace (you must do this
143     *  yourself if you want it there).
144     *  The result is an attribute with no container.
145     *  @param workspace The workspace for the cloned object.
146     *  @exception CloneNotSupportedException Not thrown in this base class
147     *  @return The new Attribute.
148     */
149    @Override
150    public Object clone(Workspace workspace) throws CloneNotSupportedException {
151        Attribute newObject = (Attribute) super.clone(workspace);
152        newObject._container = null;
153        return newObject;
154    }
155
156    /** Move this object down by one in the list of attributes of
157     *  its container. If this object is already last, do nothing.
158     *  This method gets write access on workspace
159     *  and increments the version.
160     *  @return The index of the specified object prior to moving it,
161     *   or -1 if it is not moved.
162     *  @exception IllegalActionException If this object has
163     *   no container.
164     */
165    @Override
166    public int moveDown() throws IllegalActionException {
167        NamedObj container = getContainer();
168
169        if (container == null) {
170            throw new IllegalActionException(this, "Has no container.");
171        }
172
173        try {
174            _workspace.getWriteAccess();
175
176            int result = container._attributes.moveDown(this);
177
178            // Propagate.
179            Iterator derivedObjects = getDerivedList().iterator();
180
181            while (derivedObjects.hasNext()) {
182                NamedObj derived = (NamedObj) derivedObjects.next();
183                container = derived.getContainer();
184                container._attributes.moveDown(derived);
185            }
186
187            return result;
188        } finally {
189            _workspace.doneWriting();
190        }
191    }
192
193    /** Move this object to the first position in the list
194     *  of attributes of the container. If this object is already first,
195     *  do nothing. This method gets write access on workspace
196     *  and increments the version.
197     *  @return The index of the specified object prior to moving it,
198     *   or -1 if it is not moved.
199     *  @exception IllegalActionException If this object has
200     *   no container.
201     */
202    @Override
203    public int moveToFirst() throws IllegalActionException {
204        NamedObj container = getContainer();
205
206        if (container == null) {
207            throw new IllegalActionException(this, "Has no container.");
208        }
209
210        try {
211            _workspace.getWriteAccess();
212
213            int result = container._attributes.moveToFirst(this);
214
215            // Propagate.
216            Iterator derivedObjects = getDerivedList().iterator();
217
218            while (derivedObjects.hasNext()) {
219                NamedObj derived = (NamedObj) derivedObjects.next();
220                container = derived.getContainer();
221                container._attributes.moveToFirst(derived);
222            }
223
224            return result;
225        } finally {
226            _workspace.doneWriting();
227        }
228    }
229
230    /** Move this object to the specified position in the list
231     *  of attributes of the container. If this object is already at
232     *  the specified position, do  nothing. This method gets write
233     *  access on workspace and increments the version.
234     *  @param index The zero based position to which this object is moved.
235     *  0 means the first position, 1 means the second position.
236     *  @return The index of the specified object prior to moving it,
237     *   or -1 if it is not moved.
238     *  @exception IllegalActionException If this object has
239     *   no container or if the index is out of bounds.
240     */
241    @Override
242    public int moveToIndex(int index) throws IllegalActionException {
243        NamedObj container = getContainer();
244
245        if (container == null) {
246            throw new IllegalActionException(this, "Has no container.");
247        }
248
249        try {
250            _workspace.getWriteAccess();
251
252            int result = container._attributes.moveToIndex(this, index);
253
254            // Propagate.
255            Iterator derivedObjects = getDerivedList().iterator();
256
257            while (derivedObjects.hasNext()) {
258                NamedObj derived = (NamedObj) derivedObjects.next();
259                container = derived.getContainer();
260                container._attributes.moveToIndex(derived, index);
261            }
262
263            return result;
264        } finally {
265            _workspace.doneWriting();
266        }
267    }
268
269    /** Move this object to the last position in the list
270     *  of attributes of the container.  If this object is already last,
271     *  do nothing. This method gets write access on workspace
272     *  and increments the version.
273     *  @return The index of the specified object prior to moving it,
274     *   or -1 if it is not moved.
275     *  @exception IllegalActionException If this object has
276     *   no container.
277     */
278    @Override
279    public int moveToLast() throws IllegalActionException {
280        NamedObj container = getContainer();
281
282        if (container == null) {
283            throw new IllegalActionException(this, "Has no container.");
284        }
285
286        try {
287            _workspace.getWriteAccess();
288
289            int result = container._attributes.moveToLast(this);
290
291            // Propagate.
292            Iterator derivedObjects = getDerivedList().iterator();
293
294            while (derivedObjects.hasNext()) {
295                NamedObj derived = (NamedObj) derivedObjects.next();
296                container = derived.getContainer();
297                container._attributes.moveToLast(derived);
298            }
299
300            return result;
301        } finally {
302            _workspace.doneWriting();
303        }
304    }
305
306    /** Move this object up by one in the list of
307     *  attributes of the container. If this object is already first, do
308     *  nothing. This method gets write access on workspace
309     *  and increments the version.
310     *  @return The index of the specified object prior to moving it,
311     *   or -1 if it is not moved.
312     *  @exception IllegalActionException If this object has
313     *   no container.
314     */
315    @Override
316    public int moveUp() throws IllegalActionException {
317        NamedObj container = getContainer();
318
319        if (container == null) {
320            throw new IllegalActionException(this, "Has no container.");
321        }
322
323        try {
324            _workspace.getWriteAccess();
325
326            int result = container._attributes.moveUp(this);
327
328            // Propagate.
329            Iterator derivedObjects = getDerivedList().iterator();
330
331            while (derivedObjects.hasNext()) {
332                NamedObj derived = (NamedObj) derivedObjects.next();
333                container = derived.getContainer();
334                container._attributes.moveUp(derived);
335            }
336
337            return result;
338        } finally {
339            _workspace.doneWriting();
340        }
341    }
342
343    /** Get the NamedObj that this Attribute is attached to.
344     *  @return The container, an instance of NamedObj.
345     *  @see #setContainer(NamedObj)
346     */
347    @Override
348    public NamedObj getContainer() {
349        return _container;
350    }
351
352    /** Specify the container NamedObj, adding this attribute to the
353     *  list of attributes in the container.  If the container already
354     *  contains an attribute with the same name, then throw an exception
355     *  and do not make any changes.  Similarly, if the container is
356     *  not in the same workspace as this attribute, throw an exception.
357     *  If this attribute is already contained by the NamedObj, do nothing.
358     *  If the attribute already has a container, remove
359     *  this attribute from its attribute list first.  Otherwise, remove
360     *  it from the directory of the workspace, if it is there.
361     *  If the argument is null, then remove it from its container.
362     *  It is not added to the workspace directory, so this could result in
363     *  this object being garbage collected.
364     *  Note that since an Attribute is a NamedObj, it can itself have
365     *  attributes.  However, recursive containment is not allowed, where
366     *  an attribute is an attribute of itself, or indirectly of any attribute
367     *  it contains.  This method is write-synchronized on the
368     *  workspace and increments its version number.
369     *  <p>
370     *  Subclasses may constrain the type of container by overriding
371     *  {@link #setContainer(NamedObj)}.
372     *  @param container The container to attach this attribute to..
373     *  @exception IllegalActionException If this attribute is not of the
374     *   expected class for the container, or it has no name,
375     *   or the attribute and container are not in the same workspace, or
376     *   the proposed container would result in recursive containment.
377     *  @exception NameDuplicationException If the container already has
378     *   an attribute with the name of this attribute.
379     *  @see #getContainer()
380     */
381    public void setContainer(NamedObj container)
382            throws IllegalActionException, NameDuplicationException {
383        if (container != null && _workspace != container.workspace()) {
384            throw new IllegalActionException(this, container,
385                    "Cannot set container because workspaces are different.");
386        }
387        _checkContainer(container);
388        if (deepContains(container)) {
389            throw new IllegalActionException(this, container,
390                    "Attempt to construct recursive containment "
391                            + "of attributes");
392        }
393
394        NamedObj previousContainer = getContainer();
395
396        if (previousContainer == container) {
397            return;
398        }
399
400        _notifyHierarchyListenersBeforeChange();
401
402        try {
403            _workspace.getWriteAccess();
404
405            // Do this first, because it may throw an exception.
406            if (container != null) {
407                container._addAttribute(this);
408
409                if (previousContainer == null) {
410                    _workspace.remove(this);
411                }
412
413                // We have successfully set a new container for this
414                // object. Mark it modified to ensure MoML export.
415                // FIXME: Inappropriate?
416                // setOverrideDepth(0);
417            }
418
419            _container = container;
420
421            if (previousContainer != null) {
422                previousContainer._removeAttribute(this);
423            }
424
425            if (container != null) {
426                // Transfer any queued change requests to the
427                // new container.  There could be queued change
428                // requests if this component is deferring change
429                // requests.
430                if (_changeRequests != null) {
431                    Iterator requests = _changeRequests.iterator();
432
433                    while (requests.hasNext()) {
434                        ChangeRequest request = (ChangeRequest) requests.next();
435                        container.requestChange(request);
436                    }
437
438                    _changeRequests = null;
439                }
440            }
441        } finally {
442            try {
443                // Since we definitely notified the listeners
444                // before the change, we must definitely notify
445                // them after the change, even if the change caused
446                // some exceptions. Note that this too may trigger
447                // exceptions.
448                _notifyHierarchyListenersAfterChange();
449            } finally {
450                _workspace.doneWriting();
451            }
452        }
453    }
454
455    /** Set the name of the attribute. If there is already an attribute
456     *  of the container with the same name, then throw a
457     *  NameDuplicationException.
458     *  @exception IllegalActionException If the name contains a period.
459     *  @exception NameDuplicationException If there is already an
460     *       attribute with the same name in the container.
461     */
462    @Override
463    public void setName(String name)
464            throws IllegalActionException, NameDuplicationException {
465        if (name == null) {
466            name = "";
467        }
468
469        NamedObj container = getContainer();
470
471        if (container != null) {
472            Attribute another = container.getAttribute(name);
473
474            if (another != null && another != this) {
475                throw new NameDuplicationException(container,
476                        "Name duplication: " + name);
477            }
478        }
479
480        super.setName(name);
481    }
482
483    /** Update the content of this attribute.
484     *  In this base class, nothing is performed.
485     *  Subclasses need to override this class to update the attribute.
486     *  @exception InternalErrorException Not thrown in this base class.
487     */
488    public void updateContent() throws InternalErrorException {
489    }
490
491    ///////////////////////////////////////////////////////////////////
492    ////                         protected methods                 ////
493
494    /** Check that the specified container is of a suitable class for
495     *  this attribute.  In this base class, this method returns immediately
496     *  without doing anything.
497     *  @param container The proposed container.
498     *  @exception IllegalActionException If the container is not of
499     *   an acceptable class.  Not thrown in this base class.
500     */
501    protected void _checkContainer(NamedObj container)
502            throws IllegalActionException {
503    }
504
505    /** Get an attribute with the specified name in the specified container.
506     *  The type of object sought is an instance of the same class as
507     *  this object.  The returned object is assured of being an
508     *  instance of the same class as this object.
509     *  @param relativeName The name relative to the container.
510     *  @param container The container expected to contain the object.
511     *  @return An object of the same class as this object, or null
512     *   if there is none.
513     *  @exception IllegalActionException If the object exists
514     *   and has the wrong class.
515     */
516    @Override
517    protected NamedObj _getContainedObject(NamedObj container,
518            String relativeName) throws IllegalActionException {
519        Attribute candidate = container.getAttribute(relativeName);
520
521        if (candidate != null && !getClass().isInstance(candidate)) {
522            throw new IllegalActionException(this,
523                    "Expected " + candidate.getFullName()
524                            + " to be an instance of " + getClass().getName()
525                            + ", but it is " + candidate.getClass().getName());
526        }
527
528        return candidate;
529    }
530
531    /** Propagate existence of this object to the
532     *  specified object. This overrides the base class
533     *  to set the container.
534     *  @param container Object to contain the new object.
535     *  @exception IllegalActionException If the object
536     *   cannot be cloned.
537     *  @return A new object of the same class and name
538     *   as this one.
539     */
540    @Override
541    protected NamedObj _propagateExistence(NamedObj container)
542            throws IllegalActionException {
543        try {
544            Attribute newObject = (Attribute) super._propagateExistence(
545                    container);
546            newObject.setContainer(container);
547            return newObject;
548        } catch (NameDuplicationException e) {
549            throw new InternalErrorException(e);
550        }
551    }
552
553    ///////////////////////////////////////////////////////////////////
554    ////                         private variables                 ////
555
556    /** @serial Container of this attribute. */
557    private NamedObj _container;
558}