001/* A token that contains a set of label/token pairs.
002
003 Copyright (c) 1997-2018 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.HashSet;
032import java.util.Iterator;
033import java.util.Map;
034import java.util.Set;
035import java.util.TreeMap;
036
037import ptolemy.data.expr.ASTPtRootNode;
038import ptolemy.data.expr.ParseTreeEvaluator;
039import ptolemy.data.expr.PtParser;
040import ptolemy.data.type.BaseType;
041import ptolemy.data.type.RecordType;
042import ptolemy.data.type.Type;
043import ptolemy.kernel.util.IllegalActionException;
044import ptolemy.kernel.util.InternalErrorException;
045import ptolemy.util.StringUtilities;
046
047///////////////////////////////////////////////////////////////////
048//// RecordToken
049
050/**
051 A token that contains a set of label/token pairs. Record labels may be
052 arbitrary strings. Operations on record tokens result in new record tokens
053 containing only the common fields, where the operation specifies how to
054 combine the data in the common fields.  Thus, for example, if two record
055 tokens are added or subtracted, then common records (those with the same
056 labels) will be added or subtracted, and the disjoint records will not
057 appear in the result.
058
059 @author Yuhong Xiong, Steve Neuendorffer, Elaine Cheong, Edward Lee; contributors: J. S. Senecal, Marten Lohstroh
060 @version $Id$
061 @since Ptolemy II 1.0
062 @Pt.ProposedRating Green (neuendor)
063 @Pt.AcceptedRating Yellow (cxh)
064 */
065public class RecordToken extends AbstractNotConvertibleToken {
066
067    /** Construct a RecordToken with no fields.
068     */
069    public RecordToken() {
070        _initializeStorage();
071        String[] labels = new String[0];
072        Token[] values = new Token[0];
073        try {
074            _initialize(labels, values);
075        } catch (IllegalActionException e) {
076            throw new InternalErrorException(e);
077        }
078    }
079
080    /** Construct a RecordToken with the specified labels and values.
081     *  The labels and values arrays must have the same length, and have one
082     *  to one correspondence with each other.  That is, the i'th entry in
083     *  the labels array is the label for the i'th value in the values array.
084     *  If both arrays are empty, this creates an empty record token.
085     *
086     *  @param labels An array of labels.
087     *  @param values An array of Tokens.
088     *  @exception IllegalActionException If the labels or the values array
089     *   do not have the same length, or contains null element,
090     *   or the labels array contains duplicate elements.
091     */
092    public RecordToken(String[] labels, Token[] values)
093            throws IllegalActionException {
094        _initializeStorage();
095        _initialize(labels, values);
096    }
097
098    /** Construct a RecordToken from the specified string.
099     *
100     * <p>Record labels that contain any non-Java identifier characters
101     * must be presented as a string i.e., surrounded with single or double
102     * quotes. Quotes within label strings must be escaped using a backslash.
103     * </p>
104     *
105     *  @param init A string expression of a record.
106     *  @exception IllegalActionException If the string does not
107     *  contain a parsable record.
108     */
109    public RecordToken(String init) throws IllegalActionException {
110        _initializeStorage();
111        PtParser parser = new PtParser();
112        ASTPtRootNode tree = parser.generateParseTree(init);
113
114        ParseTreeEvaluator evaluator = new ParseTreeEvaluator();
115        Token token = evaluator.evaluateParseTree(tree);
116
117        if (token instanceof RecordToken) {
118            RecordToken recordToken = (RecordToken) token;
119            Object[] labelObjects = recordToken.labelSet().toArray();
120            String[] labels = new String[labelObjects.length];
121            Token[] values = new Token[labelObjects.length];
122
123            for (int i = 0; i < labelObjects.length; i++) {
124                labels[i] = (String) labelObjects[i];
125                values[i] = recordToken.get(labels[i]);
126            }
127
128            _initialize(labels, values);
129        } else {
130            if (init.trim().equals("{}")) {
131                throw new IllegalActionException("A record token cannot be"
132                        + " created from the expression '" + init
133                        + "' because '{}' could be either an empty record"
134                        + " or an empty array.  To create an empty record, "
135                        + "use 'emptyRecord()'.");
136
137            }
138            throw new IllegalActionException("A record token cannot be"
139                    + " created from the expression '" + init + "'");
140        }
141    }
142
143    /** Construct a RecordToken with the labels and values specified
144     *  by a given Map object. The object cannot contain any null keys
145     *  or values.
146     *  @param fieldMap A Map that has keys of type String and
147     *  values of type Token.
148     *  @exception IllegalActionException If the map contains null
149     *  keys or values, or if it contains non-String keys or non-Token
150     *  values.
151     */
152    public RecordToken(Map<String, Token> fieldMap)
153            throws IllegalActionException {
154        _initializeStorage();
155
156        // iterate through map and put values under key in local map
157        for (Map.Entry<String, Token> entry : fieldMap.entrySet()) {
158            String key = entry.getKey();
159            Token val = entry.getValue();
160
161            if (key == null || val == null) {
162                throw new IllegalActionException("RecordToken: given "
163                        + "map contains either null keys " + "or null values.");
164            }
165
166            _fields.put(key, val);
167        }
168    }
169
170    ///////////////////////////////////////////////////////////////////
171    ////                         public methods                    ////
172
173    /** Return true if the class of the argument is RecordToken, and
174     *  the argument has the same set of labels as this token and the
175     *  corresponding fields are equal, as determined by the equals
176     *  method of the contained tokens.
177     *  @param object An instance of Object.
178     *  @return True if the argument is equal to this token.
179     *  @see #hashCode()
180     */
181    @Override
182    public boolean equals(Object object) {
183        if (object == null) {
184            return false;
185        }
186        // This test rules out instances of a subclass.
187        if (object.getClass() != getClass()) {
188            return false;
189        }
190
191        RecordToken recordToken = (RecordToken) object;
192
193        Set<String> myLabelSet = _fields.keySet();
194        Set<String> argLabelSet = recordToken._fields.keySet();
195
196        if (!myLabelSet.equals(argLabelSet)) {
197            return false;
198        }
199
200        Iterator<String> iterator = myLabelSet.iterator();
201
202        while (iterator.hasNext()) {
203            String label = iterator.next();
204            Token token1 = get(label);
205            Token token2 = recordToken.get(label);
206
207            if (!token1.equals(token2)) {
208                return false;
209            }
210        }
211
212        return true;
213    }
214
215    /** Return the token with the specified label. If this token does not
216     *  contain the specified label, return null.
217     *  @param label A String label.
218     *  @return A Token.
219     */
220    public Token get(String label) {
221        return _fields.get(label);
222    }
223
224    /** Return the type of this token.
225     *  @return An instance of RecordType.
226     */
227    @Override
228    public Type getType() {
229        Object[] labelsObjects = _fields.keySet().toArray();
230        int size = labelsObjects.length;
231        String[] labels = new String[size];
232        Type[] types = new Type[size];
233
234        for (int i = 0; i < size; i++) {
235            labels[i] = (String) labelsObjects[i];
236            types[i] = get(labels[i]).getType();
237        }
238
239        return new RecordType(labels, types);
240    }
241
242    /** Return a hash code value for this token. This method returns the sum
243     *  of the hash codes of the element tokens.
244     *  @return A hash code value for this token.
245     */
246    @Override
247    public int hashCode() {
248        int code = 0;
249        Set<String> labelSet = _fields.keySet();
250        Iterator<String> iterator = labelSet.iterator();
251
252        while (iterator.hasNext()) {
253            String label = iterator.next();
254            Token token = get(label);
255            code += token.hashCode();
256        }
257
258        return code;
259    }
260
261    /** Return the labels of this token as a Set.
262     *  @return A Set containing labels.
263     */
264    public Set<String> labelSet() {
265        return _fields.keySet();
266    }
267
268    /** Return the length of this token.
269     *  @return The length of this token, which is greater than or equal
270     *   to zero.
271     */
272    public int length() {
273        return _fields.size();
274    }
275
276    /** Return a new token created by merging the two specified tokens,
277     *  where preference is given to the first token when field labels
278     *  are the same.
279     *  @param token1 The higher priority record token.
280     *  @param token2 The lower priority record token.
281     *  @return A new RecordToken.
282     */
283    public static RecordToken merge(RecordToken token1, RecordToken token2) {
284        Set<String> unionSet = new HashSet<String>();
285        Set<String> labelSet1 = token1._fields.keySet();
286        Set<String> labelSet2 = token2._fields.keySet();
287        unionSet.addAll(labelSet1);
288        unionSet.addAll(labelSet2);
289
290        Object[] labelsObjects = unionSet.toArray();
291        int size = labelsObjects.length;
292        String[] labels = new String[size];
293        Token[] values = new Token[size];
294
295        for (int i = 0; i < size; i++) {
296            labels[i] = (String) labelsObjects[i];
297
298            Token value1 = token1.get(labels[i]);
299
300            if (value1 != null) {
301                values[i] = value1;
302            } else {
303                values[i] = token2.get(labels[i]);
304            }
305        }
306
307        try {
308            return new RecordToken(labels, values);
309        } catch (IllegalActionException ex) {
310            throw new InternalErrorException(ex);
311        }
312    }
313
314    /** Return the (exact) return type of the merge function above.
315     *  If the arguments are both record type, then return a record
316     *  type that contains all of the fields (and types) of the first
317     *  record, and all of the fields of the second record that are
318     *  not in the first record,  otherwise return BaseType.UNKNOWN.
319     *  @param type1 The type of the first argument to the
320     *  corresponding function.
321     *  @param type2 The type of the second argument to the
322     *  corresponding function.
323     *  @return The type of the value returned from the corresponding function.
324     */
325    public static Type mergeReturnType(Type type1, Type type2) {
326        if (type1 instanceof RecordType && type2 instanceof RecordType) {
327            RecordType recordType1 = (RecordType) type1;
328            RecordType recordType2 = (RecordType) type2;
329
330            Set<String> unionSet = new HashSet<String>();
331            Set<String> labelSet1 = recordType1.labelSet();
332            Set<String> labelSet2 = recordType2.labelSet();
333            unionSet.addAll(labelSet1);
334            unionSet.addAll(labelSet2);
335
336            Object[] labelsObjects = unionSet.toArray();
337            int size = labelsObjects.length;
338            String[] labels = new String[size];
339            Type[] types = new Type[size];
340
341            for (int i = 0; i < size; i++) {
342                labels[i] = (String) labelsObjects[i];
343
344                Type fieldType = recordType1.get(labels[i]);
345
346                if (fieldType != null) {
347                    types[i] = fieldType;
348                } else {
349                    types[i] = recordType2.get(labels[i]);
350                }
351            }
352
353            return new RecordType(labels, types);
354        } else {
355            return BaseType.UNKNOWN;
356        }
357    }
358
359    /** Returns a new RecordToken representing the multiplicative identity.
360     *  The returned token has the same set of labels as this one, and
361     *  each field contains the multiplicative identity of the corresponding
362     *  field of this token.
363     *  @return A RecordToken.
364     *  @exception IllegalActionException If multiplicative identity is not
365     *   supported by any element token.
366     */
367    @Override
368    public Token one() throws IllegalActionException {
369        Object[] labelsObjects = _fields.keySet().toArray();
370        int size = labelsObjects.length;
371        String[] labels = new String[size];
372        Token[] values = new Token[size];
373
374        for (int i = 0; i < size; i++) {
375            labels[i] = (String) labelsObjects[i];
376            values[i] = get(labels[i]).one();
377        }
378
379        return _createRecordToken(labels, values);
380    }
381
382    /** Return the value of this token as a string.
383     *  The syntax is similar to the ML record:
384     *  <code>{<i>label</i> = <i>value</i>, <i>label</i> = <i>value</i>, ...}</code>
385     *  The record fields are listed in the lexicographical order of the
386     *  labels determined by the java.lang.String.compareTo() method.
387     *
388     *  <p>Record labels that contain any non-Java identifier characters
389     *  or contain only numbers are surrounded with double quotes.
390     *  Quotes within label strings are escaped using a backslash.
391     *  </p>
392     *
393     *  @return A String beginning with "{" that contains label and value
394     *  pairs separated by commas, ending with "}".
395     */
396    @Override
397    public String toString() {
398        // RecordToken.toString() now outputs 'emptyRecord()' for a
399        // RecordToken of length zero.  Formerly, it outputted '{}'.
400        // This could cause serious trouble.  However, we need it for
401        // handling empty JavaScript objects returned by the CapeCode
402        // WebServer accessor.
403        if (length() == 0) {
404            return "emptyRecord()";
405        }
406        Object[] labelsObjects = _fields.keySet().toArray();
407
408        // order the labels
409        int size = labelsObjects.length;
410
411        for (int i = 0; i < size - 1; i++) {
412            for (int j = i + 1; j < size; j++) {
413                String labeli = (String) labelsObjects[i];
414                String labelj = (String) labelsObjects[j];
415
416                if (labeli.compareTo(labelj) >= 0) {
417                    Object temp = labelsObjects[i];
418                    labelsObjects[i] = labelsObjects[j];
419                    labelsObjects[j] = temp;
420                }
421            }
422        }
423
424        // construct the string representation of this token.
425        StringBuffer stringRepresentation = new StringBuffer("{");
426
427        for (int i = 0; i < size; i++) {
428            String label = (String) labelsObjects[i];
429            String value = get(label).toString();
430
431            if (i != 0) {
432                stringRepresentation.append(", ");
433            }
434            // quote and escape labels that are not valid Java identifiers
435            if (!StringUtilities.isValidIdentifier(label)) {
436                label = "\"" + StringUtilities.escapeString(label) + "\"";
437            }
438            stringRepresentation.append(label + " = " + value);
439        }
440
441        return stringRepresentation.toString() + "}";
442    }
443
444    /** Returns a new RecordToken representing the additive identity.
445     *  The returned token has the same set of labels as this one, and
446     *  each field contains the additive identity of the corresponding
447     *  field of this token.
448     *  @return A RecordToken.
449     *  @exception IllegalActionException If additive identity is not
450     *   supported by any element token.
451     */
452    @Override
453    public Token zero() throws IllegalActionException {
454        Object[] labelsObjects = _fields.keySet().toArray();
455        int size = labelsObjects.length;
456        String[] labels = new String[size];
457        Token[] values = new Token[size];
458
459        for (int i = 0; i < size; i++) {
460            labels[i] = (String) labelsObjects[i];
461            values[i] = get(labels[i]).zero();
462        }
463
464        return _createRecordToken(labels, values);
465    }
466
467    ///////////////////////////////////////////////////////////////////
468    ////                         public variables                  ////
469
470    /** Empty Record. */
471    public static final RecordToken EMPTY_RECORD = new RecordToken();
472
473    ///////////////////////////////////////////////////////////////////
474    ////                         protected methods                 ////
475
476    /** Return a new token whose value is the field-wise addition of
477     *  this token and the argument. It is assumed that the class of
478     *  the argument is RecordToken.
479     *  @param rightArgument The token to add to this token.
480     *  @return A new RecordToken.
481     *  @exception IllegalActionException If calling the add method on
482     *  one of the record fields throws it.
483     */
484    @Override
485    protected Token _add(Token rightArgument) throws IllegalActionException {
486        RecordToken recordToken = (RecordToken) rightArgument;
487
488        Set<String> intersectionSet = _createSet();
489        intersectionSet.addAll(_fields.keySet());
490        intersectionSet.retainAll(recordToken._fields.keySet());
491
492        Iterator<String> labels = intersectionSet.iterator();
493        int size = intersectionSet.size();
494        String[] newLabels = new String[size];
495        Token[] newValues = new Token[size];
496        int i = 0;
497
498        while (labels.hasNext()) {
499            String label = labels.next();
500            Token token1 = get(label);
501            Token token2 = recordToken.get(label);
502
503            newLabels[i] = label;
504            newValues[i] = token1.add(token2);
505
506            i++;
507        }
508
509        return _createRecordToken(newLabels, newValues);
510    }
511
512    /** Return a new token whose value is the field-wise division of
513     *  this token and the argument. It is assumed that the class of
514     *  the argument is RecordToken.
515     *  @param rightArgument The token to divide this token by.
516     *  @return A new RecordToken.
517     *  @exception IllegalActionException If calling the divide method on
518     *  one of the record fields throws it.
519     */
520    @Override
521    protected Token _divide(Token rightArgument) throws IllegalActionException {
522        RecordToken recordToken = (RecordToken) rightArgument;
523
524        Set<String> intersectionSet = _createSet();
525        intersectionSet.addAll(_fields.keySet());
526        intersectionSet.retainAll(recordToken._fields.keySet());
527
528        Iterator<String> labels = intersectionSet.iterator();
529        int size = intersectionSet.size();
530        String[] newLabels = new String[size];
531        Token[] newValues = new Token[size];
532        int i = 0;
533
534        while (labels.hasNext()) {
535            String label = labels.next();
536            Token token1 = get(label);
537            Token token2 = recordToken.get(label);
538
539            newLabels[i] = label;
540            newValues[i] = token1.divide(token2);
541
542            i++;
543        }
544
545        return _createRecordToken(newLabels, newValues);
546    }
547
548    /** Test whether the value of this token is close to the first
549     *  argument, where "close" means that the distance between them
550     *  is less than or equal to the second argument.  This method
551     *  only makes sense for tokens where the distance between them is
552     *  reasonably represented as a double. It is assumed that the
553     *  argument is an RecordToken, and the isCloseTo() method of the
554     *  fields is used.  If the fields do not match, then the
555     *  return value is false.
556     *  @param rightArgument The token to compare to this token.
557     *  @param epsilon The value that we use to determine whether two
558     *   tokens are close.
559     *  @return A token containing true if the value of the first
560     *   argument is close to the value of this token.
561     *  @exception IllegalActionException If throw while checking
562     *  the closeness of an element of the record.
563     */
564    @Override
565    protected BooleanToken _isCloseTo(Token rightArgument, double epsilon)
566            throws IllegalActionException {
567        RecordToken recordToken = (RecordToken) rightArgument;
568
569        Set<String> myLabelSet = _fields.keySet();
570        Set<String> argLabelSet = recordToken._fields.keySet();
571
572        if (!myLabelSet.equals(argLabelSet)) {
573            return BooleanToken.FALSE;
574        }
575
576        // Loop through all of the fields, checking each one for closeness.
577        Iterator<String> iterator = myLabelSet.iterator();
578
579        while (iterator.hasNext()) {
580            String label = iterator.next();
581            Token token1 = get(label);
582            Token token2 = recordToken.get(label);
583            BooleanToken result = token1.isCloseTo(token2, epsilon);
584
585            if (result.booleanValue() == false) {
586                return BooleanToken.FALSE;
587            }
588        }
589
590        return BooleanToken.TRUE;
591    }
592
593    /** Return true if the specified token is equal to this one.
594     *  Equal means that both tokens have the same labels with the
595     *  same values.  This method is different from equals() in that
596     *  _isEqualTo() looks for equalities of values irrespective of
597     *  their types.  It is assumed that the type of the argument is
598     *  RecordToken.
599     *  @param rightArgument The token to compare to this token.
600     *  @exception IllegalActionException If this method is not
601     *  supported by the derived class.
602     *  @return True if the argument is equal to this.
603     */
604    @Override
605    protected BooleanToken _isEqualTo(Token rightArgument)
606            throws IllegalActionException {
607        RecordToken recordToken = (RecordToken) rightArgument;
608
609        Set<String> myLabelSet = _fields.keySet();
610        Set<String> argLabelSet = recordToken._fields.keySet();
611
612        if (!myLabelSet.equals(argLabelSet)) {
613            return BooleanToken.FALSE;
614        }
615
616        Iterator<String> iterator = myLabelSet.iterator();
617
618        while (iterator.hasNext()) {
619            String label = iterator.next();
620            Token token1 = get(label);
621            Token token2 = recordToken.get(label);
622            BooleanToken result = token1.isEqualTo(token2);
623
624            if (result.booleanValue() == false) {
625                return BooleanToken.FALSE;
626            }
627        }
628
629        return BooleanToken.TRUE;
630    }
631
632    /** Return a new token whose value is the field-wise modulo of
633     *  this token and the argument. It is assumed that the class of
634     *  the argument is RecordToken.
635     *  @param rightArgument The token to modulo this token by.
636     *  @return A new RecordToken.
637     *  @exception IllegalActionException If calling the modulo method on
638     *  one of the record fields throws it.
639     */
640    @Override
641    protected Token _modulo(Token rightArgument) throws IllegalActionException {
642        RecordToken recordToken = (RecordToken) rightArgument;
643
644        Set<String> intersectionSet = _createSet();
645        intersectionSet.addAll(_fields.keySet());
646        intersectionSet.retainAll(recordToken._fields.keySet());
647
648        Iterator<String> labels = intersectionSet.iterator();
649        int size = intersectionSet.size();
650        String[] newLabels = new String[size];
651        Token[] newValues = new Token[size];
652        int i = 0;
653
654        while (labels.hasNext()) {
655            String label = labels.next();
656            Token token1 = get(label);
657            Token token2 = recordToken.get(label);
658
659            newLabels[i] = label;
660            newValues[i] = token1.modulo(token2);
661
662            i++;
663        }
664
665        return _createRecordToken(newLabels, newValues);
666    }
667
668    /** Return a new token whose value is the field-wise
669     *  multiplication of this token and the argument. It is assumed
670     *  that the class of the argument is RecordToken.
671     *  @param rightArgument The token to multiply this token by.
672     *  @return A new RecordToken.
673     *  @exception IllegalActionException If calling the multiply method on
674     *  one of the record fields throws it.
675     */
676    @Override
677    protected Token _multiply(Token rightArgument)
678            throws IllegalActionException {
679        RecordToken recordToken = (RecordToken) rightArgument;
680
681        Set<String> intersectionSet = _createSet();
682        intersectionSet.addAll(_fields.keySet());
683        intersectionSet.retainAll(recordToken._fields.keySet());
684
685        Iterator<String> labels = intersectionSet.iterator();
686        int size = intersectionSet.size();
687        String[] newLabels = new String[size];
688        Token[] newValues = new Token[size];
689        int i = 0;
690
691        while (labels.hasNext()) {
692            String label = labels.next();
693            Token token1 = get(label);
694            Token token2 = recordToken.get(label);
695
696            newLabels[i] = label;
697            newValues[i] = token1.multiply(token2);
698
699            i++;
700        }
701
702        return _createRecordToken(newLabels, newValues);
703    }
704
705    /** Return a new token whose value is the field-wise subtraction
706     *  of this token and the argument. It is assumed that the class
707     *  of the argument is RecordToken.
708     *  @param rightArgument The token to subtract from this token.
709     *  @return A new RecordToken.
710     *  @exception IllegalActionException If calling the subtract
711     *  method on one of the record fields throws it.
712     */
713    @Override
714    protected Token _subtract(Token rightArgument)
715            throws IllegalActionException {
716        RecordToken recordToken = (RecordToken) rightArgument;
717
718        Set<String> intersectionSet = _createSet();
719        intersectionSet.addAll(_fields.keySet());
720        intersectionSet.retainAll(recordToken._fields.keySet());
721
722        Iterator<String> labels = intersectionSet.iterator();
723        int size = intersectionSet.size();
724        String[] newLabels = new String[size];
725        Token[] newValues = new Token[size];
726        int i = 0;
727
728        while (labels.hasNext()) {
729            String label = labels.next();
730            Token token1 = get(label);
731            Token token2 = recordToken.get(label);
732
733            newLabels[i] = label;
734            newValues[i] = token1.subtract(token2);
735
736            i++;
737        }
738
739        return _createRecordToken(newLabels, newValues);
740    }
741
742    /**
743     * Subclasses of RecordToken may choose a different Map implementation
744     * TreeMap is used in the base class to provide naturally-ordered labels
745     * This may not be desired in some applications.
746     */
747    protected void _initializeStorage() {
748        _fields = new TreeMap<String, Token>();
749    }
750
751    /**
752     * Create a new RecordToken.
753     * Subclasses of RecordToken may return a different subclass instance.
754     * @param labels An array of String labels for the RecordToken to be created.
755     * @param values An array of Token values for the RecordToken to be created.
756     * @return a new RecordToken.
757     * @exception IllegalActionException If thrown while constructing the RecordToken
758     */
759    protected RecordToken _createRecordToken(String[] labels, Token[] values)
760            throws IllegalActionException {
761        return new RecordToken(labels, values);
762    }
763
764    /**
765     * Create a Set implementation appropriate for operations on this RecordToken
766     * Subclasses of RecordToken may return a different implementation.
767     * @return a new Set.
768     */
769    protected Set<String> _createSet() {
770        return new HashSet<String>();
771    }
772
773    ///////////////////////////////////////////////////////////////////
774    ////                         private methods                   ////
775    // initialize this token using the specified labels and values.
776    // This method is called by the constructor.
777    private void _initialize(String[] labels, Token[] values)
778            throws IllegalActionException {
779        if (labels == null || values == null
780                || labels.length != values.length) {
781            throw new IllegalActionException("RecordToken: the labels or "
782                    + "the values array do not have the same length, "
783                    + "or is null.");
784        }
785
786        for (int i = 0; i < labels.length; i++) {
787            if (labels[i] == null || values[i] == null) {
788                throw new IllegalActionException("RecordToken: the " + i
789                        + "'th element of the labels or values array is null");
790            }
791
792            labels[i] = labels[i];
793            if (!_fields.containsKey(labels[i])) {
794                _fields.put(labels[i], values[i]);
795            } else {
796                throw new IllegalActionException("RecordToken: The "
797                        + "labels array contain duplicate element: "
798                        + labels[i]);
799            }
800        }
801    }
802
803    ///////////////////////////////////////////////////////////////////
804    ////                         private variables                 ////
805
806    /** The map of fields that has keys of type String and values of
807     *  type token.
808     *  Subclasses can use alternative Map implementations (for ordering).
809     */
810    protected Map<String, Token> _fields = null;
811}