View Javadoc
1   package pl.matsuo.core.util;
2   
3   import java.math.BigDecimal;
4   
5   import static java.math.BigDecimal.*;
6   
7   /*
8    * NumberSpeaker.java
9    *
10   * Created on 08.11.2004
11   *
12   * Copyright (c) 2004, Klaudiusz Kulik <kulikk@post.pl>
13   * All rights reserved.
14   *
15   * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
16   *
17   *   Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
18   *   Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation
19   *   and/or other materials  provided with the distribution.
20   *
21   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22   * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
23   * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
24   * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26   * DAMAGE.
27   */
28  
29  /**
30   * Zamiana liczb na postać słowną
31   * @author Klaudiusz Kulik kulikk(at)monstrum.org
32   */
33  public abstract class NumberSpeaker {
34  
35    private static final String[] teens = { "", "jeden", "dwa", "trzy", "cztery", "pięć", "sześć",
36        "siedem", "osiem", "dziewięć", "dziesięć", "jedenaście", "dwanaście", "trzynaście",
37        "czternaście", "piętnaście", "szesnaście", "siedemnaście", "osiemnaście", "dziewiętnaście" };
38    private static final String _20 = "dwadzieścia";
39    private static final String _30 = "trzydzieści";
40    private static final String _40 = "czterdzieści";
41    private static final String _1e1 = "dziesiąt";
42    private static final String _100 = "sto";
43    private static final String _200 = "dwieście";
44    private static final String _300 = "trzysta";
45    private static final String _400 = "czterysta";
46    private static final String _1e2 = "set";
47    private static final String _1000 = "tysiąc";
48  
49    private static final String[] _1e3suff = { "ące", "ęcy" };
50    private static final String[] _1e6suff = { "y", "ów" };
51  
52    private static final String _1e3 = "tysi";
53  
54    private static final String _1e6 = "milion";
55    private static final String _1e9 = "miliard";
56    private static final String _1e12 = "bilion";
57    private static final String _1e15 = "biliard";
58    private static final String _1e18 = "trylion";
59  
60    private static final String _1e21 = "tryliard";
61    private static final String _1e24 = "kwadrylion";
62  
63    private static final String[] zloteSuffixes = { "złoty", // 1
64        "złote", // 2-4
65        "złotych" // >5
66    };
67    private static final String[] groszeSuffixes = { "grosz", // 1
68        "grosze", // 2-4
69        "groszy", // >5
70        "groszy", // x1
71    };
72  
73    private static final int secSize = 3; // po 3 cyfry w sekcji
74  
75    /**
76     * Metoda zwracająca słowną reprezentację liczby
77     * 
78     * @param value
79     *          liczba
80     * @return słowna reprezentacja
81     */
82    public static String speakNumber(long value) {
83      StringBuffer buffer = new StringBuffer();
84      String stringValue = String.valueOf(value);
85  
86      int length = stringValue.length();
87      int steps = length / secSize;
88      int remain = length % secSize;
89      if (length < secSize) {
90        steps = 0;
91      }
92      if (remain != 0) {
93        steps++;
94      }
95      // Bierzemy poszczególne sekcje po 3 cyfry
96      for (int i = 1; i <= steps; i++) {
97        int begin = length - (i * secSize);
98        if (begin < 0) {
99          begin = 0;
100       }
101       int end = length - ((i - 1) * secSize);
102       String subValue = stringValue.substring(begin, end);
103       // tworzymy sekcje wraz z sufiksami
104       buffer.insert(0, decode(subValue) + " " + determineSuffix(subValue, i) + " ");
105     }
106 
107     return buffer.toString().trim();
108   }
109 
110   public static String speakNumber(int value) {
111     return speakNumber((long) value);
112   }
113 
114   public static String speakNumber(byte value) {
115     return speakNumber((long) value);
116   }
117 
118   public static String speakNumber(String value) {
119     return speakNumber(Long.parseLong(value));
120   }
121 
122   /**
123    * Metoda zamienia liczbę z przedziału 0-999 na jej słowną reprezentację
124    * 
125    * @param number
126    *          liczba do konwersji
127    * @return napis ze słowną reprezentacją Metoda jest rekurencyjna! Wywołuje przeciążoną metodę.
128    *         TODO: Uprościć
129    */
130   private static String decode(String number) {
131     String retval;
132     int num = Integer.parseInt(number);
133 
134     if (num < 20) {
135       retval = (teens[num]);
136     } else if ((num >= 20) && (num < 30)) {
137       retval = (_20 + " " + teens[num - 20]);
138     } else if ((num >= 30) && (num < 40)) {
139       retval = (_30 + " " + teens[num - 30]);
140     } else if ((num >= 40) && (num < 50)) {
141       retval = (_40 + " " + teens[num - 40]);
142     } else if ((num >= 50) && (num < 100)) {
143       int i = num / 10;
144       int j = num % 10;
145       retval = (teens[i] + _1e1 + " " + teens[j]);
146     } else if ((num >= 100) && (num < 200)) {
147       retval = (_100 + " " + decode(num % 100));
148     } else if ((num >= 200) && (num < 300)) {
149       retval = (_200 + " " + decode(num % 200));
150     } else if ((num >= 300) && (num < 400)) {
151       retval = (_300 + " " + decode(num % 300));
152     } else if ((num >= 400) && (num < 500)) {
153       retval = (_400 + " " + decode(num % 400));
154     } else if ((num >= 500) && (num < 1000)) {
155       int i = num / 100;
156       int j = num % 100;
157       retval = (teens[i] + _1e2 + " " + decode(j));
158     } else
159       retval = null;
160     return retval;
161   }
162 
163   /**
164    * Przeciążona metoda konwertująca. Dodana w celu uproszczenia rekurencji
165    */
166   private static String decode(int number) {
167     return decode(String.valueOf(number));
168   }
169 
170   /**
171    * Metoda określa przyrostki dodawane do poszczególnych trójek liczb.
172    * 
173    * @param number
174    *          liczba jako string
175    * @param position
176    *          pozycja danej trójki
177    * @return napis zawierający prawidłowy przyrostek
178    */
179   private static String determineSuffix(String number, int position) {
180     String suffix = "";
181     if (number.equals("000")) {
182       return suffix;
183     }
184     // pozycja 2 - tysiące, 3 - miliony, 4 - miliardy itd.
185     switch (position) {
186     case 1:
187       return suffix;
188     case 2: // tysiące
189       if (number.endsWith("1") && (number.length() == 1))
190         suffix = _1000;
191       if (!(number.endsWith("1") && number.length() == 1)) {
192         suffix = _1e3;
193         suffix += suffixHelper(number, _1e3suff);
194       }
195       return suffix;
196     case 3: // miliony
197       suffix = _1e6;
198       break;
199     case 4: // miliardy
200       suffix = _1e9;
201       break;
202     case 5: // biliony
203       suffix = _1e12;
204       break;
205     case 6: // biliardy
206       suffix = _1e15;
207       break;
208     case 7: // tryliony
209       suffix = _1e18;
210       break;
211     }
212     // od miliona w górę odmiana regularna
213     if (!(number.endsWith("1") && number.length() == 1))
214       suffix += suffixHelper(number, _1e6suff);
215     return suffix;
216   }
217 
218   /**
219    * Metoda upraszczająca kod dla tworzenia sufisków
220    * 
221    * @param number
222    *          liczba jako string
223    * @return napis zawierający końcówkę
224    */
225   private static String suffixHelper(String number, String[] suffixes) {
226 
227     if (number.equals("")) {
228       return "";
229     }
230     if ((number.endsWith("11")) || (number.endsWith("12")) || (number.endsWith("13"))
231         || (number.endsWith("14"))) {
232       return suffixes[1];
233     } else {
234       if ((number.endsWith("2")) || (number.endsWith("3")) || (number.endsWith("4"))) {
235         return suffixes[0];
236       } else {
237         return suffixes[1];
238       }
239     }
240   }
241 
242   private static String currencyDeclension(long number, String[] suffixes) {
243     long unities = number % 10;
244     long tens = (number-unities)/10 % 10; 
245     if (unities == 1)
246       if(suffixes.length == 4 && tens > 0)
247         return suffixes[3];
248       else
249         return suffixes[0];
250     if (unities > 1 && unities < 5)
251       return suffixes[1];
252     if (unities == 0 || unities > 5)
253       return suffixes[2];
254     return null; // nigdy nie osiagane
255   }
256 
257 
258   public static String speakCashAmount(BigDecimal value) {
259     String result = "";
260 
261     if (value.compareTo(ZERO) < 0) {
262       result += "minus ";
263       value = value.abs();
264     }
265 
266     String upperPart = "";
267     if (value.longValue() != 0)
268       upperPart = speakNumber(value.longValue()) + " "
269           + currencyDeclension(value.longValue(), zloteSuffixes);
270     long lowerPart = (long) (value.doubleValue() * 100 % 100);
271     if (lowerPart == 0)
272       result += upperPart + " zero ";
273     else
274       result += upperPart + " " + speakNumber(lowerPart) + " ";
275     result += currencyDeclension(lowerPart, groszeSuffixes);
276     return result.trim().replaceAll("  ", " ");
277   }
278 }