001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.util;
020
021import java.math.RoundingMode;
022import java.text.DecimalFormat;
023import java.util.Locale;
024
025import lucee.commons.lang.StringUtil;
026
027
028/**
029 * Number formation class
030 */
031public final class NumberFormat  {
032
033        private static byte LEFT = 0;
034        private static byte CENTER = 1;
035        private static byte RIGHT = 2;
036        
037        /**
038         * formats a number
039         * @param number
040         * @return formatted number as string
041         */
042        public String format(Locale locale,double number) {
043                
044                DecimalFormat df=getDecimalFormat(locale);
045                df.applyPattern(",0");
046                df.setGroupingSize(3);
047                
048                
049                return df.format(number).replace('\'',',');
050        }
051
052        /**
053         * format a number with given mask
054         * @param number
055         * @param mask
056         * @return formatted number as string
057         * @throws InvalidMaskException
058         */
059        public String format(Locale locale,double number, String mask) throws InvalidMaskException  {
060                byte justification = RIGHT;
061                
062                boolean useBrackets = false;
063                boolean usePlus = false;
064                boolean useMinus = false;
065                boolean useDollar = false;
066                boolean useComma = false;
067                boolean foundDecimal = false;
068                boolean symbolsFirst = false;
069                boolean foundZero=false;
070                
071                int maskLen = mask.length();
072                if(maskLen == 0) throw new InvalidMaskException("mask can't be a empty value");
073                
074                
075                
076                StringBuffer maskBuffer = new StringBuffer(mask);
077                
078                        String mod=StringUtil.replace(mask, ",", "", true);
079                        if(StringUtil.startsWith(mod, '_'))symbolsFirst = true;
080                        if(mask.startsWith(",."))       {
081                                maskBuffer.replace(0, 1, ",0");
082                        }
083                        //if(maskBuffer.charAt(0) == '.')maskBuffer.insert(0, '0');
084                //print.out(maskBuffer);
085                boolean addZero=false;
086                for(int i = 0; i < maskBuffer.length();) {
087                        
088                        boolean removeChar = false;
089                        switch(maskBuffer.charAt(i)) {
090                        case '_':
091                        case '9':
092                                if(foundDecimal || foundZero)   maskBuffer.setCharAt(i, '0');
093                                else                                                    maskBuffer.setCharAt(i, '#');// #
094                        break;
095
096                        case '.':
097                                if(i>0 && maskBuffer.charAt(i-1)=='#')maskBuffer.setCharAt(i-1, '0');
098                                if(foundDecimal)        removeChar = true;
099                                else                            foundDecimal = true;
100                                if(i==0)addZero=true;
101                        break;
102
103                        case '(':
104                        case ')':
105                                useBrackets = true;
106                                removeChar = true;
107                        break;
108
109                        case '+':
110                                usePlus = true;
111                                removeChar = true;
112                        break;
113
114                        case '-':
115                                useMinus = true;
116                                removeChar = true;
117                        break;
118
119                        case ',':
120                                useComma = true;
121                                if(true) {
122                                        removeChar = true;
123                                        maskLen++;
124                                }
125                        break;
126
127                        case 'L':
128                                justification = LEFT;
129                                removeChar = true;
130                        break;
131
132                        case 'C':
133                                justification = CENTER;
134                                removeChar = true;
135                        break;
136
137                        case '$':
138                                useDollar = true;
139                                removeChar = true;
140                        break;
141
142                        case '^':
143                                removeChar = true;
144                        break;
145
146                        case '0':
147                                if(!foundDecimal){
148                                        for(int y = 0; y < i;y++) {
149                                                if(maskBuffer.charAt(y)=='#')
150                                                        maskBuffer.setCharAt(y, '0');
151                                        }
152                                }
153                                foundZero=true;
154                                break;
155
156                        default:
157                            throw new InvalidMaskException("invalid charcter ["+maskBuffer.charAt(i)+"], valid characters are ['_', '9', '.', '0', '(', ')', '+', '-', ',', 'L', 'C', '$', '^']");
158                        
159                        }
160                        if(removeChar) {
161                                maskBuffer.deleteCharAt(i);
162                                maskLen--;
163                        } 
164                        else {
165                                i++;
166                        }
167                }
168
169                if(addZero)
170                        maskBuffer.insert(0, '0');
171                
172                
173                mask = new String(maskBuffer);
174                maskLen=mask.length();
175                DecimalFormat df = getDecimalFormat(locale);//(mask);
176                int gs=df.getGroupingSize();
177                df.applyPattern(mask);
178                df.setGroupingSize(gs);
179                df.setGroupingUsed(useComma);
180                df.setRoundingMode(RoundingMode.HALF_UP);
181                
182                String formattedNum = df.format(StrictMath.abs(number));
183                StringBuffer formattedNumBuffer = new StringBuffer(formattedNum);
184                if(symbolsFirst) {
185                        int widthBefore = formattedNumBuffer.length();
186                        applySymbolics(formattedNumBuffer, number, usePlus, useMinus, useDollar, useBrackets);
187                        int offset = formattedNumBuffer.length() - widthBefore;
188                        
189                        if(formattedNumBuffer.length() < maskLen + offset) {
190                                int padding = (maskLen + offset) - formattedNumBuffer.length();
191                                applyJustification(formattedNumBuffer,justification, padding);
192                        }
193                                
194                                
195                        
196                } 
197                else {
198                        int widthBefore = formattedNumBuffer.length();
199                        
200                        StringBuffer temp = new StringBuffer(formattedNumBuffer.toString());
201                        applySymbolics(temp, number, usePlus, useMinus, useDollar, useBrackets);
202                        int offset = temp.length() - widthBefore;
203                        
204                        if(temp.length() < maskLen + offset) {
205                                int padding = (maskLen + offset) - temp.length();
206                                applyJustification(formattedNumBuffer,justification, padding);
207                        }
208                        applySymbolics(formattedNumBuffer, number, usePlus, useMinus, useDollar, useBrackets);
209                }
210                /*/ TODO better impl, this is just a quick fix
211                formattedNum=formattedNumBuffer.toString();
212                 
213                int index=formattedNum.indexOf('.');
214                if(index==0) {
215                        formattedNumBuffer.insert(0, '0');
216                        formattedNum=formattedNumBuffer.toString();
217                }
218                else if(index>0){
219                        
220                }
221                        
222                String tmp=formattedNum.trim();
223                if(tmp.length()>0 && tmp.charAt(0)=='.')
224                */
225                return formattedNumBuffer.toString();
226        }
227        
228
229
230        private void applyJustification(StringBuffer _buffer, int _just, int padding) {
231                if(_just == CENTER)             centerJustify(_buffer, padding);
232                else if(_just == LEFT)  leftJustify(_buffer, padding);
233                else                                    rightJustify(_buffer, padding);
234        }
235
236        private void applySymbolics(StringBuffer _buffer, double _no, boolean _usePlus, boolean _useMinus, boolean _useDollar, boolean _useBrackets) {
237                if(_useBrackets && _no < 0.0D) {
238                        _buffer.insert(0, '(');
239                        _buffer.append(')');
240                }
241                if(_usePlus)
242                        _buffer.insert(0, _no <= 0.0D ? '-' : '+');
243                if(_no < 0.0D && !_useBrackets && !_usePlus)
244                        _buffer.insert(0, '-');
245                else
246                if(_useMinus)
247                        _buffer.insert(0, ' ');
248                if(_useDollar)
249                        _buffer.insert(0, '$');
250        }
251
252        private void centerJustify(StringBuffer _src, int _padding) {
253                int padSplit = _padding / 2 + 1;
254                rightJustify(_src, padSplit);
255                leftJustify(_src, padSplit);
256        }
257
258        private void rightJustify(StringBuffer _src, int _padding) {
259                for(int x = 0; x < _padding; x++)
260                        _src.insert(0, ' ');
261
262        }
263
264        private void leftJustify(StringBuffer _src, int _padding) {
265                for(int x = 0; x < _padding; x++)
266                        _src.append(' ');
267
268        }
269
270        private DecimalFormat getDecimalFormat(Locale locale) {
271                java.text.NumberFormat format = java.text.NumberFormat.getInstance(locale);
272                if(format instanceof DecimalFormat) {
273                        return ((DecimalFormat)format);
274                        
275                }
276                return new DecimalFormat();
277        }
278
279}