001/* A scope extending attribute that reads multiple values from a file. 002 003 Copyright (c) 2006-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 @ProposedRating Red (liuxj) 028 @AcceptedRating Red (liuxj) 029 030 */ 031package ptolemy.actor.parameters; 032 033import java.io.IOException; 034import java.io.InputStream; 035import java.net.URL; 036import java.net.URLConnection; 037import java.util.Iterator; 038import java.util.LinkedList; 039import java.util.List; 040import java.util.Map; 041import java.util.Properties; 042 043import ptolemy.actor.CompositeActor; 044import ptolemy.actor.Executable; 045import ptolemy.actor.Initializable; 046import ptolemy.data.BooleanToken; 047import ptolemy.data.expr.FileParameter; 048import ptolemy.data.expr.Parameter; 049import ptolemy.data.expr.ScopeExtendingAttribute; 050import ptolemy.data.expr.StringParameter; 051import ptolemy.data.expr.Variable; 052import ptolemy.data.type.BaseType; 053import ptolemy.kernel.util.Attribute; 054import ptolemy.kernel.util.IllegalActionException; 055import ptolemy.kernel.util.NameDuplicationException; 056import ptolemy.kernel.util.NamedObj; 057import ptolemy.kernel.util.Settable; 058 059/////////////////////////////////////////////////////////////////// 060//// ParameterSet 061 062/** 063 An attribute that reads multiple values from a file and sets 064 corresponding parameters in the container. 065 The values are in the form: 066 <pre> 067 <i>attributeName</i> = <i>value</i> 068 </pre> 069 where <code><i>variableName</i></code> is the name of the attribute 070 in a format suitable for {@link ptolemy.kernel.util.NamedObj#setName(String)} 071 (i.e., does not contain periods) and <code><i>value</i></code> is 072 the expression in the Ptolemy expression language. 073 Comments are lines that begin with the <code>#</code> character. 074 Each line in the file is interpreted as a separate assignment. 075 076 <p>The attributes that are created will have the same 077 visibility as parameters of the container of the attribute. 078 They are shadowed, however, by parameters of the container. 079 That is, if the container has a parameter with the same name 080 as one in the parameter set, the one in the container provides 081 the value to any observer. 082 083 <p>If the file is modified during execution of a model, by default 084 this will not be noticed until the next run. If you set the 085 <i>checkForFileUpdates</i> parameter to <i>true</i>, then 086 on each prefiring of the enclosing opaque composite actor, 087 this parameter will check for updates of the file. Otherwise, 088 it will only check between runs of the model or when the file 089 name or URL gets changed. 090 091 <p>Note that the order the parameters are created is arbitrary, 092 this is because we read the file in using java.util.Properties.load(), 093 which uses a HashMap to store the properties. We use a Properties.load() 094 because it provides a nice parser for the files and can read and write 095 values in both text and XML. 096 097 @author Christopher Brooks, contributor: Edward A. Lee 098 @version $Id$ 099 @since Ptolemy II 5.2 100 @see ptolemy.data.expr.Variable 101 */ 102public class ParameterSet extends ScopeExtendingAttribute 103 implements Executable { 104 /** Construct an attribute with the given name contained by the specified 105 * entity. The container argument must not be null, or a 106 * NullPointerException will be thrown. This attribute will use the 107 * workspace of the container for synchronization and version counts. 108 * If the name argument is null, then the name is set to the empty string. 109 * Increment the version of the workspace. 110 * @param container The container. 111 * @param name The name of this attribute. 112 * @exception IllegalActionException If the attribute is not of an 113 * acceptable class for the container, or if the name contains a period. 114 * @exception NameDuplicationException If the name coincides with 115 * an attribute already in the container. 116 */ 117 public ParameterSet(NamedObj container, String name) 118 throws IllegalActionException, NameDuplicationException { 119 super(container, name); 120 fileOrURL = new FileParameter(this, "fileOrURL"); 121 fileOrURL.setExpression(""); 122 123 checkForFileUpdates = new Parameter(this, "checkForFileUpdates"); 124 checkForFileUpdates.setExpression("false"); 125 checkForFileUpdates.setTypeEquals(BaseType.BOOLEAN); 126 127 StringParameter initialDefaultContents = new StringParameter(this, 128 "initialDefaultContents"); 129 initialDefaultContents.setExpression( 130 "# This file defines parameters in the current container.\n# Each non-comment line in the file is interpreted as a separate assignment.\n# The lines are of the form:\n# attributeName = value\n# where variableName is the name of the attribute\n# in a format suitable for ptolemy.kernel.util.NamedObj.setName()\n# (i.e., does not contain periods) and value is\n# the expression in the Ptolemy expression language.\n# Comments are lines that begin with the # character.\n# FIXME: After saving, you need to update the fileOrURLParameter by hand.\n# Sample line (remove the leading #):\n# foo = \"bar\"\n"); 131 initialDefaultContents.setPersistent(false); 132 initialDefaultContents.setVisibility(Settable.EXPERT); 133 } 134 135 /////////////////////////////////////////////////////////////////// 136 //// parameters //// 137 138 /** If this parameter is set to true, then the specified file or 139 * URL will be checked for updates on every prefiring of the 140 * enclosing opaque composite actor. Otherwise, it will check 141 * for updates only between runs. This is a boolean that 142 * defaults to false. 143 */ 144 public Parameter checkForFileUpdates; 145 146 /** A parameter naming the file or URL to be read that contains 147 * attribute names and values. The file should be in a format 148 * suitable for java.util.Properties.load(), see the class 149 * comment of this class for details. 150 * This initial default value is the empty string "", 151 * which means that no file will be read and no parameter 152 * values will be defined. 153 */ 154 public FileParameter fileOrURL; 155 156 /////////////////////////////////////////////////////////////////// 157 //// public methods //// 158 159 /** Add the specified object to the list of objects whose 160 * preinitialize(), initialize(), and wrapup() 161 * methods should be invoked upon invocation of the corresponding 162 * methods of this object. 163 * @param initializable The object whose methods should be invoked. 164 * @see #removeInitializable(Initializable) 165 * @see ptolemy.actor.CompositeActor#addPiggyback(Executable) 166 */ 167 @Override 168 public void addInitializable(Initializable initializable) { 169 if (_initializables == null) { 170 _initializables = new LinkedList<Initializable>(); 171 } 172 _initializables.add(initializable); 173 } 174 175 /** If the parameter is <i>fileOrURL</i>, and the specified file 176 * name is not null, then open and read the file. 177 * @param attribute The attribute that changed. 178 * @exception IllegalActionException If the superclass throws it, or 179 * if the file cannot be read, or if the file parameters cannot 180 * be evaluated. 181 */ 182 @Override 183 public void attributeChanged(Attribute attribute) 184 throws IllegalActionException { 185 if (attribute == fileOrURL) { 186 // Do not read the file if the name is the same as 187 // what was previously read. EAL 9/8/06 188 if (!fileOrURL.getExpression().equals(_fileName)) { 189 try { 190 read(); 191 validate(); 192 } catch (Throwable throwable) { 193 throw new IllegalActionException(this, throwable, 194 "Failed to read file: " 195 + fileOrURL.getExpression()); 196 } 197 } 198 } else { 199 super.attributeChanged(attribute); 200 } 201 } 202 203 /** Expand the scope of the container by creating any required attributes. 204 * This method reads the specified file if it has not already been read 205 * or if has changed since it was last read. 206 * @exception IllegalActionException If any required attribute cannot be 207 * created. 208 */ 209 @Override 210 public void expand() throws IllegalActionException { 211 _reReadIfNeeded(); 212 // Do not call validate. 213 } 214 215 /** Do nothing. 216 */ 217 @Override 218 public void fire() throws IllegalActionException { 219 } 220 221 /** Do nothing except invoke the initialize methods 222 * of objects that have been added using addInitializable(). 223 * @exception IllegalActionException If one of the added objects 224 * throws it. 225 */ 226 @Override 227 public void initialize() throws IllegalActionException { 228 // Invoke initializable methods. 229 if (_initializables != null) { 230 for (Initializable initializable : _initializables) { 231 initializable.initialize(); 232 } 233 } 234 } 235 236 /** Return true. 237 * @return True. 238 */ 239 @Override 240 public boolean isFireFunctional() { 241 return true; 242 } 243 244 /** Return false. 245 * @return False. 246 */ 247 @Override 248 public boolean isStrict() { 249 return false; 250 } 251 252 /** Check to see whether the specified file has changed, and if so, 253 * re-read it. 254 * @param count The number of iterations to perform, ignored by this 255 * method. 256 * @exception IllegalActionException If re-reading the file fails. 257 * @return Executable.COMPLETED. 258 */ 259 @Override 260 public int iterate(int count) throws IllegalActionException { 261 if (((BooleanToken) checkForFileUpdates.getToken()).booleanValue()) { 262 if (_reReadIfNeeded()) { 263 validate(); 264 } 265 } 266 return Executable.COMPLETED; 267 } 268 269 /** Do nothing. 270 * @return True. 271 */ 272 @Override 273 public boolean postfire() { 274 return true; 275 } 276 277 /** Check to see whether the specified file has changed, and if so, 278 * re-read it. 279 * @return True. 280 * @exception IllegalActionException If re-reading the file fails. 281 */ 282 @Override 283 public boolean prefire() throws IllegalActionException { 284 if (((BooleanToken) checkForFileUpdates.getToken()).booleanValue()) { 285 if (_reReadIfNeeded()) { 286 validate(); 287 } 288 } 289 return true; 290 } 291 292 /** Check to see whether the specified file has changed, and if so, 293 * re-read it, and invoke the preinitialize() methods 294 * of objects that have been added using addInitializable(). 295 * @exception IllegalActionException If one of the added objects 296 * throws it, or if re-reading the file fails. 297 */ 298 @Override 299 public void preinitialize() throws IllegalActionException { 300 // Invoke initializable methods. 301 if (_initializables != null) { 302 for (Initializable initializable : _initializables) { 303 initializable.preinitialize(); 304 } 305 } 306 if (_reReadIfNeeded()) { 307 validate(); 308 } 309 } 310 311 /** Read the contents of the file named by this parameter and create 312 * attributes in the current scope. 313 * @exception IOException If there is a problem reading the file. 314 * @exception IllegalActionException If there is a problem 315 * reading the previous attribute or validating the settables 316 * @exception NameDuplicationException If there is a problem removing 317 * a previous attribute or creating a new variable. 318 */ 319 public void read() throws IllegalActionException, NameDuplicationException, 320 IOException { 321 322 _fileName = fileOrURL.getExpression(); 323 324 if (_fileName == null || _fileName.trim().equals("")) { 325 // Delete all previously defined attributes. 326 if (_properties != null) { 327 Iterator attributeNames = _properties.keySet().iterator(); 328 while (attributeNames.hasNext()) { 329 String attributeName = (String) attributeNames.next(); 330 getAttribute(attributeName).setContainer(null); 331 } 332 _properties = null; 333 } 334 return; 335 } 336 337 URL url = fileOrURL.asURL(); 338 339 if (url == null) { 340 throw new IOException("Could not convert \"" 341 + fileOrURL.getExpression() + "\" with base \"" 342 + fileOrURL.getBaseDirectory() + "\" to a URL."); 343 } 344 // NOTE: Properties are unordered, which is not 345 // strictly right in Ptolemy II semantics. However, 346 // we wait until all are loaded before validating them, 347 // so it should be OK. 348 Properties properties = new Properties(); 349 InputStream inputStream = null; 350 try { 351 URLConnection connection = url.openConnection(); 352 inputStream = connection.getInputStream(); 353 properties.load(url.openStream()); 354 _date = connection.getDate(); 355 } finally { 356 if (inputStream != null) { 357 try { 358 inputStream.close(); 359 } catch (Throwable throwable) { 360 // Ignore. 361 } 362 } 363 } 364 365 if (_properties != null) { 366 // Remove previous parameters that are not defined 367 // in the new set. 368 Iterator attributeNames = _properties.keySet().iterator(); 369 while (attributeNames.hasNext()) { 370 String attributeName = (String) attributeNames.next(); 371 if (!properties.containsKey(attributeName)) { 372 getAttribute(attributeName).setContainer(null); 373 } 374 } 375 } 376 377 _properties = properties; 378 379 // Iterate through all the properties and either create new parameters 380 // or set current parameters. 381 // Use entrySet for performance reasons. 382 Iterator attributeMapEntries = properties.entrySet().iterator(); 383 while (attributeMapEntries.hasNext()) { 384 Map.Entry attributeNames = (Map.Entry) attributeMapEntries.next(); 385 String attributeName = (String) attributeNames.getKey(); 386 String attributeValue = (String) attributeNames.getValue(); 387 Variable variable = (Variable) getAttribute(attributeName); 388 if (variable == null) { 389 variable = new Variable(this, attributeName); 390 } 391 variable.setExpression(attributeValue); 392 } 393 } 394 395 /** Remove the specified object from the list of objects whose 396 * preinitialize(), initialize(), and wrapup() 397 * methods should be invoked upon invocation of the corresponding 398 * methods of this object. If the specified object is not 399 * on the list, do nothing. 400 * @param initializable The object whose methods should no longer be invoked. 401 * @see #addInitializable(Initializable) 402 * @see ptolemy.actor.CompositeActor#removePiggyback(Executable) 403 */ 404 @Override 405 public void removeInitializable(Initializable initializable) { 406 if (_initializables != null) { 407 _initializables.remove(initializable); 408 if (_initializables.size() == 0) { 409 _initializables = null; 410 } 411 } 412 } 413 414 /** Override the base class to register as a piggyback with the nearest opaque 415 * composite actor above in the hierarchy. 416 * @param container The proposed container. 417 * @exception IllegalActionException If the action would result in a 418 * recursive containment structure, or if 419 * this entity and container are not in the same workspace. 420 * @exception NameDuplicationException If the container already has 421 * an entity with the name of this entity. 422 */ 423 @Override 424 public void setContainer(NamedObj container) 425 throws IllegalActionException, NameDuplicationException { 426 if (container != getContainer()) { 427 // May need to unregister as a piggyback with the previous container. 428 NamedObj previousContainer = getContainer(); 429 if (previousContainer instanceof CompositeActor) { 430 ((CompositeActor) previousContainer).removePiggyback(this); 431 } 432 } 433 super.setContainer(container); 434 if (container instanceof CompositeActor) { 435 ((CompositeActor) container).addPiggyback(this); 436 } 437 } 438 439 /** Do nothing. 440 */ 441 @Override 442 public void stop() { 443 } 444 445 /** Do nothing. 446 */ 447 @Override 448 public void stopFire() { 449 } 450 451 /** Do nothing. 452 */ 453 @Override 454 public void terminate() { 455 } 456 457 /** Check to see whether the specified file has changed, and if so, 458 * re-read it, and invoke the wrapup() methods 459 * of objects that have been added using addInitializable(). 460 * @exception IllegalActionException If one of the added objects 461 * throws it, or if re-reading the file fails. 462 */ 463 @Override 464 public void wrapup() throws IllegalActionException { 465 // Invoke initializable methods. 466 if (_initializables != null) { 467 for (Initializable initializable : _initializables) { 468 initializable.wrapup(); 469 } 470 } 471 if (_reReadIfNeeded()) { 472 validate(); 473 } 474 } 475 476 /////////////////////////////////////////////////////////////////// 477 //// private methods //// 478 479 /** If either the file name or the date on the file have changed 480 * since the last reading, then re-read the file. 481 * @return True if re-reading was done. 482 * @exception IllegalActionException If re-reading the file fails. 483 */ 484 private boolean _reReadIfNeeded() throws IllegalActionException { 485 try { 486 String currentFileName = fileOrURL.getExpression(); 487 if (!currentFileName.equals(_fileName)) { 488 // File name has changed. Must re-read. 489 read(); 490 return true; 491 } 492 URL url = fileOrURL.asURL(); 493 if (url == null) { 494 throw new IOException("Could not convert \"" 495 + fileOrURL.getExpression() + "\" with base \"" 496 + fileOrURL.getBaseDirectory() + "\" to a URL."); 497 } 498 long date = url.openConnection().getDate(); 499 if (date == 0L || date != _date) { 500 read(); 501 return true; 502 } 503 return false; 504 } catch (NameDuplicationException ex) { 505 // Two separate exceptions to get FindBugs to shut up. 506 throw new IllegalActionException(this, ex, 507 "Failed to re-read parameter set, problem with dupliate names."); 508 } catch (IOException ex2) { 509 throw new IllegalActionException(this, ex2, 510 "Failed to re-read parameter set."); 511 } 512 } 513 514 /////////////////////////////////////////////////////////////////// 515 //// private fields //// 516 517 /** Date of the file when last read. */ 518 private long _date = 0L; 519 520 /** The previously read file name. */ 521 private String _fileName; 522 523 /** List of objects whose (pre)initialize() and wrapup() methods 524 * should be slaved to these. 525 */ 526 private transient List<Initializable> _initializables; 527 528 /** Cached copy of the last hashset of properties, used to remove old 529 * properties. 530 */ 531 private Properties _properties; 532}