001/* Abstract base class for tokens that contain a scalar. 002 003 Copyright (c) 1997-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 @Pt.ProposedRating Yellow (neuendor) 028 @Pt.AcceptedRating Red (yuhong) 029 030 */ 031package ptolemy.data.unit; 032 033import java.util.ArrayList; 034import java.util.HashMap; 035 036import ptolemy.kernel.util.NamedObj; 037 038/////////////////////////////////////////////////////////////////// 039//// UnitUtilities. 040 041/** 042 A set of manipulation routines that are useful for factoring most of 043 the difficulty of dealing with units out of individual token classes. 044 Furthermore, having these as static methods which do not depend on 045 token classes can improve generated code. Generally, the methods in this 046 class manipulate arrays of integers, where each index in the array 047 corresponds to a different category of units, and the value of each 048 element in the array corresponds to the factor in that unit. 049 Generally, multiplying two tokens adds adds their units, dividing two 050 tokens subtracts their units, and adding and subtracting tokens assert that 051 the units are the same. 052 053 <p> Note that a null units array is considered to be a 'unitless' 054 value to reduce memory allocation for tokens that have no units. In 055 other words, the exponent associated with each unit category is zero. 056 In general, the methods in this class return null whenever a unitless 057 unit array is encountered. 058 059 @author Steve Neuendorffer 060 @version $Id$ 061 @since Ptolemy II 0.2 062 @Pt.ProposedRating Red (cxh) 063 @Pt.AcceptedRating Red (cxh) 064 */ 065public class UnitUtilities { 066 /** There are no instances of this class. 067 */ 068 private UnitUtilities() { 069 } 070 071 /** Add the given unit arrays, and return the result in a new 072 * array. The size of the returned array will be the maximum of 073 * the size of the two input arrays, or null if both input arrays 074 * are unitless. 075 * @param units1 The first array of units. 076 * @param units2 The second array of units. 077 * @return The unit sum of the two arrays. 078 */ 079 public static int[] addUnitsArray(int[] units1, int[] units2) { 080 boolean isUnitless1 = isUnitless(units1); 081 boolean isUnitless2 = isUnitless(units2); 082 083 if (isUnitless1 && isUnitless2) { 084 return null; 085 } else if (isUnitless1) { 086 // units2 is not unitless. 087 return copyUnitsArray(units2); 088 } else if (isUnitless2) { 089 // units1 is not unitless. 090 return copyUnitsArray(units1); 091 } else { 092 // both have units. 093 int units1Length = units1.length; 094 int units2Length = units2.length; 095 int[] result; 096 097 if (units1Length < units2Length) { 098 result = new int[units2Length]; 099 System.arraycopy(units2, 0, result, 0, units2Length); 100 101 for (int i = 0; i < units1Length; i++) { 102 result[i] += units1[i]; 103 } 104 } else { 105 result = new int[units1Length]; 106 System.arraycopy(units1, 0, result, 0, units1Length); 107 108 for (int i = 0; i < units2Length; i++) { 109 result[i] += units2[i]; 110 } 111 } 112 113 if (isUnitless(result)) { 114 return null; 115 } 116 117 return result; 118 } 119 } 120 121 /** Return true if the units of this token are the same as that of the 122 * argument token. If both tokens do not have units, return true. 123 * @param units1 The first array of units. 124 * @param units2 The second array of units. 125 * @return True if the units of this token is the same as that of 126 * the argument token; false otherwise. 127 */ 128 public static boolean areUnitArraysEqual(int[] units1, int[] units2) { 129 boolean isUnitless1 = isUnitless(units1); 130 boolean isUnitless2 = isUnitless(units2); 131 132 // Either this token, or the argument token, or both have non null 133 // exponent arrays. 134 if (isUnitless1 && isUnitless2) { 135 return true; 136 } else if (isUnitless1 || isUnitless2) { 137 // one is unitless, the other is not. 138 return false; 139 } else { 140 // both are not unitless. 141 int units1Length = units1.length; 142 int units2Length = units2.length; 143 int shorterLength = Math.min(units1Length, units2Length); 144 145 for (int i = 0; i < shorterLength; i++) { 146 if (units1[i] != units2[i]) { 147 return false; 148 } 149 } 150 151 for (int i = shorterLength; i < units1Length; i++) { 152 if (units1[i] != 0) { 153 return false; 154 } 155 } 156 157 for (int i = shorterLength; i < units2Length; i++) { 158 if (units2[i] != 0) { 159 return false; 160 } 161 } 162 163 return true; 164 } 165 } 166 167 /** Return a copy of the category list. 168 * @return a copy of the category list 169 */ 170 public static ArrayList categoryList() { 171 // The port configuration window uses this. 172 synchronized (_indexTable) { 173 return new ArrayList(_categoryList); 174 } 175 } 176 177 /** Return a copy of the given units array. If the given array is 178 * unitless, then return null. 179 * @param units The given array of units. 180 * @return An int array that is a copy of the unit category 181 * exponents of this token. 182 */ 183 public static int[] copyUnitsArray(int[] units) { 184 if (isUnitless(units)) { 185 return null; 186 } 187 188 int length = units.length; 189 int[] newUnits = new int[length]; 190 System.arraycopy(units, 0, newUnits, 0, length); 191 return newUnits; 192 } 193 194 /** Return the name of the base unit of the specified category. 195 * @param categoryIndex The index of the unit category. 196 * @return The name of the base unit of the category. 197 */ 198 public static String getBaseUnitName(int categoryIndex) { 199 synchronized (_indexTable) { 200 if (categoryIndex < 0 || categoryIndex >= _categories) { 201 // FIXME: exception? 202 return "unknown"; 203 } else { 204 String categoryName = (String) _categoryList.get(categoryIndex); 205 206 if (categoryName != null) { 207 return categoryName; 208 } else { 209 // FIXME: exception? 210 return "unknown"; 211 } 212 } 213 } 214 } 215 216 /** Return the number of currently registered categories. 217 * @return the number of currently registered categories. 218 */ 219 public static int getNumCategories() { 220 synchronized (_indexTable) { 221 return _categories; 222 } 223 } 224 225 /** Return the index assigned to the specified unit category. 226 * @param categoryName The unit category. 227 * @return The index assigned to the category. 228 */ 229 public static int getUnitCategoryIndex(String categoryName) { 230 synchronized (_indexTable) { 231 Integer index = (Integer) _indexTable.get(categoryName); 232 233 if (index == null) { 234 //FIXME: throw an exception? 235 return -1; 236 } else { 237 return index.intValue(); 238 } 239 } 240 } 241 242 /** Return true if the given unit array is null, or the exponents for 243 * each index are zero. 244 * @param exponents The unit array to be checked. 245 * @return true if the given unit array is unitless. 246 */ 247 public static boolean isUnitless(int[] exponents) { 248 if (exponents != null) { 249 for (int exponent : exponents) { 250 if (exponent != 0) { 251 return false; 252 } 253 } 254 } 255 256 return true; 257 } 258 259 /////////////////////////////////////////////////////////////////// 260 //// public methods //// 261 262 /** Return a new units array that has the element at the given 263 * index set to one. 264 * @param index The unit category index. 265 * @return a new unit array in the specified category 266 */ 267 public static int[] newUnitArrayInCategory(int index) { 268 int[] units = new int[index + 1]; 269 270 // No need to initialize the array values to zero, it happens 271 // for us. 272 units[index] = 1; 273 return units; 274 } 275 276 /** Register the specified unit category name. 277 * If the category is not already registered, assign a unique index 278 * for the category. 279 * This method is static, so a category added here will be 280 * available throughout the system. 281 * <p>Note that the 282 * {@link UnitCategory#UnitCategory(NamedObj, String)} constructor 283 * calls this method. 284 * 285 * @param categoryName The unit categoryName to be registered. 286 */ 287 public static void registerUnitCategory(String categoryName) { 288 synchronized (_indexTable) { 289 Integer index = (Integer) _indexTable.get(categoryName); 290 291 if (index != null) { 292 return; 293 } else { 294 index = Integer.valueOf(_categories); 295 _indexTable.put(categoryName, index); 296 ++_categories; 297 _categoryList.add(categoryName); 298 } 299 } 300 } 301 302 /** Reset the internal state of the UnitSystem. This method is 303 * only useful for testing, and should not be called under most 304 * circumstances, since it will cause any previously created 305 * UnitTokens to have incorrect units. 306 */ 307 public static void resetUnitCategories() { 308 // This method is necessary for testing. 309 // UnitSystem has static state, so it makes it difficult 310 // to create multiple small UnitSystems. 311 synchronized (_indexTable) { 312 _indexTable.clear(); 313 _categories = 0; 314 _categoryList.clear(); 315 } 316 } 317 318 /** Subtract the given unit arrays and return the result in a new array. 319 * @param units1 The first array of units. 320 * @param units2 The second array of units. 321 * @return The unit difference of the two arrays. 322 */ 323 public static int[] subtractUnitsArray(int[] units1, int[] units2) { 324 // negate the exponents of the argument token and add to 325 // this token. 326 int[] negation = null; 327 328 if (!isUnitless(units2)) { 329 int length = units2.length; 330 negation = new int[length]; 331 332 for (int i = 0; i < length; i++) { 333 negation[i] = -units2[i]; 334 } 335 } 336 337 return addUnitsArray(units1, negation); 338 } 339 340 /** Return a string representation of the UnitSystem. 341 * @return A string representation of the UnitSystem 342 */ 343 public static String summarizeUnitCategories() { 344 synchronized (_indexTable) { 345 return "The registered categories are: " + _categories + " " 346 + _categoryList.toString(); 347 } 348 } 349 350 /** Return the string representation of the given array of units. 351 * The general format of the returned string is 352 * "(l_1 * l_2 * ... * l_m) / (s_1 * s_2 * ... * s_n)". 353 * For example: "(meter * kilogram) / (second * second)". 354 * If m or n is 1, then the parenthesis above or below "/" is 355 * omitted. For example: "meter / second". 356 * If there is no term above "/", the format becomes 357 * "1 / (s_1 * s_2 * ... * s_n)". For example: "1 / meter". 358 * If the unit array is unitless, then return an empty string. 359 * @param units the given array of units. 360 * @return A string representation of the given units array. 361 */ 362 public static String unitsString(int[] units) { 363 synchronized (_indexTable) { 364 // FIXME: use StringBuffer. 365 if (isUnitless(units)) { 366 return ""; 367 } 368 369 //System.out.println(summarizeUnitCategories()); 370 StringBuffer positiveUnits = new StringBuffer(); 371 StringBuffer negativeUnits = new StringBuffer(); 372 boolean justOnePositive = true; 373 boolean justOneNegative = true; 374 375 for (int i = 0; i < units.length; i++) { 376 int exponent = units[i]; 377 378 if (exponent != 0) { 379 String baseString = null; 380 baseString = UnitUtilities.getBaseUnitName(i); 381 382 if (exponent > 0) { 383 for (int j = 0; j < exponent; j++) { 384 if (positiveUnits.length() == 0) { 385 positiveUnits = new StringBuffer(baseString); 386 } else { 387 positiveUnits.append(" * " + baseString); 388 justOnePositive = false; 389 } 390 } 391 } else { 392 for (int j = 0; j < -exponent; j++) { 393 if (negativeUnits.length() == 0) { 394 negativeUnits = new StringBuffer(baseString); 395 } else { 396 negativeUnits.append(" * " + baseString); 397 justOneNegative = false; 398 } 399 } 400 } 401 } 402 } 403 404 if (positiveUnits.length() == 0 && negativeUnits.length() == 0) { 405 return ""; 406 } 407 408 if (positiveUnits.length() == 0) { 409 positiveUnits = new StringBuffer("1"); 410 } else if (!justOnePositive) { 411 positiveUnits = new StringBuffer("(" + positiveUnits + ")"); 412 } 413 414 if (negativeUnits.length() == 0) { 415 return positiveUnits.toString(); 416 } else if (justOneNegative) { 417 return positiveUnits.toString() + " / " 418 + negativeUnits.toString(); 419 } else { 420 return positiveUnits.toString() + " / (" 421 + negativeUnits.toString() + ")"; 422 } 423 } 424 } 425 426 /////////////////////////////////////////////////////////////////// 427 //// private variables //// 428 // The hash table that maps the name of a unit category to its 429 // index. This object also serves as a synchronization object for 430 // the three fields of this class. 431 private static HashMap _indexTable = new HashMap(); 432 433 // The number of registered unit categories. 434 private static int _categories = 0; 435 436 // The vector that contains all registered category names ordered 437 // by index. 438 private static ArrayList _categoryList = new ArrayList(); 439}