1 package org.djunits.formatter;
2
3 import java.math.BigDecimal;
4 import java.math.MathContext;
5 import java.math.RoundingMode;
6 import java.text.DecimalFormat;
7 import java.text.DecimalFormatSymbols;
8 import java.util.Locale;
9
10 import org.djunits.quantity.def.Reference;
11 import org.djunits.unit.Unit;
12 import org.djunits.unit.Units;
13 import org.djunits.value.Value;
14
15
16
17
18
19
20
21
22
23
24
25
26 @SuppressWarnings({"checkstyle:needbraces", "checkstyle:visibilitymodifier"})
27 public abstract class Formatter<C extends FormatContext>
28 {
29
30 final C ctx;
31
32
33 final Value<?, ?> value;
34
35
36 Unit<?, ?> unit;
37
38
39 String unitStr = null;
40
41
42 boolean useSi = false;
43
44
45
46
47
48 Formatter(final Value<?, ?> value, final C ctx)
49 {
50 this.ctx = ctx;
51 this.value = value;
52 this.unit = value.getDisplayUnit();
53 }
54
55
56
57
58
59 abstract String format();
60
61
62
63
64
65 boolean checkSiUnits()
66 {
67 if (!this.ctx.siUnits)
68 return false;
69 this.unit = this.unit.siUnit();
70 this.useSi = true;
71 this.unitStr = this.unit.siUnit().format(this.ctx.siDivisionSymbol, this.ctx.siDotSeparator, this.ctx.siPowerPrefix,
72 this.ctx.siPowerPostfix);
73 return true;
74 }
75
76
77
78
79
80 boolean checkUnitString()
81 {
82 if (this.ctx.unitString != null)
83 {
84 try
85 {
86 this.unit = Units.resolve(this.unit.getClass(), this.ctx.unitString);
87 this.useSi = false;
88 return true;
89 }
90 catch (Exception e)
91 {
92
93 }
94 }
95 return false;
96 }
97
98
99
100
101
102 boolean checkDisplayUnit()
103 {
104 if (this.ctx.displayUnit != null)
105 {
106 try
107 {
108 this.unit = Units.resolve(this.unit.getClass(), this.ctx.displayUnit.getId());
109 this.useSi = false;
110 return true;
111 }
112 catch (Exception e)
113 {
114
115 }
116 }
117 return false;
118 }
119
120
121
122
123 @SuppressWarnings("checkstyle:needbraces")
124 void formatUnit()
125 {
126 boolean formatted = checkSiUnits();
127 if (!formatted)
128 formatted = checkUnitString();
129 if (!formatted)
130 checkDisplayUnit();
131 if (this.unitStr == null)
132 this.unitStr = this.ctx.textual ? this.unit.getTextualAbbreviation() : this.unit.getDisplayAbbreviation();
133 }
134
135
136
137
138
139
140
141 static String formatReference(final FormatContext ctx, final Reference<?, ?, ?> reference)
142 {
143 if (!ctx.printReference)
144 {
145 return "";
146 }
147 return ctx.referencePrefix + reference.getId() + ctx.referencePostfix;
148 }
149
150
151
152
153
154
155 String formatValue(final double val)
156 {
157 return switch (this.ctx.formatMode)
158 {
159 case VARIABLE_LENGTH -> formatVariableLength(val);
160 case FIXED_FLOAT -> formatFixedFloat(val);
161 case SCIENTIFIC_ALWAYS -> formatScientific(val);
162 case ENGINEERING_ALWAYS -> formatEngineering(val);
163 case FIXED_WITH_SCI_FALLBACK -> formatFixedSciFallback(val);
164 case FIXED_WITH_ENG_FALLBACK -> formatFixedEngFallback(val);
165 case FORMAT_STRING -> String.format(this.ctx.formatString, val);
166 };
167 }
168
169
170
171
172
173
174 String formatVariableLength(final double val)
175 {
176 if (val == 0.0)
177 return "0";
178 if (Double.isNaN(val))
179 return "NaN";
180 if (Double.isInfinite(val))
181 return val > 0 ? "Inf" : "-Inf";
182
183 double abs = Math.abs(val);
184 int exponent = (int) Math.floor(Math.log10(abs));
185
186
187 BigDecimal bd = BigDecimal.valueOf(val).round(new MathContext(this.ctx.maxSigDigits, RoundingMode.HALF_UP));
188
189
190 boolean useScientific = exponent >= this.ctx.maxSigDigits || exponent < this.ctx.sciThreshold;
191
192
193 DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.getDefault());
194 DecimalFormat df = new DecimalFormat();
195 df.setDecimalFormatSymbols(symbols);
196 df.setGroupingUsed(this.ctx.groupingSeparator);
197 df.setMaximumFractionDigits(340);
198 df.setMinimumFractionDigits(0);
199 df.setMinimumIntegerDigits(1);
200
201
202 BigDecimal mantissa = bd.movePointLeft(exponent).round(new MathContext(this.ctx.maxSigDigits, RoundingMode.HALF_UP))
203 .stripTrailingZeros();
204 String mantissaStr = df.format(mantissa);
205 if (!useScientific)
206 return df.format(bd);
207 String expStr = String.format("%+03d", exponent);
208 return mantissaStr + (this.ctx.upperE ? "E" : "e") + expStr;
209 }
210
211
212
213
214
215
216 String formatFixedFloat(final double val)
217 {
218 String gs = this.ctx.groupingSeparator ? "%," : "%";
219 String fmt = gs + this.ctx.width + "." + this.ctx.decimals + "f";
220 return String.format(fmt, val);
221 }
222
223
224
225
226
227
228 String formatScientific(final double val)
229 {
230 String fmt = "%" + this.ctx.width + "." + this.ctx.decimals + (this.ctx.upperE ? "E" : "e");
231 return String.format(fmt, val);
232 }
233
234
235
236
237
238
239 String formatEngineering(final double val)
240 {
241 double abs = Math.abs(val);
242 int exp = (int) Math.floor(Math.log10(abs));
243 int engExp = exp - (exp % 3);
244 double mantissa = val / Math.pow(10, engExp);
245
246
247 String mantFmt = "%." + this.ctx.decimals + "f";
248 String mant = String.format(mantFmt, mantissa);
249 String result = mant + (this.ctx.upperE ? "E" : "e") + String.format("%+03d", engExp);
250 return pad(result, this.ctx.width);
251 }
252
253
254
255
256
257
258
259 String formatFixedSciFallback(final double val)
260 {
261 String fixed = formatFixedFloat(val);
262
263
264 if (fixed.length() > this.ctx.width)
265 {
266 return formatScientific(val);
267 }
268
269
270 if (val != 0.0 && Math.abs(val) < Math.pow(10, -this.ctx.decimals))
271 {
272 return formatScientific(val);
273 }
274
275 return fixed;
276 }
277
278
279
280
281
282
283
284 String formatFixedEngFallback(final double val)
285 {
286 String fixed = formatFixedFloat(val);
287
288
289 if (fixed.length() > this.ctx.width)
290 {
291 return formatEngineering(val);
292 }
293
294
295 if (val != 0.0 && Math.abs(val) < Math.pow(10, -this.ctx.decimals))
296 {
297 return formatEngineering(val);
298 }
299
300 return fixed;
301 }
302
303
304
305
306
307
308
309 static String pad(final String s, final int width)
310 {
311 if (s.length() >= width)
312 return s;
313 return String.format("%" + width + "s", s);
314 }
315
316
317
318
319
320
321 static Locale saveLocale(final Locale newLocale)
322 {
323 if (newLocale != null)
324 {
325 Locale oldLocale = Locale.getDefault();
326 Locale.setDefault(newLocale);
327 return oldLocale;
328 }
329 return null;
330 }
331
332
333
334
335
336 static void restoreLocale(final Locale oldLocale)
337 {
338 if (oldLocale != null)
339 {
340 Locale.setDefault(oldLocale);
341 }
342 }
343
344 }