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