001/* A directory of open models.
002
003 Copyright (c) 1999-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 */
027package ptolemy.actor.gui;
028
029import java.net.URL;
030import java.util.Iterator;
031import java.util.List;
032
033import ptolemy.kernel.ComponentEntity;
034import ptolemy.kernel.CompositeEntity;
035import ptolemy.kernel.attributes.URIAttribute;
036import ptolemy.kernel.util.IllegalActionException;
037import ptolemy.kernel.util.InternalErrorException;
038import ptolemy.kernel.util.KernelException;
039import ptolemy.kernel.util.NameDuplicationException;
040import ptolemy.kernel.util.StringAttribute;
041import ptolemy.moml.MoMLParser;
042
043///////////////////////////////////////////////////////////////////
044//// ModelDirectory
045
046/**
047 A directory of open models. An instance of this class is contained
048 by a Configuration. Each open model is represented by an instance of
049 Effigy.  An effigy represents the model data.
050 It contains a string attribute named "identifier"
051 with a string value that uniquely identifies the model.
052 A typical choice (which depend on the configuration)
053 is the canonical URL for a MoML file that describes the model.
054 An effigy also contains all open instances of Tableau associated
055 with the model.
056
057 @author Steve Neuendorffer and Edward A. Lee
058 @version $Id$
059 @since Ptolemy II 1.0
060 @Pt.ProposedRating Yellow (eal)
061 @Pt.AcceptedRating Red (cxh)
062 @see Configuration
063 @see Effigy
064 @see Tableau
065 */
066public class ModelDirectory extends CompositeEntity {
067    /** Construct a model directory with the specified container and name.
068     *  @param container The configuration that contains this directory.
069     *  @param name The name of the directory.
070     *  @exception IllegalActionException If the entity cannot be contained
071     *   by the proposed container.  This should not be thrown.
072     *  @exception NameDuplicationException If the name coincides with
073     *   an entity already in the container.
074     */
075    public ModelDirectory(CompositeEntity container, String name)
076            throws IllegalActionException, NameDuplicationException {
077        super(container, name);
078    }
079
080    ///////////////////////////////////////////////////////////////////
081    ////                         public methods                    ////
082
083    /** Get the effigy of the model that corresponds to the specified
084     *  identifier.
085     *  @param identifier The identifier for the model, such as a URL.
086     *  @return The effigy for the model, or null if the model is not
087     *   in the directory.
088     */
089    public Effigy getEffigy(String identifier) {
090        Iterator entities = entityList(Effigy.class).iterator();
091
092        while (entities.hasNext()) {
093            Effigy entity = (Effigy) entities.next();
094            StringAttribute id = (StringAttribute) entity
095                    .getAttribute("identifier");
096
097            if (id != null) {
098                String idString = id.getExpression();
099
100                if (idString.equals(identifier)) {
101                    return entity;
102                }
103            }
104        }
105
106        return null;
107    }
108
109    ///////////////////////////////////////////////////////////////////
110    ////                         protected methods                 ////
111
112    /** Remove the specified entity, and if there are no more models
113     *  in the directory, except possibly the configuration, then
114     *  remove this directory from its container.
115     *  This method should not be used directly.  Call the setContainer()
116     *  method of the entity instead with a null argument.
117     *  The entity is assumed to be contained by this composite (otherwise,
118     *  nothing happens). This does not alter the entity in any way.
119     *  This method is <i>not</i> synchronized on the workspace, so the
120     *  caller should be.
121     *  This class overrides the superclass to check if this composite is
122     *  empty, and if so, calls system.exit
123     *  @param entity The entity to remove.
124     */
125    @Override
126    protected void _removeEntity(ComponentEntity entity) {
127        super._removeEntity(entity);
128
129        List remainingEntities = entityList(Effigy.class);
130
131        if (remainingEntities.size() == 0) {
132            try {
133                // This clause gets called when File->Exit is invoked.
134                _purgeConfigurationURL();
135                setContainer(null);
136            } catch (KernelException ex) {
137                throw new InternalErrorException("Cannot remove directory!");
138            }
139        } else {
140            if (remainingEntities.size() == 1) {
141                // Check to see whether what remains is only the configuration.
142                Object remaining = remainingEntities.get(0);
143
144                if (remaining instanceof PtolemyEffigy) {
145                    if (((PtolemyEffigy) remaining)
146                            .getModel() instanceof Configuration) {
147                        try {
148                            // This clause gets called when the window is closed.
149                            _purgeConfigurationURL();
150                            setContainer(null);
151                        } catch (KernelException ex) {
152                            throw new InternalErrorException(
153                                    "Cannot remove directory!");
154                        }
155                    }
156                }
157            }
158
159            // Finally, we might have a case where none of the effigies in
160            // the application have a tableau, in which case the application
161            // no longer has a UI.  If this happens, then we want to remove
162            // the directory, triggering the application to exit.
163            boolean anyTableau = false;
164
165            // Check to see if the remaining effigies have any tableaux.
166            for (Iterator effigies = remainingEntities.iterator(); effigies
167                    .hasNext() && !anyTableau;) {
168                Effigy effigy = (Effigy) effigies.next();
169
170                if (effigy.numberOfOpenTableaux() > 0) {
171                    anyTableau = true;
172                }
173            }
174
175            // If we can't find any tableau for any of the effigies, then exi
176            if (!anyTableau) {
177                try {
178                    // This gets reentrant...  Ugh..
179                    for (Iterator effigies = remainingEntities
180                            .iterator(); effigies.hasNext();) {
181                        Effigy effigy = (Effigy) effigies.next();
182                        effigy.setContainer(null);
183                    }
184                } catch (KernelException ex) {
185                    throw new InternalErrorException(
186                            "Cannot remove directory!");
187                }
188            }
189        }
190    }
191
192    /** If the configuration is present, then purge the model record
193     * of the configuration.  This is done so that if we re-read the configuration.xml
194     * file, then we get the ModelDirectory.
195     * This came up with applets that bring up a separate window from
196     * the browser.  To reproduce:
197     * <ol>
198     * <li> point your browser at a Ptolemy applet demo.</li>
199     * <li> The toplevel applet window containing the demo comes up.</li>
200     * <li> Close the toplevel applet window.</li>
201     * <li> Reload the browser page.</li>
202     * </ol>
203     * Formerly, we were seeing exceptions about missing directory.
204     */
205    private void _purgeConfigurationURL() {
206        if (getContainer() != null) {
207            List attributes = getContainer().attributeList(URIAttribute.class);
208            if (attributes.size() > 0) {
209                // The entity has a URI, which was probably
210                // inserted by MoMLParser.
211                URL url = null;
212                try {
213                    url = ((URIAttribute) attributes.get(0)).getURL();
214                    MoMLParser.purgeModelRecord(url);
215                } catch (Exception ex) {
216                    throw new InternalErrorException("Cannot purge " + url);
217                }
218            }
219        }
220    }
221}