001/* A file parameter that has an associated port. 002 003 Copyright (c) 2004-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.actor.parameters; 029 030import java.io.BufferedReader; 031import java.io.File; 032import java.io.IOException; 033import java.io.Writer; 034import java.net.URI; 035import java.net.URL; 036 037import ptolemy.data.StringToken; 038import ptolemy.data.type.BaseType; 039import ptolemy.kernel.attributes.FileOrURLAccessor; 040import ptolemy.kernel.attributes.URIAttribute; 041import ptolemy.kernel.util.IllegalActionException; 042import ptolemy.kernel.util.NameDuplicationException; 043import ptolemy.kernel.util.NamedObj; 044import ptolemy.kernel.util.Workspace; 045import ptolemy.util.ClassUtilities; 046import ptolemy.util.FileUtilities; 047 048/////////////////////////////////////////////////////////////////// 049//// FilePortParameter 050 051/** 052 This file parameter creates an associated port that can be used to update 053 the current value of the parameter. The value of this 054 parameter, accessed by getExpression(), is a string that names a file 055 or URL, possibly containing references to variables defined in scope 056 using the syntax $ID, ${ID} or $(ID). The value returned by getToken() 057 is name of the file with such references resolved. 058 <p> 059 If the model containing this port 060 parameter has been saved to a MoML file, then the file name can be 061 given relative to the directory containing that MoML file. 062 If the model has not been saved to a file, 063 then the classpath is used for identifying relative file names. 064 <p> 065 Files can be given relative to a <i>base</i>, where the base is 066 the URI of the first container above this one that has a URIAttribute. 067 Normally, this URI specifies the file or URL containing the model 068 definition. Thus, files that are referred to here can be kept in the 069 same directory as the model, or in a related directory, and can 070 moved together with the model. 071 <p> 072 The following special file names are understood: 073 <ul> 074 <li> System.in: Standard input. 075 <li> System.out: Standard output. 076 </ul> 077 Note, however, that these file names cannot be converted to URLs 078 using the asURL() method. 079 A file name can also contain the following strings that start 080 with "$", which get substituted 081 with the appropriate values. 082 <table> 083 <caption>Variables that get substituted.</caption> 084 <tr> 085 <th>String</th> 086 <th>Description</th> 087 <th>Property</th> 088 </tr> 089 <tr> 090 <td><code>$CWD</code></td> 091 <td>The current working directory</td> 092 <td><code>user.dir</code></td> 093 </tr> 094 <tr> 095 <td><code>$HOME</code></td> 096 <td>The user's home directory</td> 097 <td><code>user.home</code></td> 098 </tr> 099 <tr> 100 <td><code>$PTII</code></td> 101 <td>The home directory of the Ptolemy II installation</td> 102 <td><code>ptolemy.ptII.dir</code></td> 103 </tr> 104 <tr> 105 <td><code>$TMPDIR</code></td> 106 <td>The temporary directory</td> 107 <td><code>java.io.tmpdir</code></td> 108 </tr> 109 <tr> 110 <td><code>$USERNAME</code></td> 111 <td>The user's account name</td> 112 <td><code>user.name</code></td> 113 </tr> 114 </table> 115 The above properties are normally set when a Ptolemy II application starts. 116 <p> 117 If a file name begins with the reference "$CLASSPATH", then when 118 the file is opened for reading, the openForReading() method 119 will search for the file relative to the classpath (using the 120 getResource() method of the current class loader). This will only 121 work for a file that exists, and thus the openForWriting() method 122 will not understand the "$CLASSPATH" string; this makes sense 123 since the classpath typically has several directories, and it 124 would not be obvious where to create the file. The asURL() 125 method also recognizes the "$CLASSPATH" string, but not the asFile() 126 method (which is typically used when accessing a file for writing). 127 NOTE: If the container of this parameter also contains a variable 128 named CLASSPATH, then the value of that variable is used instead 129 of the Java classpath. 130 <p> 131 This parameter has two values, 132 which may not be equal, a <i>current value</i> and a <i>persistent value</i>. 133 The persistent value is returned by 134 getExpression() and is set by any of three different mechanisms: 135 <ul> 136 <li> calling setExpression(); 137 <li> calling setToken(); and 138 <li> specifying a value as a constructor argument. 139 </ul> 140 All three of these will also set the current value, which is then 141 equal to the persistent value. 142 The current value is returned by get getToken() 143 and is set by any of three different mechanisms: 144 <ul> 145 <li> calling setCurrentValue(); 146 <li> calling update() sets the current value if there is an associated 147 port, and that port has a token to consume; and 148 </ul> 149 These three techniques do not change the persistent value, so after 150 these are used, the persistent value and current value may be different. 151 <p> 152 When using this parameter in an actor, care must be exercised 153 to call update() exactly once per firing prior to calling getToken(). 154 Each time update() is called, a new token will be consumed from 155 the associated port (if the port is connected and has a token). 156 If this is called multiple times in an iteration, it may result in 157 consuming tokens that were intended for subsequent iterations. 158 Thus, for example, update() should not be called in fire() and then 159 again in postfire(). Moreover, in some domains (such as DE), 160 it is essential that if a token is provided on a port, that it 161 is consumed. In DE, the actor will be repeatedly fired until 162 the token is consumed. Thus, it is an error to not call update() 163 once per iteration. For an example of an actor that uses this 164 mechanism, see Ramp. 165 <p> 166 If this actor is placed in a container that does not implement 167 the TypedActor interface, then no associated port is created, 168 and it functions as an ordinary file parameter. This is useful, 169 for example, if this is put in a library, where one would not 170 want the associated port to appear. 171 172 <p>There are a few situations where FilePortParameter might not do what 173 you expect: 174 175 <ol> 176 <li> If it is used in a transparent composite actor, then a token provided 177 to a FilePortParameter will never be read. A transparent composite actor 178 is one without a director. 179 180 <br>Workaround: Put a director in the composite. 181 182 <li> Certain actors read parameter 183 values only during initialization. During initialization, a 184 FilePortParameter can only have a value set via the parameter (it 185 can't have yet received a token). So if the initial value 186 is set to the value of the FilePortParameter, then it will 187 see only the parameter value, never the value provided via the 188 port. 189 190 <br>Workaround: Use a RunCompositeActor to contain the model. 191 192 </ol> 193 194 @see ptolemy.data.expr.FileParameter 195 @see ParameterPort 196 @author Edward A. Lee 197 @version $Id$ 198 @since Ptolemy II 4.1 199 @Pt.ProposedRating Yellow (eal) 200 @Pt.AcceptedRating Red (cxh) 201 */ 202public class FilePortParameter extends PortParameter 203 implements FileOrURLAccessor { 204 /** Construct a parameter with the given name contained by the specified 205 * entity. The container argument must not be null, or a 206 * NullPointerException will be thrown. This parameter will create 207 * an associated port in the same container. 208 * @param container The container. 209 * @param name The name of the parameter. 210 * @exception IllegalActionException If the parameter is not of an 211 * acceptable class for the container. 212 * @exception NameDuplicationException If the name coincides with 213 * a parameter already in the container. 214 */ 215 public FilePortParameter(NamedObj container, String name) 216 throws IllegalActionException, NameDuplicationException { 217 super(container, name); 218 setStringMode(true); 219 setTypeEquals(BaseType.STRING); 220 } 221 222 /** Construct a Parameter with the given container, name, and Token. 223 * The token defines the initial persistent and current values. 224 * The container argument must not be null, or a 225 * NullPointerException will be thrown. This parameter will use the 226 * workspace of the container for synchronization and version counts. 227 * If the name argument is null, then the name is set to the empty string. 228 * The object is not added to the list of objects in the workspace 229 * unless the container is null. 230 * Increment the version of the workspace. 231 * If the name argument is null, then the name is set to the empty 232 * string. 233 * @param container The container. 234 * @param name The name. 235 * @param token The Token contained by this Parameter. 236 * @exception IllegalActionException If the parameter is not of an 237 * acceptable class for the container. 238 * @exception NameDuplicationException If the name coincides with 239 * an parameter already in the container. 240 */ 241 public FilePortParameter(NamedObj container, String name, 242 ptolemy.data.Token token) 243 throws IllegalActionException, NameDuplicationException { 244 super(container, name, token); 245 setStringMode(true); 246 setTypeEquals(BaseType.STRING); 247 } 248 249 /////////////////////////////////////////////////////////////////// 250 //// public methods //// 251 // NOTE: This code is duplicated from FileParameter, but without 252 // multiple inheritance, I see no way around this. 253 254 /** Return the file as a File object. This method first attempts 255 * to directly use the file name to construct the File. If the 256 * resulting File is not absolute, then it attempts to resolve it 257 * relative to the base directory returned by getBaseDirectory(). 258 * If there is no such base URI, then it simply returns the 259 * relative File object. 260 * <p> 261 * The file need not exist for this method to succeed. Thus, 262 * this method can be used to determine whether a file with a given 263 * name exists, prior to calling openForWriting(). 264 * A typical usage looks like this: 265 * <pre> 266 * FileParameter fileParameter; 267 * ... 268 * File file = fileParameter.asFile(); 269 * if (file.exists()) { 270 * ... Ask the user if it's OK to overwrite... 271 * ... Throw an exception if not... 272 * } 273 * // The following will overwrite an existing file. 274 * Writer writer = new PrintWriter(fileParameter.openForWriting()); 275 * </pre> 276 * @return A File, or null if no file name has been specified. 277 * @see #getBaseDirectory() 278 * @exception IllegalActionException If a parse error occurs 279 * reading the file name. 280 */ 281 @Override 282 public File asFile() throws IllegalActionException { 283 String name = stringValue(); 284 285 try { 286 File file = FileUtilities.nameToFile(name, getBaseDirectory()); 287 if (file != null) { 288 if (file.toString().indexOf("!/") != -1 289 || file.toString().indexOf("!\\") != -1) { 290 // We have a jar url, try dereferencing it. 291 // ModelReference.xml needed this under Webstart. 292 try { 293 URL possibleJarURL = ClassUtilities 294 .jarURLEntryResource(name); 295 296 if (possibleJarURL != null) { 297 file = new File(possibleJarURL.getFile()); 298 } 299 } catch (Throwable throwable) { 300 //Ignored, our attempt failed 301 } 302 } 303 } 304 return file; 305 } catch (IllegalArgumentException ex) { 306 // Java 1.4.2 some times reports: 307 // java.lang.IllegalArgumentException: URI is not absolute 308 throw new IllegalActionException(this, ex, 309 "Failed to create a file with name '" + name + "'."); 310 } 311 } 312 313 /** Return the file as a URL. If the file name is relative, then 314 * it is interpreted as being relative to the directory returned 315 * by getBaseDirectory(). If the name begins with "$CLASSPATH", 316 * then search for the file relative to the classpath. 317 * If no file is found, then it throws an exception. 318 * @return A URL, or null if no file name or URL has been specified. 319 * @exception IllegalActionException If the file cannot be read, or 320 * if the file cannot be represented as a URL (e.g. System.in), or 321 * the name specification cannot be parsed. 322 */ 323 @Override 324 public URL asURL() throws IllegalActionException { 325 String name = stringValue(); 326 327 try { 328 return FileUtilities.nameToURL(name, getBaseDirectory(), 329 getClass().getClassLoader()); 330 } catch (IOException ex) { 331 throw new IllegalActionException(this, ex, 332 "Cannot read file '" + name + "'"); 333 } 334 } 335 336 /** Clone the attribute into the specified workspace. The resulting 337 * object has no base directory name nor any reference to any open stream. 338 * @param workspace The workspace for the cloned object. 339 * @return A new attribute. 340 * @exception CloneNotSupportedException If a derived class contains 341 * an attribute that cannot be cloned. 342 */ 343 @Override 344 public Object clone(Workspace workspace) throws CloneNotSupportedException { 345 FilePortParameter newObject = (FilePortParameter) super.clone( 346 workspace); 347 newObject._baseDirectory = null; 348 newObject._reader = null; 349 newObject._writer = null; 350 return newObject; 351 } 352 353 /** Close the file. If it has not been opened using openForReading() 354 * or openForWriting(), then do nothing. Also, if the file is 355 * System.in or System.out, then do not close it (it does not make 356 * sense to close these files). 357 * @exception IllegalActionException If the file or URL cannot be 358 * closed. 359 */ 360 @Override 361 public void close() throws IllegalActionException { 362 if (_reader != null) { 363 if (_reader != FileUtilities.STD_IN) { 364 try { 365 _reader.close(); 366 } catch (IOException ex) { 367 // This is typically caused by the stream being 368 // already closed, so we ignore. 369 } 370 } 371 } 372 373 if (_writer != null) { 374 try { 375 _writer.flush(); 376 377 if (_writer != FileUtilities.STD_OUT) { 378 _writer.close(); 379 } 380 } catch (IOException ex) { 381 // This is typically caused by the stream being 382 // already closed, so we ignore. 383 } 384 } 385 } 386 387 /** Return the directory to use as the base for relative file or URL names. 388 * If setBaseDirectory() has been called, then that directory is 389 * returned. Otherwise, the directory containing the file returned 390 * by URIAttribute.getModelURI() is returned, which is the URI 391 * of the first container above this attribute in the hierarchy that 392 * has a URIAttribute, or null if there none. 393 * @return A directory name, or null if there is none. 394 * @see URIAttribute#getModelURI(NamedObj) 395 * @see #setBaseDirectory(URI) 396 */ 397 @Override 398 public URI getBaseDirectory() { 399 if (_baseDirectory != null) { 400 return _baseDirectory; 401 } else { 402 return URIAttribute.getModelURI(this); 403 } 404 } 405 406 /** Open the file or URL for reading. If the name begins with 407 * "$CLASSPATH", then search for the file relative to the classpath. 408 * If the name is relative, then it is relative to the directory 409 * returned by getBaseDirectory(). 410 * @return A buffered reader. 411 * @see #getBaseDirectory() 412 * @exception IllegalActionException If the file or URL cannot be 413 * opened. 414 */ 415 @Override 416 public BufferedReader openForReading() throws IllegalActionException { 417 try { 418 _reader = FileUtilities.openForReading(stringValue(), 419 getBaseDirectory(), getClass().getClassLoader()); 420 return _reader; 421 } catch (IOException ex) { 422 throw new IllegalActionException(this, ex, 423 "Cannot open file or URL"); 424 } 425 } 426 427 /** Open the file for writing. If the file does not exist, then 428 * create it. If the file name is not absolute, the it is assumed 429 * to be relative to the base directory returned by getBaseDirectory(). 430 * If permitted, this method will return a Writer that will simply 431 * overwrite the contents of the file. It is up to the user of this 432 * method to check whether this is OK (by first calling asFile() 433 * and calling exists() on the returned value). 434 * @see #getBaseDirectory() 435 * @see #asFile() 436 * @return A writer, or null if no file name has been specified. 437 * @exception IllegalActionException If the file cannot be opened 438 * or created. 439 */ 440 @Override 441 public Writer openForWriting() throws IllegalActionException { 442 return openForWriting(false); 443 } 444 445 /** Open the file for writing or appending. 446 * If the file does not exist, then 447 * create it. If the file name is not absolute, the it is assumed 448 * to be relative to the base directory returned by getBaseDirectory(). 449 * If permitted, this method will return a Writer that will simply 450 * overwrite the contents of the file. It is up to the user of this 451 * method to check whether this is OK (by first calling asFile() 452 * and calling exists() on the returned value). 453 * @see #getBaseDirectory() 454 * @see #asFile() 455 * @param append If true, then append to the file rather than 456 * overwriting. 457 * @return A writer, or null if no file name has been specified. 458 * @exception IllegalActionException If the file cannot be opened 459 * or created. 460 */ 461 @Override 462 public Writer openForWriting(boolean append) throws IllegalActionException { 463 try { 464 _writer = FileUtilities.openForWriting(stringValue(), 465 getBaseDirectory(), append); 466 return _writer; 467 } catch (IOException ex) { 468 throw new IllegalActionException(this, ex, 469 "Cannot open file for writing"); 470 } 471 } 472 473 /** Set the directory to use as the base for relative file or URL names. 474 * If this is not called, then the default is the directory 475 * containing the file returned by URIAttribute.getModelURI(). 476 * @param directory The base directory. 477 * @see URIAttribute#getModelURI(NamedObj) 478 * @see #getBaseDirectory() 479 */ 480 @Override 481 public void setBaseDirectory(URI directory) { 482 _baseDirectory = directory; 483 } 484 485 /** Return the string value of this parameter. This is 486 * equivalent to 487 * <pre> 488 * ((StringToken)this.getToken()).stringValue() 489 * </pre> 490 * @return The string value of this parameter. 491 * @exception IllegalActionException If the expression cannot 492 * be parsed or cannot be evaluated, or if the result of evaluation 493 * violates type constraints, or if the result of evaluation is null 494 * and there are variables that depend on this one. 495 */ 496 public String stringValue() throws IllegalActionException { 497 return ((StringToken) getToken()).stringValue(); 498 } 499 500 /////////////////////////////////////////////////////////////////// 501 //// private members //// 502 503 /** The base directory to use for relative file names. */ 504 private URI _baseDirectory; 505 506 /** The current reader for the input file. */ 507 private BufferedReader _reader; 508 509 /** The current writer for the output file. */ 510 private Writer _writer; 511}