View Javadoc
1   /*
2    * Copyright (C) 2020 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.units;
17  
18  import java.text.ParseException;
19  import java.util.Locale;
20  
21  /**
22   * Formats and parses volume value and unit.
23   */
24  public class VolumeFormatter extends MeasureFormatter<Volume, VolumeUnit> {
25  
26      /**
27       * Cubic centimeter symbol.
28       */
29      public static final String CUBIC_CENTIMETER = "cm³";
30  
31      /**
32       * Milliliter symbol.
33       */
34      public static final String MILLILITER = "mL";
35  
36      /**
37       * Cubic decimeter symbol.
38       */
39      public static final String CUBIC_DECIMETER = "dm³";
40  
41      /**
42       * Liter symbol.
43       */
44      public static final String LITER = "L";
45  
46      /**
47       * Hectoliter symbol.
48       */
49      public static final String HECTOLITER = "hL";
50  
51      /**
52       * Cubic meter symbol.
53       */
54      public static final String CUBIC_METER = "m³";
55  
56      /**
57       * Cubic inch symbol.
58       */
59      public static final String CUBIC_INCH = "in³";
60  
61      /**
62       * Pint symbol.
63       */
64      public static final String PINT = "pt";
65  
66      /**
67       * Gallon symbol.
68       */
69      public static final String GALLON = "gal";
70  
71      /**
72       * Cubic foot symbol.
73       */
74      public static final String CUBIC_FOOT = "ft³";
75  
76      /**
77       * Barrel symbol.
78       */
79      public static final String BARREL = "bbl";
80  
81      /**
82       * Constructor.
83       */
84      public VolumeFormatter() {
85          super();
86      }
87  
88      /**
89       * Constructor with locale.
90       *
91       * @param locale locale.
92       * @throws IllegalArgumentException if locale is null.
93       */
94      public VolumeFormatter(final Locale locale) {
95          super(locale);
96      }
97  
98      /**
99       * Copy constructor.
100      *
101      * @param formatter input instance to copy from.
102      * @throws NullPointerException if provided formatter is null.
103      */
104     public VolumeFormatter(final VolumeFormatter formatter) {
105         this(formatter.getLocale());
106     }
107 
108     /**
109      * Determines if two volume formatters are equal by comparing all of their internal
110      * parameters.
111      *
112      * @param obj another object to compare.
113      * @return true if provided object is assumed to be equal to this instance.
114      */
115     @Override
116     public boolean equals(final Object obj) {
117         final var equals = super.equals(obj);
118         return (obj instanceof VolumeFormatter) && equals;
119     }
120 
121     /**
122      * Hash code generated for this instance.
123      * Hash codes can be internally used by some collections to coarsely compare objects.
124      * This implementation only calls parent implementation to avoid static analyzer warning.
125      *
126      * @return hash code.
127      */
128     @Override
129     public int hashCode() {
130         return super.hashCode();
131     }
132 
133     /**
134      * Gets unit system for detected unit into provided string representation
135      * of a measurement.
136      *
137      * @param source a measurement string representation to be checked.
138      * @return a unit system (either metric or imperial) or null if unit cannot be determined.
139      */
140     @Override
141     public UnitSystem getUnitSystem(final String source) {
142         final var unit = findUnit(source);
143         return unit != null ? VolumeUnit.getUnitSystem(unit) : null;
144     }
145 
146     /**
147      * Parses provided string and tries to determine a volume value and unit.
148      *
149      * @param source a string to be parsed.
150      * @return a volume containing a value and unit.
151      * @throws ParseException       if provided string cannot be parsed.
152      * @throws UnknownUnitException if unit cannot be determined.
153      */
154     @Override
155     public Volume parse(final String source) throws ParseException, UnknownUnitException {
156         return internalParse(source, new Volume());
157     }
158 
159     /**
160      * Attempts to determine a volume unit within a measurement string representation.
161      *
162      * @param source a volume measurement string representation.
163      * @return a volume unit, or null if none can be determined.
164      */
165     @Override
166     public VolumeUnit findUnit(final String source) {
167         if (source.contains(CUBIC_CENTIMETER + " ") || source.endsWith(CUBIC_CENTIMETER)) {
168             return VolumeUnit.CUBIC_CENTIMETER;
169         }
170         if (source.contains(MILLILITER + " ") || source.endsWith(MILLILITER)) {
171             return VolumeUnit.MILLILITER;
172         }
173         if (source.contains(CUBIC_DECIMETER + " ") || source.endsWith(CUBIC_DECIMETER)) {
174             return VolumeUnit.CUBIC_DECIMETER;
175         }
176         if (source.contains(HECTOLITER + " ") || source.endsWith(HECTOLITER)) {
177             return VolumeUnit.HECTOLITER;
178         }
179         if (source.contains(LITER + " ") || source.endsWith(LITER)) {
180             return VolumeUnit.LITER;
181         }
182         if (source.contains(CUBIC_METER + " ") || source.endsWith(CUBIC_METER)) {
183             return VolumeUnit.CUBIC_METER;
184         }
185         if (source.contains(CUBIC_INCH + " ") || source.endsWith(CUBIC_INCH)) {
186             return VolumeUnit.CUBIC_INCH;
187         }
188         if (source.contains(PINT + " ") || source.endsWith(PINT)) {
189             return VolumeUnit.PINT;
190         }
191         if (source.contains(GALLON + " ") || source.endsWith(GALLON)) {
192             return VolumeUnit.GALLON;
193         }
194         if (source.contains(CUBIC_FOOT + " ") || source.endsWith(CUBIC_FOOT)) {
195             return VolumeUnit.CUBIC_FOOT;
196         }
197         if (source.contains(BARREL + " ") || source.endsWith(BARREL)) {
198             return VolumeUnit.BARREL;
199         }
200 
201         return null;
202     }
203 
204     /**
205      * Formats and converts provided volume value and unit using provided unit system.
206      * If provided value is too large for provided unit, this method will convert it
207      * to a more appropriate unit using provided unit system (either metric or imperial).
208      *
209      * @param value  a volume value.
210      * @param unit   a volume unit.
211      * @param system system unit to convert measurement to.
212      * @return a string representation of volume value and unit.
213      */
214     @Override
215     public String formatAndConvert(final Number value, final VolumeUnit unit, final UnitSystem system) {
216         if (system == UnitSystem.IMPERIAL) {
217             return formatAndConvertImperial(value, unit);
218         } else {
219             return formatAndConvertMetric(value, unit);
220         }
221     }
222 
223     /**
224      * Formats and converts provided volume value and unit using metric unit system.
225      * If provided volume value is too large for provided volume unit,
226      * this method will convert to a more appropriate unit.
227      *
228      * @param value a volume value.
229      * @param unit a volume unit.
230      * @return a string representation of volume value and unit using metric unit
231      * system.
232      */
233     public String formatAndConvertMetric(final Number value, final VolumeUnit unit) {
234         final var v = value.doubleValue();
235 
236         final var cubicCentimeter = VolumeConverter.convert(v, unit, VolumeUnit.CUBIC_CENTIMETER);
237         if (Math.abs(cubicCentimeter) < (VolumeConverter.CUBIC_METER_PER_LITER
238                 / VolumeConverter.CUBIC_METER_PER_CUBIC_CENTIMETER)) {
239             return format(cubicCentimeter, VolumeUnit.CUBIC_CENTIMETER);
240         }
241 
242         final var liter = VolumeConverter.convert(v, unit, VolumeUnit.LITER);
243         if (Math.abs(liter) < (VolumeConverter.CUBIC_METER_PER_HECTOLITER / VolumeConverter.CUBIC_METER_PER_LITER)) {
244             return format(liter, VolumeUnit.LITER);
245         }
246 
247         final var hectoliter = VolumeConverter.convert(v, unit, VolumeUnit.HECTOLITER);
248         if (Math.abs(hectoliter) < (1.0 / VolumeConverter.CUBIC_METER_PER_HECTOLITER)) {
249             return format(hectoliter, VolumeUnit.HECTOLITER);
250         }
251 
252         final var cubicMeter = VolumeConverter.convert(v, unit, VolumeUnit.CUBIC_METER);
253         return format(cubicMeter, VolumeUnit.CUBIC_METER);
254     }
255 
256     /**
257      * Formats and converts provided volume value and unit using imperial unit system.
258      * If provided volume value is too large for provided volume unit,
259      * this method will convert to a more appropriate unit.
260      *
261      * @param value a volume value.
262      * @param unit a volume unit.
263      * @return a string representation of volume value and unit using imperial unit
264      * system.
265      */
266     public String formatAndConvertImperial(final Number value, final VolumeUnit unit) {
267         final var v = value.doubleValue();
268 
269         final var cubicInch = VolumeConverter.convert(v, unit, VolumeUnit.CUBIC_INCH);
270         if (Math.abs(cubicInch) < (VolumeConverter.CUBIC_METER_PER_PINT / VolumeConverter.CUBIC_METER_PER_CUBIC_INCH)) {
271             return format(cubicInch, VolumeUnit.CUBIC_INCH);
272         }
273 
274         final var pint = VolumeConverter.convert(v, unit, VolumeUnit.PINT);
275         if (Math.abs(pint) < (VolumeConverter.CUBIC_METER_PER_GALLON / VolumeConverter.CUBIC_METER_PER_PINT)) {
276             return format(pint, VolumeUnit.PINT);
277         }
278 
279         final var gallon = VolumeConverter.convert(v, unit, VolumeUnit.GALLON);
280         if (Math.abs(gallon) < (VolumeConverter.CUBIC_METER_PER_CUBIC_FOOT / VolumeConverter.CUBIC_METER_PER_GALLON)) {
281             return format(gallon, VolumeUnit.GALLON);
282         }
283 
284         final var cubicFoot = VolumeConverter.convert(v, unit, VolumeUnit.CUBIC_FOOT);
285         if (Math.abs(cubicFoot) < (VolumeConverter.CUBIC_METER_PER_BARREL
286                 / VolumeConverter.CUBIC_METER_PER_CUBIC_FOOT)) {
287             return format(cubicFoot, VolumeUnit.CUBIC_FOOT);
288         }
289 
290         final var barrel = VolumeConverter.convert(v, unit, VolumeUnit.BARREL);
291         return format(barrel, VolumeUnit.BARREL);
292     }
293 
294     /**
295      * Returns unit string representation.
296      *
297      * @param unit a volume unit.
298      * @return its string representation.
299      */
300     @SuppressWarnings("DuplicatedCode")
301     @Override
302     public String getUnitSymbol(final VolumeUnit unit) {
303         return switch (unit) {
304             case CUBIC_CENTIMETER -> CUBIC_CENTIMETER;
305             case MILLILITER -> MILLILITER;
306             case CUBIC_DECIMETER -> CUBIC_DECIMETER;
307             case LITER -> LITER;
308             case HECTOLITER -> HECTOLITER;
309             case CUBIC_INCH -> CUBIC_INCH;
310             case PINT -> PINT;
311             case GALLON -> GALLON;
312             case CUBIC_FOOT -> CUBIC_FOOT;
313             case BARREL -> BARREL;
314             default -> CUBIC_METER;
315         };
316     }
317 }