001/* A named object that can be either a class or an instance.
002
003 Copyright (c) 2003-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;
029
030import java.io.IOException;
031import java.io.Writer;
032import java.lang.ref.WeakReference;
033import java.util.Collections;
034import java.util.LinkedList;
035import java.util.List;
036import java.util.ListIterator;
037
038import ptolemy.kernel.util.IllegalActionException;
039import ptolemy.kernel.util.Instantiable;
040import ptolemy.kernel.util.NameDuplicationException;
041import ptolemy.kernel.util.NamedObj;
042import ptolemy.kernel.util.Workspace;
043import ptolemy.util.StringUtilities;
044
045///////////////////////////////////////////////////////////////////
046//// InstantiableNamedObj
047
048/**
049 An InstantiableNamedObj is a named object that can be either a class
050 definition or an instance.  If it is a class definition, then "instances" of
051 that class definition can be created by the instantiate() method. Those
052 instances are called the "children" of this "parent." Changes
053 to the parent propagate automatically to the children as described
054 in the {@link Instantiable} interface.
055 <p>
056 Note that the {@link #instantiate(NamedObj, String)} permits instantiating
057 an object into a workspace that is different from the one associated with
058 this object.  This means that some care must be exercised when propagating
059 changes from a parent to a child, since they may be in different workspaces.
060 Suppose for example that the change that has to propagate is made via a
061 change request. Although it may be a safe time to execute a change request
062 in the parent, it is not necessarily a safe time to execute a change request
063 in the child.  Classes that restrict these safe times should override
064 the propagateExistence(), propagateValue(), and propagateValues() methods
065 to ensure that the destinations of the propagation are in a state that
066 they can accept changes.
067
068 @author Edward A. Lee
069 @version $Id$
070 @since Ptolemy II 4.0
071 @see Instantiable
072 @Pt.ProposedRating Green (eal)
073 @Pt.AcceptedRating Green (neuendor)
074 */
075public class InstantiableNamedObj extends NamedObj implements Instantiable {
076    /** Construct an object in the default workspace with an empty string
077     *  as its name.
078     *  The object is added to the workspace directory.
079     *  Increment the version number of the workspace.
080     */
081    public InstantiableNamedObj() {
082        super();
083    }
084
085    /** Construct an object in the default workspace with the given name.
086     *  If the name argument
087     *  is null, then the name is set to the empty string.
088     *  The object is added to the workspace directory.
089     *  Increment the version number of the workspace.
090     *  @param name The name of this object.
091     *  @exception IllegalActionException If the name has a period.
092     */
093    public InstantiableNamedObj(String name) throws IllegalActionException {
094        super(name);
095    }
096
097    /** Construct an object in the given workspace with an empty string
098     *  as a name.
099     *  If the workspace argument is null, use the default workspace.
100     *  The object is added to the workspace directory.
101     *  Increment the version of the workspace.
102     *  @param workspace The workspace for synchronization and version tracking.
103     */
104    public InstantiableNamedObj(Workspace workspace) {
105        super(workspace);
106    }
107
108    /** Construct an object in the given workspace with the given name.
109     *  If the workspace argument is null, use the default workspace.
110     *  If the name argument
111     *  is null, then the name is set to the empty string.
112     *  The object is added to the workspace directory.
113     *  Increment the version of the workspace.
114     *  @param workspace The workspace for synchronization and version tracking.
115     *  @param name The name of this object.
116     *  @exception IllegalActionException If the name has a period.
117     */
118    public InstantiableNamedObj(Workspace workspace, String name)
119            throws IllegalActionException {
120        super(workspace, name);
121    }
122
123    ///////////////////////////////////////////////////////////////////
124    ////                         public methods                    ////
125
126    /** Clone the object into the specified workspace. The new object is
127     *  <i>not</i> added to the directory of that workspace (you must do this
128     *  yourself if you want it there). The result is a new instance of
129     *  InstantiableNamedObj that is a child of the parent of this object,
130     *  if this object has a parent. The new instance has no children.
131     *  This method gets read access on the workspace associated with
132     *  this object.
133     *  @param workspace The workspace for the cloned object.
134     *  @exception CloneNotSupportedException If one of the attributes
135     *   cannot be cloned.
136     *  @return A new instance of InstantiableNamedObj.
137     */
138    @Override
139    public Object clone(Workspace workspace) throws CloneNotSupportedException {
140        try {
141            workspace().getReadAccess();
142
143            InstantiableNamedObj newObject = (InstantiableNamedObj) super.clone(
144                    workspace);
145
146            // The new object does not have any other objects deferring
147            // their MoML definitions to it, so we have to reset this.
148            newObject._children = null;
149
150            // Set the parent using _setParent() rather than the default
151            // clone to get the side effects.
152            newObject._parent = null;
153
154            // NOTE: This used to do the following,
155            // but we now rely on _adjustDeferrals()
156            // to fix the parent relationships.
157
158            /*
159             if (_parent != null) {
160             try {
161             newObject._setParent(_parent);
162             } catch (IllegalActionException ex) {
163             throw new InternalErrorException(ex);
164             }
165             }
166             */
167            return newObject;
168        } finally {
169            workspace().doneReading();
170        }
171    }
172
173    /** Write a MoML description of this object with the specified
174     *  indentation depth and with the specified name substituting
175     *  for the name of this object. The description has one of two
176     *  forms, depending on whether this is a class definition.
177     *  If it is, then the exported MoML looks like this:
178     *  <pre>
179     *      &lt;class name="<i>name</i>" extends="<i>classname</i> source="<i>source</i>"&gt;
180     *          <i>body, determined by _exportMoMLContents()</i>
181     *      &lt;/class&gt;
182     *  </pre>
183     *  Otherwise, the exported MoML is that generated by the
184     *  superclass method that this overrides.
185     *  <p>
186     *  If this object has no container and the depth argument is zero,
187     *  then this method prepends XML file header information, which is:
188     *  <pre>
189     *  &lt;?xml version="1.0" standalone="no"?&gt;
190     *  &lt;!DOCTYPE entity PUBLIC "-//UC Berkeley//DTD MoML 1//EN"
191     *      "http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd"&gt;
192     *  </pre>
193     *  In the above, "entity" may be replaced by "property" or
194     *  "port" if what is being exported is an attribute or a port.
195     *  <p>
196     *  The text that is written is indented according to the specified
197     *  depth, with each line (including the last one)
198     *  terminated with a newline.
199     *  Derived classes can override this method to change the MoML
200     *  description of an object.  They can override the protected
201     *  method _exportMoMLContents() if they need to only change which
202     *  contents are described.
203     *  <p>
204     *  If this object is not persistent, or if there is no MoML
205     *  description of this object, or if this object is implied
206     *  by a parent-child relationship that less than <i>depth</i>
207     *  levels up in the containment hierarchy and it has not
208     *  been overridden, then write nothing.
209     *  @param output The output stream to write to.
210     *  @param depth The depth in the hierarchy, to determine indenting.
211     *  @param name The name to use in the exported MoML.
212     *  @exception IOException If an I/O error occurs.
213     *  @see ptolemy.kernel.util.MoMLExportable
214     */
215    @Override
216    public void exportMoML(Writer output, int depth, String name)
217            throws IOException {
218
219        if (!isClassDefinition()) {
220            super.exportMoML(output, depth, name);
221            return;
222        }
223
224        // escape any < character in name. unescapeForXML occurs in
225        // NamedObj.setName(String)
226        // If we don't escape the name here then we generate
227        // MoML that is not valid XML.  See MoMLParser-34.0 in
228        // moml/test/MoMLParser.tcl
229        name = StringUtilities.escapeForXML(name);
230
231        // If the object is not persistent, and we are not
232        // at level 0, do nothing.
233        if (_isMoMLSuppressed(depth)) {
234            return;
235        }
236
237        if (depth == 0 && getContainer() == null) {
238            // No container, and this is a top level moml element.
239            // Generate header information.
240            // NOTE: Currently, there is only one class designation,
241            // and it always applies to an Entity. Attributes that
242            // are classes are not yet supported. When they are,
243            // then "class" below may need to replaced with something
244            // else.
245            output.write("<?xml version=\"1.0\" standalone=\"no\"?>\n"
246                    + "<!DOCTYPE class PUBLIC "
247                    + "\"-//UC Berkeley//DTD MoML 1//EN\"\n"
248                    + "    \"http://ptolemy.eecs.berkeley.edu"
249                    + "/xml/dtd/MoML_1.dtd\">\n");
250        }
251
252        output.write(_getIndentPrefix(depth) + "<class name=\"" + name
253                + "\" extends=\"" + getClassName() + "\"");
254
255        if (getSource() != null) {
256            output.write(" source=\"" + getSource() + "\">\n");
257        } else {
258            output.write(">\n");
259        }
260
261        _exportMoMLContents(output, depth + 1);
262
263        // Write the close of the element.
264        output.write(_getIndentPrefix(depth) + "</class>\n");
265    }
266
267    /** Get a list of weak references to instances of Instantiable
268     *  that are children of this object.  This method
269     *  may return null or an empty list to indicate that there are
270     *  no children.
271     *  @return An unmodifiable list of instances of
272     *   java.lang.ref.WeakReference that refer to
273     *   instances of Instantiable or null if this object
274     *   has no children.
275     *  @see Instantiable
276     *  @see java.lang.ref.WeakReference
277     */
278    @Override
279    public List getChildren() {
280        if (_children == null) {
281            return null;
282        }
283
284        return Collections.unmodifiableList(_children);
285    }
286
287    /** Get the MoML element name. If this is a class definition, then
288     *  return "class". Otherwise, defer to the base class.
289     *  @return The MoML element name for this object.
290     *  @see ptolemy.kernel.util.MoMLExportable
291     */
292    @Override
293    public String getElementName() {
294        if (isClassDefinition()) {
295            return "class";
296        } else {
297            return super.getElementName();
298        }
299    }
300
301    /** Return the parent of this object, or null if there is none.
302     *  @return The parent of this object, or null if there is none.
303     *  @see #_setParent(Instantiable)
304     *  @see Instantiable
305     */
306    @Override
307    public Instantiable getParent() {
308        return _parent;
309    }
310
311    /** Return a list of prototypes for this object. The list is ordered
312     *  so that more local prototypes are listed before more remote
313     *  prototypes. Specifically, if this object has a parent, then the
314     *  parent is listed first. If the container has a parent, and
315     *  that parent contains an object whose name matches the name
316     *  of this object, then that object is listed next.
317     *  If the container of the container has a parent, and that parent
318     *  (deeply) contains a prototype, then that prototype is listed next.
319     *  And so on up the hierarchy.
320     *  @return A list of prototypes for this object, each of which is
321     *   assured of being an instance of the same (Java) class as this
322     *   object, or an empty list if there are no prototypes.
323     *  @exception IllegalActionException If a prototype with the right
324     *   name but the wrong class is found.
325     *  @see ptolemy.kernel.util.Derivable
326     */
327    @Override
328    public List getPrototypeList() throws IllegalActionException {
329        List result = super.getPrototypeList();
330
331        if (getParent() != null) {
332            result.add(0, getParent());
333        }
334
335        return result;
336    }
337
338    /** Create an instance by (deeply) cloning this object and then adjusting
339     *  the parent-child relationships between the clone and its parent.
340     *  Specifically, the clone defers its definition to this object,
341     *  which becomes its "parent." The "child" inherits all the objects
342     *  contained by this object. If this object is a composite, then this
343     *  method adjusts any parent-child relationships that are entirely
344     *  contained within the child. That is, for any parent-child relationship
345     *  that is entirely contained within this object (i.e., both the parent
346     *  and the child are deeply contained by this object), a corresponding
347     *  parent-child relationship is created within the clone such that
348     *  both the parent and the child are entirely contained within
349     *  the clone.
350     *  <p>
351     *  The new object is not a class definition by default (it is an
352     *  "instance" rather than a "class").  To make it a class
353     *  definition (a "subclass"), call {@link #setClassDefinition(boolean)}
354     *  with a <i>true</i> argument.
355     *  <p>
356     *  In this base class, the container argument is ignored except that
357     *  it provides the workspace into which to clone this object. Derived
358     *  classes with setContainer() methods are responsible for overriding
359     *  this and calling setContainer().
360     *  <p>
361     *  Note that the workspace for the instantiated object can be different
362     *  from the workspace for this object. This means that propagation of
363     *  changes from a parent to a child may not be able to be safely
364     *  performed in the child even when they are safely performed in the
365     *  parent. Subclasses that restrict when changes are performed are
366     *  therefore required to check whether the workspaces are the same
367     *  before propagating changes.
368     *  @param container The container for the instance, or null
369     *   to instantiate it at the top level. Note that this base class
370     *   does not set the container. It uses the container argument to
371     *   get the workspace. Derived classes are responsible for
372     *   setting the container.
373     *  @param name The name for the instance.
374     *  @return A new instance that is a clone of this object
375     *   with adjusted parent-child relationships.
376     *  @exception CloneNotSupportedException If this object
377     *   cannot be cloned.
378     *  @exception IllegalActionException If this object is not a
379     *   class definition or the proposed container is not acceptable.
380     *  @exception NameDuplicationException If the name collides with
381     *   an object already in the container.
382     *  @see #setClassDefinition(boolean)
383     *  @see Instantiable
384     */
385    @Override
386    public Instantiable instantiate(NamedObj container, String name)
387            throws CloneNotSupportedException, IllegalActionException,
388            NameDuplicationException {
389        if (!isClassDefinition()) {
390            throw new IllegalActionException(this,
391                    "Cannot instantiate an object that is not a "
392                            + "class definition");
393        }
394
395        // Use the workspace of the container, if there is one,
396        // or the workspace of this object, if there isn't.
397        Workspace workspace = workspace();
398
399        if (container != null) {
400            workspace = container.workspace();
401        }
402
403        InstantiableNamedObj clone = (InstantiableNamedObj) clone(workspace);
404
405        // The cloning process results an object that defers change
406        // requests.  By default, we do not want to defer change
407        // requests, but more importantly, we need to execute
408        // any change requests that may have been queued
409        // during cloning. The following call does that.
410        clone.setDeferringChangeRequests(false);
411
412        // Set the name before the container to not get
413        // spurious name conflicts.
414        clone.setName(name);
415        clone._setParent(this);
416        clone.setClassDefinition(false);
417        clone.setClassName(getFullName());
418
419        // Mark the contents of the instantiated object as being derived.
420        clone._markContentsDerived(0);
421
422        return clone;
423    }
424
425    /** Return true if this object is a class definition, which means that
426     *  it can be instantiated.
427     *  @return True if this object is a class definition.
428     *  @see #setClassDefinition(boolean)
429     *  @see Instantiable
430     */
431    @Override
432    public final boolean isClassDefinition() {
433        return _isClassDefinition;
434    }
435
436    /** Return true if this object is a class definition or is within
437     *  a class definition, which means that
438     *  any container above it in the hierarchy is
439     *  a class definition.
440     *  @return True if this object is within a class definition.
441     *  @see #setClassDefinition(boolean)
442     *  @see Instantiable
443     */
444    public final boolean isWithinClassDefinition() {
445        if (_isClassDefinition) {
446            return true;
447        } else {
448            NamedObj container = getContainer();
449            while (container != null) {
450                if (container instanceof InstantiableNamedObj) {
451                    if (((InstantiableNamedObj) container)._isClassDefinition) {
452                        return true;
453                    }
454                }
455                container = container.getContainer();
456            }
457            return false;
458        }
459    }
460
461    /** Specify whether this object is a class definition.
462     *  This method is write synchronized on the workspace.
463     *  @param isClass True to make this object a class definition, false
464     *   to make it an instance.
465     *  @exception IllegalActionException If there are subclasses and/or
466     *   instances and the argument is false.
467     *  @see #isClassDefinition()
468     *  @see Instantiable
469     */
470    public void setClassDefinition(boolean isClass)
471            throws IllegalActionException {
472        workspace().getWriteAccess();
473
474        try {
475            if (!isClass && _isClassDefinition && getChildren() != null
476                    && getChildren().size() > 0) {
477                throw new IllegalActionException(this,
478                        "Cannot change from a class to an instance because"
479                                + " there are subclasses and/or instances.");
480            }
481
482            if (_isClassDefinition != isClass) {
483                // Changing the class status is a hierarchy event that
484                // contained objects need to be notified of.
485                _notifyHierarchyListenersBeforeChange();
486                _isClassDefinition = isClass;
487            }
488        } finally {
489            workspace().doneWriting();
490            _notifyHierarchyListenersAfterChange();
491        }
492    }
493
494    /** Specify the parent for this object.  This  method should be called
495     *  to make this object either an instance or a subclass of
496     *  the other object. When generating
497     *  a MoML description of this object, instead of giving a detailed
498     *  description, this object will henceforth refer to the
499     *  specified other object.  The name of that other object goes
500     *  into the "class" or "extends" attribute of the MoML element
501     *  defining this object (depending on whether this is an instance
502     *  or a subclass).  This method is called when this object
503     *  is constructed using the {@link #instantiate(NamedObj, String)}
504     *  method. This method is write synchronized on
505     *  the workspace because it modifies the object that is the
506     *  argument to refer back to this one.
507     *  <p>
508     *  Note that a parent references a child via a weak reference.
509     *  This means that the parent will not prevent the child from
510     *  being garbage collected. However, as long as the child has
511     *  not been garbage collected, changes to the parent will
512     *  propagate to the child even if there are no other live
513     *  references to the child. If there are a large number of
514     *  such dangling children, this could create performance
515     *  problems when making changes to the parent.
516     *  @param parent The parent, or null to specify that there is
517     *   no parent.
518     *  @exception IllegalActionException If the parent is not an
519     *   instance of InstantiableNamedObj.
520     *  @see #exportMoML(Writer, int)
521     *  @see #getParent()
522     *  @see Instantiable
523     */
524    protected void _setParent(Instantiable parent)
525            throws IllegalActionException {
526        if (parent != null && !(parent instanceof InstantiableNamedObj)) {
527            throw new IllegalActionException(this,
528                    "Parent of an InstantiableNamedObj must also "
529                            + "be an InstantiableNamedObj.");
530        }
531
532        try {
533            _workspace.getWriteAccess();
534
535            if (_parent != null) {
536                // Previously existing deferral.
537                // Remove it.
538                // NOTE: If WeakReference overrides equal(),
539                // then this could probably be done more simply.
540                List deferredFromList = _parent._children;
541
542                if (deferredFromList != null) {
543                    // Removing a previous reference.
544                    // Note that this is a list of weak references, so
545                    // it is not sufficient to just remove this!
546                    ListIterator references = deferredFromList.listIterator();
547
548                    while (references.hasNext()) {
549                        WeakReference reference = (WeakReference) references
550                                .next();
551
552                        if (reference == null || reference.get() == this) {
553                            references.remove();
554                        }
555                    }
556                }
557            }
558
559            _parent = (InstantiableNamedObj) parent;
560
561            if (_parent != null) {
562                if (_parent._children == null) {
563                    _parent._children = new LinkedList();
564                }
565
566                // NOTE: These need to be weak references.
567                _parent._children.add(new WeakReference(this));
568            }
569        } finally {
570            _workspace.doneWriting();
571        }
572    }
573
574    ///////////////////////////////////////////////////////////////////
575    ////                         private variables                 ////
576
577    /** List of weak references to children for which this object
578     *  is the parent.
579     */
580    private List _children;
581
582    /** Parent to which this object defers its definition to, or
583     *  null if there is none.
584     */
585    private InstantiableNamedObj _parent;
586
587    /** Indicator of whether this is a class definition. */
588    private boolean _isClassDefinition;
589}