001/* Utilities for User Actor Libraries
002
003 Copyright (c) 2006-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.io.File;
030import java.io.FileWriter;
031import java.io.IOException;
032import java.io.StringWriter;
033import java.net.URL;
034
035import ptolemy.kernel.CompositeEntity;
036import ptolemy.kernel.Entity;
037import ptolemy.kernel.attributes.URIAttribute;
038import ptolemy.kernel.util.ChangeRequest;
039import ptolemy.kernel.util.IllegalActionException;
040import ptolemy.kernel.util.InternalErrorException;
041import ptolemy.kernel.util.NameDuplicationException;
042import ptolemy.kernel.util.StringAttribute;
043import ptolemy.moml.LibraryBuilder;
044import ptolemy.moml.MoMLChangeRequest;
045import ptolemy.moml.MoMLParser;
046import ptolemy.util.StringUtilities;
047
048///////////////////////////////////////////////////////////////////
049//// UserActorLibrary
050
051/**
052 Access the User Actor Library.
053
054 @author Christopher Brooks, based on work by Steve Neuendorffer, Edward A. Lee, Contributor: Chad Berkeley (Kepler)
055 @version $Id$
056 @since Ptolemy II 5.2
057 @Pt.ProposedRating Red (neuendor)
058 @Pt.AcceptedRating Red (neuendor)
059 */
060public class UserActorLibrary {
061    /**
062     *  Open the user actor library as a new library in the actor
063     *  library for this application.
064     *
065     *  <p>The name of the user actor library consists of the
066     *  values of {@link ptolemy.util.StringUtilities#preferencesDirectory()}
067     *  and {@link #USER_LIBRARY_NAME} and ".xml" concatenated.
068     *
069     *  <p>An alternate class can be used to build the library if reading the
070     *  MoML is not desired.  The class must extend ptolemy.moml.LibraryBuilder
071     *  and the _alternateLibraryBuilder property must be set with the 'value'
072     *  set to the class that extends LibraryBuilder.
073     *
074     *  @param configuration The configuration where we look for the
075     *  actor library.
076     *  @exception Exception If there is a problem opening the configuration,
077     *  opening the MoML file, or opening the MoML file as a new library.
078     */
079    public static void openUserLibrary(Configuration configuration)
080            throws Exception {
081
082        // FIXME: If the name is something like
083        // "vergilUserLibrary.xml" then when we save an actor in the
084        // library and then save the window that comes up the name of
085        // entity gets set to vergilUserLibrary instead of the value
086        // of USER_LIBRARY_NAME. This causes problems when we
087        // try to save another file. The name of the entity gets
088        // changed by the saveAs code.
089
090        String libraryName = null;
091
092        try {
093            libraryName = StringUtilities.preferencesDirectory()
094                    + USER_LIBRARY_NAME + ".xml";
095        } catch (Exception ex) {
096            System.out.println("Warning: Failed to get the preferences "
097                    + "directory (-sandbox always causes this): " + ex);
098        }
099
100        if (libraryName != null) {
101            File file = new File(libraryName);
102
103            if (!file.isFile() || !file.exists()) {
104                // File might exist under an old name.
105                // Try to read it.
106                String oldLibraryName = StringUtilities.preferencesDirectory()
107                        + "user library.xml";
108                File oldFile = new File(oldLibraryName);
109
110                if (oldFile.isFile() && oldFile.exists()) {
111                    if (!oldFile.renameTo(file)) {
112                        throw new IOException("Failed to rename \"" + oldFile
113                                + "\" to \"" + file + "\".");
114                    }
115                }
116            }
117
118            if (!file.isFile() || !file.exists()) {
119                FileWriter writer = null;
120
121                try {
122                    if (!file.createNewFile()) {
123                        throw new Exception(file + "already exists?");
124                    }
125
126                    writer = new FileWriter(file);
127                    writer.write("<entity name=\"" + USER_LIBRARY_NAME
128                            + "\" class=\"ptolemy.moml.EntityLibrary\"/>");
129                    writer.close();
130                } catch (Exception ex) {
131                    throw new Exception("Failed to create an empty user "
132                            + "library: " + libraryName, ex);
133                } finally {
134                    if (writer != null) {
135                        writer.close();
136                    }
137                }
138            }
139            openLibrary(configuration, file);
140        }
141    }
142
143    /**
144     *  Open the MoML file at the given location as a new library in
145     *  the actor library for this application.
146     *
147     *  <p>An alternate class can be used to build the library if reading
148     *  the MoML is not desired.  The class must extend
149     *  ptolemy.moml.LibraryBuilder and the _alternateLibraryBuilder
150     *  property must be set with the 'value' set to the class that
151     *  extends LibraryBuilder.</p>
152     *
153     *  <p>A library of components is a .xml file that defines a MoML Class
154     *  that extends ptolemy.moml.EntityLibrary, for example, the following
155     *  file creates a library called "MyActors" that has an XYPlotter
156     *  in the library:</p>
157     *  <pre>
158     *  &lt;?xml version="1.0" standalone="no"?&gt;
159     *  &lt;!DOCTYPE class PUBLIC "-//UC Berkeley//DTD MoML 1//EN"
160     *      "http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd"&gt;
161     *  &lt;class name="MyActors" extends="ptolemy.moml.EntityLibrary"&gt;
162     *    &lt;configure&gt;
163     *      &lt;group&gt;
164     *         &lt;entity name="XY Plotter" class="ptolemy.actor.lib.gui.XYPlotter"/&gt;
165     *      &lt;/group&gt;
166     *    &lt;/configure&gt;
167     *  &lt;/class&gt;
168     *  </pre>
169     *
170     *  <p>Note that one restriction is to see the new library, one must
171     *  open a new Graph viewer (New -&gt; Graph).</p>
172     *
173     *  @param configuration The configuration where we look for the
174     *  actor library.
175     *  @param file The MoML file to open.
176     *  @exception Exception If there is a problem opening the
177     *  configuration, opening the MoML file, or opening the MoML file
178     *  as a new library.
179     */
180    public static void openLibrary(Configuration configuration, File file)
181            throws Exception {
182        CompositeEntity library = null;
183        final CompositeEntity libraryContainer = (CompositeEntity) configuration
184                .getEntity("actor library");
185
186        final ModelDirectory directory = (ModelDirectory) configuration
187                .getEntity(Configuration._DIRECTORY_NAME);
188
189        if (directory == null) {
190            return;
191        }
192
193        if (libraryContainer == null) {
194            return;
195        }
196
197        StringAttribute alternateLibraryBuilderAttribute = (StringAttribute) libraryContainer
198                .getAttribute("_alternateLibraryBuilder");
199
200        // If the _alternateLibraryBuilder attribute is present,
201        // then we use the specified class to build the library
202        // instead of just reading the moml.
203
204        if (alternateLibraryBuilderAttribute != null) {
205            // Get the class that will build the library from the plugins
206            String libraryBuilderClassName = alternateLibraryBuilderAttribute
207                    .getExpression();
208
209            // Dynamically load the library builder and build the library
210            Class libraryBuilderClass = Class.forName(libraryBuilderClassName);
211            LibraryBuilder libraryBuilder = (LibraryBuilder) libraryBuilderClass
212                    .newInstance();
213
214            // Set the attributes defined in the moml to the attributes of the
215            // LibraryBuilder
216            libraryBuilder.addAttributes(
217                    alternateLibraryBuilderAttribute.attributeList());
218
219            try {
220                library = libraryBuilder
221                        .buildLibrary(libraryContainer.workspace());
222            } catch (Exception ex) {
223                ex.printStackTrace();
224                throw new Exception(
225                        "Cannot create library with " + "LibraryBuilder: ", ex);
226            }
227        }
228
229        // If we have a jar URL, convert spaces to %20
230        URL fileURL = JNLPUtilities.canonicalizeJarURL(file.toURI().toURL());
231
232        String identifier = fileURL.toExternalForm();
233
234        // Check to see whether the library is already open.
235        Effigy libraryEffigy = directory.getEffigy(identifier);
236
237        if (libraryEffigy == null) {
238            if (library == null) {
239                // Only do this if the library hasn't been set above
240                // by a LibraryBuilder
241                // No previous libraryEffigy exists that is identified
242                // by this URL. Parse the user library into the
243                // workspace of the actor library.
244                MoMLParser parser = new MoMLParser(
245                        libraryContainer.workspace());
246
247                // Set the ErrorHandler so that if we have
248                // compatibility problems between devel and production
249                // versions, we can skip that element.
250                // MoMLParser.setErrorHandler(new VergilErrorHandler());
251                parser.parse(fileURL, fileURL);
252
253                library = (CompositeEntity) parser.getToplevel();
254            }
255
256            // library.setContainer(libraryContainer); //i don't know if this is
257            // needed
258            // Now create the effigy with no tableau.
259            final PtolemyEffigy finalLibraryEffigy = new PtolemyEffigy(
260                    directory.workspace());
261            finalLibraryEffigy.setSystemEffigy(true);
262
263            // Correct old library name, if the loaded library happens
264            // to be user library.
265            if (library == null) {
266                throw new NullPointerException("library == null?");
267            }
268            if (library.getName().equals("user library")) {
269                library.setName(USER_LIBRARY_NAME);
270            }
271
272            finalLibraryEffigy.setName(directory.uniqueName(library.getName()));
273
274            _instantiateLibrary(library, directory, configuration, file,
275                    libraryContainer, finalLibraryEffigy);
276
277            finalLibraryEffigy.setModel(library);
278
279            // Identify the URL from which the model was read
280            // by inserting an attribute into both the model
281            // and the effigy.
282            URIAttribute uri = new URIAttribute(library, "_uri");
283            uri.setURL(fileURL);
284
285            // This is used by TableauFrame in its _save() method.
286            finalLibraryEffigy.uri.setURL(fileURL);
287
288            finalLibraryEffigy.identifier.setExpression(identifier);
289        }
290    }
291
292    /** Save the given entity in the user library in the given
293     *  configuration.
294     *  @param configuration The configuration.
295     *  @param entity The entity to save.
296     *  @exception IOException if the user library cannot be found.
297     *  @exception IllegalActionException If there is a problem creating
298     *  the entity in the library.
299     *  @exception NameDuplicationException If a entity with the same
300     *  name already exists in the library.
301     *  @since Ptolemy II 5.2
302     */
303    public static void saveComponentInLibrary(Configuration configuration,
304            Entity entity) throws IOException, IllegalActionException,
305            NameDuplicationException {
306        if (entity == null) {
307            throw new NullPointerException("Save in library failed. "
308                    + "entity was null, cannot save a null entity.");
309        }
310        CompositeEntity libraryInstance = (CompositeEntity) configuration
311                .getEntity("actor library." + USER_LIBRARY_NAME);
312
313        if (libraryInstance == null) {
314            throw new IOException("Save In Library failed: "
315                    + "Could not find user library with name \""
316                    + USER_LIBRARY_NAME + "\".");
317        }
318
319        // Note that the library in the configuration is an
320        // instance of another model. We have to go get the
321        // original model to make sure that the change propagates
322        // back to the file from which the library is loaded.
323        Tableau libraryTableau = configuration.openModel(libraryInstance);
324        PtolemyEffigy libraryEffigy = (PtolemyEffigy) libraryTableau
325                .getContainer();
326        CompositeEntity library = (CompositeEntity) libraryEffigy.getModel();
327
328        StringWriter buffer = new StringWriter();
329
330        // Check whether there is already something existing in the
331        // user library with this name.
332        if (library == null) {
333            throw new InternalErrorException("Save in library failed. "
334                    + "libraryEffigy.getModel() returned null.");
335        }
336        if (library.getEntity(entity.getName()) != null) {
337            throw new NameDuplicationException(entity,
338                    "Save In Library failed: An object"
339                            + " already exists in the user library with name "
340                            + "\"" + entity.getName() + "\".");
341        }
342
343        if (entity.getName().trim().equals("")) {
344            entity.exportMoML(buffer, 1, "Unnamed");
345        } else {
346            entity.exportMoML(buffer, 1);
347        }
348
349        ChangeRequest request = new MoMLChangeRequest(entity, library,
350                buffer.toString());
351        library.requestChange(request);
352    }
353
354    ///////////////////////////////////////////////////////////////////
355    ////                         public variables                  ////
356
357    /** The name of the user library.  The default value is
358     *  "UserLibrary".  The value of this variable is what appears
359     *  in the Vergil left hand tree menu.
360     *  <p>This variable is not final so that users of this class
361     *  may change it.
362     */
363    public static/* final */String USER_LIBRARY_NAME = "UserLibrary";
364
365    ///////////////////////////////////////////////////////////////////
366    ////                         private methods                   ////
367
368    /**
369     * instantiate a ComponentEntity and create the changeRequest to
370     * implement it in the model
371     */
372    private static void _instantiateLibrary(final CompositeEntity library,
373            final ModelDirectory directory, Configuration configuration,
374            File file, final CompositeEntity libraryContainer,
375            final PtolemyEffigy finalLibraryEffigy) throws Exception {
376        ChangeRequest request = new ChangeRequest(configuration,
377                file.toURI().toURL().toString()) {
378            @Override
379            protected void _execute() throws Exception {
380                // The library is a class!
381                library.setClassDefinition(true);
382                library.instantiate(libraryContainer, library.getName());
383                finalLibraryEffigy.setContainer(directory);
384            }
385        };
386
387        libraryContainer.requestChange(request);
388        request.waitForCompletion();
389    }
390
391}