View Javadoc
1   /*
2    * Oceanus: Java Utilities
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.oceanus.decimal;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusLocale;
20  
21  import java.util.Currency;
22  import java.util.Locale;
23  
24  /**
25   * Parsing methods for decimals in a particular locale.
26   *
27   * @author Tony Washer
28   */
29  public class OceanusDecimalParser {
30      /**
31       * Parse Error message.
32       */
33      private static final String ERROR_PARSE = "Non Decimal Numeric Value: ";
34  
35      /**
36       * Bounds Error message.
37       */
38      private static final String ERROR_BOUNDS = "Value out of range: ";
39  
40      /**
41       * PerCent adjustment.
42       */
43      public static final int ADJUST_PERCENT = 2;
44  
45      /**
46       * PerMille adjustment.
47       */
48      public static final int ADJUST_PERMILLE = 3;
49  
50      /**
51       * The locale.
52       */
53      private OceanusDecimalLocale theLocale;
54  
55      /**
56       * Do we use strict # of decimals?
57       */
58      private boolean useStrictDecimals = true;
59  
60      /**
61       * Constructor.
62       */
63      public OceanusDecimalParser() {
64          /* Use default locale */
65          this(OceanusLocale.getDefaultLocale());
66      }
67  
68      /**
69       * Constructor.
70       *
71       * @param pLocale the locale
72       */
73      public OceanusDecimalParser(final Locale pLocale) {
74          /* Store locale */
75          setLocale(pLocale);
76      }
77  
78      /**
79       * Should we parse to strict decimals.
80       *
81       * @param bStrictDecimals true/false
82       */
83      public void setStrictDecimals(final boolean bStrictDecimals) {
84          /* Set accounting mode on and set the width */
85          useStrictDecimals = bStrictDecimals;
86      }
87  
88      /**
89       * Set the locale.
90       *
91       * @param pLocale the locale
92       */
93      public final void setLocale(final Locale pLocale) {
94          /* Store the locale */
95          theLocale = new OceanusDecimalLocale(pLocale);
96      }
97  
98      /**
99       * Obtain the default currency.
100      *
101      * @return the default currency
102      */
103     public final Currency getDefaultCurrency() {
104         return theLocale.getDefaultCurrency();
105     }
106 
107     /**
108      * Parse a string into a decimal.
109      *
110      * @param pValue  The value to parse.
111      * @param pResult the decimal to hold the result in
112      * @throws IllegalArgumentException on invalid decimal
113      */
114     protected static void parseDecimalValue(final String pValue,
115                                             final OceanusDecimal pResult) {
116         parseDecimalValue(pValue, OceanusDecimalFormatter.LOCALE_DEFAULT, false, pResult);
117     }
118 
119     /**
120      * Parse a string into a decimal.
121      *
122      * @param pValue          The value to parse.
123      * @param pLocale         the Decimal locale
124      * @param useMoneyDecimal use money decimal rather than standard decimal true/false
125      * @param pResult         the decimal to hold the result in
126      * @throws IllegalArgumentException on invalid decimal
127      */
128     protected static void parseDecimalValue(final String pValue,
129                                             final OceanusDecimalLocale pLocale,
130                                             final boolean useMoneyDecimal,
131                                             final OceanusDecimal pResult) {
132         /* Handle null value */
133         if (pValue == null) {
134             throw new IllegalArgumentException();
135         }
136 
137         /* Create a working copy */
138         final StringBuilder myWork = new StringBuilder(pValue.trim());
139 
140         /* If the value is negative, strip the leading minus sign */
141         final boolean isNegative = !myWork.isEmpty()
142                 && myWork.charAt(0) == pLocale.getMinusSign();
143         if (isNegative) {
144             myWork.deleteCharAt(0);
145         }
146 
147         /* Remove any grouping characters from the value */
148         final String myGrouping = pLocale.getGrouping();
149         int myPos;
150         while (true) {
151             myPos = myWork.indexOf(myGrouping);
152             if (myPos == -1) {
153                 break;
154             }
155             myWork.deleteCharAt(myPos);
156         }
157 
158         /* Trim leading and trailing blanks again */
159         trimBuffer(myWork);
160 
161         /* Locate the exponent if present */
162         int myExponent = 0;
163         myPos = myWork.indexOf("e");
164         if (myPos != -1) {
165             /* Obtain the exponent and remove from decimals */
166             final String myExp = myWork.substring(myPos + 1);
167             myWork.setLength(myPos);
168 
169             /* Parse the integral part */
170             try {
171                 myExponent = Integer.parseInt(myExp);
172             } catch (NumberFormatException e) {
173                 throw new IllegalArgumentException(ERROR_PARSE
174                         + pValue, e);
175             }
176         }
177 
178         /* Locate the decimal point if present */
179         myPos = myWork.indexOf(useMoneyDecimal
180                 ? pLocale.getMoneyDecimal()
181                 : pLocale.getDecimal());
182 
183         /* Assume no decimals */
184         StringBuilder myDecimals = null;
185         int myScale = 0;
186 
187         /* If we have a decimal point */
188         if (myPos != -1) {
189             /* Split into the two parts being careful of a trailing decimal point */
190             if ((myPos + 1) < myWork.length()) {
191                 myDecimals = new StringBuilder(myWork.substring(myPos + 1));
192             }
193             myWork.setLength(myPos);
194         }
195 
196         /* If we have a positive exponent */
197         if (myExponent > 0) {
198             /* Determine the number of decimals */
199             int myNumDec = myDecimals == null
200                     ? 0
201                     : myDecimals.length();
202 
203             /* Shift decimals across */
204             while (myExponent > 0 && myNumDec > 0) {
205                 /* Copy decimal across */
206                 final char myChar = myDecimals.charAt(0);
207                 myDecimals.deleteCharAt(0);
208                 myWork.append(myChar);
209 
210                 /* Adjust counters */
211                 myExponent--;
212                 myNumDec--;
213             }
214 
215             /* Finish off with zeroes */
216             while (myExponent > 0) {
217                 myWork.append(OceanusDecimalFormatter.CHAR_ZERO);
218                 myExponent--;
219             }
220 
221             /* If we now have no decimals remove decimal indication */
222             if (myNumDec == 0) {
223                 myDecimals = null;
224             }
225             /* If we have a negative exponent */
226         } else if (myExponent < 0) {
227             /* Determine the number of integer digits */
228             int myNumDigits = myWork.length();
229             final StringBuilder myCopy = new StringBuilder();
230 
231             /* Shift decimals across */
232             while (myExponent < 0 && myNumDigits > 0) {
233                 /* Copy digit across */
234                 final char myChar = myWork.charAt(myNumDigits - 1);
235                 myWork.deleteCharAt(myNumDigits - 1);
236                 myCopy.insert(0, myChar);
237 
238                 /* Adjust counters */
239                 myExponent++;
240                 myNumDigits--;
241             }
242 
243             /* Finish off with zeroes */
244             while (myExponent < 0) {
245                 myCopy.insert(0, OceanusDecimalFormatter.CHAR_ZERO);
246                 myExponent++;
247             }
248 
249             /* If we have decimals already */
250             if (myDecimals != null) {
251                 myDecimals.insert(0, myCopy);
252             } else {
253                 myDecimals = myCopy;
254             }
255         }
256 
257         /* Handle leading decimal point on value */
258         if (myWork.isEmpty()) {
259             myWork.append(OceanusDecimalFormatter.CHAR_ZERO);
260         }
261 
262         /* Parse the integral part */
263         long myValue;
264         try {
265             myValue = Long.parseLong(myWork.toString());
266         } catch (NumberFormatException e) {
267             throw new IllegalArgumentException(ERROR_PARSE
268                     + pValue, e);
269         }
270 
271         /* If we have a decimal part */
272         if (myDecimals != null) {
273             /* If we have too many decimals */
274             char myLastDigit = OceanusDecimalFormatter.CHAR_ZERO;
275             myScale = myDecimals.length();
276             if (myScale > OceanusDecimal.MAX_DECIMALS) {
277                 /* Extract most significant trailing digit and truncate the value */
278                 myLastDigit = myDecimals.charAt(OceanusDecimal.MAX_DECIMALS);
279                 myDecimals.setLength(OceanusDecimal.MAX_DECIMALS);
280                 myScale = myDecimals.length();
281             }
282 
283             /* Adjust the value to make room for the decimals */
284             myValue *= OceanusDecimal.getFactor(myScale);
285 
286             /* Parse the decimals */
287             try {
288                 myValue += Long.parseLong(myDecimals.toString());
289             } catch (NumberFormatException e) {
290                 throw new IllegalArgumentException(ERROR_PARSE
291                         + pValue, e);
292             }
293 
294             /* Round value according to most significant discarded decimal digit */
295             if (myLastDigit >= Character.forDigit(OceanusDecimal.RADIX_TEN >> 1, OceanusDecimal.RADIX_TEN)) {
296                 myValue++;
297             }
298         }
299 
300         /* If the value is negative, negate the number */
301         if (isNegative) {
302             myValue = -myValue;
303         }
304 
305         /* Store the result into the decimal */
306         pResult.setValue(myValue, myScale);
307     }
308 
309     /**
310      * Adjust to desired decimals.
311      *
312      * @param pValue    the value to adjust
313      * @param pDecimals the desired decimals
314      */
315     private void adjustDecimals(final OceanusDecimal pValue,
316                                 final int pDecimals) {
317         /* If we are using strict decimals */
318         if (useStrictDecimals) {
319             /* Correct the scale */
320             pValue.adjustToScale(pDecimals);
321 
322             /* else we should honour what we can */
323         } else {
324             /* Calculate the standard correction */
325             final int myAdjust = pDecimals
326                     - pValue.scale();
327 
328             /* If we have too few decimals */
329             if (myAdjust > 0) {
330                 /* Adjust the value appropriately */
331                 pValue.movePointLeft(myAdjust);
332 
333                 /* else if we have too many */
334             } else if (myAdjust < 0) {
335                 /* remove redundant decimal places */
336                 pValue.reduceScale(pDecimals);
337             }
338         }
339     }
340 
341     /**
342      * Parse a string to extract currency information.
343      *
344      * @param pWork           the buffer to parse
345      * @param pDeemedCurrency the assumed currency if no currency identifier
346      * @return the parsed currency
347      * @throws IllegalArgumentException on invalid currency
348      */
349     private Currency parseCurrency(final StringBuilder pWork,
350                                    final Currency pDeemedCurrency) {
351         /* Look for a currency separator */
352         final int iPos = pWork.indexOf(OceanusDecimalFormatter.STR_CURRSEP);
353         if (iPos > -1) {
354             /* Extract currency detail and determine currency */
355             final String myCurr = pWork.substring(0, iPos);
356             pWork.delete(0, iPos + 1);
357             return Currency.getInstance(myCurr);
358         }
359 
360         /* Set default currency */
361         Currency myCurrency = pDeemedCurrency;
362         final char myMinus = theLocale.getMinusSign();
363 
364         /* If we have a leading minus sign */
365         int iNumChars = pWork.length();
366         boolean isNegative = false;
367         if ((iNumChars > 0)
368                 && (pWork.charAt(0) == myMinus)) {
369             /* Delete it and note the presence */
370             pWork.deleteCharAt(0);
371             iNumChars--;
372             isNegative = true;
373         }
374 
375         /* Look for currency symbol as leading non-digits and non-whitespace */
376         int iNumSymbols = 0;
377         while (iNumSymbols < iNumChars) {
378             final char c = pWork.charAt(iNumSymbols);
379             if (Character.isDigit(c)
380                     || (c == OceanusDecimalFormatter.CHAR_MINUS)
381                     || Character.isWhitespace(c)) {
382                 break;
383             }
384             iNumSymbols++;
385         }
386 
387         /* If we have a symbol */
388         if (iNumSymbols > 0) {
389             /* Extract Symbol from buffer */
390             final String mySymbol = pWork.substring(0, iNumSymbols);
391             pWork.delete(0, iNumSymbols);
392 
393             /* Parse the currency symbol */
394             myCurrency = theLocale.parseCurrencySymbol(mySymbol);
395         }
396 
397         /* If we were negative */
398         if (isNegative) {
399             /* Reinsert the minus sign */
400             pWork.insert(0, myMinus);
401         }
402 
403         /* Return the currency */
404         return myCurrency;
405     }
406 
407     /**
408      * Parse a long value.
409      *
410      * @param pValue  The value to parse.
411      * @param pLocale the Decimal locale
412      * @return the long value
413      * @throws IllegalArgumentException on invalid decimal
414      */
415     protected static long parseLongValue(final String pValue,
416                                          final OceanusDecimalLocale pLocale) {
417         /* Handle null value */
418         if (pValue == null) {
419             throw new IllegalArgumentException();
420         }
421 
422         /* Create a working copy */
423         final StringBuilder myWork = new StringBuilder(pValue.trim());
424 
425         /* If the value is negative, strip the leading minus sign */
426         final boolean isNegative = !myWork.isEmpty()
427                 && myWork.charAt(0) == pLocale.getMinusSign();
428         if (isNegative) {
429             myWork.deleteCharAt(0);
430         }
431 
432         /* Remove any grouping characters from the value */
433         final String myGrouping = pLocale.getGrouping();
434         int myPos;
435         while (true) {
436             myPos = myWork.indexOf(myGrouping);
437             if (myPos == -1) {
438                 break;
439             }
440             myWork.deleteCharAt(myPos);
441         }
442 
443         /* Trim leading and trailing blanks again */
444         trimBuffer(myWork);
445 
446         /* Parse the long value */
447         long myValue;
448         try {
449             myValue = Long.parseLong(myWork.toString());
450         } catch (NumberFormatException e) {
451             throw new IllegalArgumentException(ERROR_PARSE
452                     + pValue, e);
453         }
454 
455         /* If the value is negative, negate the number */
456         if (isNegative) {
457             myValue = -myValue;
458         }
459 
460         /* return the result */
461         return myValue;
462     }
463 
464     /**
465      * Obtain a new zero money value for the default currency.
466      *
467      * @return the new money
468      */
469     public OceanusMoney zeroMoney() {
470         return new OceanusMoney(theLocale.getDefaultCurrency());
471     }
472 
473     /**
474      * Obtain a new zero money value for the currency.
475      *
476      * @param pCurrency the currency
477      * @return the new money
478      */
479     public OceanusMoney zeroMoney(final Currency pCurrency) {
480         return new OceanusMoney(pCurrency);
481     }
482 
483     /**
484      * Parse Money value.
485      *
486      * @param pValue the string value to parse.
487      * @return the parsed money
488      * @throws IllegalArgumentException on invalid money value
489      */
490     public OceanusMoney parseMoneyValue(final String pValue) {
491         return parseMoneyValue(pValue, null);
492     }
493 
494     /**
495      * Parse Money value.
496      *
497      * @param pValue          the string value to parse.
498      * @param pDeemedCurrency the assumed currency if no currency identifier
499      * @return the parsed money
500      * @throws IllegalArgumentException on invalid money value
501      */
502     public OceanusMoney parseMoneyValue(final String pValue,
503                                         final Currency pDeemedCurrency) {
504         /* Handle null value */
505         if (pValue == null) {
506             return null;
507         }
508 
509         /* Create a working trimmed copy */
510         final StringBuilder myWork = new StringBuilder(pValue.trim());
511 
512         /* Determine currency */
513         final Currency myCurrency = parseCurrency(myWork, pDeemedCurrency == null
514                 ? getDefaultCurrency()
515                 : pDeemedCurrency);
516         final char myMinus = theLocale.getMinusSign();
517 
518         /* If we have a leading minus sign */
519         if (!myWork.isEmpty()
520                 && myWork.charAt(0) == myMinus) {
521             /* Ensure there is no whitespace between minus sign and number */
522             myWork.deleteCharAt(0);
523             trimBuffer(myWork);
524             myWork.insert(0, myMinus);
525         }
526 
527         /* Create the new Money object */
528         final OceanusMoney myMoney = new OceanusMoney(myCurrency);
529 
530         /* Parse the remaining string */
531         parseDecimalValue(myWork.toString(), theLocale, true, myMoney);
532 
533         /* Correct the scale */
534         adjustDecimals(myMoney, myCurrency.getDefaultFractionDigits());
535 
536         /* return the parsed money object */
537         return myMoney;
538     }
539 
540     /**
541      * Obtain a new zero price value for the default currency.
542      *
543      * @return the new price
544      */
545     public OceanusPrice zeroPrice() {
546         return new OceanusPrice(theLocale.getDefaultCurrency());
547     }
548 
549     /**
550      * Obtain a new zero price value for the currency.
551      *
552      * @param pCurrency the currency
553      * @return the new price
554      */
555     public OceanusPrice zeroPrice(final Currency pCurrency) {
556         return new OceanusPrice(pCurrency);
557     }
558 
559     /**
560      * Parse Price value.
561      *
562      * @param pValue the string value to parse.
563      * @return the parsed price
564      * @throws IllegalArgumentException on invalid price value
565      */
566     public OceanusPrice parsePriceValue(final String pValue) {
567         return parsePriceValue(pValue, null);
568     }
569 
570     /**
571      * Parse Price value.
572      *
573      * @param pValue          the string value to parse.
574      * @param pDeemedCurrency the assumed currency if no currency identifier
575      * @return the parsed price
576      * @throws IllegalArgumentException on invalid price value
577      */
578     public OceanusPrice parsePriceValue(final String pValue,
579                                         final Currency pDeemedCurrency) {
580         /* Handle null value */
581         if (pValue == null) {
582             return null;
583         }
584 
585         /* Create a working trimmed copy */
586         final StringBuilder myWork = new StringBuilder(pValue.trim());
587 
588         /* Look for explicit currency */
589         final Currency myCurrency = parseCurrency(myWork, pDeemedCurrency == null
590                 ? getDefaultCurrency()
591                 : pDeemedCurrency);
592         final char myMinus = theLocale.getMinusSign();
593 
594         /* If we have a leading minus sign */
595         if (myWork.charAt(0) == myMinus) {
596             /* Ensure there is no whitespace between minus sign and number */
597             myWork.deleteCharAt(0);
598             trimBuffer(myWork);
599             myWork.insert(0, myMinus);
600         }
601 
602         /* Create the new Price object */
603         final OceanusPrice myPrice = new OceanusPrice(myCurrency);
604 
605         /* Parse the remaining string */
606         parseDecimalValue(myWork.toString(), theLocale, true, myPrice);
607 
608         /* Correct the scale */
609         adjustDecimals(myPrice, myCurrency.getDefaultFractionDigits()
610                 + OceanusPrice.XTRA_DECIMALS);
611 
612         /* return the parsed price object */
613         return myPrice;
614     }
615 
616     /**
617      * Parse Rate value.
618      *
619      * @param pValue the string value to parse.
620      * @return the parsed rate
621      * @throws IllegalArgumentException on invalid rate value
622      */
623     public OceanusRate parseRateValue(final String pValue) {
624         /* Handle null value */
625         if (pValue == null) {
626             return null;
627         }
628 
629         /* Create a working trimmed copy */
630         final StringBuilder myWork = new StringBuilder(pValue.trim());
631         int myXtraDecimals = 0;
632 
633         /* If there is a trailing perCent, remove any percent sign from the end of the string */
634         final int myLast = myWork.length() - 1;
635         if (myWork.charAt(myLast) == theLocale.getPerCent()) {
636             myWork.deleteCharAt(myLast);
637             myXtraDecimals = ADJUST_PERCENT;
638 
639             /*
640              * If there is a trailing perMille, remove any percent sign from the end of the string
641              */
642         } else if (myWork.charAt(myLast) == theLocale.getPerMille()) {
643             myWork.deleteCharAt(myLast);
644             myXtraDecimals = ADJUST_PERMILLE;
645         }
646 
647         /* Create the new Rate object */
648         final OceanusRate myRate = new OceanusRate();
649 
650         /* Parse the remaining string */
651         parseDecimalValue(myWork.toString(), theLocale, false, myRate);
652 
653         /* If we have extra Decimals to add */
654         if (myXtraDecimals > 0) {
655             /* Adjust the value appropriately */
656             myRate.recordScale(myXtraDecimals
657                     + myRate.scale());
658         }
659 
660         /* Correct the scale */
661         adjustDecimals(myRate, OceanusRate.NUM_DECIMALS);
662 
663         /* return the parsed rate object */
664         return myRate;
665     }
666 
667     /**
668      * Parse Units value.
669      *
670      * @param pValue the string value to parse.
671      * @return the parsed units
672      * @throws IllegalArgumentException on invalid units value
673      */
674     public OceanusUnits parseUnitsValue(final String pValue) {
675         /* Handle null value */
676         if (pValue == null) {
677             return null;
678         }
679 
680         /* Create the new Units object */
681         final OceanusUnits myUnits = new OceanusUnits();
682 
683         /* Parse the remaining string */
684         parseDecimalValue(pValue.trim(), theLocale, false, myUnits);
685 
686         /* Correct the scale */
687         adjustDecimals(myUnits, OceanusUnits.NUM_DECIMALS);
688 
689         /* return the parsed units object */
690         return myUnits;
691     }
692 
693     /**
694      * Parse Ratio value.
695      *
696      * @param pValue the string value to parse.
697      * @return the parsed ratio
698      * @throws IllegalArgumentException on invalid ratio value
699      */
700     public OceanusRatio parseRatioValue(final String pValue) {
701         /* Handle null value */
702         if (pValue == null) {
703             return null;
704         }
705 
706         /* Create the new Ratio object */
707         final OceanusRatio myRatio = new OceanusRatio();
708 
709         /* Parse the remaining string */
710         parseDecimalValue(pValue.trim(), theLocale, false, myRatio);
711 
712         /* Correct the scale */
713         adjustDecimals(myRatio, OceanusRatio.NUM_DECIMALS);
714 
715         /* return the parsed ratio object */
716         return myRatio;
717     }
718 
719     /**
720      * Parse Decimal value.
721      *
722      * @param pValue the string value to parse.
723      * @param pScale the scale of the resulting decimal
724      * @return the parsed decimal
725      * @throws IllegalArgumentException on invalid decimal value
726      */
727     public OceanusDecimal parseDecimalValue(final String pValue,
728                                             final int pScale) {
729         /* Handle null value */
730         if (pValue == null) {
731             return null;
732         }
733 
734         /* Create the new Decimal object */
735         final OceanusDecimal myDecimal = new OceanusDecimal();
736 
737         /* Parse the remaining string */
738         parseDecimalValue(pValue.trim(), theLocale, false, myDecimal);
739         adjustDecimals(myDecimal, pScale);
740 
741         /* return the parsed decimal object */
742         return myDecimal;
743     }
744 
745     /**
746      * Parse Long value.
747      *
748      * @param pValue the string value to parse.
749      * @return the parsed value
750      * @throws IllegalArgumentException on invalid value
751      */
752     public Long parseLongValue(final String pValue) {
753         /* Handle null value */
754         if (pValue == null) {
755             return null;
756         }
757 
758         /* Parse the value */
759         return parseLongValue(pValue, theLocale);
760     }
761 
762     /**
763      * Parse Integer value.
764      *
765      * @param pValue the string value to parse.
766      * @return the parsed value
767      * @throws IllegalArgumentException on invalid value
768      */
769     public Integer parseIntegerValue(final String pValue) {
770         /* Handle null value */
771         if (pValue == null) {
772             return null;
773         }
774 
775         /* Parse the value */
776         final long myValue = parseLongValue(pValue, theLocale);
777 
778         /* Check bounds */
779         if (myValue > Integer.MAX_VALUE || myValue < Integer.MIN_VALUE) {
780             throw new IllegalArgumentException(ERROR_BOUNDS
781                     + pValue);
782         }
783 
784         /* Return value */
785         return (int) myValue;
786     }
787 
788     /**
789      * Parse Short value.
790      *
791      * @param pValue the string value to parse.
792      * @return the parsed value
793      * @throws IllegalArgumentException on invalid value
794      */
795     public Short parseShortValue(final String pValue) {
796         /* Handle null value */
797         if (pValue == null) {
798             return null;
799         }
800 
801         /* Parse the value */
802         final long myValue = parseLongValue(pValue, theLocale);
803 
804         /* Check bounds */
805         if ((myValue > Short.MAX_VALUE) || (myValue < Short.MIN_VALUE)) {
806             throw new IllegalArgumentException(ERROR_BOUNDS
807                     + pValue);
808         }
809 
810         /* Return value */
811         return (short) myValue;
812     }
813 
814     /**
815      * Trim parsing buffer.
816      *
817      * @param pBuffer the buffer to trim
818      */
819     private static void trimBuffer(final StringBuilder pBuffer) {
820         /* Remove leading blanks */
821         while (!pBuffer.isEmpty()
822                 && Character.isWhitespace(pBuffer.charAt(0))) {
823             pBuffer.deleteCharAt(0);
824         }
825 
826         /* Remove trailing blanks */
827         int myLen = pBuffer.length();
828         while (myLen-- > 0) {
829             if (!Character.isWhitespace(pBuffer.charAt(myLen))) {
830                 break;
831             }
832             pBuffer.deleteCharAt(myLen);
833         }
834     }
835 
836     /**
837      * create Money from double.
838      *
839      * @param pValue the double value.
840      * @return the parsed money
841      * @throws IllegalArgumentException on invalid money value
842      */
843     public OceanusMoney createMoneyFromDouble(final Double pValue) {
844         /* Handle null value */
845         if (pValue == null) {
846             return null;
847         }
848 
849         /* Use default currency */
850         final Currency myCurrency = theLocale.getDefaultCurrency();
851         return createMoneyFromDouble(pValue, myCurrency.getCurrencyCode());
852     }
853 
854     /**
855      * create Money from double.
856      *
857      * @param pValue    the double value.
858      * @param pCurrCode the currency code
859      * @return the parsed money
860      * @throws IllegalArgumentException on invalid money value
861      */
862     public OceanusMoney createMoneyFromDouble(final Double pValue,
863                                               final String pCurrCode) {
864         /* Handle null value */
865         if (pValue == null) {
866             return null;
867         }
868 
869         /* Determine currency */
870         final Currency myCurrency = Currency.getInstance(pCurrCode);
871 
872         /* Create the new Money object */
873         final OceanusMoney myMoney = new OceanusMoney(myCurrency);
874 
875         /* Parse the remaining string */
876         parseDecimalValue(pValue.toString(), theLocale, true, myMoney);
877 
878         /* Correct the scale */
879         adjustDecimals(myMoney, myCurrency.getDefaultFractionDigits());
880 
881         /* return the parsed money object */
882         return myMoney;
883     }
884 
885     /**
886      * create Price from double.
887      *
888      * @param pValue the double value.
889      * @return the parsed price
890      * @throws IllegalArgumentException on invalid price value
891      */
892     public OceanusPrice createPriceFromDouble(final Double pValue) {
893         /* Handle null value */
894         if (pValue == null) {
895             return null;
896         }
897 
898         /* Use default currency */
899         final Currency myCurrency = theLocale.getDefaultCurrency();
900         return createPriceFromDouble(pValue, myCurrency.getCurrencyCode());
901     }
902 
903     /**
904      * create Price from double.
905      *
906      * @param pValue    the double value.
907      * @param pCurrCode the currency code
908      * @return the parsed price
909      * @throws IllegalArgumentException on invalid price value
910      */
911     public OceanusPrice createPriceFromDouble(final Double pValue,
912                                               final String pCurrCode) {
913         /* Handle null value */
914         if (pValue == null) {
915             return null;
916         }
917 
918         /* Determine currency */
919         final Currency myCurrency = Currency.getInstance(pCurrCode);
920 
921         /* Create the new Price object */
922         final OceanusPrice myPrice = new OceanusPrice(myCurrency);
923 
924         /* Parse the remaining string */
925         parseDecimalValue(pValue.toString(), theLocale, false, myPrice);
926 
927         /* Correct the scale */
928         adjustDecimals(myPrice, myCurrency.getDefaultFractionDigits()
929                 + OceanusPrice.XTRA_DECIMALS);
930 
931         /* return the parsed price object */
932         return myPrice;
933     }
934 
935     /**
936      * create Rate from double.
937      *
938      * @param pValue the double value.
939      * @return the parsed rate
940      * @throws IllegalArgumentException on invalid rate value
941      */
942     public OceanusRate createRateFromDouble(final Double pValue) {
943         /* Handle null value */
944         if (pValue == null) {
945             return null;
946         }
947 
948         /* Create the new Rate object */
949         final OceanusRate myRate = new OceanusRate();
950 
951         /* Parse the remaining string */
952         parseDecimalValue(pValue.toString(), theLocale, false, myRate);
953 
954         /* Correct the scale */
955         adjustDecimals(myRate, OceanusRate.NUM_DECIMALS);
956 
957         /* return the parsed rate object */
958         return myRate;
959     }
960 
961     /**
962      * create Units from double.
963      *
964      * @param pValue the double value.
965      * @return the parsed units
966      * @throws IllegalArgumentException on invalid units value
967      */
968     public OceanusUnits createUnitsFromDouble(final Double pValue) {
969         /* Handle null value */
970         if (pValue == null) {
971             return null;
972         }
973 
974         /* Create the new Units object */
975         final OceanusUnits myUnits = new OceanusUnits();
976 
977         /* Parse the remaining string */
978         parseDecimalValue(pValue.toString(), theLocale, false, myUnits);
979 
980         /* Correct the scale */
981         adjustDecimals(myUnits, OceanusUnits.NUM_DECIMALS);
982 
983         /* return the parsed units object */
984         return myUnits;
985     }
986 
987     /**
988      * create Ratio from double.
989      *
990      * @param pValue the double value.
991      * @return the parsed ratio
992      * @throws IllegalArgumentException on invalid ratio value
993      */
994     public OceanusRatio createRatioFromDouble(final Double pValue) {
995         /* Handle null value */
996         if (pValue == null) {
997             return null;
998         }
999 
1000         /* Create the new Ratio object */
1001         final OceanusRatio myRatio = new OceanusRatio();
1002 
1003         /* Parse the remaining string */
1004         parseDecimalValue(pValue.toString(), theLocale, false, myRatio);
1005 
1006         /* Correct the scale */
1007         adjustDecimals(myRatio, OceanusRatio.NUM_DECIMALS);
1008 
1009         /* return the parsed ratio object */
1010         return myRatio;
1011     }
1012 }