View Javadoc
1   package org.opentrafficsim.core.unit;
2   
3   import java.util.EnumMap;
4   
5   /**
6    * <p>
7    * Copyright (c) 2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
8    * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
9    * <p>
10   * @version Jun 15, 2014 <br>
11   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
12   */
13  public class SICoefficients
14  {
15      /** the map with SI base units and corresponding coefficients. */
16      private final EnumMap<SI, Integer> coefficientsMap;
17  
18      /**
19       * Construct an instance of SICoefficients.
20       * @param coefficients the map with SI base units and corresponding coefficients
21       */
22      protected SICoefficients(final EnumMap<SI, Integer> coefficients)
23      {
24          this.coefficientsMap = coefficients;
25      }
26  
27      /** {@inheritDoc} */
28      @Override
29      public final String toString()
30      {
31          return enumMapToString(this.coefficientsMap);
32      }
33  
34      /**
35       * Convert an enumMap of coefficient to the normalized string representation.
36       * @param map EnumMap&lt;{@link SI}, Integer&gt;; the EnumMap
37       * @return String
38       */
39      protected static String enumMapToString(final EnumMap<SI, Integer> map)
40      {
41          StringBuffer result = new StringBuffer();
42          boolean first = true;
43          for (SI si : map.keySet())
44          {
45              if (map.get(si) > 0)
46              {
47                  if (first)
48                  {
49                      first = false;
50                  }
51                  else
52                  {
53                      result.append(".");
54                  }
55                  result.append(si.name());
56                  if (map.get(si) != 1)
57                  {
58                      result.append(map.get(si));
59                  }
60              }
61          }
62  
63          if (result.length() == 0)
64          {
65              result.append("1");
66          }
67  
68          for (SI si : map.keySet())
69          {
70              if (map.get(si) < 0)
71              {
72                  result.append("/" + si.name());
73                  if (map.get(si) != -1)
74                  {
75                      result.append(-map.get(si));
76                  }
77              }
78          }
79          return result.toString();
80      }
81  
82      /**
83       * @return coefficientsMap
84       */
85      public final EnumMap<SI, Integer> getCoefficientsMap()
86      {
87          return this.coefficientsMap;
88      }
89  
90      /**
91       * Convert a coefficient string to <i>standard format</i>.
92       * @param coefficientString String; the string to convert
93       * @return String; the normalized coefficient string
94       * @throws UnitException when the coefficientString could not be parsed
95       */
96      public static String normalize(final String coefficientString) throws UnitException
97      {
98          return enumMapToString(parse(coefficientString));
99      }
100 
101     /**
102      * @param coefficientString such as kgm/s2 or kg-2m^3/s2A or Kmmol3/Askcd4 or mol. <br>
103      *            The grammar of a coefficientString is:<br>
104      *            <table summary="">
105      *            <tr>
106      *            <td>coefficientString</td>
107      *            <td>::=</td>
108      *            <td>&lt;empty&gt; | [ 1 | powerString ] | [ '1 /' powerString ]</td>
109      *            </tr>
110      *            <tr>
111      *            <td>powerString</td>
112      *            <td>::=</td>
113      *            <td>unitName [ [ ^ ] integer ] [ [ dotOrSlash ] powerString ]</td>
114      *            </tr>
115      *            <tr>
116      *            <td>dotOrSlash</td>
117      *            <td>::=</td>
118      *            <td>. | /</td>
119      *            </tr>
120      *            <tr>
121      *            <td>unitName</td>
122      *            <td>::=</td>
123      *            <td>kg | m | s | A | K | cd | mol</td>
124      *            </tr>
125      *            </table>
126      * <br>
127      *            White space can appear anywhere in a coefficientString. <br>
128      *            If "integer" does not fit in an Integer, the resulting coefficient will be very wrong.
129      * @return an instance of SICoefficients
130      * @throws UnitException if the coefficientString is not parsable.
131      */
132     public static EnumMap<SI, Integer> parse(final String coefficientString) throws UnitException
133     {
134         // System.out.println("coefficientString is \"" + coefficientString + "\"");
135         EnumMap<SI, Integer> coefficients = new EnumMap<SI, Integer>(SI.class);
136         String cs = coefficientString;
137         cs = cs.replace(".", "").replace(" ", "");
138         if (cs.equals("1")) // This is a special case...
139         {
140             return coefficients;
141         }
142         if (cs.startsWith("1/"))
143         {
144             cs = cs.substring(1); // remove the leading "1"
145         }
146         while (cs.length() > 0)
147         {
148             int factor = 1;
149             if (cs.startsWith("/"))
150             {
151                 cs = cs.substring(1);
152                 if (cs.length() < 1)
153                 {
154                     throw new UnitException("No SI name after slash in " + coefficientString);
155                 }
156                 factor = -1;
157             }
158             boolean parsedPowerString = false;
159             for (SI si : SI.values())
160             {
161                 String name = si.name();
162                 if (!cs.startsWith(name))
163                 {
164                     continue;
165                 }
166                 int endPos = name.length();
167                 if (cs.substring(endPos).startsWith("ol"))
168                 {
169                     continue; // Don't confuse "m" (for meter) and "mol"
170                 }
171                 // Found the unit name
172                 if (cs.substring(endPos).startsWith("^"))
173                 {
174                     endPos++;
175                 }
176                 int value = 1;
177                 int digitsSeen = 0;
178                 if (cs.substring(endPos).startsWith("-"))
179                 {
180                     factor *= -1;
181                     endPos++;
182                 }
183                 while (cs.length() > endPos)
184                 {
185                     char digit = cs.charAt(endPos);
186                     if (digit >= '0' && digit <= '9')
187                     {
188                         if (0 == digitsSeen)
189                         {
190                             value = 0;
191                         }
192                         value = value * 10 + digit - '0';
193                         endPos++;
194                         digitsSeen++;
195                     }
196                     else
197                     {
198                         break;
199                     }
200                 }
201                 Integer oldValue = coefficients.get(si);
202                 if (null == oldValue)
203                 {
204                     oldValue = 0;
205                 }
206                 coefficients.put(si, oldValue + value * factor);
207                 parsedPowerString = true;
208                 cs = cs.substring(endPos);
209                 break;
210             }
211             if (!parsedPowerString)
212             {
213                 throw new UnitException("Not an SI unit name in \"" + coefficientString + "\" at \"" + cs + "\"");
214             }
215         }
216         return coefficients;
217     }
218 
219     /**
220      * @param a the first set of coefficients
221      * @param b the second set of coefficients
222      * @return the coefficients of a*b (coefficients are added)
223      */
224     public static SICoefficients multiply(final SICoefficients a, final SICoefficients b)
225     {
226         EnumMap<SI, Integer> coefficients = new EnumMap<SI, Integer>(SI.class);
227         for (SI si : a.getCoefficientsMap().keySet())
228         {
229             coefficients.put(si, a.getCoefficientsMap().get(si));
230         }
231 
232         for (SI si : b.getCoefficientsMap().keySet())
233         {
234             if (coefficients.containsKey(si))
235             {
236                 coefficients.put(si, coefficients.get(si) + b.getCoefficientsMap().get(si));
237             }
238             else
239             {
240                 coefficients.put(si, b.getCoefficientsMap().get(si));
241             }
242         }
243 
244         for (SI si : coefficients.keySet())
245         {
246             if (coefficients.get(si) == 0)
247             {
248                 coefficients.remove(si);
249             }
250         }
251         return new SICoefficients(coefficients);
252     }
253 
254     /**
255      * @param a the first set of coefficients
256      * @param b the second set of coefficients
257      * @return the coefficients of a/b (coefficients are subtracted)
258      */
259     public static SICoefficients divide(final SICoefficients a, final SICoefficients b)
260     {
261         EnumMap<SI, Integer> coefficients = new EnumMap<SI, Integer>(SI.class);
262         for (SI si : a.getCoefficientsMap().keySet())
263         {
264             coefficients.put(si, a.getCoefficientsMap().get(si));
265         }
266 
267         for (SI si : b.getCoefficientsMap().keySet())
268         {
269             if (coefficients.containsKey(si))
270             {
271                 coefficients.put(si, coefficients.get(si) - b.getCoefficientsMap().get(si));
272             }
273             else
274             {
275                 coefficients.put(si, -b.getCoefficientsMap().get(si));
276             }
277         }
278 
279         for (SI si : coefficients.keySet())
280         {
281             if (coefficients.get(si) == 0)
282             {
283                 coefficients.remove(si);
284             }
285         }
286         return new SICoefficients(coefficients);
287     }
288 
289 }