001/* An attribute that represents a location of a node in a schematic. 002 003 Copyright (c) 2002-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 */ 028package ptolemy.kernel.util; 029 030import java.io.IOException; 031import java.io.Writer; 032import java.util.Collection; 033import java.util.Iterator; 034import java.util.LinkedList; 035import java.util.List; 036import java.util.StringTokenizer; 037 038import ptolemy.util.StringUtilities; 039 040/////////////////////////////////////////////////////////////////// 041//// Location 042 043/** 044 An attribute that represents the center location of a node in a 045 schematic. 046 047 <p>By default, an instance of this class is not visible in a user 048 interface. This is indicated to the user interface by returning NONE 049 to the getVisibility() method. The location is specified by calling 050 setExpression() with a string that has the form "x,y" or "[x,y]" or 051 "{x,y}", where x and y can be parsed into doubles. 052 053 <p>The default location is a two dimensional location with value {0.0, 0.0}. 054 This class can also handle locations with greater than two dimensions. 055 056 @author Steve Neuendorffer and Edward A. Lee 057 @version $Id$ 058 @since Ptolemy II 2.1 059 @Pt.ProposedRating Green (cxh) 060 @Pt.AcceptedRating Green (cxh) 061 */ 062public class Location extends SingletonAttribute implements Locatable { 063 // FIXME: Note that this class does not extend from StringAttribute 064 // because it is a singleton. Thus, there is a bunch of code 065 // duplication here. The fix would be to modify StringAttribute 066 // so that we could have a singleton. 067 068 /** Construct an attribute in the specified workspace with an empty 069 * string as a name. 070 * If the workspace argument is null, then use the default workspace. 071 * The object is added to the directory of the workspace. 072 * Increment the version number of the workspace. 073 * @param workspace The workspace that will list the attribute. 074 */ 075 public Location(Workspace workspace) { 076 super(workspace); 077 } 078 079 /** Construct an attribute with the given container and name. 080 * @param container The container. 081 * @param name The name of the vertex. 082 * @exception IllegalActionException If the attribute is not of an 083 * acceptable class for the container. 084 * @exception NameDuplicationException If the name coincides with 085 * an attribute already in the container. 086 */ 087 public Location(NamedObj container, String name) 088 throws IllegalActionException, NameDuplicationException { 089 super(container, name); 090 } 091 092 /////////////////////////////////////////////////////////////////// 093 //// public methods //// 094 095 /** Add a listener to be notified when the value of this attribute changes. 096 * If the listener is already on the list of listeners, then do nothing. 097 * @param listener The listener to add. 098 * @see #removeValueListener(ValueListener) 099 */ 100 @Override 101 public void addValueListener(ValueListener listener) { 102 if (_valueListeners == null) { 103 _valueListeners = new LinkedList(); 104 } 105 106 if (!_valueListeners.contains(listener)) { 107 _valueListeners.add(listener); 108 } 109 } 110 111 /** Clone the location into the specified workspace. The new object is 112 * <i>not</i> added to the directory of that workspace (you must do this 113 * yourself if you want it there). 114 * @param workspace The workspace for the cloned object. 115 * @exception CloneNotSupportedException If the base class throws it. 116 * @return A new Location. 117 */ 118 @Override 119 public Object clone(Workspace workspace) throws CloneNotSupportedException { 120 Location newObject = (Location) super.clone(workspace); 121 122 // Copy the location so that the reference in the new object 123 // does not refer to the same array. 124 // _location can never be null because setLocation() will 125 // not handle it. 126 int length = _location.length; 127 newObject._location = new double[length]; 128 System.arraycopy(_location, 0, newObject._location, 0, length); 129 130 newObject._valueListeners = null; 131 return newObject; 132 } 133 134 /** Write a MoML description of this object. 135 * MoML is an XML modeling markup language. 136 * In this class, the object is identified by the "property" 137 * element, with "name", "class", and "value" (XML) attributes. 138 * The body of the element, between the "<property>" 139 * and "</property>", is written using 140 * the _exportMoMLContents() protected method, so that derived classes 141 * can override that method alone to alter only how the contents 142 * of this object are described. 143 * The text that is written is indented according to the specified 144 * depth, with each line (including the last one) 145 * terminated with a newline. If this object is non-persistent, 146 * then nothing is written. 147 * @param output The output writer to write to. 148 * @param depth The depth in the hierarchy, to determine indenting. 149 * @param name The name to use instead of the current name. 150 * @exception IOException If an I/O error occurs. 151 * @see #isPersistent() 152 */ 153 @Override 154 public void exportMoML(Writer output, int depth, String name) 155 throws IOException { 156 // If the object is not persistent, and we are not 157 // at level 0, do nothing. 158 if (_isMoMLSuppressed(depth)) { 159 return; 160 } 161 162 String value = getExpression(); 163 String valueTerm = ""; 164 165 if (value != null && !value.equals("")) { 166 valueTerm = " value=\"" + StringUtilities.escapeForXML(value) 167 + "\""; 168 } 169 170 // It might be better to use multiple writes here for performance. 171 output.write(_getIndentPrefix(depth) + "<" + _elementName + " name=\"" 172 + name + "\" class=\"" + getClassName() + "\"" + valueTerm 173 + ">\n"); 174 _exportMoMLContents(output, depth + 1); 175 output.write(_getIndentPrefix(depth) + "</" + _elementName + ">\n"); 176 } 177 178 /** Return the default value of this Settable, 179 * if there is one. If this is a derived object, then the default 180 * is the value of the object from which this is derived (the 181 * "prototype"). If this is not a derived object, then the default 182 * is the first value set using setExpression(), or null if 183 * setExpression() has not been called. 184 * @return The default value of this attribute, or null 185 * if there is none. 186 * @see #setExpression(String) 187 */ 188 @Override 189 public String getDefaultExpression() { 190 try { 191 List prototypeList = getPrototypeList(); 192 193 if (prototypeList.size() > 0) { 194 return ((Settable) prototypeList.get(0)).getExpression(); 195 } 196 } catch (IllegalActionException e) { 197 // This should not occur. 198 throw new InternalErrorException(e); 199 } 200 201 return _default; 202 } 203 204 /** Return a name to present to the user, which 205 * is the same as the name returned by getName(). 206 * @return A name to present to the user. 207 */ 208 @Override 209 public String getDisplayName() { 210 return getName(); 211 } 212 213 /** Get the value that has been set by setExpression() or by 214 * setLocation(), whichever was most recently called, or return 215 * an empty string if neither has been called. 216 * 217 * <p>If setExpression(String value) was called, then the return 218 * value is exactly what ever was passed in as the argument to 219 * setExpression. This means that there is no guarantee that 220 * the return value of getExpression() is a well formed Ptolemy 221 * array expression. 222 * 223 * <p>If setLocation(double[] location) was called, then the 224 * return value is a well formed Ptolemy array expression that 225 * starts with "{" and ends with "}", for example "{0.0, 0.0}" 226 * 227 * @return The expression. 228 * @see #setExpression(String) 229 */ 230 @Override 231 public String getExpression() { 232 if (_expressionSet) { 233 // FIXME: If setExpression() was called with a string that does 234 // not begin and end with curly brackets, then getExpression() 235 // will not return something that is parseable by setExpression() 236 return _expression; 237 } 238 239 if (_location == null || _location.length == 0) { 240 return ""; 241 } 242 243 // We tack on { } around the value that is returned so that it 244 // can be passed to setExpression(). 245 StringBuffer result = new StringBuffer("{"); 246 247 for (int i = 0; i < _location.length - 1; i++) { 248 result.append(_location[i]); 249 result.append(", "); 250 } 251 252 result.append(_location[_location.length - 1] + "}"); 253 return result.toString(); 254 } 255 256 /** Get the center location in some cartesian coordinate system. 257 * @return The location. 258 * @see #setLocation(double[]) 259 */ 260 @Override 261 public double[] getLocation() { 262 return _location; 263 } 264 265 /** Get the value of the attribute, which is the evaluated expression. 266 * @return The same as getExpression(). 267 * @see #getExpression() 268 */ 269 @Override 270 public String getValueAsString() { 271 return getExpression(); 272 } 273 274 /** Get the visibility of this attribute, as set by setVisibility(). 275 * The visibility is set by default to NONE. 276 * @return The visibility of this attribute. 277 * @see #setVisibility(Settable.Visibility) 278 */ 279 @Override 280 public Settable.Visibility getVisibility() { 281 return _visibility; 282 } 283 284 /** Remove a listener from the list of listeners that is 285 * notified when the value of this variable changes. If no such listener 286 * exists, do nothing. 287 * @param listener The listener to remove. 288 * @see #addValueListener(ValueListener) 289 */ 290 @Override 291 public void removeValueListener(ValueListener listener) { 292 if (_valueListeners != null) { 293 _valueListeners.remove(listener); 294 } 295 } 296 297 /** Set the value of the attribute by giving some expression. 298 * This expression is not parsed until validate() is called, and 299 * the container and value listeners are not notified until validate() 300 * is called. See the class comment for a description of the format. 301 * @param expression The value of the attribute. 302 * @see #getExpression() 303 */ 304 @Override 305 public void setExpression(String expression) { 306 if (_default == null) { 307 _default = expression; 308 } 309 310 _expression = expression; 311 _expressionSet = true; 312 } 313 314 /** Set the center location in some cartesian coordinate system, 315 * and notify the container and any value listeners of the new 316 * location. Setting the location involves maintaining a local 317 * copy of the passed parameter. No notification is done if the 318 * location is the same as before. This method propagates the 319 * value to any derived objects. 320 * @param location The location. 321 * @exception IllegalActionException If throw when attributeChanged() 322 * is called. 323 * @see #getLocation() 324 */ 325 @Override 326 public void setLocation(double[] location) throws IllegalActionException { 327 _expressionSet = false; 328 329 if (_setLocation(location)) { 330 // If the location was modified in _setLocation(), 331 // then make sure the new value is exported in MoML. 332 setPersistent(true); 333 } 334 335 propagateValue(); 336 } 337 338 /** Set the visibility of this attribute. The argument should be one 339 * of the public static instances in Settable. 340 * @param visibility The visibility of this attribute. 341 * @see #getVisibility() 342 */ 343 @Override 344 public void setVisibility(Settable.Visibility visibility) { 345 _visibility = visibility; 346 } 347 348 /** Get a description of the class, which is the class name and 349 * the location in parentheses. 350 * @return A string describing the object. 351 */ 352 @Override 353 public String toString() { 354 String className = getClass().getName(); 355 356 if (_location == null) { 357 return "(" + className + ", Location = null)"; 358 } 359 360 return "(" + className + ", Location = " + getExpression() + ")"; 361 } 362 363 /** Parse the location specification given by setExpression(), if there 364 * has been one, and otherwise set the location to 0.0, 0.0. 365 * Notify the container and any value listeners of the new location, 366 * if it has changed. 367 * See the class comment for a description of the format. 368 * @return Null, indicating that no other instances of Settable are 369 * validated. 370 * @exception IllegalActionException If the expression is invalid. 371 */ 372 @Override 373 public Collection validate() throws IllegalActionException { 374 // If the value has not been set via setExpression(), there is 375 // nothing to do. 376 if (!_expressionSet) { 377 return null; 378 } 379 380 double[] location; 381 382 if (_expression == null) { 383 location = new double[2]; 384 location[0] = 0.0; 385 location[1] = 0.0; 386 } else { 387 // Parse the specification: a comma specified list of doubles, 388 // optionally surrounded by square or curly brackets. 389 StringTokenizer tokenizer = new StringTokenizer(_expression, 390 ",[]{}"); 391 location = new double[tokenizer.countTokens()]; 392 393 int count = tokenizer.countTokens(); 394 395 for (int i = 0; i < count; i++) { 396 String next = tokenizer.nextToken().trim(); 397 location[i] = Double.parseDouble(next); 398 } 399 } 400 401 // Set and notify. 402 _setLocation(location); 403 404 // FIXME: If _setLocation() returns true, should we call 405 // setModifiedFromClass() like we do elsewhere? 406 407 return null; 408 } 409 410 /////////////////////////////////////////////////////////////////// 411 //// protected methods //// 412 413 /** Propagate the value of this object to the 414 * specified object. The specified object is required 415 * to be an instance of the same class as this one, or 416 * a ClassCastException will be thrown. 417 * @param destination Object to which to propagate the 418 * value. 419 * @exception IllegalActionException If the value cannot 420 * be propagated. 421 */ 422 @Override 423 protected void _propagateValue(NamedObj destination) 424 throws IllegalActionException { 425 // NOTE: Cannot use the _location value because the 426 // expression may not have yet been evaluated. 427 ((Location) destination).setExpression(getExpression()); 428 } 429 430 /////////////////////////////////////////////////////////////////// 431 //// private methods //// 432 433 /** Set the location without altering the modified status. 434 * @param location The location. 435 * @return True if the location was modified. 436 * @exception IllegalActionException If the call to attributeChanged() 437 * throws it. 438 */ 439 private boolean _setLocation(double[] location) 440 throws IllegalActionException { 441 // If the location is unchanged, return false. 442 if (_location != null) { 443 if (_location.length == location.length) { 444 boolean match = true; 445 446 for (int i = 0; i < location.length; i++) { 447 if (_location[i] != location[i]) { 448 match = false; 449 break; 450 } 451 } 452 453 if (match) { 454 return false; 455 } 456 } else { 457 // _location.length != location.length 458 // If location is of size 3, then we end up here. 459 _location = new double[location.length]; 460 } 461 } else { 462 // _location == null 463 _location = new double[location.length]; 464 } 465 466 // FindBugs: _location cannot be null here, so don't check. 467 468 // Copy location array into member array _location. 469 // Just referencing _location to location isn't enough, we need 470 // to maintain a local copy of the double array. 471 for (int i = 0; i < location.length; i++) { 472 _location[i] = location[i]; 473 } 474 475 NamedObj container = getContainer(); 476 477 if (container != null) { 478 container.attributeChanged(this); 479 } 480 481 if (_valueListeners != null) { 482 Iterator listeners = _valueListeners.iterator(); 483 484 while (listeners.hasNext()) { 485 ValueListener listener = (ValueListener) listeners.next(); 486 listener.valueChanged(this); 487 } 488 } 489 490 return true; 491 } 492 493 /////////////////////////////////////////////////////////////////// 494 //// private variables //// 495 // The default value. 496 private String _default = null; 497 498 // The expression given in setExpression(). 499 private String _expression; 500 501 // Indicator that the expression is the most recent spec for the location. 502 private boolean _expressionSet = false; 503 504 // The location. 505 private double[] _location = { 0.0, 0.0 }; 506 507 // Listeners for changes in value. 508 private List _valueListeners; 509 510 // The visibility of this attribute, which defaults to NONE. 511 private Settable.Visibility _visibility = Settable.NONE; 512}