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}