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}