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}