001/* An attribute that specifies a file or URL. 002 003 Copyright (c) 2001-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.kernel.attributes; 029 030import java.io.BufferedReader; 031import java.io.File; 032import java.io.IOException; 033import java.io.Writer; 034import java.net.MalformedURLException; 035import java.net.URI; 036import java.net.URL; 037 038import ptolemy.kernel.util.IllegalActionException; 039import ptolemy.kernel.util.NameDuplicationException; 040import ptolemy.kernel.util.NamedObj; 041import ptolemy.kernel.util.StringAttribute; 042import ptolemy.kernel.util.Workspace; 043import ptolemy.util.FileUtilities; 044import ptolemy.util.StringUtilities; 045 046/////////////////////////////////////////////////////////////////// 047//// FileAttribute 048 049/** 050 This is an attribute that specifies a file or URL. The value of this 051 attribute, accessed by getExpression(), is a string that names a file 052 or URL. If the model containing this attribute has been saved to a 053 MoML file, then the file name can be given relative to the directory 054 containing that MoML file. If the model has not been saved to a file, 055 then the classpath is used for identifying relative file names. 056 <p> 057 Files can be given relative to a <i>base</i>, where the base is 058 the URI of the first container above this one that has a URIAttribute. 059 Normally, this URI specifies the file or URL containing the model 060 definition. Thus, files that are referred to here can be kept in the 061 same directory as the model, or in a related directory, and can 062 moved together with the model. 063 <p> 064 The following special file names are understood: 065 <ul> 066 <li> System.in: Standard input. 067 <li> System.out: Standard output. 068 </ul> 069 Note, however, that these file names cannot be converted to URLs 070 using the asURL() method. 071 <p> 072 A file name can also contain the following strings that start 073 with "$", which get substituted 074 with the appropriate values</p> 075 <table> 076 <caption>Variables that are substituted.</caption> 077 <tr> 078 <th>String</th> 079 <th>Description</th> 080 <th>Property</th> 081 </tr> 082 <tr> 083 <td><code>$CWD</code></td> 084 <td>The current working directory</td> 085 <td><code>user.dir</code></td> 086 </tr> 087 <tr> 088 <td><code>$HOME</code></td> 089 <td>The user's home directory</td> 090 <td><code>user.home</code></td> 091 </tr> 092 <tr> 093 <td><code>$PTII</code></td> 094 <td>The home directory of the Ptolemy II installation</td> 095 <td><code>ptolemy.ptII.dir</code></td> 096 </tr> 097 <tr> 098 <td><code>$TMPDIR</code></td> 099 <td>The temporary directory</td> 100 <td><code>java.io.tmpdir</code></td> 101 </tr> 102 </table> 103 The above properties are normally set when a Ptolemy II application starts. 104 <p> 105 If a file name begins with the string "$CLASSPATH", followed by either 106 "/" or "\", then when the file 107 is opened for reading, the openForReading() method 108 will search for the file relative to the classpath (using the 109 getResource() method of the current class loader). This will only 110 work for a file that exists, and thus the openForWriting() method 111 will not understand the "$CLASSPATH" string; this makes sense 112 since the classpath typically has several directories, and it 113 would not be obvious where to create the file. The asURL() 114 method also recognizes the "$CLASSPATH" string, but not the asFile() 115 method (which is typically used when accessing a file for writing). 116 <p> 117 @author Edward A. Lee 118 @version $Id$ 119 @see URIAttribute 120 @since Ptolemy II 3.0 121 @deprecated Use {@link ptolemy.data.expr.FileParameter} instead. 122 @Pt.ProposedRating Green (eal) 123 @Pt.AcceptedRating Yellow (cxh) 124 */ 125@Deprecated 126public class FileAttribute extends StringAttribute 127 implements FileOrURLAccessor { 128 /** Construct an attribute with the given name contained by the 129 * specified container. The container argument must not be null, or a 130 * NullPointerException will be thrown. This attribute will use the 131 * workspace of the container for synchronization and version counts. 132 * If the name argument is null, then the name is set to the empty 133 * string. Increment the version of the workspace. 134 * @param container The container. 135 * @param name The name of this attribute. 136 * @exception IllegalActionException If the attribute is not of an 137 * acceptable class for the container, or if the name contains a period. 138 * @exception NameDuplicationException If the name coincides with 139 * an attribute already in the container. 140 */ 141 public FileAttribute(NamedObj container, String name) 142 throws IllegalActionException, NameDuplicationException { 143 super(container, name); 144 } 145 146 /////////////////////////////////////////////////////////////////// 147 //// public methods //// 148 149 /** Return the file as a File object. This method first attempts 150 * to directly use the file name to construct the File. If the 151 * resulting File is not absolute, then it attempts to resolve it 152 * relative to the base directory returned by getBaseDirectory(). 153 * If there is no such base URI, then it simply returns the 154 * relative File object. 155 * <p> 156 * The file need not exist for this method to succeed. Thus, 157 * this method can be used to determine whether a file with a given 158 * name exists, prior to calling openForWriting(). 159 * A typical usage looks like this: 160 * <pre> 161 * FileAttribute fileAttribute; 162 * ... 163 * File file = fileAttribute.asFile(); 164 * if (file.exists()) { 165 * ... Ask the user if it's OK to overwrite... 166 * ... Throw an exception if not... 167 * } 168 * // The following will overwrite an existing file. 169 * Writer writer = new PrintWriter(fileAttribute.openForWriting()); 170 * </pre> 171 * @return A File, or null if no file name has been specified. 172 * @see #getBaseDirectory() 173 * @exception IllegalActionException If a parse error occurs 174 * reading the file name. 175 */ 176 @Override 177 public File asFile() throws IllegalActionException { 178 String name = _substituteSpecialStrings(getExpression()); 179 180 try { 181 return FileUtilities.nameToFile(name, getBaseDirectory()); 182 } catch (IllegalArgumentException ex) { 183 // Java 1.4.2 some times reports: 184 // java.lang.IllegalArgumentException: URI is not absolute 185 throw new IllegalActionException(this, ex, 186 "Failed to create a file with name '" + name + "'."); 187 } 188 } 189 190 /** Return the file as a URL. If the file name is relative, then 191 * it is interpreted as being relative to the directory returned 192 * by getBaseDirectory(). If the name begins with "$CLASSPATH", 193 * then search for the file relative to the classpath. 194 * If no file is found, then it throws an exception. 195 * @return A URL, or null if no file name or URL has been specified. 196 * @exception IllegalActionException If the file cannot be read, or 197 * if the file cannot be represented as a URL (e.g. System.in). 198 */ 199 @Override 200 public URL asURL() throws IllegalActionException { 201 String name = _substituteSpecialStrings(getExpression()); 202 203 if (name == null || name.trim().equals("")) { 204 return null; 205 } 206 207 // If the name begins with "$CLASSPATH", then attempt to 208 // open the file relative to the classpath. 209 if (name.startsWith("$CLASSPATH")) { 210 // Try relative to classpath. 211 String trimmedName = name.substring(11); 212 URL result = getClass().getClassLoader().getResource(trimmedName); 213 214 if (result == null) { 215 throw new IllegalActionException(this, 216 "Cannot find file in classpath: " + name); 217 } 218 219 return result; 220 } 221 222 File file = new File(name); 223 224 if (file.isAbsolute()) { 225 if (!file.canRead()) { 226 throw new IllegalActionException(this, 227 "Cannot read file: " + name); 228 } 229 230 try { 231 return file.toURL(); 232 } catch (MalformedURLException ex) { 233 throw new IllegalActionException(this, 234 "Cannot open file: " + ex.toString()); 235 } 236 } else { 237 // Try relative to the base directory. 238 URI modelURI = getBaseDirectory(); 239 240 if (modelURI != null) { 241 try { 242 // Try to resolve the URI. 243 URI newURI = modelURI.resolve(name); 244 return newURI.toURL(); 245 } catch (MalformedURLException e) { 246 throw new IllegalActionException(this, 247 "Unable to open as a file or URL: " + name); 248 } 249 } 250 251 // As a last resort, try an absolute URL. 252 try { 253 // Try an absolute URL 254 return new URL(name); 255 } catch (MalformedURLException e) { 256 throw new IllegalActionException(this, 257 "Unable to open as a file or URL: " + name); 258 } 259 } 260 } 261 262 /** Clone the attribute into the specified workspace. The resulting 263 * object has no base directory name nor any reference to any open stream. 264 * @param workspace The workspace for the cloned object. 265 * @return A new attribute. 266 * @exception CloneNotSupportedException If a derived class contains 267 * an attribute that cannot be cloned. 268 */ 269 @Override 270 public Object clone(Workspace workspace) throws CloneNotSupportedException { 271 FileAttribute newObject = (FileAttribute) super.clone(workspace); 272 newObject._baseDirectory = null; 273 newObject._reader = null; 274 newObject._writer = null; 275 return newObject; 276 } 277 278 /** Close the file. If it has not been opened using openForReading() 279 * or openForWriting(), then do nothing. Also, if the file is 280 * System.in or System.out, then do not close it (it does not make 281 * sense to close these files). 282 * @exception IllegalActionException If the file or URL cannot be 283 * closed. 284 */ 285 @Override 286 public void close() throws IllegalActionException { 287 if (_reader != null) { 288 if (_reader != FileUtilities.STD_IN) { 289 try { 290 _reader.close(); 291 } catch (IOException ex) { 292 // This is typically caused by the stream being 293 // already closed, so we ignore. 294 } 295 } 296 } 297 298 if (_writer != null) { 299 try { 300 _writer.flush(); 301 302 if (_writer != FileUtilities.STD_OUT) { 303 _writer.close(); 304 } 305 } catch (IOException ex) { 306 // This is typically caused by the stream being 307 // already closed, so we ignore. 308 } 309 } 310 } 311 312 /** Return the directory to use as the base for relative file or URL names. 313 * If setBaseDirectory() has been called, then that directory is 314 * returned. Otherwise, the directory containing the file returned 315 * by URIAttribute.getModelURI() is returned, which is the URI 316 * of the first container above this attribute in the hierarchy that 317 * has a URIAttribute, or null if there none. 318 * @return A directory name, or null if there is none. 319 * @see #setBaseDirectory(URI) 320 * @see URIAttribute#getModelURI(NamedObj) 321 */ 322 @Override 323 public URI getBaseDirectory() { 324 if (_baseDirectory != null) { 325 return _baseDirectory; 326 } else { 327 return URIAttribute.getModelURI(this); 328 } 329 } 330 331 /** Open the file or URL for reading. If the name begins with 332 * "$CLASSPATH", then search for the file relative to the classpath. 333 * If the name is relative, then it is relative to the directory 334 * returned by getBaseDirectory(). 335 * @return A buffered reader. 336 * @see #getBaseDirectory() 337 * @exception IllegalActionException If the file or URL cannot be 338 * opened. 339 */ 340 @Override 341 public BufferedReader openForReading() throws IllegalActionException { 342 try { 343 _reader = FileUtilities.openForReading(getExpression(), 344 getBaseDirectory(), getClass().getClassLoader()); 345 return _reader; 346 } catch (IOException ex) { 347 throw new IllegalActionException(this, ex, 348 "Cannot open file or URL"); 349 } 350 } 351 352 /** Open the file for writing. If the file does not exist, then 353 * create it. If the file name is not absolute, the it is assumed 354 * to be relative to the base directory returned by getBaseDirectory(). 355 * If permitted, this method will return a Writer that will simply 356 * overwrite the contents of the file. It is up to the user of this 357 * method to check whether this is OK (by first calling asFile() 358 * and calling exists() on the returned value). 359 * @see #getBaseDirectory() 360 * @see #asFile() 361 * @return A writer, or null if no file name has been specified. 362 * @exception IllegalActionException If the file cannot be opened 363 * or created. 364 */ 365 @Override 366 public Writer openForWriting() throws IllegalActionException { 367 return openForWriting(false); 368 } 369 370 /** Open the file for writing or appending. 371 * If the file does not exist, then 372 * create it. If the file name is not absolute, the it is assumed 373 * to be relative to the base directory returned by getBaseDirectory(). 374 * If permitted, this method will return a Writer that will simply 375 * overwrite the contents of the file. It is up to the user of this 376 * method to check whether this is OK (by first calling asFile() 377 * and calling exists() on the returned value). 378 * @see #getBaseDirectory() 379 * @see #asFile() 380 * @param append If true, then append to the file rather than 381 * overwriting. 382 * @return A writer, or null if no file name has been specified. 383 * @exception IllegalActionException If the file cannot be opened 384 * or created. 385 */ 386 @Override 387 public Writer openForWriting(boolean append) throws IllegalActionException { 388 try { 389 _writer = FileUtilities.openForWriting(getExpression(), 390 getBaseDirectory(), append); 391 return _writer; 392 } catch (IOException ex) { 393 throw new IllegalActionException(this, ex, 394 "Cannot open file for writing"); 395 } 396 } 397 398 /** Set the directory to use as the base for relative file or URL names. 399 * If this is not called, then the default is the directory 400 * containing the file returned by URIAttribute.getModelURI(). 401 * @param directory The base directory. 402 * @see #getBaseDirectory() 403 * @see URIAttribute#getModelURI(NamedObj) 404 */ 405 @Override 406 public void setBaseDirectory(URI directory) { 407 _baseDirectory = directory; 408 } 409 410 /////////////////////////////////////////////////////////////////// 411 //// private methods //// 412 413 /** Return a string that is the current value of this attribute 414 * with the strings "$CWD, "$HOME", "$PTII" and "$TMPDIR" 415 * replaced by their respective values. 416 * @param string The string in which to do the substitution. 417 * @return A new string. 418 */ 419 private static String _substituteSpecialStrings(String string) { 420 String result = string; 421 422 // Keep these alphabetized. 423 if (result.indexOf("$CWD") >= 0) { 424 result = StringUtilities.substitute(result, "$CWD", 425 StringUtilities.getProperty("user.dir")); 426 } 427 428 if (result.indexOf("$HOME") >= 0) { 429 result = StringUtilities.substitute(result, "$HOME", 430 StringUtilities.getProperty("user.home")); 431 } 432 433 if (result.indexOf("$PTII") >= 0) { 434 result = StringUtilities.substitute(result, "$PTII", 435 StringUtilities.getProperty("ptolemy.ptII.dir")); 436 } 437 438 if (result.indexOf("$TMPDIR") >= 0) { 439 result = StringUtilities.substitute(result, "$TMPDIR", 440 StringUtilities.getProperty("java.io.tmpdir")); 441 } 442 443 return result; 444 } 445 446 /////////////////////////////////////////////////////////////////// 447 //// private members //// 448 449 /** The base directory to use for relative file names. */ 450 private URI _baseDirectory; 451 452 /** The current reader for the input file. */ 453 private BufferedReader _reader; 454 455 /** The current writer for the output file. */ 456 private Writer _writer; 457}