You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2010/11/13 22:02:36 UTC

svn commit: r1034873 - in /myfaces/commons/branches/jsf_20/myfaces-commons-converters/src/main/java/org/apache/myfaces/commons/converter: AbstractTypedNumberConverter.java TypedNumberConverter.java

Author: lu4242
Date: Sat Nov 13 21:02:35 2010
New Revision: 1034873

URL: http://svn.apache.org/viewvc?rev=1034873&view=rev
Log:
TOMAHAWK-1010 Default JSF Converters evaluate value-bindings only at converter creation time, MFCOMMONS-12 Add ConverterBase class to allow create converters that evaluate EL expressions at render time

Added:
    myfaces/commons/branches/jsf_20/myfaces-commons-converters/src/main/java/org/apache/myfaces/commons/converter/AbstractTypedNumberConverter.java
Removed:
    myfaces/commons/branches/jsf_20/myfaces-commons-converters/src/main/java/org/apache/myfaces/commons/converter/TypedNumberConverter.java

Added: myfaces/commons/branches/jsf_20/myfaces-commons-converters/src/main/java/org/apache/myfaces/commons/converter/AbstractTypedNumberConverter.java
URL: http://svn.apache.org/viewvc/myfaces/commons/branches/jsf_20/myfaces-commons-converters/src/main/java/org/apache/myfaces/commons/converter/AbstractTypedNumberConverter.java?rev=1034873&view=auto
==============================================================================
--- myfaces/commons/branches/jsf_20/myfaces-commons-converters/src/main/java/org/apache/myfaces/commons/converter/AbstractTypedNumberConverter.java (added)
+++ myfaces/commons/branches/jsf_20/myfaces-commons-converters/src/main/java/org/apache/myfaces/commons/converter/AbstractTypedNumberConverter.java Sat Nov 13 21:02:35 2010
@@ -0,0 +1,553 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.commons.converter;
+
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Currency;
+import java.util.Locale;
+
+import javax.el.ValueExpression;
+import javax.faces.FacesException;
+import javax.faces.application.FacesMessage;
+import javax.faces.component.StateHolder;
+import javax.faces.component.UIComponent;
+import javax.faces.context.FacesContext;
+import javax.faces.convert.ConverterException;
+
+import org.apache.commons.beanutils.ConvertUtils;
+import org.apache.commons.beanutils.Converter;
+import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFConverter;
+import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
+import org.apache.myfaces.commons.util.MessageUtils;
+
+/**
+ * Converter which uses either the manually set <code>destType</code> or the value binding to determine the 
+ * correct destination type to convert the number to
+ * 
+ * This tag creates a number formatting converter and associates it with the nearest 
+ * parent UIComponent. It uses either the manually set destType or the value 
+ * binding to determine the correct destination type to convert the number to. 
+ * 
+ * Unless otherwise specified, all attributes accept static values or EL expressions.
+ * 
+ *   
+ * @author imario@apache.org
+ */
+@JSFConverter(
+   name = "mcc:convertNumber",
+   clazz = "org.apache.myfaces.commons.converter.TypedNumberConverter",
+   tagClass = "org.apache.myfaces.commons.converter.TypedNumberConverterTag",
+   serialuidtag = "-6592309048440572608L")
+public abstract class AbstractTypedNumberConverter extends ConverterBase
+{
+    public static final String CONVERTER_ID = "org.apache.myfaces.custom.convertNumber.TypedNumberConverter";
+
+    public AbstractTypedNumberConverter()
+    {
+    }
+    
+    public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value)
+    {
+        Object convertedValue = _getAsObject(facesContext, uiComponent, value);
+        if (convertedValue == null)
+        {
+            return null;
+        }
+
+        Class destType = getDestType(); 
+        if (destType == null)
+        {
+            ValueExpression valueBinding = uiComponent.getValueExpression("value");
+            if (valueBinding != null)
+            {
+                destType = valueBinding.getType(facesContext.getELContext());
+            }
+        }
+        
+        if (destType != null)
+        {
+            Converter converter = ConvertUtils.lookup(destType);
+            if (converter == null)
+            {
+                throw new UnsupportedOperationException("cant deal with " + destType);
+            }
+
+            // setting type to null, in fact the documentation is wrong here and this type is never used
+            convertedValue = converter.convert(null, convertedValue);
+        }
+        
+        
+        return convertedValue;
+    }
+
+    /**
+     * The java class name the value should be converted to. 
+     * 
+     * Default: automatically determined through valueBinding
+     * 
+     */
+    @JSFProperty
+    public Class getDestType()
+    {
+        return (Class) getStateHelper().get(PropertyKeys.destType);
+    }
+
+    public void setDestType(Class destType)
+    {
+        getStateHelper().put(PropertyKeys.destType, destType);
+    }
+
+    /* ORIGINAL STUFF COPIED FROM javax.faces.convert.NumberConverter */
+    
+    // internal constants
+    // private static final String CONVERSION_MESSAGE_ID = "javax.faces.convert.NumberConverter.CONVERSION";
+    public static final String STRING_ID = "javax.faces.converter.STRING";
+    public static final String CURRENCY_ID = "javax.faces.converter.NumberConverter.CURRENCY";
+    public static final String NUMBER_ID = "javax.faces.converter.NumberConverter.NUMBER";
+    public static final String PATTERN_ID = "javax.faces.converter.NumberConverter.PATTERN";
+    public static final String PERCENT_ID = "javax.faces.converter.NumberConverter.PERCENT";
+
+    private static final boolean JAVA_VERSION_14;
+
+    static
+    {
+        JAVA_VERSION_14 = checkJavaVersion14();
+    }
+
+    private boolean _transient;
+
+
+
+    // METHODS
+    public Object _getAsObject(FacesContext facesContext, UIComponent uiComponent, String value)
+    {
+        if (facesContext == null) throw new NullPointerException("facesContext");
+        if (uiComponent == null) throw new NullPointerException("uiComponent");
+
+        if (value != null)
+        {
+            value = value.trim();
+            if (value.length() > 0)
+            {
+                NumberFormat format = getNumberFormat(facesContext);
+                format.setParseIntegerOnly(isIntegerOnly());
+                
+                DecimalFormat df = (DecimalFormat)format;
+                
+                // The best we can do in this case is check if there is a ValueExpression
+                // with a BigDecimal as returning type , and if that so enable BigDecimal parsing
+                // to prevent loss in precision, and do not break existing examples (since
+                // in those cases it is expected to return Double). See MYFACES-1890 and TRINIDAD-1124
+                // for details
+                Class destType = getDestType(); 
+                if (destType == null)
+                {
+                    ValueExpression valueBinding = uiComponent.getValueExpression("value");
+                    if (valueBinding != null)
+                    {
+                        destType = valueBinding.getType(facesContext.getELContext());
+                    }
+                }
+                if (destType != null && BigDecimal.class.isAssignableFrom(destType))
+                {
+                    df.setParseBigDecimal(true);
+                }
+                
+                DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
+                boolean changed = false;
+                if(dfs.getGroupingSeparator() == '\u00a0')
+                {
+                  dfs.setGroupingSeparator(' ');
+                  df.setDecimalFormatSymbols(dfs);
+                  changed = true;
+                }
+                
+                formatCurrency(format);
+                
+                try
+                {
+                    return format.parse(value);
+                }
+                catch (ParseException e)
+                {
+                  if(changed)
+                  {
+                    dfs.setGroupingSeparator('\u00a0');
+                    df.setDecimalFormatSymbols(dfs);
+                  }
+                  try
+                  {
+                    return format.parse(value);
+                  }
+                  catch (ParseException pe)
+                  {
+
+                    if(getPattern() != null)
+                        throw new ConverterException(MessageUtils.getMessage(FacesMessage.FACES_MESSAGES, FacesMessage.SEVERITY_ERROR,
+                                                                                    PATTERN_ID,
+                                                                                    new Object[]{value,"$###,###",MessageUtils.getLabel(facesContext, uiComponent)},facesContext));
+                    else if(getType().equals("number"))
+                        throw new ConverterException(MessageUtils.getMessage(FacesMessage.FACES_MESSAGES, FacesMessage.SEVERITY_ERROR,
+                                                                                    NUMBER_ID,
+                                                                                    new Object[]{value,format.format(21),MessageUtils.getLabel(facesContext, uiComponent)},facesContext));
+                    else if(getType().equals("currency"))
+                        throw new ConverterException(MessageUtils.getMessage(FacesMessage.FACES_MESSAGES, FacesMessage.SEVERITY_ERROR,
+                                                                                    CURRENCY_ID,
+                                                                                    new Object[]{value,format.format(42.25),MessageUtils.getLabel(facesContext, uiComponent)},facesContext));
+                    else if(getType().equals("percent"))
+                        throw new ConverterException(MessageUtils.getMessage(FacesMessage.FACES_MESSAGES, FacesMessage.SEVERITY_ERROR,
+                                                                                    PERCENT_ID,
+                                                                                    new Object[]{value,format.format(.90),MessageUtils.getLabel(facesContext, uiComponent)},facesContext));
+                  }
+                }
+            }
+        }
+        return null;
+    }
+
+    public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value)
+    {
+        if (facesContext == null) throw new NullPointerException("facesContext");
+        if (uiComponent == null) throw new NullPointerException("uiComponent");
+
+        if (value == null)
+        {
+            return "";
+        }
+        if (value instanceof String)
+        {
+            return (String)value;
+        }
+
+        NumberFormat format = getNumberFormat(facesContext);
+        format.setGroupingUsed(isGroupingUsed());
+        Integer maxFractionDigits = getMaxFractionDigits();
+        Integer maxIntegerDigits = getMaxIntegerDigits();
+        Integer minFractionDigits = getMinFractionDigits();
+        Integer minIntegerDigits = getMinIntegerDigits();
+        if (maxFractionDigits != null) format.setMaximumFractionDigits(maxFractionDigits);
+        if (maxIntegerDigits != null) format.setMaximumIntegerDigits(maxIntegerDigits);
+        if (minFractionDigits != null) format.setMinimumFractionDigits(minFractionDigits);
+        if (minIntegerDigits != null) format.setMinimumIntegerDigits(minIntegerDigits);
+        formatCurrency(format);
+        try
+        {
+            return format.format(value);
+        }
+        catch (Exception e)
+        {
+            throw new ConverterException(MessageUtils.getMessage(FacesMessage.FACES_MESSAGES, FacesMessage.SEVERITY_ERROR, STRING_ID, new Object[]{value,MessageUtils.getLabel(facesContext, uiComponent)}, facesContext),e);
+        }
+    }
+
+    private NumberFormat getNumberFormat(FacesContext facesContext)
+    {
+        Locale lokale = getLocale();
+
+        if (getPattern() == null && getType() == null)
+        {
+            throw new ConverterException("Cannot get NumberFormat, either type or pattern needed.");
+        }
+
+        // pattern
+        if (getPattern() != null)
+        {
+            return new DecimalFormat(getPattern(), new DecimalFormatSymbols(lokale));
+        }
+
+        // type
+        if (getType().equals("number"))
+        {
+            return NumberFormat.getNumberInstance(lokale);
+        }
+        else if (getType().equals("currency"))
+        {
+            return NumberFormat.getCurrencyInstance(lokale);
+        }
+        else if (getType().equals("percent"))
+        {
+            return NumberFormat.getPercentInstance(lokale);
+        }
+        throw new ConverterException("Cannot get NumberFormat, illegal type " + getType());
+    }
+
+    private void formatCurrency(NumberFormat format)
+    {
+        if (getLocalCurrencyCode() == null && getLocalCurrencySymbol() == null)
+        {
+            return;
+        }
+
+        boolean useCurrencyCode;
+        if (JAVA_VERSION_14)
+        {
+            useCurrencyCode = getLocalCurrencyCode() != null;
+        }
+        else
+        {
+            useCurrencyCode = getLocalCurrencySymbol() == null;
+        }
+
+        if (useCurrencyCode)
+        {
+            // set Currency
+            try
+            {
+                format.setCurrency(Currency.getInstance(getLocalCurrencyCode()));
+            }
+            catch (Exception e)
+            {
+                throw new ConverterException("Unable to get Currency instance for currencyCode " +
+                        getLocalCurrencyCode());
+            }
+        }
+        else if (format instanceof DecimalFormat)
+
+        {
+            DecimalFormat dFormat = (DecimalFormat)format;
+            DecimalFormatSymbols symbols = dFormat.getDecimalFormatSymbols();
+            symbols.setCurrencySymbol(getLocalCurrencySymbol());
+            dFormat.setDecimalFormatSymbols(symbols);
+        }
+    }
+
+    // STATE SAVE/RESTORE
+
+    // GETTER & SETTER
+    
+    /**
+     * ISO 4217 currency code
+     * 
+     */
+    @JSFProperty
+    public String getCurrencyCode()
+    {
+        String value = (String) getStateHelper().eval(PropertyKeys.currencyCode);
+        if (value != null)
+        {
+            return value;
+        }
+        return getDecimalFormatSymbols().getInternationalCurrencySymbol();
+    }
+    
+    protected String getLocalCurrencyCode()
+    {
+        return (String) getStateHelper().eval(PropertyKeys.currencyCode);
+    }
+
+    public void setCurrencyCode(String currencyCode)
+    {
+        getStateHelper().put(PropertyKeys.currencyCode, currencyCode);
+    }
+
+    /**
+     * The currency symbol used to format a currency value. 
+     * 
+     * Defaults to the currency symbol for locale.
+     * 
+     */
+    @JSFProperty
+    public String getCurrencySymbol()
+    {
+        String value = (String) getStateHelper().eval(PropertyKeys.currencySymbol);
+        if (value != null)
+        {
+            return value;
+        }
+        return getDecimalFormatSymbols().getCurrencySymbol();
+    }
+
+    protected String getLocalCurrencySymbol()
+    {
+        return (String) getStateHelper().eval(PropertyKeys.currencySymbol);
+    }
+    
+    public void setCurrencySymbol(String currencySymbol)
+    {
+        getStateHelper().put(PropertyKeys.currencySymbol, currencySymbol);
+    }
+
+    /**
+     * Specifies whether output will contain grouping separators. 
+     * 
+     * Default: true.
+     * 
+     */
+    @JSFProperty(defaultValue="true")
+    public abstract boolean isGroupingUsed();
+
+    /**
+     * Specifies whether only the integer part of the input will be parsed. 
+     * 
+     * Default: false.
+     * 
+     */
+    @JSFProperty(defaultValue="false")
+    public abstract boolean isIntegerOnly();
+
+    /**
+     * The name of the locale to be used, instead of the default as specified 
+     * in the faces configuration file.
+     * 
+     */
+    @JSFProperty(deferredValueType="java.lang.Object")
+    public Locale getLocale()
+    {    
+        Object value = getStateHelper().eval(PropertyKeys.locale);
+        if (value instanceof String)
+        {
+           value = org.apache.myfaces.commons.util.TagUtils.getLocale(value);
+        }
+        if (value != null)
+        {
+            return (Locale) value;
+        }
+        FacesContext context = FacesContext.getCurrentInstance();
+        return context.getViewRoot().getLocale();
+    }
+
+    public void setLocale(Locale locale)
+    {
+        getStateHelper().put(PropertyKeys.locale, locale);
+    }
+
+    /**
+     * The maximum number of digits in the fractional portion of the number.
+     * 
+     */
+    @JSFProperty
+    public abstract Integer getMaxFractionDigits();
+
+    /**
+     * The maximum number of digits in the integer portion of the number.
+     * 
+     */
+    @JSFProperty
+    public abstract Integer getMaxIntegerDigits();
+
+    /**
+     * The minimum number of digits in the fractional portion of the number.
+     * 
+     */
+    @JSFProperty
+    public abstract Integer getMinFractionDigits();
+
+    /**
+     * The minimum number of digits in the integer portion of the number.
+     * 
+     */
+    @JSFProperty
+    public abstract Integer getMinIntegerDigits();
+
+    /**
+     * A custom Date formatting pattern, in the format used by java.text.SimpleDateFormat.
+     * 
+     */
+    @JSFProperty
+    public abstract String getPattern();
+
+    public boolean isTransient()
+    {
+        return _transient;
+    }
+
+    public void setTransient(boolean aTransient)
+    {
+        _transient = aTransient;
+    }
+
+    /**
+     * The type of formatting/parsing to be performed. 
+     * 
+     * Values include: number, currency, and percentage. Default: number.
+     * 
+     */
+    @JSFProperty(defaultValue="number")
+    public abstract String getType();
+
+    private static boolean checkJavaVersion14()
+    {
+        String version = System.getProperty("java.version");
+        if (version == null)
+        {
+            return false;
+        }
+        byte java14 = 0;
+        for (int idx = version.indexOf('.'), i = 0; idx > 0 || version != null; i++)
+        {
+            if (idx > 0)
+            {
+                byte value = Byte.parseByte(version.substring(0, 1));
+                version = version.substring(idx + 1, version.length());
+                idx = version.indexOf('.');
+                switch (i)
+                {
+                    case 0:
+                        if (value == 1)
+                        {
+                            java14 = 1;
+                            break;
+                        }
+                        else if (value > 1)
+                        {
+                            java14 = 2;
+                        }
+                    case 1:
+                        if (java14 > 0 && value >= 4)
+                        {
+                            java14 = 2;
+                        }
+                        ;
+                    default:
+                        idx = 0;
+                        version = null;
+                        break;
+                }
+            }
+            else
+            {
+                byte value = Byte.parseByte(version.substring(0, 1));
+                if (java14 > 0 && value >= 4)
+                {
+                    java14 = 2;
+                }
+                break;
+            }
+        }
+        return java14 == 2;
+    }
+
+
+    private DecimalFormatSymbols getDecimalFormatSymbols()
+    {
+        return new DecimalFormatSymbols(getLocale());
+    }
+    
+    enum PropertyKeys
+    {
+        destType,
+        currencyCode,
+        currencySymbol,
+        locale
+    }
+}