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 * <?xml version="1.0" standalone="no"?> 159 * <!DOCTYPE class PUBLIC "-//UC Berkeley//DTD MoML 1//EN" 160 * "http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd"> 161 * <class name="MyActors" extends="ptolemy.moml.EntityLibrary"> 162 * <configure> 163 * <group> 164 * <entity name="XY Plotter" class="ptolemy.actor.lib.gui.XYPlotter"/> 165 * </group> 166 * </configure> 167 * </class> 168 * </pre> 169 * 170 * <p>Note that one restriction is to see the new library, one must 171 * open a new Graph viewer (New -> 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}