• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

unitsofmeasurement / indriya / 2315

30 Mar 2025 05:06PM UTC coverage: 71.238% (-0.01%) from 71.252%
2315

push

circleci

keilw
Fixed pattern use

5 of 5 new or added lines in 1 file covered. (100.0%)

4 existing lines in 1 file now uncovered.

3745 of 5257 relevant lines covered (71.24%)

0.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.53
/src/main/java/tech/units/indriya/format/SimpleQuantityFormat.java
1
/*
2
 * Units of Measurement Reference Implementation
3
 * Copyright (c) 2005-2025, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
4
 *
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without modification,
8
 * are permitted provided that the following conditions are met:
9
 *
10
 * 1. Redistributions of source code must retain the above copyright notice,
11
 *    this list of conditions and the following disclaimer.
12
 *
13
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
14
 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
15
 *
16
 * 3. Neither the name of JSR-385, Indriya nor the names of their contributors may be used to endorse or promote products
17
 *    derived from this software without specific prior written permission.
18
 *
19
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
 */
30
package tech.units.indriya.format;
31

32
import static tech.units.indriya.format.CommonFormatter.parseMixedAsLeading;
33

34
import java.io.IOException;
35
import java.text.NumberFormat;
36
import java.text.ParsePosition;
37
import java.util.Objects;
38

39
import javax.measure.Quantity;
40
import javax.measure.Unit;
41
import javax.measure.format.MeasurementParseException;
42

43
import tech.units.indriya.AbstractUnit;
44
import tech.units.indriya.internal.format.RationalNumberScanner;
45
import tech.units.indriya.quantity.MixedQuantity;
46
import tech.units.indriya.quantity.Quantities;
47
import tech.units.indriya.spi.Range;
48

49
/**
50
 * A simple implementation of {@link QuantityFormat}
51
 * 
52
 * <br>
53
 * The following pattern letters are defined:
54
 * <blockquote>
55
 * <table class="striped">
56
 * <caption style="display:none">Chart shows pattern letters, date/time component, presentation, and examples.</caption>
57
 * <thead>
58
 *     <tr>
59
 *         <th style="text-align:left">Letter
60
 *         <th style="text-align:left">Quantity Component
61
 *         <th style="text-align:left">Presentation
62
 *         <th style="text-align:left">Examples
63
 * </thead>
64
 * <tbody>
65
 *     <tr>
66
 *         <td><code>n</code>
67
 *         <td>Numeric value
68
 *         <td><a href="#number">Number</a>
69
 *         <td><code>27</code>
70
 *     <tr>
71
 *         <td><code>u</code>
72
 *         <td>Unit
73
 *         <td><a href="#text">Text</a>
74
 *         <td><code>m</code>
75
 *    <tr>
76
 *         <td><code>~</code>
77
 *         <td>Mixed radix
78
 *         <td><a href="#text">Text</a>
79
 *         <td><code>1 m</code>; 27 <code>cm</code>
80
<tr>
81
 *         <td><code>rc</code>
82
 *         <td>Range
83
 *         <td><a href="#text">Compact</a>
84
 *         <td><code>min=1 m, max=5 m</code>         
85
 * </tbody>
86
 * </table>
87
 * </blockquote>
88
 * Pattern letters are usually repeated, as their number determines the
89
 * exact presentation:
90
 * <ul>
91
 * <li><strong><a id="text">Text:</a></strong>
92
 *     For formatting, if the number of pattern letters is 4 or more,
93
 *     the full form is used; otherwise a short or abbreviated form
94
 *     is used if available.
95
 *     For parsing, both forms are accepted, independent of the number
96
 *     of pattern letters.<br><br></li>
97
 * <li><strong><a id="number">Number:</a></strong>
98
 *     For formatting, the number of pattern letters is the minimum
99
 *     number of digits, and shorter numbers are zero-padded to this amount.
100
 *     For parsing, the number of pattern letters is ignored unless
101
 *     it's needed to separate two adjacent fields.<br><br></li>
102
 *     
103
 *<li><strong><a id="radix">Mixed Radix:</a></strong>
104
 *     The Mixed radix marker <code>"~"</code> is followed by a character sequence acting as mixed radix delimiter. This character sequence must not contain <code>"~"</code> itself or any numeric values.<br><br></li>
105
 *     
106
 *<li><strong><a id="radix">Range:</a></strong>
107
 *     The Range compact part <code>"rc"</code> only applies to formatting instances of {@link Range} via <code>formatRange()</code>. It may be combined with the others. If set alone, then the default number and unit formatting is assumed.<br></li>    
108
 * </ul> 
109
 * @version 2.3.1, Mar 30, 2025
110
 * @since 2.0
111
 */
