/*
* 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.faces.el.ValueBinding;
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",
tagHandler = "org.apache.myfaces.commons.converter.TypedNumberConverterTagHandler")
public abstract class AbstractTypedNumberConverter extends ConverterBase
{
public static final String CONVERTER_ID = "org.apache.myfaces.custom.convertNumber.TypedNumberConverter";
private Class destType;
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)
{
ValueBinding valueBinding = uiComponent.getValueBinding("value");
if (valueBinding != null)
{
destType = valueBinding.getType(facesContext);
}
}
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;
}
public void restoreState(FacesContext facesContext, Object state)
{
Object[] states = (Object[]) state;
_restoreState(facesContext, states[0]);
destType = (Class) states[1];
}
public Object saveState(FacesContext facesContext)
{
return new Object[]
{
_saveState(facesContext),
destType
};
}
/**
* The java class name the value should be converted to.
*
* Default: automatically determined through valueBinding
*
*/
@JSFProperty
public Class getDestType()
{
return destType;
}
public void setDestType(Class destType)
{
this.destType = destType;
}
/* ORIGINAL STUFF COPIED FROM javax.faces.convert.NumberConverter */
// internal constants
private static final String CONVERSION_MESSAGE_ID = "javax.faces.convert.NumberConverter.CONVERSION";
private static final boolean JAVA_VERSION_14;
static
{
JAVA_VERSION_14 = checkJavaVersion14();
}
private String _currencyCode;
private String _currencySymbol;
private Locale _locale;
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)
{
ValueBinding valueBinding = uiComponent.getValueBinding("value");
if (valueBinding != null)
{
destType = valueBinding.getType(facesContext);
}
}
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)
{
FacesMessage message = MessageUtils.getMessage(FacesMessage.FACES_MESSAGES, facesContext, CONVERSION_MESSAGE_ID, new Object[]{uiComponent.getId(),value});
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ConverterException(message, e);
}
}
}
}
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("Cannot convert value '" + value + "'");
}
}
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
public void _restoreState(FacesContext facesContext, Object state)
{
Object values[] = (Object[])state;
super.restoreState(facesContext, values[0]);
_currencyCode = (String)values[1];
_currencySymbol = (String)values[2];
_locale = (Locale)values[3];
}
public Object _saveState(FacesContext facesContext)
{
Object values[] = new Object[4];
values[0] = super.saveState(facesContext);
values[1] = _currencyCode;
values[2] = _currencySymbol;
values[3] = _locale;
return values;
}
// GETTER & SETTER
/**
* ISO 4217 currency code
*
*/
@JSFProperty
public String getCurrencyCode()
{
if (_currencyCode != null)
{
return _currencyCode;
}
ValueBinding vb = getValueBinding("currencyCode");
if (vb != null)
{
return (String) vb.getValue(getFacesContext());
}
return getDecimalFormatSymbols().getInternationalCurrencySymbol();
}
protected String getLocalCurrencyCode()
{
if (_currencyCode != null)
{
return _currencyCode;
}
ValueBinding vb = getValueBinding("currencyCode");
if (vb != null)
{
return (String) vb.getValue(getFacesContext());
}
return null;
}
public void setCurrencyCode(String currencyCode)
{
this._currencyCode = currencyCode;
}
/**
* The currency symbol used to format a currency value.
*
* Defaults to the currency symbol for locale.
*
*/
@JSFProperty
public String getCurrencySymbol()
{
if (_currencySymbol != null)
{
return _currencySymbol;
}
ValueBinding vb = getValueBinding("currencySymbol");
if (vb != null)
{
return (String) vb.getValue(getFacesContext());
}
return getDecimalFormatSymbols().getCurrencySymbol();
}
public String getLocalCurrencySymbol()
{
if (_currencySymbol != null)
{
return _currencySymbol;
}
ValueBinding vb = getValueBinding("currencySymbol");
if (vb != null)
{
return (String) vb.getValue(getFacesContext());
}
return null;
}
public void setCurrencySymbol(String currencySymbol)
{
this._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
public Locale getLocale()
{
if (_locale != null)
{
return _locale;
}
ValueBinding vb = getValueBinding("locale");
if (vb != null)
{
Object _localeValue = vb.getValue(getFacesContext());
if (_localeValue instanceof String)
{
_localeValue = org.apache.myfaces.commons.util.TagUtils.getLocale((String)_localeValue);
}
return (java.util.Locale)_localeValue;
}
FacesContext context = FacesContext.getCurrentInstance();
return context.getViewRoot().getLocale();
}
public void setLocale(Locale locale)
{
_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());
}
}