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 **/
019/**
020 * Implements the CFML Function numberformat
021 */
022package lucee.runtime.functions.displayFormatting;
023
024import java.util.Locale;
025
026import lucee.runtime.PageContext;
027import lucee.runtime.exp.ExpressionException;
028import lucee.runtime.exp.FunctionException;
029import lucee.runtime.exp.PageException;
030import lucee.runtime.ext.function.Function;
031import lucee.runtime.op.Caster;
032import lucee.runtime.op.Decision;
033import lucee.runtime.util.InvalidMaskException;
034
035/**
036 * Formats a Number by given pattern
037 */
038public final class NumberFormat implements Function {
039
040    /**
041     * @param pc
042     * @param object
043     * @return formated number
044     * @throws ExpressionException
045     */
046    public static String call(PageContext pc, Object object) throws PageException {
047        return new lucee.runtime.util.NumberFormat().format(Locale.US,toNumber(pc,object));
048    }
049    
050    /**
051     * @param pc
052     * @param object
053     * @param mask
054     * @return formated number
055     * @throws ExpressionException
056     */
057    public static String call(PageContext pc , Object object, String mask) throws PageException {
058        if(mask==null) return call(pc, object);
059        if(mask.equalsIgnoreCase("roman")) {
060             return intToRoman(pc,(int)toNumber(pc,object));
061         }
062         else if(mask.equalsIgnoreCase("hex")) {
063             return Integer.toHexString((int)toNumber(pc,object));
064         }
065        else if(mask.equalsIgnoreCase(",")) {
066            return call(pc, object);
067        }
068        
069        try {
070            return new lucee.runtime.util.NumberFormat().format(Locale.US,toNumber(pc,object),mask);
071        } 
072        catch (InvalidMaskException e) {
073            throw new FunctionException(pc,"numberFormat",2,"mask",e.getMessage());
074        }
075    }
076    
077    public static double toNumber(PageContext pc, Object object) throws PageException {
078        double d=Caster.toDoubleValue(object,true,Double.NaN);
079        if(Decision.isValid(d)) return d;
080        
081        String str=Caster.toString(object);
082        if(str.length()==0) return 0;
083        throw new FunctionException(pc,"numberFormat",1,"number","can't cast value ["+str+"] to a number");
084    }
085    
086
087    private static String intToRoman(PageContext pc, int value) throws FunctionException {
088        if(value == 0) 
089            throw new FunctionException(pc,"numberFormat",1,"number","a roman value can't be 0");
090        if(value < 0)
091            throw new FunctionException(pc,"numberFormat",1,"number","a roman value can't be less than 0");
092        if(value > 3999)
093            throw new FunctionException(pc,"numberFormat",1,"number","a roman value can't be greater than 3999");
094        
095        StringBuilder roman = new StringBuilder();
096        
097        
098        
099            while (value / 1000 >= 1) {
100                roman.append('M');
101                value = value - 1000;
102            }
103            if (value / 900 >= 1) {
104                roman.append("CM");
105                value = value - 900;
106            }
107            if (value / 500 >= 1) {
108                roman.append("D");
109                value = value - 500;
110            }
111            if (value / 400 >= 1) {
112                roman.append("CD");
113                value = value - 400;
114            }
115            while (value / 100 >= 1) {
116                roman.append("C");
117                value = value - 100;
118            }
119            if (value / 90 >= 1) {
120                roman.append("XC");
121                value = value - 90;
122            }
123            if (value / 50 >= 1) {
124                roman.append("L");
125                value = value - 50;
126            }
127            if (value / 40 >= 1) {
128                roman.append("XL");
129                value = value - 40;
130            }
131            while (value / 10 >= 1) {
132                roman.append("X");
133                value = value - 10;
134            }
135            if (value / 9 >= 1) {
136                roman.append("IX");
137                value = value - 9;
138            }
139            if (value / 5 >= 1) {
140                roman.append("V");
141                value = value - 5;
142            }
143            if (value / 4 >= 1) {
144                roman.append("IV");
145                value = value - 4;
146            }
147            while (value >= 1) {
148                roman.append("I");
149                value = value - 1;
150            }
151            return roman.toString();
152    }
153    
154}