112
@SuppressWarnings("rawtypes")
113
public class SimpleQuantityFormat extends AbstractQuantityFormat {
114
        /**
115
         * Holds the default format instance.
116
         */
117
        private static final SimpleQuantityFormat DEFAULT = new SimpleQuantityFormat();
1✔
118

119
        private static final String NUM_PART = "n";
120
        private static final String UNIT_PART = "u";
121
        private static final String RADIX = "~";
122
        private static final String RANGE_COMPACT = "rc";
123
        
124
        private static final String DEFAULT_PATTERN = "n u";
125
        
126
        /**
127
         * The pattern string of this formatter. This is always a non-localized pattern.
128
         * May not be null. See class documentation for details.
129
         * 
130
         * @serial
131
         */
132
        private final String pattern;
133
        
134
        private String delimiter;
135
        
136
        private String mixDelimiter;
137
        
138
        private final boolean rangeCompact;
139

140
        /**
141
         *
142
         */
143
        private static final long serialVersionUID = 2758248665095734058L;
144

145
        /**
146
         * Constructs a <code>SimpleQuantityFormat</code> using the given pattern.
147
         * <p>
148
         * 
149
         * @param pattern
150
         *            the pattern describing the quantity and unit format
151
         * @exception NullPointerException
152
         *                if the given pattern is null
153
         * @exception IllegalArgumentException
154
         *                if the given pattern is empty or invalid
155
         */
156
        public SimpleQuantityFormat(String pattern) {
1✔
157
                Objects.requireNonNull(pattern);                
1✔
158
                if (pattern != null && !pattern.isEmpty()) {
1✔
159
                   if (RANGE_COMPACT.equals(pattern)) {
1✔
160
                           rangeCompact = true;
1✔
161
                           this.pattern = DEFAULT_PATTERN;
1✔
162
                   } else if (pattern.contains(RANGE_COMPACT)) {
1✔
UNCOV
163
                           this.pattern = pattern;
×
164
                           rangeCompact = true;
×
165
                   } else {
166
                           this.pattern = pattern;
1✔
167
                           rangeCompact = false;
1✔
168
                   }
169
                   if (this.pattern.contains(RADIX)) {
1✔
170
                       final String singlePattern = this.pattern.substring(0, this.pattern.indexOf(RADIX));
1✔
171
                       mixDelimiter = this.pattern.substring(this.pattern.indexOf(RADIX) + 1);
1✔
172
                       delimiter = singlePattern.substring(this.pattern.indexOf(NUM_PART)+1, this.pattern.indexOf(UNIT_PART));
1✔
173
                   } else {
1✔
174
                       delimiter = this.pattern.substring(this.pattern.indexOf(NUM_PART)+1, this.pattern.indexOf(UNIT_PART));
1✔
175
                   }
176
                } else {
UNCOV
177
                        throw new IllegalArgumentException("Pattern cannot be empty");
×
178
                }
179
        }
1✔
180

181
        /**
182
         * Constructs a <code>SimpleQuantityFormat</code> using the default pattern. For
183
         * full coverage, use the factory methods.
184
         */
185
        protected SimpleQuantityFormat() {
186
                this(DEFAULT_PATTERN);
1✔
187
        }
1✔
188

189
        @Override
190
        public Appendable format(Quantity<?> quantity, Appendable dest) throws IOException {
191
                final Unit unit = quantity.getUnit();
1✔
192
        /*
193
                if (unit instanceof MixedUnit) {
194
            if (quantity instanceof MixedQuantity) {
195
                final MixedQuantity<?> compQuant = (MixedQuantity<?>) quantity;
196
                final MixedUnit<?> compUnit = (MixedUnit<?>) unit;
197
                final Number[] values = compQuant.getValues();
198
                if (values.length == compUnit.getUnits().size()) {
199
                    final StringBuffer sb = new StringBuffer(); // we use StringBuffer here because of java.text.Format compatibility
200
                    for (int i = 0; i < values.length; i++) {
201
                       sb.append(SimpleQuantityFormat.getInstance().format(
202
                               Quantities.getQuantity(values[i], compUnit.getUnits().get(i), compQuant.getScale())));
203
                       if (i < values.length-1) {
204
                           sb.append(delimiter);
205
                       }
206
                    }
207
                    return sb;
208
                } else {
209
                    throw new IllegalArgumentException(String.format("%s values don't match %s in mixed unit", values.length, compUnit.getUnits().size()));
210
                }
211
            } else {
212
                throw new MeasurementException("The quantity is not a mixed quantity");
213
            }
214
        } else { */
215
                    dest.append(quantity.getValue().toString());
1✔
216
                    if (quantity.getUnit().equals(AbstractUnit.ONE))
1✔
UNCOV
217
                            return dest;
×
218
                    dest.append(delimiter);
1✔
219
                    return SimpleUnitFormat.getInstance().format(unit, dest);
1✔
220
        //}
221
        }
222
        
223
        /**
224
         * Formats a {@link Range}.<br>
225
         * If the special pattern part "rc" is applied, the compact format like "min=", "max=" is used, 
226
         * otherwise the full words like "minimum", "maximum", "resolution".
227
         * @param range
228
         *            the range to format.
229
         * @return the formatted range.
230
     * @since 2.3
231
         */
232
        public String formatRange(Range<?> range) {
233
                final StringBuilder sb = new StringBuilder().append(rangeCompact ? "min=" : "minimum=")
1✔
234
                                .append(range.getMinimum()).append(rangeCompact ? ", max=" : ", maximum=")
1✔
235
                                .append(range.getMaximum());
1✔
236
                if (range.getResolution() != null) {
1✔
237
                        sb.append(rangeCompact ? ", res=" : ", resolution=").append(range.getResolution());
1✔
238
                }
239
                return sb.toString();
1✔
240
        }
241
        
242
        @SuppressWarnings("unchecked")
243
        @Override
244
        public Quantity<?> parse(CharSequence csq, ParsePosition cursor) throws MeasurementParseException {
245
            
246
            final NumberFormat numberFormat = NumberFormat.getInstance();
1✔
247
            final SimpleUnitFormat simpleUnitFormat = SimpleUnitFormat.getInstance();
1✔
248
            
249
        if (mixDelimiter != null && !mixDelimiter.equals(delimiter)) {
1✔
250
            return parseMixedAsLeading(csq.toString(), numberFormat, simpleUnitFormat, delimiter, mixDelimiter, cursor.getIndex());
1✔
251
        } else if (mixDelimiter != null && mixDelimiter.equals(delimiter)) {
1✔
252
            return parseMixedAsLeading(csq.toString(), numberFormat, simpleUnitFormat, delimiter, cursor.getIndex());
1✔
253
        }
254
        
255
        final RationalNumberScanner scanner = new RationalNumberScanner(csq, cursor, null /*TODO should'nt this be numberFormat as well*/);
1✔
256
        final Number number = scanner.getNumber();
1✔
257
                
258
                Unit unit = simpleUnitFormat.parse(csq, cursor);
1✔
259
                return Quantities.getQuantity(number, unit);
1✔
260
        }
261

262
        @Override
263
        protected Quantity<?> parse(CharSequence csq, int index) throws MeasurementParseException {
UNCOV
264
                return parse(csq, new ParsePosition(index));
×
265
        }
266

267
        @Override
268
        public Quantity<?> parse(CharSequence csq) throws MeasurementParseException {
269
                return parse(csq, new ParsePosition(0));
1✔
270
        }
271

272
        /**
273
         * Returns the quantity format for the default locale. The default format
274
         * assumes the quantity is composed of a decimal number and a {@link Unit}
275
         * separated by whitespace(s).
276
         *
277
         * @return a default <code>SimpleQuantityFormat</code> instance.
278
         */
279
        public static SimpleQuantityFormat getInstance() {
280
                return DEFAULT;
1✔
281
        }
282
        
283
        /**
284
         * Returns a <code>SimpleQuantityFormat</code> using the given pattern.
285
         * <p>
286
         * 
287
         * @param pattern
288
         *            the pattern describing the quantity and unit format
289
         *
290
         * @return <code>SimpleQuantityFormat.getInstance(a pattern)</code>
291
         */
292
        public static SimpleQuantityFormat getInstance(String pattern) {
293
                return new SimpleQuantityFormat(pattern);
1✔
294
        }
295

296
        @Override
297
        public String toString() {
298
            return getClass().getSimpleName();
1✔
299
        }
300
          
301
        /**
302
         * Returns the pattern of this format.
303
         * <p>
304
         *
305
         * @return a <code>pattern</code>
306
         */        
307
        public String getPattern() {
308
                return pattern;
1✔
309
        }
310
        
311
    @Override
312
    protected StringBuffer formatMixed(MixedQuantity<?> mixed, StringBuffer dest) {
313
        final StringBuffer sb = new StringBuffer();
1✔
314
        int i = 0;
1✔
315
        for (Quantity<?> q : mixed.getQuantities()) {
1✔
316
            sb.append(format(q));
1✔
317
            if (i < mixed.getQuantities().size() - 1 ) {
1✔
318
                sb.append((mixDelimiter != null ? mixDelimiter : DEFAULT_DELIMITER)); // we need null for parsing but not
1✔
319
            }
320
            i++;
1✔
321
        }
1✔
322
        return sb;
1✔
323
    }
324
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc