001/* An actor that writes input data to the specified file.
002
003 @Copyright (c) 1998-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;
029
030import java.io.IOException;
031import java.io.OutputStreamWriter;
032
033import ptolemy.actor.parameters.FilePortParameter;
034import ptolemy.data.BooleanToken;
035import ptolemy.data.StringToken;
036import ptolemy.data.Token;
037import ptolemy.data.expr.SingletonParameter;
038import ptolemy.data.type.BaseType;
039import ptolemy.kernel.CompositeEntity;
040import ptolemy.kernel.util.IllegalActionException;
041import ptolemy.kernel.util.NameDuplicationException;
042import ptolemy.kernel.util.Workspace;
043
044/**
045 This actor reads tokens from any number of input channels and writes
046 their string values to the specified output file. The input type
047 can be anything. If a StringToken is received, then its stringValue()
048 method will be used to get the string to write to the file. Otherwise,
049 the toString() method of the received token will be used.  If no file name
050 is given, then the values are written to the standard output.
051 If multiple input channels are provided on the input port, then
052 the values received are written separated by a tab character.
053 Each time a new name is received on the <i>filename</i> input, a
054 new file will be opened for writing. If no new filename is received,
055 then the data will be appended to previously used file. When appending,
056 the values received on subsequent firings are separated by a newline
057 character (a newline character will be inserted if one is not already
058 provide by the input string).
059 Unlike @see{ExpressionWriter}, this actor makes no changes to the
060 input string. It writes to the file exactly what it receives on its
061 input.
062
063 @author  Yuhong Xiong, Edward A. Lee
064 @version $Id$
065 @since Ptolemy II 0.4
066 @Pt.ProposedRating Yellow (yuhong)
067 @Pt.AcceptedRating Yellow (mudit)
068 */
069public class FileWriter extends Sink {
070    /** Construct an actor with the given container and name.
071     *  @param container The container.
072     *  @param name The name of this actor.
073     *  @exception IllegalActionException If the actor cannot be contained
074     *   by the proposed container.
075     *  @exception NameDuplicationException If the container already has an
076     *   actor with this name.
077     */
078    public FileWriter(CompositeEntity container, String name)
079            throws IllegalActionException, NameDuplicationException {
080        super(container, name);
081
082        if (_stdOut == null) {
083            _stdOut = new OutputStreamWriter(System.out);
084        }
085
086        _setWriter(_stdOut);
087
088        filename = new FilePortParameter(this, "filename");
089        filename.setExpression("");
090        filename.setTypeEquals(BaseType.STRING);
091        new SingletonParameter(filename.getPort(), "_showName")
092                .setToken(BooleanToken.TRUE);
093
094        new SingletonParameter(input, "_showName").setToken(BooleanToken.TRUE);
095
096        _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-25\" y=\"-20\" "
097                + "width=\"50\" height=\"40\" " + "style=\"fill:white\"/>\n"
098                + "<polygon points=\"-15,-10 -12,-10 -8,-14 -1,-14 3,-10"
099                + " 15,-10 15,10, -15,10\" " + "style=\"fill:red\"/>\n"
100                + "</svg>\n");
101    }
102
103    ///////////////////////////////////////////////////////////////////
104    ////                     ports and parameters                  ////
105
106    /** The name of the file to write to.
107     *  By default, this parameter contains an empty string, which
108     *  is interpreted to mean that output should be directed to the
109     *  standard output.
110     *  See {@link ptolemy.actor.parameters.FilePortParameter} for
111     *  details about relative path names.
112     */
113    public FilePortParameter filename;
114
115    ///////////////////////////////////////////////////////////////////
116    ////                         public methods                    ////
117
118    /** Clone the actor.
119     *  @exception CloneNotSupportedException If the superclass throws it.
120     */
121    @Override
122    public Object clone(Workspace workspace) throws CloneNotSupportedException {
123        FileWriter newObject = (FileWriter) super.clone(workspace);
124        newObject._previousFilename = null;
125        newObject._writer = null;
126        return newObject;
127    }
128
129    /** Read at most one token from each input channel and write its
130     *  string value.  If the filename input has changed since the
131     *  last writing, then open the new file for writing. Otherwise,
132     *  append to the previous file. If there are multiple channels
133     *  connected to the input, then the output values from each
134     *  channel are separated by tab characters.
135     *  If an input channel has no data, then two consecutive tab
136     *  characters are written.
137     *  @exception IllegalActionException If an IO error occurs.
138     */
139    @Override
140    public boolean postfire() throws IllegalActionException {
141        filename.update();
142        try {
143            // NOTE: getExpression() will not get the current value
144            // of this sort of PortParameter. Instead, it gets the
145            // default value. Have to use getToken().
146            String filenameValue = ((StringToken) filename.getToken())
147                    .stringValue();
148
149            if (filenameValue == null || filenameValue.equals("\"\"")) {
150                // See $PTII/ptolemy/domains/sdf/kernel/test/auto/zeroRate_delay5.xml, which sets
151                // the filename to a string that has two doublequotes. ""
152                _setWriter(null);
153            } else if (!filenameValue.equals(_previousFilename)) {
154                // New filename. Close the previous.
155                _previousFilename = filenameValue;
156                _setWriter(null);
157                if (!filenameValue.trim().equals("")) {
158                    java.io.Writer writer = filename.openForWriting();
159                    // Findbugs warns about the writer being created but
160                    // not closed. But it is closed in postfire().
161                    _setWriter(writer);
162                }
163            }
164            String last = "";
165            int width = input.getWidth();
166
167            for (int i = 0; i < width; i++) {
168                if (i > 0) {
169                    _writer.write("\t");
170                }
171
172                if (input.hasToken(i)) {
173                    Token inputToken = input.get(i);
174                    if (inputToken instanceof StringToken) {
175                        last = ((StringToken) inputToken).stringValue();
176                    } else {
177                        last = inputToken.toString();
178                    }
179                    _writer.write(last);
180                } else {
181                    last = "";
182                }
183            }
184            // Write a newline character only if the last
185            // string does not already have one.
186            if (!last.endsWith("\n")) {
187                _writer.write("\n");
188            }
189            _writer.flush();
190            return super.postfire();
191        } catch (IOException ex) {
192            throw new IllegalActionException(this, ex, "postfire() failed");
193        }
194    }
195
196    /** Close the file, if there is one.
197     *  @exception IllegalActionException If an IO error occurs.
198     */
199    @Override
200    public void wrapup() throws IllegalActionException {
201        super.wrapup();
202
203        try {
204            if (_writer != null) {
205                _writer.flush();
206            }
207        } catch (IOException ex) {
208            throw new IllegalActionException(this, ex,
209                    "wrapup(" + _writer + ") failed");
210        }
211
212        // To get the file to close.
213        _setWriter(null);
214
215        // Since we have closed the writer, we also need to clear
216        // _previousFilename, so that a new writer will be opened for this
217        // filename if the model is executed again
218        _previousFilename = null;
219    }
220
221    ///////////////////////////////////////////////////////////////////
222    ////                         private methods                   ////
223
224    /** Set the writer.  If there was a previous writer, close it.
225     *  To set standard output, call this method with argument null.
226     *  @param writer The writer to write to.
227     *  @exception IllegalActionException If an IO error occurs.
228     */
229    private void _setWriter(java.io.Writer writer)
230            throws IllegalActionException {
231        try {
232            if (_writer != null && _writer != _stdOut) {
233                _writer.close();
234            }
235        } catch (IOException ex) {
236            throw new IllegalActionException(this, ex,
237                    "setWriter(" + writer + ") failed");
238        }
239
240        if (writer != null) {
241            _writer = writer;
242        } else {
243            _writer = _stdOut;
244        }
245    }
246
247    ///////////////////////////////////////////////////////////////////
248    ////                         private fields                    ////
249
250    /** The previously used filename, or null if none has been previously used. */
251    private String _previousFilename = null;
252
253    /** Standard out as a writer. */
254    private static java.io.Writer _stdOut = null;
255
256    /** The writer to write to. */
257    private java.io.Writer _writer = null;
258}