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