001/* A hierarchical library of components specified in MoML.
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.moml;
029
030import java.io.IOException;
031import java.io.StringWriter;
032import java.io.Writer;
033import java.net.URL;
034import java.util.Iterator;
035import java.util.List;
036
037import ptolemy.actor.Librariable;
038import ptolemy.kernel.ComponentEntity;
039import ptolemy.kernel.CompositeEntity;
040import ptolemy.kernel.util.Attribute;
041import ptolemy.kernel.util.IllegalActionException;
042import ptolemy.kernel.util.InternalErrorException;
043import ptolemy.kernel.util.InvalidStateException;
044import ptolemy.kernel.util.KernelException;
045import ptolemy.kernel.util.LazyComposite;
046import ptolemy.kernel.util.NameDuplicationException;
047import ptolemy.kernel.util.NamedObj;
048import ptolemy.kernel.util.Workspace;
049import ptolemy.util.MessageHandler;
050
051///////////////////////////////////////////////////////////////////
052//// EntityLibrary
053
054/**
055 This class provides a hierarchical library of components specified
056 in MoML.  The contents are typically specified via the configure()
057 method, which is passed MoML code.  The MoML is evaluated
058 lazily; i.e. it is not actually evaluated until there is a request
059 for its contents, via a call to getEntity(), numEntities(),
060 entityList(), or any related method. You can also force evaluation
061 of the MoML by calling populate().  When you export MoML for this
062 object, the MoML description of the contents is wrapped in a configure
063 element.  This object contains an attribute with name "_libraryMarker",
064 which marks it as a library.  This is used by the library browser in
065 vergil to know to expand the composite entity.
066 <p>
067 The contents of the library can be entities, ports, relations, or
068 attributes.  I.e., it can contain anything contained by a CompositeEntity.
069 An attempt to access any of these will trigger populating the library.
070 <p>
071 The configure method can be given a URL or MoML text or both.
072 If it is given MoML text, that text will normally be wrapped in a
073 processing instruction, as follows:
074 <pre>
075 &lt;?moml
076 &lt;group&gt;[
077 ... <i>MoML elements giving library contents</i> ...
078 &lt;/group&gt;
079 ?&gt;
080 </pre>
081 The processing instruction, which is enclosed in "&lt;?" and "?&gt;"
082 prevents premature evaluation of the MoML.  The processing instruction
083 has a <i>target</i>, "moml", which specifies that it contains MoML code.
084 The keyword "moml" in the processing instruction must
085 be exactly as above, or the entire processing instruction will
086 be ignored.  The populate() method
087 strips off the processing instruction and evaluates the MoML elements.
088 The group element allows the library contents to be given as a set
089 of elements (the MoML parser requires that there always be a single
090 top-level element, which in this case will be the group element).
091 <p>
092 One subtlety in using this class arises because of a problem typical
093 of lazy evaluation.  A number of exceptions may be thrown because of
094 errors in the MoML code when the MoML code is evaluated.  However,
095 since that code is evaluated lazily, it is evaluated in a context
096 where these exceptions are not expected.  There is no completely
097 clean solution to this problem; our solution is to translate all
098 exceptions to runtime exceptions in the populate() method.
099 This method, therefore, violates the condition for using runtime
100 exceptions in that the condition that causes these exceptions to
101 be thrown is not a testable precondition.
102 <p>
103 A second subtlety involves cloning.  When this class is cloned,
104 if the configure MoML text has not yet been evaluated, then the clone
105 is created with the same (unevaluated) MoML text, rather than being
106 populated with the contents specified by that text.  If the object
107 is cloned after being populated, the clone will also be populated.
108 <p>
109 A third subtlety involves the doc element.  Unfortunately, MoML
110 semantics define the doc element to replace any previously existing
111 doc elements.  But to find out whether there is any previously
112 existing doc element, the MoML parser calls getAttribute, which
113 has the effect of populating the library.  Thus, doc elements
114 should go inside the group, not outside.  For example, the
115 following organization results in no deferred evaluation:
116 <pre>
117 &lt;entity name="director library" class="ptolemy.moml.EntityLibrary"&gt;
118 &lt;doc&gt;default director library&lt;/doc&gt;
119 &lt;configure&gt;
120 &lt;?moml
121 &lt;group&gt;
122 ...
123 &lt;/group&gt;
124 ?&gt;
125 &lt;/configure&gt;
126 &lt;/entity&gt;
127 </pre>
128 The following, by contrast, is OK:
129 <pre>
130 &lt;entity name="director library" class="ptolemy.moml.EntityLibrary"&gt;
131 &lt;configure&gt;
132 &lt;?moml
133 &lt;group&gt;
134 &lt;doc&gt;default director library&lt;/doc&gt;
135 ...
136 &lt;/group&gt;
137 ?&gt;
138 &lt;/configure&gt;
139 &lt;/entity&gt;
140 </pre>
141
142 @author Edward A. Lee
143 @version $Id$
144 @since Ptolemy II 1.0
145 @Pt.ProposedRating Red (eal)
146 @Pt.AcceptedRating Red (bilung)
147 */
148
149// FIXME: Have to do ports and relations.  Only done attributes and entities.
150public class EntityLibrary extends CompositeEntity
151        implements LazyComposite, Librariable {
152    /** Construct a library in the default workspace with no
153     *  container and an empty string as its name. Add the library to the
154     *  workspace directory.
155     *  Increment the version number of the workspace.
156     */
157    public EntityLibrary() {
158        super();
159
160        try {
161            // NOTE: Used to call uniqueName() here to choose the name for the
162            // marker.  This is a bad idea.  This calls getEntity(), which
163            // triggers populate() on the library, defeating deferred
164            // evaluation.
165            new Attribute(this, "_libraryMarker");
166        } catch (KernelException ex) {
167            throw new InternalErrorException(null, ex, null);
168        }
169        _populated = false;
170    }
171
172    /** Construct a library in the specified workspace with
173     *  no container and an empty string as a name. You can then change
174     *  the name with setName(). If the workspace argument is null, then
175     *  use the default workspace. Add the actor to the workspace directory.
176     *  Increment the version number of the workspace.
177     *  @param workspace The workspace that will list the actor.
178     */
179    public EntityLibrary(Workspace workspace) {
180        super(workspace);
181
182        try {
183            // NOTE: Used to call uniqueName() here to choose the name for the
184            // marker.  This is a bad idea.  This calls getEntity(), which
185            // triggers populate() on the library, defeating deferred
186            // evaluation.
187            new Attribute(this, "_libraryMarker");
188        } catch (KernelException ex) {
189            throw new InternalErrorException(ex.toString());
190        }
191        _populated = false;
192    }
193
194    /** Construct a library with the given container and name.
195     *  @param container The container.
196     *  @param name The name of this library.
197     *  @exception IllegalActionException If the entity cannot be contained
198     *   by the proposed container.
199     *  @exception NameDuplicationException If the container already has an
200     *   actor with this name.
201     */
202    public EntityLibrary(CompositeEntity container, String name)
203            throws NameDuplicationException, IllegalActionException {
204        super(container, name);
205
206        // NOTE: Used to call uniqueName() here to choose the name for the
207        // marker.  This is a bad idea.  This calls getEntity(), which
208        // triggers populate() on the library, defeating deferred
209        // evaluation.
210        new Attribute(this, "_libraryMarker");
211        _populated = false;
212    }
213
214    ///////////////////////////////////////////////////////////////////
215    ////                         public methods                    ////
216
217    /** Return a list of the attributes contained by this object.
218     *  If there are no attributes, return an empty list.
219     *  This overrides the base class
220     *  to first populate the library, if necessary, by calling populate().
221     *  This method is read-synchronized on the workspace.
222     *  @return An unmodifiable list of instances of Attribute.
223     */
224    @Override
225    public List attributeList() {
226        populate();
227        return super.attributeList();
228    }
229
230    /** Return a list of the attributes contained by this object that
231     *  are instances of the specified class.  If there are no such
232     *  instances, then return an empty list.
233     *  This overrides the base class
234     *  to first populate the library, if necessary, by calling populate().
235     *  This method is read-synchronized on the workspace.
236     *  @param filter The class of attribute of interest.
237     *  @return A list of instances of specified class.
238     */
239    @Override
240    public List attributeList(Class filter) {
241        populate();
242        return super.attributeList(filter);
243    }
244
245    /** Clone the library into the specified workspace. The new object is
246     *  <i>not</i> added to the directory of that workspace (you must do this
247     *  yourself if you want it there). If the library has not yet been
248     *  populated, then the clone will also not have been populated.
249     *  @param workspace The workspace for the cloned object.
250     *  @exception CloneNotSupportedException If the library contains
251     *   level crossing transitions so that its connections cannot be cloned,
252     *   or if one of the attributes cannot be cloned.
253     *  @return A new EntityLibrary.
254     */
255    @Override
256    public Object clone(Workspace workspace) throws CloneNotSupportedException {
257        // To prevent populating during cloning, we set a flag.
258        _cloning = true;
259
260        try {
261            EntityLibrary result = (EntityLibrary) super.clone(workspace);
262            result._cloning = false;
263            return result;
264        } finally {
265            _cloning = false;
266        }
267    }
268
269    /** Specify the library contents by giving either a URL (the
270     *  <i>source</i> argument), or by directly giving the MoML text
271     *  (the <i>text</i> argument), or both.  The MoML is evaluated
272     *  when the populate() method is called.  This occurs
273     *  lazily, when there is a request for the contents of the library.
274     *  @param base The base relative to which references within the input
275     *   are found, or null if this is not known, or there is none.
276     *  @param source The input source, which specifies a URL, or null
277     *   if none.
278     *  @param text Configuration information given as text, or null if
279     *   none.
280     */
281    @Override
282    public void configure(URL base, String source, String text) {
283        // NOTE: This may be called more than once, in which case we need
284        // to accumulate the changes that are specified.
285        if (_configureText != null) {
286            populate();
287        }
288        _base = base;
289        _configureSource = source;
290        _configureText = text;
291        _configureDone = false;
292    }
293
294    /** List the contained class definitions in the order they were added
295     *  (using their setContainer() method).
296     *  The returned list is static in the sense
297     *  that it is not affected by any subsequent additions or removals
298     *  of entities.  This overrides the base class
299     *  to first populate the library, if necessary, by calling populate().
300     *  Note that this may result in a runtime exception being thrown
301     *  (if there is an error evaluating the MoML).
302     *  This method is read-synchronized on the workspace.
303     *  @return An unmodifiable list of ComponentEntity objects.
304     */
305    @Override
306    public List classDefinitionList() {
307        populate();
308        return super.classDefinitionList();
309    }
310
311    /** Return true if this object contains the specified object,
312     *  directly or indirectly.  That is, return true if the specified
313     *  object is contained by an object that this contains, or by an
314     *  object contained by an object contained by this, etc.
315     *  This method ignores whether the entities report that they are
316     *  atomic (see CompositeEntity), and always returns false if the entities
317     *  are not in the same workspace.
318     *  This method is read-synchronized on the workspace.
319     *  @param inside The NamedObj that is searched for.
320     *  @see ptolemy.kernel.CompositeEntity#isAtomic()
321     *  @return True if this contains the argument, directly or indirectly.
322     */
323    @Override
324    public boolean deepContains(NamedObj inside) {
325        // If this has not yet been populated, then it can't possibly contain
326        // the proposed object because the proposed object would not
327        // exist yet.  Therefore, we do not need to populate.
328        // Note that this makes this method override completely
329        // unnecessary, but we keep it here anyway to record the fact.
330        // Note that if we do call populate here, then the library
331        // will be populated whenever setContainer() is called on this
332        // library, which means that deferred evaluation will not ever
333        // actually be deferred.
334        // populate();
335        return super.deepContains(inside);
336    }
337
338    /** List the opaque entities that are directly or indirectly
339     *  contained by this entity.  The list will be empty if there
340     *  are no such contained entities.  This overrides the base class
341     *  to first populate the library, if necessary, by calling populate().
342     *  Note that this may result in a runtime exception being thrown
343     *  (if there is an error evaluating the MoML).
344     *  This method is read-synchronized on the workspace.
345     *  @return A list of opaque ComponentEntity objects.
346     */
347    @Override
348    public List deepEntityList() {
349        populate();
350        return super.deepEntityList();
351    }
352
353    /** List the contained entities in the order they were added
354     *  (using their setContainer() method).
355     *  The returned list is static in the sense
356     *  that it is not affected by any subsequent additions or removals
357     *  of entities.  This overrides the base class
358     *  to first populate the library, if necessary, by calling populate().
359     *  Note that this may result in a runtime exception being thrown
360     *  (if there is an error evaluating the MoML).
361     *  This method is read-synchronized on the workspace.
362     *  @return An unmodifiable list of ComponentEntity objects.
363     */
364    @Override
365    public List entityList() {
366        populate();
367        return super.entityList();
368    }
369
370    /** Get the attribute with the given name. The name may be compound,
371     *  with fields separated by periods, in which case the attribute
372     *  returned is contained by a (deeply) contained attribute.
373     *  This overrides the base class
374     *  to first populate the library, if necessary, by calling populate().
375     *  This method is read-synchronized on the workspace.
376     *  @param name The name of the desired attribute.
377     *  @return The requested attribute if it is found, null otherwise.
378     */
379    @Override
380    public Attribute getAttribute(String name) {
381        populate();
382        return super.getAttribute(name);
383    }
384
385    /** Get a contained entity by name. The name may be compound,
386     *  with fields separated by periods, in which case the entity
387     *  returned is contained by a (deeply) contained entity.
388     *  This overrides the base class
389     *  to first populate the library, if necessary, by calling populate().
390     *  Note that this may result in a runtime exception being thrown
391     *  (if there is an error evaluating the MoML).
392     *  This method is read-synchronized on the workspace.
393     *  @param name The name of the desired entity.
394     *  @return An entity with the specified name, or null if none exists.
395     */
396    @Override
397    public ComponentEntity getEntity(String name) {
398        populate();
399        return super.getEntity(name);
400    }
401
402    /** Return the input source that was specified the last time the configure
403     *  method was called.
404     *  @return The string representation of the input URL, or null if the
405     *  no source has been used to configure this object, or null if no
406     *  external source need be used to configure this object.
407     */
408    @Override
409    public String getConfigureSource() {
410        return _configureSource;
411    }
412
413    /** Return the text string that represents the current configuration of
414     *  this object.  Note that any configuration that was previously
415     *  specified using the source attribute need not be represented here
416     *  as well.
417     *  @return A configuration string, or null if no configuration
418     *  has been used to configure this object, or null if no
419     *  configuration string need be used to configure this object.
420     */
421    @Override
422    public String getConfigureText() {
423        // This method gets called by MoMLParser upon encountering
424        // </configure> as a protection against exceptions.
425        // In particular, it is possible for propagation to fail
426        // (albeit unlikely), so MoMLParser makes a backup copy
427        // of the previous configure text to restore the value
428        // if an exception occurs.  However, calling this method
429        // used to trigger populate(), which defeats the purpose of lazy
430        // evaluation.  Hence, as an optimization, we check here
431        // for the situation where configure() has not been called
432        // and no attributes or entities have been manually added.
433        // In that situation, we return an empty string, and avoid
434        // calling populate().
435        if (!_populated) {
436            return _configureText;
437        }
438        try {
439            StringWriter stringWriter = new StringWriter();
440            stringWriter.write("<group>\n");
441
442            Iterator classes = classDefinitionList().iterator();
443
444            while (classes.hasNext()) {
445                ComponentEntity entity = (ComponentEntity) classes.next();
446                entity.exportMoML(stringWriter, 1);
447            }
448
449            Iterator entities = entityList().iterator();
450
451            while (entities.hasNext()) {
452                ComponentEntity entity = (ComponentEntity) entities.next();
453                entity.exportMoML(stringWriter, 1);
454            }
455
456            stringWriter.write("</group>");
457            return stringWriter.toString();
458        } catch (IOException ex) {
459            return "";
460        }
461    }
462
463    /** Override the base class to prevent populating.
464     *  @return An iterator over instances of NamedObj contained by this
465     *   object.
466     */
467    @Override
468    public Iterator lazyContainedObjectsIterator() {
469        boolean previous = _populating;
470        try {
471            _populating = true;
472            return containedObjectsIterator();
473        } finally {
474            _populating = previous;
475        }
476    }
477
478    /** Return the number of contained entities. This overrides the base class
479     *  to first populate the library, if necessary, by calling populate().
480     *  Note that this may result in a runtime exception being thrown
481     *  (if there is an error evaluating the MoML).
482     *  This method is read-synchronized on the workspace.
483     *  @return The number of entities.
484     */
485    @Override
486    public int numberOfEntities() {
487        populate();
488        return super.numberOfEntities();
489    }
490
491    /** Populate the actor by reading the file specified by the
492     *  <i>source</i> parameter.  Note that the exception thrown here is
493     *  a runtime exception, inappropriately.  This is because execution of
494     *  this method is deferred to the last possible moment, and it is often
495     *  evaluated in a context where a compile-time exception cannot be
496     *  thrown.  Thus, extra care should be exercised to provide valid
497     *  MoML specifications.
498     *  @exception InvalidStateException If the source cannot be read, or if
499     *   an exception is thrown parsing its MoML data.
500     */
501    @Override
502    public void populate() throws InvalidStateException {
503        if (_populating) {
504            return;
505        }
506        try {
507            // Avoid populating during cloning.
508            if (_cloning) {
509                return;
510            }
511
512            _populating = true;
513
514            if (!_configureDone) {
515                // NOTE: If you suspect this is being called prematurely,
516                // then uncomment the following to see who is doing the
517                // calling.
518                // System.out.println("-----------------------");
519                // (new Exception()).printStackTrace();
520                // NOTE: Set this early to prevent repeated attempts to
521                // evaluate if an exception occurs.  This way, it will
522                // be possible to examine a partially populated entity.
523                _configureDone = true;
524
525                // NOTE: This does not seem like the right thing to do!
526                // removeAllEntities();
527                MoMLParser parser = new MoMLParser(workspace());
528
529                parser.setContext(this);
530
531                if (_configureSource != null && !_configureSource.equals("")) {
532                    URL xmlFile = new URL(_base, _configureSource);
533                    parser.parse(xmlFile, xmlFile);
534                }
535
536                if (_configureText != null && !_configureText.equals("")) {
537                    // NOTE: Regrettably, the XML parser we are using cannot
538                    // deal with having a single processing instruction at the
539                    // outer level.  Thus, we have to strip it.
540                    String trimmed = _configureText.trim();
541
542                    if (trimmed.startsWith("<?") && trimmed.endsWith("?>")) {
543                        trimmed = trimmed.substring(2, trimmed.length() - 2)
544                                .trim();
545
546                        if (trimmed.startsWith("moml")) {
547                            trimmed = trimmed.substring(4).trim();
548                            parser.parse(_base, trimmed);
549                        }
550
551                        // If it's not a moml processing instruction, ignore.
552                    } else {
553                        // Data is not enclosed in a processing instruction.
554                        // Must have been given in a CDATA section.
555                        parser.parse(_base, _configureText);
556                    }
557                }
558            }
559            // Set this if everything succeeds.
560            _populated = true;
561        } catch (Exception ex) {
562            MessageHandler.error("Failed to populate library.", ex);
563
564            // Oddly, under JDK1.3.1, we may see the line
565            // "Exception occurred during event dispatching:"
566            // in the console window, but there is no stack trace.
567            // If we change this exception to a RuntimeException, then
568            // the stack trace appears.  My guess is this indicates a
569            // bug in the ptolemy.kernel.Exception* classes or in JDK1.3.1
570            // Note that under JDK1.4, the stack trace is printed in
571            // both cases.
572            throw new InvalidStateException(this, ex,
573                    "Failed to populate Library");
574        } finally {
575            _populating = false;
576        }
577    }
578
579    ///////////////////////////////////////////////////////////////////
580    ////                         protected methods                 ////
581
582    /** Override the base class to prevent triggering a populate() call
583     *  when this occurs.
584     *  @param name The name of the attribute.
585     *  @param text The text with which to configure the attribute.
586     */
587    @Override
588    protected void _attachText(String name, String text) {
589        boolean previous = _populating;
590        try {
591            _populating = true;
592            super._attachText(name, text);
593        } finally {
594            _populating = previous;
595        }
596    }
597
598    /** Write a MoML description of the contents of this object, wrapped
599     *  in a configure element.  This is done by first populating the model,
600     *  and then exporting its contents into a configure element. This method
601     *  is called by exportMoML().  Each description is indented according to
602     *  the specified depth and terminated with a newline character.
603     *  @param output The output stream to write to.
604     *  @param depth The depth in the hierarchy, to determine indenting.
605     *  @exception IOException If an I/O error occurs.
606     */
607    @Override
608    protected void _exportMoMLContents(Writer output, int depth)
609            throws IOException {
610        output.write(_getIndentPrefix(depth) + "<configure>\n");
611        output.write(_getIndentPrefix(depth + 1) + "<group>\n");
612        super._exportMoMLContents(output, depth + 2);
613        output.write(_getIndentPrefix(depth + 1) + "</group>\n");
614        output.write(_getIndentPrefix(depth) + "</configure>\n");
615    }
616
617    ///////////////////////////////////////////////////////////////////
618    ////                         protected variables               ////
619
620    /** The base specified by the configure() method. */
621    protected URL _base;
622
623    /** Indicate that we are cloning. */
624    protected boolean _cloning = false;
625
626    /** Indicate whether data given by configure() has been processed. */
627    protected boolean _configureDone = false;
628
629    /** URL specified to the configure() method. */
630    protected String _configureSource;
631
632    /** Text specified to the configure() method. */
633    protected String _configureText;
634
635    /** Flag indicating populate() has been called, and hence
636     *  the configuration should be obtained from the current contents
637     *  (in case they have changed) rather than from the _configureText.
638     */
639    protected boolean _populated = false;
640
641    /** Indicator that we are in the midst of populating. */
642    protected boolean _populating = false;
643}