001/* An actor that outputs strings read from a text file or URL. 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.BufferedReader; 031import java.io.IOException; 032 033import ptolemy.actor.TypedIOPort; 034import ptolemy.actor.lib.Source; 035import ptolemy.actor.parameters.FilePortParameter; 036import ptolemy.data.BooleanToken; 037import ptolemy.data.IntToken; 038import ptolemy.data.StringToken; 039import ptolemy.data.expr.FileParameter; 040import ptolemy.data.expr.Parameter; 041import ptolemy.data.expr.SingletonParameter; 042import ptolemy.data.type.BaseType; 043import ptolemy.kernel.CompositeEntity; 044import ptolemy.kernel.util.Attribute; 045import ptolemy.kernel.util.IllegalActionException; 046import ptolemy.kernel.util.NameDuplicationException; 047import ptolemy.kernel.util.Workspace; 048 049/////////////////////////////////////////////////////////////////// 050//// LineReader 051 052/** 053 <p>This actor reads a file or URL, one line at a time, and outputs each line 054 as a string. The file or URL is specified using any form acceptable 055 to FileParameter. Before an end of file is reached, the <i>endOfFile</i> 056 output produces <i>false</i>. In the iteration where the last line 057 of the file is read and produced on the <i>output</i> port, this actor 058 produces <i>true</i> on the <i>endOfFile</i> port. In that iteration, 059 postfire() returns false. If the actor is iterated again, after the end 060 of file, then prefire() and postfire() will both return false, <i>output</i> 061 will produce the string "EOF", and <i>endOfFile</i> will produce <i>true</i>. 062 </p> 063 <p> 064 In some domains (such as SDF), returning false in postfire() 065 causes the model to cease executing. 066 In other domains (such as DE), this causes the director to avoid 067 further firings of this actor. So usually, the actor will not be 068 invoked again after the end of file is reached.</p> 069 <p> 070 This actor reads ahead in the file so that it can produce an output 071 <i>true</i> on <i>endOfFile</i> in the same iteration where it outputs 072 the last line.</p> 073 <p> 074 This actor can skip some lines at the beginning of the file or URL, with 075 the number specified by the <i>numberOfLinesToSkip</i> parameter. The 076 default value of this parameter is 0.</p> 077 <p> 078 If you need to reset this line reader to start again at the beginning 079 of the file, the way to do this is to call initialize() during the run 080 of the model. This can be done, for example, using a modal model 081 with a transition where reset is enabled.</p> 082 083 @see FileParameter 084 @author Edward A. Lee, Yuhong Xiong 085 @version $Id$ 086 @since Ptolemy II 2.2 087 @Pt.ProposedRating Green (eal) 088 @Pt.AcceptedRating Yellow (cxh) 089 */ 090public class LineReader extends Source { 091 /** Construct an actor with the given container and name. 092 * @param container The container. 093 * @param name The name of this actor. 094 * @exception IllegalActionException If the actor cannot be contained 095 * by the proposed container. 096 * @exception NameDuplicationException If the container already has an 097 * actor with this name. 098 */ 099 public LineReader(CompositeEntity container, String name) 100 throws IllegalActionException, NameDuplicationException { 101 super(container, name); 102 103 output.setTypeEquals(BaseType.STRING); 104 105 endOfFile = new TypedIOPort(this, "endOfFile", false, true); 106 endOfFile.setTypeEquals(BaseType.BOOLEAN); 107 108 fileOrURL = new FilePortParameter(this, "fileOrURL"); 109 // Parameter to get Vergil to label the fileOrURL port. 110 new SingletonParameter(fileOrURL.getPort(), "_showName") 111 .setToken(BooleanToken.TRUE); 112 113 numberOfLinesToSkip = new Parameter(this, "numberOfLinesToSkip"); 114 numberOfLinesToSkip.setExpression("0"); 115 numberOfLinesToSkip.setTypeEquals(BaseType.INT); 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 /** An output port that produces <i>false</i> until the end of file 128 * is reached, at which point it produces <i>true</i>. The type 129 * is boolean. 130 */ 131 public TypedIOPort endOfFile; 132 133 /** The file name or URL from which to read. This is a string with 134 * any form accepted by FileParameter. 135 * @see FileParameter 136 */ 137 public FilePortParameter fileOrURL; 138 139 /** The number of lines to skip at the beginning of the file or URL. 140 * This parameter contains an IntToken, initially with value 0. 141 * The value of this parameter must be non-negative. 142 */ 143 public Parameter numberOfLinesToSkip; 144 145 /////////////////////////////////////////////////////////////////// 146 //// public methods //// 147 148 /** If the specified attribute is <i>fileOrURL</i> and there is an 149 * open file being read, then close that file and open the new one; 150 * if the attribute is <i>numberOfLinesToSkip</i> and its value is 151 * negative, then throw an exception. In the case of <i>fileOrURL</i>, 152 * do nothing if the file name is the same as the previous value of 153 * this attribute. 154 * @param attribute The attribute that has changed. 155 * @exception IllegalActionException If the specified attribute 156 * is <i>fileOrURL</i> and the file cannot be opened, or the previously 157 * opened file cannot be closed; or if the attribute is 158 * <i>numberOfLinesToSkip</i> and its value is negative. 159 */ 160 @Override 161 public void attributeChanged(Attribute attribute) 162 throws IllegalActionException { 163 if (attribute == fileOrURL) { 164 // NOTE: We do not want to close the file if the file 165 // has not in fact changed. We check this by just comparing 166 // name, which is not perfect... 167 String newFileOrURL = ((StringToken) fileOrURL.getToken()) 168 .stringValue(); 169 170 if (_previousFileOrURL != null 171 && !newFileOrURL.equals(_previousFileOrURL)) { 172 if (_debugging) { 173 _debug("Closing file: " + _previousFileOrURL); 174 } 175 _previousFileOrURL = newFileOrURL; 176 fileOrURL.close(); 177 178 // Ignore if the fileOrUL is blank. 179 if (newFileOrURL.trim().equals("")) { 180 _reader = null; 181 } else { 182 if (_debugging) { 183 _debug("Opening file: " + newFileOrURL); 184 } 185 _reader = fileOrURL.openForReading(); 186 } 187 } 188 } else if (attribute == numberOfLinesToSkip) { 189 int linesToSkip = ((IntToken) numberOfLinesToSkip.getToken()) 190 .intValue(); 191 192 if (linesToSkip < 0) { 193 throw new IllegalActionException(this, 194 "The number of lines " + "to skip cannot be negative."); 195 } 196 } else { 197 super.attributeChanged(attribute); 198 } 199 } 200 201 /** Clone the actor into the specified workspace. 202 * @param workspace The workspace for the new object. 203 * @return A new actor. 204 * @exception CloneNotSupportedException If a derived class contains 205 * an attribute that cannot be cloned. 206 */ 207 @Override 208 public Object clone(Workspace workspace) throws CloneNotSupportedException { 209 LineReader newObject = (LineReader) super.clone(workspace); 210 newObject._currentLine = null; 211 newObject._reader = null; 212 return newObject; 213 } 214 215 /** Output the data read in the preinitialize() or in the previous 216 * invocation of postfire(), if there is any. 217 * @exception IllegalActionException If there's no director. 218 */ 219 @Override 220 public void fire() throws IllegalActionException { 221 super.fire(); 222 fileOrURL.update(); 223 if (_firstFiring) { 224 _openAndReadFirstTwoLines(); 225 _firstFiring = false; 226 } 227 if (_currentLine != null) { 228 output.broadcast(new StringToken(_currentLine)); 229 } else { 230 throw new IllegalActionException(this, "File is empty."); 231 } 232 if (_nextLine == null) { 233 endOfFile.broadcast(BooleanToken.TRUE); 234 } else { 235 endOfFile.broadcast(BooleanToken.FALSE); 236 } 237 } 238 239 /** If this is called after prefire() has been called but before 240 * wrapup() has been called, then close any 241 * open file re-open it, skip the number of lines given by the 242 * <i>numberOfLinesToSkip</i> parameter, and read the first line to 243 * be produced in the next invocation of prefire(). This occurs if 244 * this actor is re-initialized during a run of the model. 245 * @exception IllegalActionException If the file or URL cannot be 246 * opened, or if the lines to be skipped and the first line to be 247 * sent out in the fire() method cannot be read. 248 */ 249 @Override 250 public void initialize() throws IllegalActionException { 251 super.initialize(); 252 // In case the file has been previously opened, close 253 // it and the reopen it. 254 fileOrURL.close(); 255 _reader = null; 256 _firstFiring = true; 257 } 258 259 /** Read the next line from the file. 260 * If the current line is the last line in the file, 261 * then return false. 262 * @exception IllegalActionException If there is a problem reading 263 * the file. 264 */ 265 @Override 266 public boolean postfire() throws IllegalActionException { 267 boolean returnValue = super.postfire(); 268 if (_reader == null) { 269 return false; 270 } 271 _currentLine = _nextLine; 272 // If the next line is null, then the current iteration 273 // is processing the last line. Request to not be refired. 274 if (_nextLine == null) { 275 returnValue = false; 276 } 277 try { 278 _nextLine = _reader.readLine(); 279 if (_debugging) { 280 _debug("Read line: " + _nextLine); 281 } 282 } catch (IOException ex) { 283 throw new IllegalActionException(this, ex, "Postfire failed"); 284 } 285 return returnValue; 286 } 287 288 /** Close the reader if there is one. 289 * @exception IllegalActionException If an IO error occurs. 290 */ 291 @Override 292 public void wrapup() throws IllegalActionException { 293 fileOrURL.close(); 294 _reader = null; 295 _firstFiring = true; 296 } 297 298 /////////////////////////////////////////////////////////////////// 299 //// protected members //// 300 301 /** Cache of most recently read data. */ 302 protected String _currentLine; 303 304 /** The next line after the current line. */ 305 protected String _nextLine; 306 307 /** The current reader for the input file. */ 308 protected BufferedReader _reader; 309 310 /////////////////////////////////////////////////////////////////// 311 //// protected methods //// 312 313 /** Open the file and read the first line, putting its value into 314 * the _currentLine variable. Also, read the second line, putting 315 * its value in the _nextLine variable. 316 * @exception IllegalActionException If the file cannot be read. 317 */ 318 protected void _openAndReadFirstTwoLines() throws IllegalActionException { 319 if (_debugging) { 320 _debug("Opening file: " 321 + ((StringToken) fileOrURL.getToken()).stringValue()); 322 } 323 _reader = fileOrURL.openForReading(); 324 325 if (_reader == null) { 326 throw new IllegalActionException(this, 327 "Failed to read file: " + fileOrURL.getDisplayName()); 328 } 329 330 try { 331 // Read (numberOfLinesToSkip + 1) lines 332 int numberOfLines = ((IntToken) numberOfLinesToSkip.getToken()) 333 .intValue(); 334 335 for (int i = 0; i <= numberOfLines; i++) { 336 _currentLine = _reader.readLine(); 337 338 if (_debugging && i < numberOfLines) { 339 _debug("Skipping line: " + _currentLine); 340 } 341 342 if (_currentLine == null) { 343 throw new IllegalActionException(this, 344 "The file '" + fileOrURL.stringValue() 345 + "' does not " + "have any data."); 346 } 347 } 348 _nextLine = _reader.readLine(); 349 350 if (_debugging) { 351 _debug("Read line: " + _nextLine); 352 } 353 } catch (IOException ex) { 354 throw new IllegalActionException(this, ex, 355 "Failed to read file in preinitialize()."); 356 } 357 } 358 359 /////////////////////////////////////////////////////////////////// 360 //// protected members //// 361 362 /** First firing indicator. */ 363 protected boolean _firstFiring; 364 365 /////////////////////////////////////////////////////////////////// 366 //// private members //// 367 368 /** Previous value of fileOrURL parameter. */ 369 private String _previousFileOrURL; 370}