1 package org.djunits.unit.si;
2
3 import java.io.Serializable;
4 import java.util.Arrays;
5
6 import org.djunits.unit.util.UnitException;
7 import org.djutils.exceptions.Throw;
8
9
10
11
12
13
14
15
16
17
18 public class SIDimensions implements Serializable
19 {
20
21 private static final long serialVersionUID = 20190818L;
22
23
24 public static final int NUMBER_DIMENSIONS = 9;
25
26
27 private static final byte[] UNIT_DENOMINATOR = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1};
28
29
30 private static final String[] SI_ABBREVIATIONS = new String[] {"rad", "sr", "kg", "m", "s", "A", "K", "mol", "cd"};
31
32
33 private static final int[] PARSE_ORDER = new int[] {0, 1, 2, 7, 3, 4, 5, 6, 8};
34
35
36
37
38
39
40 private final byte[] dimensions;
41
42
43 private final byte[] denominator;
44
45
46 private final boolean fractional;
47
48
49
50
51
52
53
54
55 public SIDimensions(final byte[] dimensions)
56 {
57 Throw.whenNull(dimensions, "dimensions cannot be null");
58 Throw.when(dimensions.length != NUMBER_DIMENSIONS, SIRuntimeException.class, "SIDimensions wrong dimensionality");
59 this.dimensions = dimensions.clone();
60 this.denominator = UNIT_DENOMINATOR;
61 this.fractional = false;
62 }
63
64
65
66
67
68
69
70
71
72
73
74 protected SIDimensions(final byte[] numerator, final byte[] denominator)
75 {
76
77 Throw.whenNull(numerator, "numerator cannot be null");
78 Throw.whenNull(denominator, "denominator cannot be null");
79 Throw.when(numerator.length != NUMBER_DIMENSIONS, SIRuntimeException.class, "numerator has wrong dimensionality");
80 Throw.when(denominator.length != NUMBER_DIMENSIONS, SIRuntimeException.class, "denominator has wrong dimensionality");
81 this.dimensions = numerator.clone();
82 this.denominator = denominator.clone();
83 this.fractional = !Arrays.equals(denominator, UNIT_DENOMINATOR);
84 }
85
86
87
88
89
90
91
92
93
94
95
96
97
98 @SuppressWarnings("checkstyle:parameternumber")
99 public SIDimensions(final int angle, final int solidAngle, final int mass, final int length, final int time,
100 final int current, final int temperature, final int amountOfSubstance, final int luminousIntensity)
101 {
102 this.dimensions = new byte[NUMBER_DIMENSIONS];
103 this.dimensions[0] = (byte) angle;
104 this.dimensions[1] = (byte) solidAngle;
105 this.dimensions[2] = (byte) mass;
106 this.dimensions[3] = (byte) length;
107 this.dimensions[4] = (byte) time;
108 this.dimensions[5] = (byte) current;
109 this.dimensions[6] = (byte) temperature;
110 this.dimensions[7] = (byte) amountOfSubstance;
111 this.dimensions[8] = (byte) luminousIntensity;
112 this.denominator = UNIT_DENOMINATOR;
113 this.fractional = false;
114 }
115
116
117
118
119
120
121
122
123
124
125
126
127 public static SIDimensions of(final String siString) throws UnitException
128 {
129 Throw.whenNull(siString, "siString cannot be null");
130 String dimString = siString.replaceAll("[ .^]", "");
131
132 if (dimString.contains("/"))
133 {
134 String[] parts = dimString.split("\\/");
135 if (parts.length != 2)
136 {
137 throw new UnitException("SI String " + dimString + " contains more than one division sign");
138 }
139 byte[] numerator = parse(parts[0]);
140 byte[] denominator = parse(parts[1]);
141 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
142 {
143 numerator[i] -= denominator[i];
144 }
145 return new SIDimensions(numerator);
146 }
147 return new SIDimensions(parse(dimString));
148 }
149
150
151
152
153
154
155
156
157
158
159 private static byte[] parse(final String siString) throws UnitException
160 {
161 Throw.whenNull(siString, "siString cannot be null");
162 byte[] result = new byte[NUMBER_DIMENSIONS];
163 if (siString.equals("1") || siString.length() == 0)
164 {
165 return result;
166 }
167 String copy = siString;
168 int copyLength = copy.length();
169 while (copyLength > 0)
170 {
171
172 for (int j = 0; j < SI_ABBREVIATIONS.length; j++)
173 {
174 int i = PARSE_ORDER[j];
175 String si = SI_ABBREVIATIONS[i];
176 if (copy.startsWith(si))
177 {
178 if (result[i] != 0)
179 {
180 throw new UnitException("SI string " + siString + " has a double entry for unit " + si);
181 }
182 copy = copy.substring(si.length());
183 if (copy.length() == 0)
184 {
185 result[i] = 1;
186 break;
187 }
188 else if (copy.startsWith("-"))
189 {
190 if (copy.length() == 1)
191 {
192 throw new UnitException("SI string " + siString + " ends with a minus sign");
193 }
194 if (Character.isDigit(copy.charAt(1)))
195 {
196 result[i] = (byte) (-1 * (copy.charAt(1) - '0'));
197 copy = copy.substring(2);
198 break;
199 }
200 throw new UnitException(
201 "SI string " + siString + " has a minus sign for unit " + si + " but no dimension");
202 }
203 else if (Character.isDigit(copy.charAt(0)))
204 {
205 result[i] = (byte) (copy.charAt(0) - '0');
206 copy = copy.substring(1);
207 break;
208 }
209 else
210 {
211 result[i] = 1;
212 break;
213 }
214 }
215 }
216 if (copy.length() == copyLength)
217 {
218
219 break;
220 }
221 copyLength = copy.length();
222 }
223 if (copy.length() != 0)
224 {
225 throw new UnitException("Trailing information in SI string " + siString);
226 }
227 return result;
228 }
229
230
231
232
233
234 public String[] siAbbreviations()
235 {
236 return SI_ABBREVIATIONS.clone();
237 }
238
239
240
241
242
243
244
245 public SIDimensions plus(final SIDimensions other)
246 {
247 byte[] result = new byte[NUMBER_DIMENSIONS];
248 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
249 {
250 result[i] = (byte) (this.dimensions[i] + other.dimensions[i]);
251 }
252 return new SIDimensions(result);
253 }
254
255
256
257
258
259
260
261 public SIDimensions minus(final SIDimensions other)
262 {
263 byte[] result = new byte[NUMBER_DIMENSIONS];
264 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
265 {
266 result[i] = (byte) (this.dimensions[i] - other.dimensions[i]);
267 }
268 return new SIDimensions(result);
269 }
270
271
272
273
274
275
276 public SIDimensions invert()
277 {
278 byte[] result = new byte[NUMBER_DIMENSIONS];
279 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
280 {
281 result[i] = (byte) (-this.dimensions[i]);
282 }
283 return new SIDimensions(result);
284 }
285
286
287
288
289
290
291
292
293 public static SIDimensions add(final SIDimensions dim1, final SIDimensions dim2)
294 {
295 byte[] dim = new byte[NUMBER_DIMENSIONS];
296 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
297 {
298 dim[i] = (byte) (dim1.dimensions[i] + dim2.dimensions[i]);
299 }
300 return new SIDimensions(dim);
301 }
302
303
304
305
306
307
308
309
310 public static SIDimensions subtract(final SIDimensions dim1, final SIDimensions dim2)
311 {
312 byte[] dim = new byte[NUMBER_DIMENSIONS];
313 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
314 {
315 dim[i] = (byte) (dim1.dimensions[i] - dim2.dimensions[i]);
316 }
317 return new SIDimensions(dim);
318 }
319
320
321
322
323
324 public boolean isFractional()
325 {
326 return this.fractional;
327 }
328
329
330 @Override
331 public int hashCode()
332 {
333 final int prime = 31;
334 int result = 1;
335 result = prime * result + Arrays.hashCode(this.denominator);
336 result = prime * result + Arrays.hashCode(this.dimensions);
337 return result;
338 }
339
340
341 @Override
342 @SuppressWarnings("checkstyle:needbraces")
343 public boolean equals(final Object obj)
344 {
345 if (this == obj)
346 return true;
347 if (obj == null)
348 return false;
349 if (getClass() != obj.getClass())
350 return false;
351 SIDimensions other = (SIDimensions) obj;
352 if (!Arrays.equals(this.denominator, other.denominator))
353 return false;
354 if (!Arrays.equals(this.dimensions, other.dimensions))
355 return false;
356 return true;
357 }
358
359
360
361
362
363
364
365
366
367 public String toString(final boolean divided, final String separator, final String powerPrefix, final String powerPostfix)
368 {
369 StringBuffer s = new StringBuffer();
370 boolean first = true;
371 boolean negative = false;
372 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
373 {
374 if (this.dimensions[i] < 0)
375 {
376 negative = true;
377 }
378 if ((!divided && this.dimensions[i] != 0) || (divided && this.dimensions[i] > 0))
379 {
380 if (!first)
381 {
382 s.append(separator);
383 }
384 else
385 {
386 first = false;
387 }
388 s.append(SI_ABBREVIATIONS[i]);
389 if (this.dimensions[i] != 1)
390 {
391 s.append(powerPrefix);
392 s.append(this.dimensions[i]);
393 s.append(powerPostfix);
394 }
395 }
396 }
397 if (s.length() == 0)
398 {
399 s.append("1");
400 }
401 if (divided && negative)
402 {
403 s.append("/");
404 }
405 if (divided)
406 {
407 first = true;
408 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
409 {
410 if (this.dimensions[i] < 0)
411 {
412 if (!first)
413 {
414 s.append(separator);
415 }
416 else
417 {
418 first = false;
419 }
420 s.append(SI_ABBREVIATIONS[i]);
421 if (this.dimensions[i] < -1)
422 {
423 s.append(powerPrefix);
424 s.append(-this.dimensions[i]);
425 s.append(powerPostfix);
426 }
427 }
428 }
429 }
430 return s.toString();
431 }
432
433
434
435
436
437
438
439 public String toString(final boolean divided, final boolean separator)
440 {
441 return toString(divided, separator ? "." : "", "", "");
442 }
443
444
445
446
447
448
449
450
451 public String toString(final boolean divided, final boolean separator, final boolean power)
452 {
453 return toString(divided, separator ? "." : "", power ? "^" : "", "");
454 }
455
456
457
458
459
460
461
462 public String toHTMLString(final boolean divided, final boolean separator)
463 {
464 return toString(divided, separator ? "." : "", "<sup>", "</sup>");
465 }
466
467
468 @Override
469 public String toString()
470 {
471 if (this.fractional)
472 {
473 StringBuffer sb = new StringBuffer();
474 sb.append("[");
475 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
476 {
477 if (i > 0)
478 {
479 sb.append(", ");
480 }
481 if (this.denominator[i] != 1 && this.dimensions[i] != 0)
482 {
483 sb.append(this.dimensions[i] + "/" + this.denominator[i]);
484 }
485 else
486 {
487 sb.append(this.dimensions[i]);
488 }
489 }
490 sb.append("]");
491 return sb.toString();
492 }
493 else
494 {
495 return Arrays.toString(this.dimensions);
496 }
497 }
498
499 }