001/* An actor that writes the value of string tokens to a file, one per line.
002
003 @Copyright (c) 2002-2014 The Regents of the University of California.
004 All rights reserved.
005
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the
009 above copyright notice and the following two paragraphs appear in all
010 copies of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION 2
026 COPYRIGHTENDKEY
027 */
028package ptolemy.actor.lib.io;
029
030import java.io.File;
031import java.io.PrintWriter;
032
033import ptolemy.actor.lib.Sink;
034import ptolemy.actor.parameters.FilePortParameter;
035import ptolemy.data.BooleanToken;
036import ptolemy.data.StringToken;
037import ptolemy.data.Token;
038import ptolemy.data.expr.FileParameter;
039import ptolemy.data.expr.Parameter;
040import ptolemy.data.expr.SingletonParameter;
041import ptolemy.data.type.BaseType;
042import ptolemy.kernel.CompositeEntity;
043import ptolemy.kernel.util.Attribute;
044import ptolemy.kernel.util.IllegalActionException;
045import ptolemy.kernel.util.NameDuplicationException;
046import ptolemy.kernel.util.Workspace;
047import ptolemy.util.MessageHandler;
048
049///////////////////////////////////////////////////////////////////
050//// LineWriter
051
052/**
053 <p>This actor reads string-valued input tokens and writes them,
054 one line at a time, to a specified file.  It does not
055 include any enclosing quotation marks in the output.
056 If you need the enclosing quotation marks, precede this
057 actor with TokenToExpression.</p>
058 <p>
059 The file is specified by the <i>fileName</i> attribute
060 using any form acceptable to {@link FileParameter}.</p>
061 <p>
062 If the <i>append</i> attribute has value <i>true</i>,
063 then the file will be appended to. If it has value <i>false</i>,
064 then if the file exists, the user will be queried for permission
065 to overwrite, and if granted, the file will be overwritten.</p>
066 <p>
067 If the <i>confirmOverwrite</i> parameter has value <i>false</i>,
068 then this actor will overwrite the specified file if it exists
069 without asking.  If <i>true</i> (the default), then if the file
070 exists, then this actor will ask for confirmation before overwriting.</p>
071
072 @see FileParameter
073 @see ExpressionWriter
074 @author  Edward A. Lee
075 @version $Id$
076 @since Ptolemy II 2.2
077 @Pt.ProposedRating Yellow (eal)
078 @Pt.AcceptedRating Red (liuj)
079 */
080public class LineWriter extends Sink {
081    /** Construct an actor with the given container and name.
082     *  @param container The container.
083     *  @param name The name of this actor.
084     *  @exception IllegalActionException If the actor cannot be contained
085     *   by the proposed container.
086     *  @exception NameDuplicationException If the container already has an
087     *   actor with this name.
088     */
089    public LineWriter(CompositeEntity container, String name)
090            throws IllegalActionException, NameDuplicationException {
091        super(container, name);
092
093        input.setTypeEquals(BaseType.STRING);
094        new SingletonParameter(input, "_showName").setToken(BooleanToken.TRUE);
095
096        fileName = new FilePortParameter(this, "fileName");
097        fileName.setExpression("System.out");
098        new SingletonParameter(fileName.getPort(), "_showName")
099                .setToken(BooleanToken.TRUE);
100
101        append = new Parameter(this, "append");
102        append.setTypeEquals(BaseType.BOOLEAN);
103        append.setToken(BooleanToken.FALSE);
104
105        confirmOverwrite = new Parameter(this, "confirmOverwrite");
106        confirmOverwrite.setTypeEquals(BaseType.BOOLEAN);
107        confirmOverwrite.setToken(BooleanToken.TRUE);
108
109        endOfLineCharacter = new Parameter(this, "endOfLineCharacter");
110        endOfLineCharacter.setTypeEquals(BaseType.STRING);
111        // Default value is null.
112
113        alwaysFlush = new Parameter(this, "alwaysFlush");
114        alwaysFlush.setTypeEquals(BaseType.BOOLEAN);
115        alwaysFlush.setToken(BooleanToken.FALSE);
116
117        _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-25\" y=\"-20\" "
118                + "width=\"50\" height=\"40\" " + "style=\"fill:white\"/>\n"
119                + "<polygon points=\"-15,-10 -12,-10 -8,-14 -1,-14 3,-10"
120                + " 15,-10 15,10, -15,10\" " + "style=\"fill:red\"/>\n"
121                + "</svg>\n");
122    }
123
124    ///////////////////////////////////////////////////////////////////
125    ////                     ports and parameters                  ////
126
127    /** If <i>true</i>, then append to the specified file.  If <i>false</i>
128     *  (the default), then overwrite any preexisting file after asking
129     *  the user for permission.
130     */
131    public Parameter append;
132
133    /** The file name to which to write.  This is a string with
134     *  any form accepted by FilePortParameter.  The default value is
135     *  "System.out".
136     *  @see FilePortParameter
137     */
138    public FilePortParameter fileName;
139
140    /** If <i>false</i>, then overwrite the specified file if it exists
141     *  without asking.  If <i>true</i> (the default), then if the file
142     *  exists, ask for confirmation before overwriting.
143     */
144    public Parameter confirmOverwrite;
145
146    /** End of line character to use. This is a string
147     *  that defaults to null, which results in the current
148     *  platform's standard end-of-line character being used.
149     *  If an empty string is specified,
150     *  then no end of line character is used after each
151     *  output written to the file.
152     */
153    public Parameter endOfLineCharacter;
154
155    /** If <i>true</i>, flush output after each line is written. If
156     *  <i>false</i> (the default), the output may not be written until the
157     *  stream is closed during wrapup().
158     */
159    public Parameter alwaysFlush;
160
161    ///////////////////////////////////////////////////////////////////
162    ////                         public methods                    ////
163
164    /** If the specified attribute is <i>fileName</i> and there is an
165     *  open file being written, then close that file.  The new file will
166     *  be opened or created when it is next written to.
167     *  @param attribute The attribute that has changed.
168     *  @exception IllegalActionException If the specified attribute
169     *   is <i>fileName</i> and the previously
170     *   opened file cannot be closed.
171     */
172    @Override
173    public void attributeChanged(Attribute attribute)
174            throws IllegalActionException {
175        if (attribute == fileName) {
176            // Do not close the file if it is the same file.
177            String newFileName = ((StringToken) fileName.getToken())
178                    .stringValue();
179
180            if (_previousFileName != null
181                    && !newFileName.equals(_previousFileName)) {
182                _previousFileName = newFileName;
183                fileName.close();
184                _writer = null;
185            }
186        } else {
187            super.attributeChanged(attribute);
188        }
189    }
190
191    /** Clone the actor into the specified workspace.
192     *  @param workspace The workspace for the new object.
193     *  @return A new actor.
194     *  @exception CloneNotSupportedException If a derived class contains
195     *   an attribute that cannot be cloned.
196     */
197    @Override
198    public Object clone(Workspace workspace) throws CloneNotSupportedException {
199        LineWriter newObject = (LineWriter) super.clone(workspace);
200        newObject._writer = null;
201        return newObject;
202    }
203
204    /** Read an input string token from each input
205     *  channel and write it to the file, one line per token.
206     *  If there is no input, do nothing.
207     *  If the file is not open for writing then open it. If the file
208     *  does not exist, then create it.  If the file already exists,
209     *  then query the user for overwrite, unless the <i>append</i>
210     *  parameter has value <i>true</i>.
211     *  @exception IllegalActionException If the file cannot be opened
212     *   or created, or if the user refuses to overwrite an existing file.
213     */
214    @Override
215    public boolean postfire() throws IllegalActionException {
216        fileName.update();
217        for (int i = 0; i < input.getWidth(); i++) {
218            if (input.hasToken(i)) {
219                Token token = input.get(i);
220
221                if (_writer == null) {
222                    // File has not been opened.
223                    boolean appendValue = ((BooleanToken) append.getToken())
224                            .booleanValue();
225
226                    String fileNameValue = fileName.stringValue();
227
228                    // If previousFileName is null, we have never opened a file.
229                    if (_previousFileName == null) {
230                        _previousFileName = fileNameValue;
231                    }
232                    if (!fileNameValue.equals("System.out")) {
233                        // Only check for append and overwrite if the
234                        // fileName is not "System.out"
235                        // Open the file.
236                        File file = fileName.asFile();
237                        boolean confirmOverwriteValue = ((BooleanToken) confirmOverwrite
238                                .getToken()).booleanValue();
239
240                        // Don't ask for confirmation in append mode, since there
241                        // will be no loss of data.
242                        if (file != null && file.exists() && !appendValue
243                                && confirmOverwriteValue) {
244                            // Query for overwrite.
245                            // FIXME: This should be called in the event thread!
246                            // There is a chance of deadlock since it is not.
247                            if (!MessageHandler.yesNoQuestion(
248                                    "OK to overwrite " + file + "?")) {
249                                throw new IllegalActionException(this,
250                                        "Please select another file name.");
251                            }
252                        }
253                    }
254
255                    _writer = new PrintWriter(
256                            fileName.openForWriting(appendValue), true);
257                }
258                _writeToken(token);
259            }
260        }
261        return super.postfire();
262    }
263
264    /** Read the value of alwaysFlush parameter.
265     *  @exception IllegalActionException If there is an error reading the
266     *  alwaysFlush parameter.
267     */
268    @Override
269    public void preinitialize() throws IllegalActionException {
270        super.preinitialize();
271        _flushValue = ((BooleanToken) alwaysFlush.getToken()).booleanValue();
272    }
273
274    /** Close the writer if there is one.
275     *  @exception IllegalActionException If an IO error occurs.
276     */
277    @Override
278    public void wrapup() throws IllegalActionException {
279        fileName.close();
280        _writer = null;
281    }
282
283    ///////////////////////////////////////////////////////////////////
284    ////                         protected methods                 ////
285
286    /** Write the specified token to the current writer.
287     *  This is protected so that derived classes can modify the
288     *  format in which the token is written.
289     *  @param token The token to write.
290     *  @exception IllegalActionException Not thrown in this base class.
291     */
292    protected void _writeToken(Token token) throws IllegalActionException {
293        String eol = "\n";
294        Token eolToken = endOfLineCharacter.getToken();
295        if (eolToken != null) {
296            eol = ((StringToken) eolToken).stringValue();
297        }
298        // In this base class, the cast is safe.
299        _writer.print(((StringToken) token).stringValue() + eol);
300
301        if (_flushValue) {
302            _writer.flush();
303        }
304
305    }
306
307    ///////////////////////////////////////////////////////////////////
308    ////                         protected members                 ////
309
310    /** If true, flush the writer after every write. */
311    protected boolean _flushValue;
312
313    /** The current writer. */
314    protected PrintWriter _writer;
315
316    ///////////////////////////////////////////////////////////////////
317    ////                         private members                   ////
318
319    /** Previous value of fileName parameter. */
320    private String _previousFileName;
321}