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<{@link SI}, Integer>; 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><empty> | [ 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 }