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