001/* An attribute whose value can be set via the MoML configure tag. 002 003 Copyright (c) 1998-2015 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 */ 028package ptolemy.kernel.util; 029 030import java.io.BufferedReader; 031import java.io.IOException; 032import java.io.InputStream; 033import java.io.InputStreamReader; 034import java.io.Writer; 035import java.net.URL; 036import java.util.Collection; 037import java.util.HashSet; 038import java.util.Iterator; 039import java.util.LinkedList; 040import java.util.List; 041 042/////////////////////////////////////////////////////////////////// 043//// ConfigurableAttribute 044 045/** 046 This class provides a simple way to get a long string into an attribute. 047 It implements Configurable, so its value can be set using a configure MoML 048 element. For example, 049 <pre> 050 <property name="x" class="ptolemy.moml.ConfigurableAttribute"> 051 <configure source="url">xxx</configure> 052 </property> 053 </pre> 054 The value of this property, obtained via the value() method, 055 will be whatever text is contained by the referenced URL (which 056 is optional), followed by the text "xxx". 057 058 @author Steve Neuendorffer and Edward A. Lee 059 @version $Id$ 060 @since Ptolemy II 1.0 061 @Pt.ProposedRating Green (eal) 062 @Pt.AcceptedRating Green (janneck) 063 */ 064public class ConfigurableAttribute extends Attribute 065 implements Configurable, Settable { 066 /** Construct a new attribute with no 067 * container and an empty string as its name. Add the attribute to the 068 * default workspace directory. 069 * Increment the version number of the workspace. 070 */ 071 public ConfigurableAttribute() { 072 super(); 073 } 074 075 /** Construct a new attribute with 076 * no container and an empty string as a name. You can then change 077 * the name with setName(). If the workspace argument is null, then 078 * use the default workspace. 079 * Add the attribute to the workspace directory. 080 * Increment the version number of the workspace. 081 * @param workspace The workspace that will list the attribute. 082 */ 083 public ConfigurableAttribute(Workspace workspace) { 084 super(workspace); 085 } 086 087 /** Construct a new attribute with the given container and name. 088 * @param container The container. 089 * @param name The name. 090 * @exception IllegalActionException If the attribute cannot be contained 091 * by the proposed container. 092 * @exception NameDuplicationException If the container already has an 093 * attribute with this name. 094 */ 095 public ConfigurableAttribute(NamedObj container, String name) 096 throws NameDuplicationException, IllegalActionException { 097 super(container, name); 098 } 099 100 /////////////////////////////////////////////////////////////////// 101 //// public methods //// 102 103 /** Add a listener to be notified when the value of this attribute changes. 104 * If the listener is already on the list of listeners, then do nothing. 105 * @param listener The listener to add. 106 * @see #removeValueListener(ValueListener) 107 */ 108 @Override 109 public void addValueListener(ValueListener listener) { 110 if (_valueListeners == null) { 111 _valueListeners = new LinkedList(); 112 } 113 114 if (!_valueListeners.contains(listener)) { 115 _valueListeners.add(listener); 116 } 117 } 118 119 /** Clone the attribute. This creates a new attribute with the same value. 120 * @param workspace The workspace in which to place the cloned variable. 121 * @exception CloneNotSupportedException Not thrown in this base class. 122 * @see java.lang.Object#clone() 123 * @return The cloned attribute. 124 */ 125 @Override 126 public Object clone(Workspace workspace) throws CloneNotSupportedException { 127 ConfigurableAttribute newObject = (ConfigurableAttribute) super.clone( 128 workspace); 129 130 newObject._base = null; 131 132 // The clone has new value listeners. 133 newObject._valueListeners = null; 134 135 return newObject; 136 } 137 138 /** Configure the object with data from the specified input source 139 * (a URL) and/or textual data. The input source, if any, is assumed 140 * to contain textual data as well. Note that the URL is not read 141 * until the value() or validate() method is called. 142 * @param base The base relative to which references within the input 143 * are found, or null if this is not known, or there is none. 144 * This argument is ignored in this method. 145 * @param source The input source, which specifies a URL. 146 * @param text Configuration information given as text. 147 * @exception Exception Not thrown in this base class. 148 */ 149 @Override 150 public void configure(URL base, String source, String text) 151 throws Exception { 152 if (_defaultText == null) { 153 _defaultText = _configureText; 154 } 155 156 _base = base; 157 _configureSource = source; 158 _configureText = text; 159 } 160 161 /** Return the base specified in the most recent call to the 162 * configure() method, or null if none. 163 * @return The base with respect to which the relative references 164 * in the source file should be interpreted. 165 */ 166 public URL getBase() { 167 return _base; 168 } 169 170 /** Return the source specified in the most recent call to the 171 * configure() method, or null if none. 172 * @return A URL specifying an external source for configure 173 * information. 174 */ 175 @Override 176 public String getConfigureSource() { 177 return _configureSource; 178 } 179 180 /** Return the text specified in the most recent call to the 181 * configure() method, or null if none. 182 * @return Text giving configure information. 183 */ 184 @Override 185 public String getConfigureText() { 186 return _configureText; 187 } 188 189 /** Return the default value of this Settable, 190 * if there is one. If this is a derived object, then the default 191 * is the value of the object from which this is derived (the 192 * "prototype"). If this is not a derived object, then the default 193 * is the first value set using setExpression(), or null if 194 * setExpression() has not been called. 195 * @return The default value of this attribute, or null 196 * if there is none. 197 * @see #setExpression(String) 198 */ 199 @Override 200 public String getDefaultExpression() { 201 try { 202 List prototypeList = getPrototypeList(); 203 204 if (prototypeList.size() > 0) { 205 return ((Settable) prototypeList.get(0)).getExpression(); 206 } 207 } catch (IllegalActionException e) { 208 // This should not occur. 209 throw new InternalErrorException(e); 210 } 211 212 return _defaultText; 213 } 214 215 /** Return the the result of calling value(). 216 * @return The value, or a description of the exception if one is thrown. 217 * @see #value() 218 * @see #setExpression(String) 219 */ 220 @Override 221 public String getExpression() { 222 try { 223 return value(); 224 } catch (Exception ex) { 225 return ex.toString(); 226 } 227 } 228 229 /** Get the value of the attribute, which is the evaluated expression. 230 * @return The same as getExpression(). 231 * @see #getExpression() 232 */ 233 @Override 234 public String getValueAsString() { 235 return getExpression(); 236 } 237 238 /** Get the visibility of this attribute, as set by setVisibility(). 239 * The visibility is set by default to NONE. 240 * @return The visibility of this attribute. 241 * @see #setVisibility(Settable.Visibility) 242 */ 243 @Override 244 public Settable.Visibility getVisibility() { 245 return _visibility; 246 } 247 248 /** Remove a listener from the list of listeners that is 249 * notified when the value of this attribute changes. If no such listener 250 * exists, do nothing. 251 * @param listener The listener to remove. 252 * @see #addValueListener(ValueListener) 253 */ 254 @Override 255 public void removeValueListener(ValueListener listener) { 256 if (_valueListeners != null) { 257 _valueListeners.remove(listener); 258 } 259 } 260 261 /** Set the value of the string attribute and notify the container 262 * of the value of this attribute by calling attributeChanged(), 263 * and notify any listeners that have 264 * been registered using addValueListener(). This is the same 265 * as calling configure with a null base and source, passing 266 * the argument as text. 267 * @param expression The text to configure the attribute with. 268 * @exception IllegalActionException If the change is not acceptable 269 * to the container. 270 * @see #getExpression() 271 */ 272 @Override 273 public void setExpression(String expression) throws IllegalActionException { 274 try { 275 configure(null, null, expression); 276 validate(); 277 } catch (IllegalActionException ex) { 278 throw ex; 279 } catch (Exception ex) { 280 throw new InternalErrorException("Unexpected exception: " + ex); 281 } 282 } 283 284 /** Set the visibility of this attribute. The argument should be one 285 * of the public static instances in Settable. 286 * @param visibility The visibility of this attribute. 287 * @see #getVisibility() 288 */ 289 @Override 290 public void setVisibility(Settable.Visibility visibility) { 291 _visibility = visibility; 292 } 293 294 /** Validate this attribute by calling {@link #value()}. 295 * Notify the container and listeners that the value of this 296 * attribute has changed. 297 * @return A list of contained instances of Settable. 298 * @exception IllegalActionException If the change is not acceptable 299 * to the container. 300 */ 301 @Override 302 public Collection validate() throws IllegalActionException { 303 // Validate by obtaining the value. 304 try { 305 value(); 306 } catch (IOException ex) { 307 throw new IllegalActionException(this, ex, 308 "Failed to read configuration at: " + _configureSource); 309 } 310 // Notify the container that the attribute has changed. 311 NamedObj container = getContainer(); 312 if (container != null) { 313 container.attributeChanged(this); 314 } 315 // Notify value listeners. 316 Collection result = new HashSet(); 317 if (_valueListeners != null) { 318 Iterator listeners = _valueListeners.iterator(); 319 320 while (listeners.hasNext()) { 321 ValueListener listener = (ValueListener) listeners.next(); 322 if (listener instanceof Settable) { 323 Collection validated = ((Settable) listener).validate(); 324 if (validated != null) { 325 result.addAll(validated); 326 } 327 result.add(listener); 328 } else { 329 listener.valueChanged(this); 330 } 331 } 332 } 333 return result; 334 } 335 336 /** Return the value given by the configure tag. This is the text 337 * read from the specified URL (if any), followed by the text 338 * specified in the body of the configure element. Note that the 339 * URL given in the configure() method, if any, is read each time 340 * this method is called. 341 * @return The value set in the configure tag. 342 * @exception IOException If the URL given in the configure method 343 * (if any) cannot be read. 344 */ 345 public String value() throws IOException { 346 StringBuffer value = new StringBuffer(); 347 348 // If a source is given, read its data. 349 if (_configureSource != null && !_configureSource.trim().equals("")) { 350 URL textFile = new URL(_configureSource); 351 InputStream stream = textFile.openStream(); 352 BufferedReader reader = null; 353 354 try { 355 reader = new BufferedReader(new InputStreamReader(stream, 356 java.nio.charset.Charset.defaultCharset())); 357 358 String line = reader.readLine(); 359 360 while (line != null) { 361 value.append(line); 362 value.append("\n"); 363 line = reader.readLine(); 364 } 365 } finally { 366 if (reader != null) { 367 reader.close(); 368 } 369 } 370 371 // NOTE: Do we need to close both? Java docs don't say. 372 stream.close(); 373 } 374 375 if (_configureText != null) { 376 value.append(_configureText); 377 } 378 379 return value.toString(); 380 } 381 382 /////////////////////////////////////////////////////////////////// 383 //// protected methods //// 384 385 /** Write a MoML description of the contents of this object. 386 * This method is called by exportMoML(). Each description 387 * is indented according to the 388 * specified depth and terminated with a newline character. 389 * @param output The output stream to write to. 390 * @param depth The depth in the hierarchy, to determine indenting. 391 * @exception IOException If an I/O error occurs. 392 */ 393 @Override 394 protected void _exportMoMLContents(Writer output, int depth) 395 throws IOException { 396 super._exportMoMLContents(output, depth); 397 398 String sourceSpec = ""; 399 400 if (_configureSource != null && !_configureSource.trim().equals("")) { 401 sourceSpec = " source=\"" + _configureSource + "\""; 402 403 if (_configureText == null) { 404 output.write(_getIndentPrefix(depth) + "<configure" + sourceSpec 405 + "/>\n"); 406 } 407 } 408 409 if (_configureText != null) { 410 output.write(_getIndentPrefix(depth) + "<configure" + sourceSpec 411 + ">" + _configureText + "</configure>\n"); 412 } 413 } 414 415 /** Propagate the value of this object to the 416 * specified object. The specified object is required 417 * to be an instance of the same class as this one, or 418 * a ClassCastException will be thrown. 419 * @param destination Object to which to propagate the 420 * value. 421 * @exception IllegalActionException If the value cannot 422 * be propagated. 423 */ 424 @Override 425 protected void _propagateValue(NamedObj destination) 426 throws IllegalActionException { 427 try { 428 ((Configurable) destination).configure(_base, _configureSource, 429 _configureText); 430 } catch (Exception ex) { 431 throw new IllegalActionException(this, ex, "Propagation failed."); 432 } 433 } 434 435 /////////////////////////////////////////////////////////////////// 436 //// private members //// 437 // The base specified in the configure() method. 438 private URL _base; 439 440 // The URL from which configure data is read. 441 private String _configureSource; 442 443 // The text in the body of the configure. 444 private String _configureText; 445 446 // The default text in the body of the configure. 447 private String _defaultText; 448 449 // Listeners for changes in value. 450 private List _valueListeners; 451 452 // The visibility of this attribute, which defaults to NONE; 453 private Settable.Visibility _visibility = Settable.NONE; 454}