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}