← joda-money  /  src/main/java/org/joda/money/format/MoneyAmountStyle.java

1
/*
2
 *  Copyright 2009-present, Stephen Colebourne
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 org.joda.money.format;
17
18
import java.io.Serializable;
19
import java.text.DecimalFormat;
20
import java.text.DecimalFormatSymbols;
21
import java.text.NumberFormat;
22
import java.util.Locale;
23
import java.util.concurrent.ConcurrentHashMap;
24
import java.util.concurrent.ConcurrentMap;
25
26
/**
27
 * Defines the style that the amount of a monetary value will be formatted with.
28
 * <p>
29
 * The style contains a number of fields that may be configured based on the locale:
30
 * <ul>
31
 * <li>character used for zero, which defined all the numbers from zero to nine
32
 * <li>character used for positive and negative symbols
33
 * <li>character used for the decimal point
34
 * <li>whether and how to group the amount
35
 * <li>character used for grouping, such as grouping thousands
36
 * <li>size for each group, such as 3 for thousands
37
 * <li>whether to always use a decimal point
38
 * </ul>
39
 * <p>
40
 * The style can be used in three basic ways.
41
 * <ul>
42
 * <li>set all the fields manually, resulting in the same amount style for all locales
43
 * <li>call {@link #localize} manually and optionally adjust to set as required
44
 * <li>leave the localized fields as {@code null} and let the locale in the
45
 *  formatter to determine the style
46
 * </ul>
47
 * <p>
48
 * This class is immutable and thread-safe.
49
 */
