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 "&lt;property&gt;"
139     *  and "&lt;/property&gt;", 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}