001/* A token that contains a set of label/token pairs - maintaining the original order.
002
003 Copyright (c) 2009-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
028 */
029package ptolemy.data;
030
031import java.util.Iterator;
032import java.util.LinkedHashMap;
033import java.util.LinkedHashSet;
034import java.util.Map;
035import java.util.Set;
036
037import ptolemy.kernel.util.IllegalActionException;
038import ptolemy.util.StringUtilities;
039
040///////////////////////////////////////////////////////////////////
041//// OrderedRecordToken
042
043/**
044 A token that contains a set of label/token pairs. Record labels may be
045 arbitrary strings. Operations on record tokens result in new record tokens
046 containing only the common fields, where the operation specifies how to
047 combine the data in the common fields.  Thus, for example, if two record
048 tokens are added or subtracted, then common records (those with the same
049 labels) will be added or subtracted, and the disjoint records will not
050 appear in the result.
051
052 <p>This implementation maintains the order of the entries as they were added.
053
054 @author Ben Leinfelder
055 @version $Id$
056 @since Ptolemy II 8.0
057 @version $Id$
058 @Pt.ProposedRating yellow (leinfelder)
059 @Pt.AcceptedRating red (leinfelder)
060 */
061public class OrderedRecordToken extends RecordToken {
062
063    /** Construct an OrderedRecordToke with now fields.
064     * @see RecordToken
065     */
066    public OrderedRecordToken() {
067        super();
068    }
069
070    /** Construct an OrderedRecordToken with the labels and values specified
071     *  by a given Map object. The object cannot contain any null keys
072     *  or values.
073     *
074     *  @param fieldMap A Map that has keys of type String and
075     *  values of type Token.
076     *  @exception IllegalActionException If the map contains null
077     *  keys or values, or if it contains non-String keys or non-Token
078     *  values.
079     */
080    public OrderedRecordToken(Map<String, Token> fieldMap)
081            throws IllegalActionException {
082        super(fieldMap);
083    }
084
085    /** Construct a RecordToken from the specified string.
086     *  <p>Record labels that contain any non-Java identifier characters
087     *  must be presented as a string i.e., surrounded with single or double
088     *  quotes. Quotes within label strings must be escaped using a backslash.
089     *  </p>
090     *
091     *  @param init A string expression of a record.
092     *  @exception IllegalActionException If the string does not
093     *  contain a parsable record.
094     */
095    public OrderedRecordToken(String init) throws IllegalActionException {
096        super(init);
097    }
098
099    /** Construct an OrderedRecordToken with the specified labels and values.
100     *  The labels and values arrays must have the same length, and have one
101     *  to one correspondence with each other.  That is, the i'th entry in
102     *  the labels array is the label for the i'th value in the values array.
103     *  If both arrays are empty, this creates an empty record token.
104     *
105     *  @param labels An array of labels.
106     *  @param values An array of Tokens.
107     *  @exception IllegalActionException If the labels or the values array
108     *   do not have the same length, or contains null element,
109     *   or the labels array contains duplicate elements.
110     */
111    public OrderedRecordToken(String[] labels, Token[] values)
112            throws IllegalActionException {
113        super(labels, values);
114    }
115
116    ///////////////////////////////////////////////////////////////////
117    ////                         public methods                    ////
118
119    /** Return true if the class of the argument is RecordToken, and
120     *  the argument has the same set of labels as this token and the
121     *  corresponding fields are equal, as determined by the equals
122     *  method of the contained tokens. Order matters
123     *  @param object An instance of Object.
124     *  @return True if the argument is equal to this token.
125     *  @see #hashCode()
126     */
127    @Override
128    public boolean equals(Object object) {
129        if (object == null) {
130            return false;
131        }
132        // This test rules out instances of a subclass.
133        if (object.getClass() != getClass()) {
134            return false;
135        }
136
137        RecordToken recordToken = (RecordToken) object;
138
139        try {
140            if (_isEqualTo(recordToken) == BooleanToken.TRUE) {
141                return true;
142            }
143        } catch (IllegalActionException ex) {
144            return false;
145        }
146
147        return false;
148    }
149
150    /** Return a hash code value for this token. This method returns the xor
151     *  of the hash codes of the labels and the element tokens.
152     *  @return A hash code value for this token.
153     */
154    @Override
155    public int hashCode() {
156        int code = 0;
157        Set<String> labelSet = _fields.keySet();
158        Iterator<String> iterator = labelSet.iterator();
159
160        while (iterator.hasNext()) {
161            String label = iterator.next();
162            Token token = get(label);
163            code ^= label.hashCode();
164            code ^= token.hashCode();
165        }
166
167        return code;
168    }
169
170    /** Return the value of this token as a string.
171     *  The syntax is similar to that of a record, but using square braces
172     *  instead of curly braces,
173     *  <code>[<i>label</i> = <i>value</i>, <i>label</i> = <i>value</i>, ...]</code>
174     *  The record fields are listed in the their original order
175     *  <p>Record labels that contain any non-Java identifier characters
176     *  are surrounded with double quotes. Quotes within label strings are
177     *  escaped using a backslash.
178     *  </p>
179     *
180     *  @return A String beginning with "[" that contains label and value
181     *  pairs separated by commas, ending with "]".
182     */
183    @Override
184    public String toString() {
185        Object[] labelsObjects = _fields.keySet().toArray();
186
187        // construct the string representation of this token.
188        StringBuffer stringRepresentation = new StringBuffer("[");
189
190        int size = labelsObjects.length;
191        for (int i = 0; i < size; i++) {
192            String label = (String) labelsObjects[i];
193            String value = get(label).toString();
194
195            if (i != 0) {
196                stringRepresentation.append(", ");
197            }
198
199            // quote and escape labels that are not valid Java identifiers
200            if (!StringUtilities.isValidIdentifier(label)) {
201                label = "\"" + StringUtilities.escapeString(label) + "\"";
202            }
203            stringRepresentation.append(label + " = " + value);
204        }
205
206        return stringRepresentation.toString() + "]";
207    }
208
209    ///////////////////////////////////////////////////////////////////
210    ////                         protected methods                 ////
211
212    /**
213     * @see RecordToken
214     */
215    @Override
216    protected RecordToken _createRecordToken(String[] labels, Token[] values)
217            throws IllegalActionException {
218        return new OrderedRecordToken(labels, values);
219    }
220
221    /**  Initialize the storage used by this token.  OrderedRecordToken
222     *   uses a LinkedHashMap so that the original order of the record
223     *   is maintained.
224     */
225    @Override
226    protected void _initializeStorage() {
227        // The OrderedRecordAssembler._newPortMap() sould probably
228        // probably use a similar Collection class.
229        _fields = new LinkedHashMap<String, Token>();
230    }
231
232    /**
233     * Create a Set implementation appropriate for operations on this RecordToken.
234     * Here we are using an ordered set.
235     * @return a new Set.
236     */
237    @Override
238    protected Set<String> _createSet() {
239        return new LinkedHashSet<String>();
240    }
241
242    /** Test whether the value of this token is close to the first
243     *  argument, where "close" means that the distance between them
244     *  is less than or equal to the second argument.  This method
245     *  only makes sense for tokens where the distance between them is
246     *  reasonably represented as a double. It is assumed that the
247     *  argument is an RecordToken, and the isCloseTo() method of the
248     *  fields is used.  If the fields do not match, then the
249     *  return value is false.
250     *  @param rightArgument The token to compare to this token.
251     *  @param epsilon The value that we use to determine whether two
252     *   tokens are close.
253     *  @return A token containing true if the value of the first
254     *   argument is close to the value of this token.
255     *  @exception IllegalActionException If throw while checking
256     *  the closeness of an element of the record.
257     */
258    @Override
259    protected BooleanToken _isCloseTo(Token rightArgument, double epsilon)
260            throws IllegalActionException {
261        OrderedRecordToken recordToken = (OrderedRecordToken) rightArgument;
262
263        Set<String> myLabelSet = _fields.keySet();
264        Set<String> argLabelSet = recordToken._fields.keySet();
265
266        if (!myLabelSet.equals(argLabelSet)) {
267            return BooleanToken.FALSE;
268        }
269
270        // Loop through all of the fields, checking each one for closeness.
271        Iterator<String> iterator = myLabelSet.iterator();
272        Iterator<String> argIterator = argLabelSet.iterator();
273
274        while (iterator.hasNext()) {
275            String label = iterator.next();
276            String argLabel = argIterator.next();
277            // labels match
278            if (!label.equals(argLabel)) {
279                return BooleanToken.FALSE;
280            }
281
282            Token token1 = get(label);
283            Token token2 = recordToken.get(argLabel);
284
285            // tokens are close
286            BooleanToken result = token1.isCloseTo(token2, epsilon);
287            if (result.booleanValue() == false) {
288                return BooleanToken.FALSE;
289            }
290        }
291
292        return BooleanToken.TRUE;
293    }
294
295    /** Return true if the specified token is equal to this one.
296     *  Equal means that both tokens have the same labels with the
297     *  same values.  This method is different from equals() in that
298     *  _isEqualTo() looks for equalities of values irrespective of
299     *  their types.  It is assumed that the type of the argument is
300     *  RecordToken.
301     *  @param rightArgument The token to compare to this token.
302     *  @exception IllegalActionException If this method is not
303     *  supported by the derived class.
304     *  @return True if the argument is equal to this.
305     */
306    @Override
307    protected BooleanToken _isEqualTo(Token rightArgument)
308            throws IllegalActionException {
309        OrderedRecordToken recordToken = (OrderedRecordToken) rightArgument;
310
311        Set<String> myLabelSet = _fields.keySet();
312        Set<String> argLabelSet = recordToken._fields.keySet();
313
314        if (!myLabelSet.equals(argLabelSet)) {
315            return BooleanToken.FALSE;
316        }
317
318        Iterator<String> iterator = myLabelSet.iterator();
319        Iterator<String> argIterator = argLabelSet.iterator();
320
321        while (iterator.hasNext()) {
322            String label = iterator.next();
323            String argLabel = argIterator.next();
324            // labels match
325            if (!label.equals(argLabel)) {
326                return BooleanToken.FALSE;
327            }
328
329            Token token1 = get(label);
330            Token token2 = recordToken.get(argLabel);
331
332            // tokens match
333            BooleanToken result = token1.isEqualTo(token2);
334            if (result.booleanValue() == false) {
335                return BooleanToken.FALSE;
336            }
337        }
338
339        return BooleanToken.TRUE;
340    }
341
342}