50
public final class MoneyAmountStyle implements Serializable {
51
52
    /**
53
     * A style that uses ASCII digits/negative sign, the decimal point
54
     * and groups large amounts in 3's using a comma.
55
     * Forced decimal point is disabled.
56
     */
57
    public static final MoneyAmountStyle ASCII_DECIMAL_POINT_GROUP3_COMMA =
58
            new MoneyAmountStyle('0', '+', '-', '.', GroupingStyle.FULL, ',', 3, 0, false, false);
59
    /**
60
     * A style that uses ASCII digits/negative sign, the decimal point
61
     * and groups large amounts in 3's using a space.
62
     * Forced decimal point is disabled.
63
     */
64
    public static final MoneyAmountStyle ASCII_DECIMAL_POINT_GROUP3_SPACE =
65
            new MoneyAmountStyle('0', '+', '-', '.', GroupingStyle.FULL, ' ', 3, 0, false, false);
66
    /**
67
     * A style that uses ASCII digits/negative sign, the decimal point
68
     * and no grouping of large amounts.
69
     * Forced decimal point is disabled.
70
     */
71
    public static final MoneyAmountStyle ASCII_DECIMAL_POINT_NO_GROUPING =
72
            new MoneyAmountStyle('0', '+', '-', '.', GroupingStyle.NONE, ',', 3, 0, false, false);
73
    /**
74
     * A style that uses ASCII digits/negative sign, the decimal comma
75
     * and groups large amounts in 3's using a dot.
76
     * Forced decimal point is disabled.
77
     */
78
    public static final MoneyAmountStyle ASCII_DECIMAL_COMMA_GROUP3_DOT =
79
            new MoneyAmountStyle('0', '+', '-', ',', GroupingStyle.FULL, '.', 3, 0, false, false);
80
    /**
81
     * A style that uses ASCII digits/negative sign, the decimal comma
82
     * and groups large amounts in 3's using a space.
83
     * Forced decimal point is disabled.
84
     */
85
    public static final MoneyAmountStyle ASCII_DECIMAL_COMMA_GROUP3_SPACE =
86
            new MoneyAmountStyle('0', '+', '-', ',', GroupingStyle.FULL, ' ', 3, 0, false, false);
87
    /**
88
     * A style that uses ASCII digits/negative sign, the decimal point
89
     * and no grouping of large amounts.
90
     * Forced decimal point is disabled.
91
     */
92
    public static final MoneyAmountStyle ASCII_DECIMAL_COMMA_NO_GROUPING =
93
            new MoneyAmountStyle('0', '+', '-', ',', GroupingStyle.NONE, '.', 3, 0, false, false);
94
    /**
95
     * A style that will be filled in with localized values using the locale of the formatter.
96
     * Grouping is enabled. Forced decimal point is disabled.
97
     */
98
    public static final MoneyAmountStyle LOCALIZED_GROUPING =
99
            new MoneyAmountStyle(-1, -1, -1, -1, GroupingStyle.FULL, -1, -1, -1, false, false);
100
    /**
101
     * A style that will be filled in with localized values using the locale of the formatter.
102
     * Grouping is disabled. Forced decimal point is disabled.
103
     */
104
    public static final MoneyAmountStyle LOCALIZED_NO_GROUPING =
105
            new MoneyAmountStyle(-1, -1, -1, -1, GroupingStyle.NONE, -1, -1, -1, false, false);
106
    /**
107
     * Cache of localized styles.
108
     */
109
    private static final ConcurrentMap<Locale, MoneyAmountStyle> LOCALIZED_CACHE = new ConcurrentHashMap<>();
110
    /**
111
     * Serialization version.
112
     */
113
    private static final long serialVersionUID = 1L;
114
115
    /**
116
     * The character defining zero, and thus the numbers zero to nine.
117
     */
118
    private final int zeroCharacter;
119
    /**
120
     * The character representing the positive sign.
121
     */
122
    private final int positiveCharacter;
123
    /**
124
     * The prefix string when the amount is negative.
125
     */
126
    private final int negativeCharacter;
127
    /**
128
     * The character used for the decimal point.
129
     */
130
    private final int decimalPointCharacter;
131
    /**
132
     * Whether to group or not.
133
     */
134
    private final GroupingStyle groupingStyle;
135
    /**
136
     * The character used for grouping.
137
     */
138
    private final int groupingCharacter;
139
    /**
140
     * The size of each group.
141
     */
142
    private final int groupingSize;
143
    /**
144
     * The size of each group.
145
     */
146
    private final int extendedGroupingSize;
147
    /**
148
     * Whether to always require the decimal point to be visible.
149
     */
150
    private final boolean forceDecimalPoint;
151
    /**
152
     * Whether to use the absolute value instead of the signed value.
153
     */
154
    private final boolean absValue;
155
156
    //-----------------------------------------------------------------------
157
    /**
158
     * Gets a localized style.
159
     * <p>
160
     * This creates a localized style for the specified locale.
161
     * Grouping will be enabled, forced decimal point will be disabled,
162
     * absolute values will be disabled.
163
     *
164
     * @param locale  the locale to use, not null
165
     * @return the new instance, never null
166
     */
167
    public static MoneyAmountStyle of(Locale locale) {
168
        return getLocalizedStyle(locale);
169
    }
170
171
    //-----------------------------------------------------------------------
172
    /**
173
     * Constructor, creating a new monetary instance.
174
     *
175
     * @param zeroCharacter  the zero character
176
     * @param positiveCharacter  the positive sign
177
     * @param negativeCharacter  the negative sign
178
     * @param decimalPointCharacter  the decimal point character
179
     * @param groupingStyle  the grouping style, not null
180
     * @param groupingCharacter  the grouping character
181
     * @param groupingSize  the grouping size
182
     * @param extendedGroupingSize  the extended grouping size
183
     * @param forceDecimalPoint  whether to always use the decimal point character
184
     * @param absValue  true to output the absolute value rather than the signed value
185
     */
186
    private MoneyAmountStyle(
187
            int zeroCharacter,
188
            int positiveCharacter,
189
            int negativeCharacter,
190
            int decimalPointCharacter,
191
            GroupingStyle groupingStyle,
192
            int groupingCharacter,
193
            int groupingSize,
194
            int extendedGroupingSize,
195
            boolean forceDecimalPoint,
196
            boolean absValue) {
197
198
        this.zeroCharacter = zeroCharacter;
199
        this.positiveCharacter = positiveCharacter;
200
        this.negativeCharacter = negativeCharacter;
201
        this.decimalPointCharacter = decimalPointCharacter;
202
        this.groupingStyle = groupingStyle;
203
        this.groupingCharacter = groupingCharacter;
204
        this.groupingSize = groupingSize;
205
        this.extendedGroupingSize = extendedGroupingSize;
206
        this.forceDecimalPoint = forceDecimalPoint;
207
        this.absValue = absValue;
208
    }
209
210
    //-----------------------------------------------------------------------
211
    /**
212
     * Returns a {@code MoneyAmountStyle} instance configured for the specified locale.
213
     * <p>
214
     * This method will return a new instance where each field that was defined
215
     * to be localized (by being set to {@code null}) is filled in.
216
     * If this instance is fully defined (with all fields non-null), then this
217
     * method has no effect. Once this method is called, no method will return null.
218
     * <p>
219
     * The settings for the locale are pulled from {@link DecimalFormatSymbols} and
220
     * {@link DecimalFormat}.
221
     *
222
     * @param locale  the locale to use, not null
223
     * @return the new instance for chaining, never null
224
     */
225
    public MoneyAmountStyle localize(Locale locale) {
226
        MoneyFormatter.checkNotNull(locale, "Locale must not be null");
227
        var result = this;
228
        var protoStyle = (MoneyAmountStyle) null;
229
        if (zeroCharacter < 0) {
230
            protoStyle = getLocalizedStyle(locale);
231
            result = result.withZeroCharacter(protoStyle.getZeroCharacter());
232
        }
233
        if (positiveCharacter < 0) {
234
            protoStyle = (protoStyle == null ? getLocalizedStyle(locale) : protoStyle);
235
            result = result.withPositiveSignCharacter(protoStyle.getPositiveSignCharacter());
236
        }
237
        if (negativeCharacter < 0) {
238
            protoStyle = (protoStyle == null ? getLocalizedStyle(locale) : protoStyle);
239
            result = result.withNegativeSignCharacter(protoStyle.getNegativeSignCharacter());
240
        }
241
        if (decimalPointCharacter < 0) {
242
            protoStyle = (protoStyle == null ? getLocalizedStyle(locale) : protoStyle);
243
            result = result.withDecimalPointCharacter(protoStyle.getDecimalPointCharacter());
244
        }
245
        if (groupingCharacter < 0) {
246
            protoStyle = (protoStyle == null ? getLocalizedStyle(locale) : protoStyle);
247
            result = result.withGroupingCharacter(protoStyle.getGroupingCharacter());
248
        }
249
        if (groupingSize < 0) {
250
            protoStyle = (protoStyle == null ? getLocalizedStyle(locale) : protoStyle);
251
            result = result.withGroupingSize(protoStyle.getGroupingSize());
252
        }
253
        if (extendedGroupingSize < 0) {
254
            protoStyle = (protoStyle == null ? getLocalizedStyle(locale) : protoStyle);
255
            result = result.withExtendedGroupingSize(protoStyle.getExtendedGroupingSize());
256
        }
257
        return result;
258
    }
259
260
    //-----------------------------------------------------------------------
261
    /**
262
     * Gets the prototype localized style for the given locale.
263
     * <p>
264
     * This uses {@link DecimalFormatSymbols} and {@link NumberFormat}.
265
     * <p>
266
     * The method {@code DecimalFormatSymbols.getInstance(locale)} will be used
267
     * in order to allow the use of locales defined as extensions.
268
     *
269
     * @param locale  the {@link Locale} used to get the correct {@link DecimalFormatSymbols}
270
     * @return the symbols, never null
271
     */
272
    private static MoneyAmountStyle getLocalizedStyle(Locale locale) {
273
        var protoStyle = LOCALIZED_CACHE.get(locale);
274
        if (protoStyle == null) {
275
            var symbols = DecimalFormatSymbols.getInstance(locale);
276
            var format = NumberFormat.getCurrencyInstance(locale);
277
            var size = (format instanceof DecimalFormat ? ((DecimalFormat) format).getGroupingSize() : 3);
278
            size = size <= 0 ? 3 : size;
279
            protoStyle = new MoneyAmountStyle(
280
                    symbols.getZeroDigit(),
281
                    '+',
282
                    symbols.getMinusSign(),
283
                    symbols.getMonetaryDecimalSeparator(),
284
                    GroupingStyle.FULL,
285
                    symbols.getGroupingSeparator(),
286
                    size,
287
                    0,
288
                    false,
289
                    false);
290
            LOCALIZED_CACHE.putIfAbsent(locale, protoStyle);
291
        }
292
        return protoStyle;
293
    }
294
295
    //-----------------------------------------------------------------------
296
    /**
297
     * Gets the character used for zero, and defining the characters zero to nine.
298
     * <p>
299
     * The UTF-8 standard supports a number of different numeric scripts.
300
     * Each script has the characters in order from zero to nine.
301
     * This method returns the zero character, which therefore also defines one to nine.
302
     *
303
     * @return the zero character, null if to be determined by locale
304
     */
305
    public Character getZeroCharacter() {
306
        return zeroCharacter < 0 ? null : (char) zeroCharacter;
307
    }
308
309
    /**
310
     * Returns a copy of this style with the specified zero character.
311
     * <p>
312
     * The UTF-8 standard supports a number of different numeric scripts.
313
     * Each script has the characters in order from zero to nine.
314
     * This method sets the zero character, which therefore also defines one to nine.
315
     * <p>
316
     * For English, this is a '0'. Some other scripts use different characters
317
     * for the numbers zero to nine.
318
     *
319
     * @param zeroCharacter  the zero character, null if to be determined by locale
320
     * @return the new instance for chaining, never null
321
     */
322
    public MoneyAmountStyle withZeroCharacter(Character zeroCharacter) {
323
        var zeroVal = (zeroCharacter == null ? -1 : zeroCharacter);
324
        if (zeroVal == this.zeroCharacter) {
325
            return this;
326
        }
327
        return new MoneyAmountStyle(
328
                zeroVal,
329
                positiveCharacter, negativeCharacter,
330
                decimalPointCharacter, groupingStyle,
331
                groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
332
    }
333
334
    //-----------------------------------------------------------------------
335
    /**
336
     * Gets the character used for the positive sign character.
337
     * <p>
338
     * The standard ASCII symbol is '+'.
339
     *
340
     * @return the format for positive amounts, null if to be determined by locale
341
     */
342
    public Character getPositiveSignCharacter() {
343
        return positiveCharacter < 0 ? null : (char) positiveCharacter;
344
    }
345
346
    /**
347
     * Returns a copy of this style with the specified positive sign character.
348
     * <p>
349
     * The standard ASCII symbol is '+'.
350
     *
351
     * @param positiveCharacter  the positive character, null if to be determined by locale
352
     * @return the new instance for chaining, never null
353
     */
354
    public MoneyAmountStyle withPositiveSignCharacter(Character positiveCharacter) {
355
        var positiveVal = (positiveCharacter == null ? -1 : positiveCharacter);
356
        if (positiveVal == this.positiveCharacter) {
357
            return this;
358
        }
359
        return new MoneyAmountStyle(
360
                zeroCharacter,
361
                positiveVal, negativeCharacter,
362
                decimalPointCharacter, groupingStyle,
363
                groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
364
    }
365
366
    //-----------------------------------------------------------------------
367
    /**
368
     * Gets the character used for the negative sign character.
369
     * <p>
370
     * The standard ASCII symbol is '-'.
371
     *
372
     * @return the format for negative amounts, null if to be determined by locale
373
     */
374
    public Character getNegativeSignCharacter() {
375
        return negativeCharacter < 0 ? null : (char) negativeCharacter;
376
    }
377
378
    /**
379
     * Returns a copy of this style with the specified negative sign character.
380
     * <p>
381
     * The standard ASCII symbol is '-'.
382
     *
383
     * @param negativeCharacter  the negative character, null if to be determined by locale
384
     * @return the new instance for chaining, never null
385
     */
386
    public MoneyAmountStyle withNegativeSignCharacter(Character negativeCharacter) {
387
        var negativeVal = (negativeCharacter == null ? -1 : negativeCharacter);
388
        if (negativeVal == this.negativeCharacter) {
389
            return this;
390
        }
391
        return new MoneyAmountStyle(
392
                zeroCharacter,
393
                positiveCharacter, negativeVal,
394
                decimalPointCharacter, groupingStyle,
395
                groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
396
    }
397
398
    //-----------------------------------------------------------------------
399
    /**
400
     * Gets the character used for the decimal point.
401
     *
402
     * @return the decimal point character, null if to be determined by locale
403
     */
404
    public Character getDecimalPointCharacter() {
405
        return decimalPointCharacter < 0 ? null : (char) decimalPointCharacter;
406
    }
407
408
    /**
409
     * Returns a copy of this style with the specified decimal point character.
410
     * <p>
411
     * For English, this is a dot.
412
     *
413
     * @param decimalPointCharacter  the decimal point character, null if to be determined by locale
414
     * @return the new instance for chaining, never null
415
     */
416
    public MoneyAmountStyle withDecimalPointCharacter(Character decimalPointCharacter) {
417
        var dpVal = (decimalPointCharacter == null ? -1 : decimalPointCharacter);
418
        if (dpVal == this.decimalPointCharacter) {
419
            return this;
420
        }
421
        return new MoneyAmountStyle(
422
                zeroCharacter,
423
                positiveCharacter, negativeCharacter,
424
                dpVal, groupingStyle,
425
                groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
426
    }
427
428
    //-----------------------------------------------------------------------
429
    /**
430
     * Gets the character used to separate groups, typically thousands.
431
     *
432
     * @return the grouping character, null if to be determined by locale
433
     */
434
    public Character getGroupingCharacter() {
435
        return groupingCharacter < 0 ? null : (char) groupingCharacter;
436
    }
437
438
    /**
439
     * Returns a copy of this style with the specified grouping character.
440
     * <p>
441
     * For English, this is a comma.
442
     *
443
     * @param groupingCharacter  the grouping character, null if to be determined by locale
444
     * @return the new instance for chaining, never null
445
     */
446
    public MoneyAmountStyle withGroupingCharacter(Character groupingCharacter) {
447
        var groupingVal = (groupingCharacter == null ? -1 : groupingCharacter);
448
        if (groupingVal == this.groupingCharacter) {
449
            return this;
450
        }
451
        return new MoneyAmountStyle(
452
                zeroCharacter,
453
                positiveCharacter, negativeCharacter,
454
                decimalPointCharacter, groupingStyle,
455
                groupingVal, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
456
    }
457
458
    //-----------------------------------------------------------------------
459
    /**
460
     * Gets the size of each group, typically 3 for thousands.
461
     *
462
     * @return the size of each group, null if to be determined by locale
463
     */
464
    public Integer getGroupingSize() {
465
        return groupingSize < 0 ? null : groupingSize;
466
    }
467
468
    /**
469
     * Returns a copy of this style with the specified grouping size.
470
     *
471
     * @param groupingSize  the size of each group, such as 3 for thousands,
472
     *          not zero or negative, null if to be determined by locale
473
     * @return the new instance for chaining, never null
474
     * @throws IllegalArgumentException if the grouping size is zero or less
475
     */
476
    public MoneyAmountStyle withGroupingSize(Integer groupingSize) {
477
        var sizeVal = (groupingSize == null ? -1 : groupingSize);
478
        if (groupingSize != null && sizeVal <= 0) {
479
            throw new IllegalArgumentException("Grouping size must be greater than zero");
480
        }
481
        if (sizeVal == this.groupingSize) {
482
            return this;
483
        }
484
        return new MoneyAmountStyle(
485
                zeroCharacter,
486
                positiveCharacter, negativeCharacter,
487
                decimalPointCharacter, groupingStyle,
488
                groupingCharacter, sizeVal, extendedGroupingSize, forceDecimalPoint, absValue);
489
    }
490
491
    //-----------------------------------------------------------------------
492
    /**
493
     * Gets the size of each group, not typically used.
494
     * <p>
495
     * This is primarily used to enable the Indian Number System, where the group
496
     * closest to the decimal point is of size 3 and other groups are of size 2.
497
     * The extended grouping size is used for groups that are not next to the decimal point.
498
     * The value zero is used to indicate that extended grouping is not needed.
499
     *
500
     * @return the size of each group, null if to be determined by locale
501
     */
502
    public Integer getExtendedGroupingSize() {
503
        return extendedGroupingSize < 0 ? null : extendedGroupingSize;
504
    }
505
506
    /**
507
     * Returns a copy of this style with the specified extended grouping size.
508
     *
509
     * @param extendedGroupingSize  the size of each group, such as 3 for thousands,
510
     *          not zero or negative, null if to be determined by locale
511
     * @return the new instance for chaining, never null
512
     * @throws IllegalArgumentException if the grouping size is zero or less
513
     */
514
    public MoneyAmountStyle withExtendedGroupingSize(Integer extendedGroupingSize) {
515
        var sizeVal = (extendedGroupingSize == null ? -1 : extendedGroupingSize);
516
        if (extendedGroupingSize != null && sizeVal < 0) {
517
            throw new IllegalArgumentException("Extended grouping size must not be negative");
518
        }
519
        if (sizeVal == this.extendedGroupingSize) {
520
            return this;
521
        }
522
        return new MoneyAmountStyle(
523
                zeroCharacter,
524
                positiveCharacter, negativeCharacter,
525
                decimalPointCharacter, groupingStyle,
526
                groupingCharacter, groupingSize, sizeVal, forceDecimalPoint, absValue);
527
    }
528
529
    //-----------------------------------------------------------------------
530
    /**
531
     * Gets the style of grouping required.
532
     *
533
     * @return the grouping style, not null
534
     */
535
    public GroupingStyle getGroupingStyle() {
536
        return groupingStyle;
537
    }
538
539
    /**
540
     * Returns a copy of this style with the specified grouping setting.
541
     *
542
     * @param groupingStyle  the grouping style, not null
543
     * @return the new instance for chaining, never null
544
     */
545
    public MoneyAmountStyle withGroupingStyle(GroupingStyle groupingStyle) {
546
        MoneyFormatter.checkNotNull(groupingStyle, "groupingStyle");
547
        if (this.groupingStyle == groupingStyle) {
548
            return this;
549
        }
550
        return new MoneyAmountStyle(
551
                zeroCharacter,
552
                positiveCharacter, negativeCharacter,
553
                decimalPointCharacter, groupingStyle,
554
                groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
555
    }
556
557
    //-----------------------------------------------------------------------
558
    /**
559
     * Gets whether to always use the decimal point, even if there is no fraction.
560
     *
561
     * @return whether to force the decimal point on output
562
     */
563
    public boolean isForcedDecimalPoint() {
564
        return forceDecimalPoint;
565
    }
566
567
    /**
568
     * Returns a copy of this style with the specified decimal point setting.
569
     *
570
     * @param forceDecimalPoint  true to force the use of the decimal point, false to use it if required
571
     * @return the new instance for chaining, never null
572
     */
573
    public MoneyAmountStyle withForcedDecimalPoint(boolean forceDecimalPoint) {
574
        if (this.forceDecimalPoint == forceDecimalPoint) {
575
            return this;
576
        }
577
        return new MoneyAmountStyle(
578
                zeroCharacter,
579
                positiveCharacter, negativeCharacter,
580
                decimalPointCharacter, groupingStyle,
581
                groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
582
    }
583
584
    //-----------------------------------------------------------------------
585
    /**
586
     * Returns true if the absolute value setting.
587
     * <p>
588
     * If this is set to true, the absolute (unsigned) value will be output.
589
     * If this is set to false, the signed value will be output.
590
     * Note that when parsing, signs are accepted.
591
     *
592
     * @return true to output the absolute value, false for the signed value
593
     */
594
    public boolean isAbsValue() {
595
        return absValue;
596
    }
597
598
    /**
599
     * Returns a copy of this style with the specified absolute value setting.
600
     * <p>
601
     * If this is set to true, the absolute (unsigned) value will be output.
602
     * If this is set to false, the signed value will be output.
603
     * Note that when parsing, signs are accepted.
604
     *
605
     * @param absValue  true to output the absolute value, false for the signed value
606
     * @return the new instance for chaining, never null
607
     */
608
    public MoneyAmountStyle withAbsValue(boolean absValue) {
609
        if (this.absValue == absValue) {
610
            return this;
611
        }
612
        return new MoneyAmountStyle(
613
                zeroCharacter,
614
                positiveCharacter, negativeCharacter,
615
                decimalPointCharacter, groupingStyle,
616
                groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
617
    }
618
619
    //-----------------------------------------------------------------------
620
    /**
621
     * Compares this style with another.
622
     *
623
     * @param other  the other style, null returns false
624
     * @return true if equal
625
     */
626
    @Override
627
    public boolean equals(Object other) {
628
        if (other == this) {
629
            return true;
630
        }
631
        if (!(other instanceof MoneyAmountStyle)) {
632
            return false;
633
        }
634
        var otherStyle = (MoneyAmountStyle) other;
635
        return (zeroCharacter == otherStyle.zeroCharacter) &&
636
                (positiveCharacter == otherStyle.positiveCharacter) &&
637
                (negativeCharacter == otherStyle.negativeCharacter) &&
638
                (decimalPointCharacter == otherStyle.decimalPointCharacter) &&
639
                (groupingStyle == otherStyle.groupingStyle) &&
640
                (groupingCharacter == otherStyle.groupingCharacter) &&
641
                (groupingSize == otherStyle.groupingSize) &&
642
                (forceDecimalPoint == otherStyle.forceDecimalPoint) &&
643
                (absValue == otherStyle.absValue);
644
    }
645
646
    /**
647
     * A suitable hash code.
648
     *
649
     * @return a hash code
650
     */
651
    @Override
652
    public int hashCode() {
653
        var hash = 13;
654
        hash += zeroCharacter * 17;
655
        hash += positiveCharacter * 17;
656
        hash += negativeCharacter * 17;
657
        hash += decimalPointCharacter * 17;
658
        hash += groupingStyle.hashCode() * 17;
659
        hash += groupingCharacter * 17;
660
        hash += groupingSize * 17;
661
        hash += (forceDecimalPoint ? 2 : 4);
662
        hash += (absValue ? 3 : 9);
663
        return hash;
664
    }
665
666
    //-----------------------------------------------------------------------
667
    /**
668
     * Gets a string summary of the style.
669
     *
670
     * @return a string summarising the style, never null
671
     */
672
    @Override
673
    public String toString() {
674
        return "MoneyAmountStyle['" + getZeroCharacter() + "','" + getPositiveSignCharacter() + "','" +
675
                getNegativeSignCharacter() + "','" + getDecimalPointCharacter() + "','" +
676
                getGroupingStyle() + "," + getGroupingCharacter() + "','" + getGroupingSize() + "'," +
677
                isForcedDecimalPoint() + "'," + isAbsValue() + "]";
678
    }
679
680
}
681