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 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
333 @Override
334 public int hashCode()
335 {
336 final int prime = 31;
337 int result = 1;
338 result = prime * result + Arrays.hashCode(this.denominator);
339 result = prime * result + Arrays.hashCode(this.dimensions);
340 return result;
341 }
342
343
344 @Override
345 @SuppressWarnings("checkstyle:needbraces")
346 public boolean equals(final Object obj)
347 {
348 if (this == obj)
349 return true;
350 if (obj == null)
351 return false;
352 if (getClass() != obj.getClass())
353 return false;
354 SIDimensions other = (SIDimensions) obj;
355 if (!Arrays.equals(this.denominator, other.denominator))
356 return false;
357 if (!Arrays.equals(this.dimensions, other.dimensions))
358 return false;
359 return true;
360 }
361
362
363
364
365
366
367
368
369
370 public String toString(final boolean divided, final String separator, final String powerPrefix, final String powerPostfix)
371 {
372 StringBuffer s = new StringBuffer();
373 boolean first = true;
374 boolean negative = false;
375 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
376 {
377 if (this.dimensions[i] < 0)
378 {
379 negative = true;
380 }
381 if ((!divided && this.dimensions[i] != 0) || (divided && this.dimensions[i] > 0))
382 {
383 if (!first)
384 {
385 s.append(separator);
386 }
387 else
388 {
389 first = false;
390 }
391 s.append(SI_ABBREVIATIONS[i]);
392 if (this.dimensions[i] != 1)
393 {
394 s.append(powerPrefix);
395 s.append(this.dimensions[i]);
396 s.append(powerPostfix);
397 }
398 }
399 }
400 if (s.length() == 0)
401 {
402 s.append("1");
403 }
404 if (divided && negative)
405 {
406 s.append("/");
407 }
408 if (divided)
409 {
410 first = true;
411 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
412 {
413 if (this.dimensions[i] < 0)
414 {
415 if (!first)
416 {
417 s.append(separator);
418 }
419 else
420 {
421 first = false;
422 }
423 s.append(SI_ABBREVIATIONS[i]);
424 if (this.dimensions[i] < -1)
425 {
426 s.append(powerPrefix);
427 s.append(-this.dimensions[i]);
428 s.append(powerPostfix);
429 }
430 }
431 }
432 }
433 if (s.toString().equals("1"))
434 {
435 return "";
436 }
437 return s.toString();
438 }
439
440
441
442
443
444
445
446 public String toString(final boolean divided, final boolean separator)
447 {
448 return toString(divided, separator ? "." : "", "", "");
449 }
450
451
452
453
454
455
456
457
458 public String toString(final boolean divided, final boolean separator, final boolean power)
459 {
460 return toString(divided, separator ? "." : "", power ? "^" : "", "");
461 }
462
463
464
465
466
467
468
469 public String toHTMLString(final boolean divided, final boolean separator)
470 {
471 return toString(divided, separator ? "." : "", "<sup>", "</sup>");
472 }
473
474
475 @Override
476 public String toString()
477 {
478 if (this.fractional)
479 {
480 StringBuffer sb = new StringBuffer();
481 sb.append("[");
482 for (int i = 0; i < NUMBER_DIMENSIONS; i++)
483 {
484 if (i > 0)
485 {
486 sb.append(", ");
487 }
488 if (this.denominator[i] != 1 && this.dimensions[i] != 0)
489 {
490 sb.append(this.dimensions[i] + "/" + this.denominator[i]);
491 }
492 else
493 {
494 sb.append(this.dimensions[i]);
495 }
496 }
497 sb.append("]");
498 return sb.toString();
499 }
500 else
501 {
502 return Arrays.toString(this.dimensions);
503 }
504 }
505
506 }