001/* An actor that writes the value of string tokens to a file, one per line. 002 003 @Copyright (c) 2012-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.util.Set; 031 032import ptolemy.data.RecordToken; 033import ptolemy.data.StringToken; 034import ptolemy.data.Token; 035import ptolemy.data.expr.FileParameter; 036import ptolemy.data.expr.StringParameter; 037import ptolemy.data.type.BaseType; 038import ptolemy.data.type.RecordType; 039import ptolemy.kernel.CompositeEntity; 040import ptolemy.kernel.util.Attribute; 041import ptolemy.kernel.util.IllegalActionException; 042import ptolemy.kernel.util.NameDuplicationException; 043import ptolemy.kernel.util.Workspace; 044 045/////////////////////////////////////////////////////////////////// 046//// CSVWriter 047 048/** 049 <p>This actor reads record-valued input tokens and writes them, 050 one line at a time, to a specified file, as comma-separated list 051 (or separated by some other delimiter given by the <i>separator</i> 052 parameter). 053 The first line contains the names of the fields of the input 054 record, separated by the same delimiter. 055 <p> 056 The file is specified by the <i>fileName</i> attribute 057 using any form acceptable to {@link FileParameter}.</p> 058 <p> 059 If the <i>append</i> attribute has value <i>true</i>, 060 then the file will be appended to. If it has value <i>false</i>, 061 then if the file exists, the user will be queried for permission 062 to overwrite, and if granted, the file will be overwritten.</p> 063 <p> 064 If the <i>confirmOverwrite</i> parameter has value <i>false</i>, 065 then this actor will overwrite the specified file if it exists 066 without asking. If <i>true</i> (the default), then if the file 067 exists, then this actor will ask for confirmation before overwriting.</p> 068 069 @see FileParameter 070 @see ExpressionWriter 071 @author Edward A. Lee 072 @version $Id$ 073 @since Ptolemy II 10.0 074 @Pt.ProposedRating Yellow (eal) 075 @Pt.AcceptedRating Red (cxh) 076 */ 077public class CSVWriter extends LineWriter { 078 /** Construct an actor with the given container and name. 079 * @param container The container. 080 * @param name The name of this actor. 081 * @exception IllegalActionException If the actor cannot be contained 082 * by the proposed container. 083 * @exception NameDuplicationException If the container already has an 084 * actor with this name. 085 */ 086 public CSVWriter(CompositeEntity container, String name) 087 throws IllegalActionException, NameDuplicationException { 088 super(container, name); 089 090 separator = new StringParameter(this, "separator"); 091 separator.setExpression("comma"); 092 separator.addChoice("comma"); 093 separator.addChoice("tab"); 094 separator.addChoice("semicolon"); 095 096 // Clear type constraint set by base class. 097 input.setTypeEquals(BaseType.UNKNOWN); 098 // Force the input type to be a record. 099 input.setTypeAtMost(RecordType.EMPTY_RECORD); 100 101 _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-25\" y=\"-20\" " 102 + "width=\"50\" height=\"40\" " + "style=\"fill:white\"/>\n" 103 + "<polygon points=\"-15,-10 -12,-10 -8,-14 -1,-14 3,-10" 104 + " 15,-10 15,10, -15,10\" " + "style=\"fill:red\"/>\n" 105 + "<text x=\"-11\" y=\"4\"" 106 + "style=\"font-size:11; fill:white; font-family:SansSerif\">" 107 + "CSV</text>\n" + "</svg>\n"); 108 } 109 110 /////////////////////////////////////////////////////////////////// 111 //// ports and parameters //// 112 113 /** A specification of the separator between items in the table. 114 * The default is "comma", which results in assuming that fields 115 * are separated by commas. If the value is changed to "tab", then 116 * a tab separator will be used. If the value is "semicolon", then 117 * a semicolon separator will be used. If the value is anything 118 * else, then the value of the parameter, whatever it is, will 119 * be the separator. 120 */ 121 public StringParameter separator; 122 123 /////////////////////////////////////////////////////////////////// 124 //// public methods //// 125 126 /** If the specified attribute is <i>separator</i> then set a local 127 * variable with the value of the separator. 128 * @param attribute The attribute that has changed. 129 * @exception IllegalActionException If the specified attribute 130 * is <i>fileOrURL</i> and the file cannot be opened, or the previously 131 * opened file cannot be closed; or if the attribute is 132 * <i>numberOfLinesToSkip</i> and its value is negative. 133 */ 134 @Override 135 public void attributeChanged(Attribute attribute) 136 throws IllegalActionException { 137 if (attribute == separator) { 138 _delimiter = separator.stringValue(); 139 if (_delimiter.equals("comma")) { 140 _delimiter = ","; 141 } else if (_delimiter.equals("tab")) { 142 _delimiter = "\t"; 143 } else if (_delimiter.equals("semicolon")) { 144 _delimiter = ";"; 145 } else { 146 _delimiter = separator.stringValue(); 147 } 148 } else { 149 super.attributeChanged(attribute); 150 } 151 } 152 153 /** Clone the actor into the specified workspace. This calls the 154 * base class and then sets the type constraints on the input. 155 * @param workspace The workspace for the new object. 156 * @return A new actor. 157 * @exception CloneNotSupportedException If a derived class contains 158 * an attribute that cannot be cloned. 159 */ 160 @Override 161 public Object clone(Workspace workspace) throws CloneNotSupportedException { 162 CSVWriter newObject = (CSVWriter) super.clone(workspace); 163 newObject.input.setTypeEquals(BaseType.UNKNOWN); 164 newObject.input.setTypeAtMost(RecordType.EMPTY_RECORD); 165 return newObject; 166 } 167 168 /** Initialize this actor. Derived classes override this method 169 * to perform actions that should occur once at the beginning of 170 * an execution, but after type resolution. Derived classes can 171 * produce output data and schedule events. 172 * @exception IllegalActionException If a derived class throws it. 173 */ 174 @Override 175 public void initialize() throws IllegalActionException { 176 super.initialize(); 177 _firstFiring = true; 178 } 179 180 /////////////////////////////////////////////////////////////////// 181 //// protected methods //// 182 183 /** Write the specified token to the current writer. 184 * The token argument is required to be a record token. 185 * @param token The token to write. 186 * @exception IllegalActionException Not thrown in this base class. 187 */ 188 @Override 189 protected void _writeToken(Token token) throws IllegalActionException { 190 RecordToken record = (RecordToken) token; 191 String eol = "\n"; 192 Token eolToken = endOfLineCharacter.getToken(); 193 if (eolToken != null) { 194 eol = ((StringToken) eolToken).stringValue(); 195 } 196 if (_firstFiring) { 197 // Write the first line, which is determined by the input. 198 199 // Note that we get the labelSet from the record, which 200 // may be ordered if this is an OrderedToken. 201 // We used to read the RecordType labelSet, which is wrong: 202 //RecordType inputType = (RecordType) input.getType(); 203 //_fieldNames = inputType.labelSet(); 204 205 _fieldNames = record.labelSet(); 206 207 boolean first = true; 208 for (String field : _fieldNames) { 209 if (!first) { 210 _writer.print(_delimiter); 211 } 212 first = false; 213 _writer.print(field); 214 } 215 _writer.print(eol); 216 _firstFiring = false; 217 } 218 boolean first = true; 219 for (String field : _fieldNames) { 220 if (!first) { 221 _writer.print(_delimiter); 222 } 223 first = false; 224 _writer.print(record.get(field)); 225 } 226 _writer.print(eol); 227 228 if (_flushValue) { 229 _writer.flush(); 230 } 231 } 232 233 /////////////////////////////////////////////////////////////////// 234 //// private members //// 235 236 /** The delimiter. */ 237 private String _delimiter = ","; 238 239 /** Field names determined from input data type. */ 240 private Set<String> _fieldNames; 241 242 /** Indicator for first firing. */ 243 private boolean _firstFiring; 244}