javax.faces.convert.DateTimeConverter Maven / Gradle / Ivy
/*
* $Id: DateTimeConverter.java,v 1.35 2007/09/26 19:05:18 rlubke Exp $
*/
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package javax.faces.convert;
import javax.faces.component.UIComponent;
import javax.faces.component.PartialStateHolder;
import javax.faces.context.FacesContext;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* {@link Converter} implementation for java.util.Date
* values.
*
* The getAsObject()
method parses a String into a
* java.util.Date
, according to the following algorithm:
*
* - If the specified String is null, return
* a
null
. Otherwise, trim leading and trailing
* whitespace before proceeding.
* - If the specified String - after trimming - has a zero length,
* return
null
.
* - If the
locale
property is not null,
* use that Locale
for managing parsing. Otherwise, use the
* Locale
from the UIViewRoot
.
* - If a
pattern
has been specified, its syntax must conform
* the rules specified by java.text.SimpleDateFormat
. Such
* a pattern will be used to parse, and the type
,
* dateStyle
, and timeStyle
properties
* will be ignored.
* - If a
pattern
has not been specified, parsing will be based
* on the type
property, which expects a date value, a time
* value, or both. Any date and time values included will be parsed in
* accordance to the styles specified by dateStyle
and
* timeStyle
, respectively.
* - If a
timezone
has been specified, it must be passed
* to the underlying DateFormat
instance. Otherwise
* the "GMT" timezone is used.
* - In all cases, parsing must be non-lenient; the given string must
* strictly adhere to the parsing format.
*
*
* The getAsString()
method expects a value of type
* java.util.Date
(or a subclass), and creates a formatted
* String according to the following algorithm:
*
* - If the specified value is null, return a zero-length String.
* - If the specified value is a String, return it unmodified.
* - If the
locale
property is not null,
* use that Locale
for managing formatting. Otherwise, use the
* Locale
from the UIViewRoot
.
* - If a
timezone
has been specified, it must be passed
* to the underlying DateFormat
instance. Otherwise
* the "GMT" timezone is used.
* - If a
pattern
has been specified, its syntax must conform
* the rules specified by java.text.SimpleDateFormat
. Such
* a pattern will be used to format, and the type
,
* dateStyle
, and timeStyle
properties
* will be ignored.
* - If a
pattern
has not been specified, formatting will be
* based on the type
property, which includes a date value,
* a time value, or both into the formatted String. Any date and time
* values included will be formatted in accordance to the styles specified
* by dateStyle
and timeStyle
, respectively.
*
*/
public class DateTimeConverter implements Converter, PartialStateHolder {
// ------------------------------------------------------ Manifest Constants
/**
* The standard converter id for this converter.
*/
public static final String CONVERTER_ID = "javax.faces.DateTime";
/**
* The message identifier of the {@link javax.faces.application.FacesMessage} to be created if
* the conversion to Date
fails. The message format
* string for this message may optionally include the following
* placeholders:
*
* {0}
replaced by the unconverted value.
* {1}
replaced by an example value.
* {2}
replaced by a String
whose value
* is the label of the input component that produced this message.
*
*/
public static final String DATE_ID =
"javax.faces.converter.DateTimeConverter.DATE";
/**
* The message identifier of the {@link javax.faces.application.FacesMessage} to be created if
* the conversion to Time
fails. The message format
* string for this message may optionally include the following
* placeholders:
*
* {0}
replaced by the unconverted value.
* {1}
replaced by an example value.
* {2}
replaced by a String
whose value
* is the label of the input component that produced this message.
*
*/
public static final String TIME_ID =
"javax.faces.converter.DateTimeConverter.TIME";
/**
* The message identifier of the {@link javax.faces.application.FacesMessage} to be created if
* the conversion to DateTime
fails. The message format
* string for this message may optionally include the following
* placeholders:
*
* {0}
replaced by the unconverted value.
* {1}
replaced by an example value.
* {2}
replaced by a String
whose value
* is the label of the input component that produced this message.
*
*/
public static final String DATETIME_ID =
"javax.faces.converter.DateTimeConverter.DATETIME";
/**
* The message identifier of the {@link javax.faces.application.FacesMessage} to be created if
* the conversion of the DateTime
value to
* String
fails. The message format string for this message
* may optionally include the following placeholders:
*
* {0}
relaced by the unconverted value.
* {1}
replaced by a String
whose value
* is the label of the input component that produced this message.
*
*/
public static final String STRING_ID =
"javax.faces.converter.STRING";
private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("GMT");
// ------------------------------------------------------ Instance Variables
private String dateStyle = "default";
private Locale locale = null;
private String pattern = null;
private String timeStyle = "default";
private TimeZone timeZone = DEFAULT_TIME_ZONE;
private String type = "date";
// -------------------------------------------------------------- Properties
/**
* Return the style to be used to format or parse dates. If not set,
* the default value, default
, is returned.
*/
public String getDateStyle() {
return (this.dateStyle);
}
/**
* Set the style to be used to format or parse dates. Valid values
* are default
, short
, medium
,
* long
, and full
.
* An invalid value will cause a {@link ConverterException} when
* getAsObject()
or getAsString()
is called.
*
* @param dateStyle The new style code
*/
public void setDateStyle(String dateStyle) {
clearInitialState();
this.dateStyle = dateStyle;
}
/**
* Return the Locale
to be used when parsing or formatting
* dates and times. If not explicitly set, the Locale
stored
* in the {@link javax.faces.component.UIViewRoot} for the current
* request is returned.
*/
public Locale getLocale() {
if (this.locale == null) {
this.locale =
getLocale(FacesContext.getCurrentInstance());
}
return (this.locale);
}
/**
* Set the Locale
to be used when parsing or formatting
* dates and times. If set to null
, the Locale
* stored in the {@link javax.faces.component.UIViewRoot} for the current
* request will be utilized.
*
* @param locale The new Locale
(or null
)
*/
public void setLocale(Locale locale) {
clearInitialState();
this.locale = locale;
}
/**
* Return the format pattern to be used when formatting and
* parsing dates and times.
*/
public String getPattern() {
return (this.pattern);
}
/**
* Set the format pattern to be used when formatting and parsing
* dates and times. Valid values are those supported by
* java.text.SimpleDateFormat
.
* An invalid value will cause a {@link ConverterException} when
* getAsObject()
or getAsString()
is called.
*
* @param pattern The new format pattern
*/
public void setPattern(String pattern) {
clearInitialState();
this.pattern = pattern;
}
/**
* Return the style to be used to format or parse times. If not set,
* the default value, default
, is returned.
*/
public String getTimeStyle() {
return (this.timeStyle);
}
/**
* Set the style to be used to format or parse times. Valid values
* are default
, short
, medium
,
* long
, and full
.
* An invalid value will cause a {@link ConverterException} when
* getAsObject()
or getAsString()
is called.
*
* @param timeStyle The new style code
*/
public void setTimeStyle(String timeStyle) {
clearInitialState();
this.timeStyle = timeStyle;
}
/**
* Return the TimeZone
used to interpret a time value.
* If not explicitly set, the default time zone of GMT
* returned.
*/
public TimeZone getTimeZone() {
return (this.timeZone);
}
/**
* Set the TimeZone
used to interpret a time value.
*
* @param timeZone The new time zone
*/
public void setTimeZone(TimeZone timeZone) {
clearInitialState();
this.timeZone = timeZone;
}
/**
* Return the type of value to be formatted or parsed.
* If not explicitly set, the default type, date
* is returned.
*/
public String getType() {
return (this.type);
}
/**
* Set the type of value to be formatted or parsed.
* Valid values are both
, date
, or
* time
.
* An invalid value will cause a {@link ConverterException} when
* getAsObject()
or getAsString()
is called.
*
* @param type The new date style
*/
public void setType(String type) {
clearInitialState();
this.type = type;
}
// ------------------------------------------------------- Converter Methods
/**
* @throws ConverterException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
if (context == null || component == null) {
throw new NullPointerException();
}
Object returnValue = null;
DateFormat parser = null;
try {
// If the specified value is null or zero-length, return null
if (value == null) {
return (null);
}
value = value.trim();
if (value.length() < 1) {
return (null);
}
// Identify the Locale to use for parsing
Locale locale = getLocale(context);
// Create and configure the parser to be used
parser = getDateFormat(locale);
if (null != timeZone) {
parser.setTimeZone(timeZone);
}
// Perform the requested parsing
returnValue = parser.parse(value);
} catch (ParseException e) {
if ("date".equals(type)) {
throw new ConverterException(MessageFactory.getMessage(
context, DATE_ID, value,
parser.format(new Date(System.currentTimeMillis())),
MessageFactory.getLabel(context, component)), e);
} else if ("time".equals(type)) {
throw new ConverterException(MessageFactory.getMessage(
context, TIME_ID, value,
parser.format(new Date(System.currentTimeMillis())),
MessageFactory.getLabel(context, component)), e);
} else if ("both".equals(type)) {
throw new ConverterException(MessageFactory.getMessage(
context, DATETIME_ID, value,
parser.format(new Date(System.currentTimeMillis())),
MessageFactory.getLabel(context, component)), e);
}
} catch (Exception e) {
throw new ConverterException(e);
}
return returnValue;
}
/**
* @throws ConverterException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public String getAsString(FacesContext context, UIComponent component,
Object value) {
if (context == null || component == null) {
throw new NullPointerException();
}
try {
// If the specified value is null, return a zero-length String
if (value == null) {
return "";
}
// If the incoming value is still a string, play nice
// and return the value unmodified
if (value instanceof String) {
return (String) value;
}
// Identify the Locale to use for formatting
Locale locale = getLocale(context);
// Create and configure the formatter to be used
DateFormat formatter = getDateFormat(locale);
if (null != timeZone) {
formatter.setTimeZone(timeZone);
}
// Perform the requested formatting
return (formatter.format(value));
} catch (ConverterException e) {
throw new ConverterException(MessageFactory.getMessage(
context, STRING_ID, value,
MessageFactory.getLabel(context, component)), e);
} catch (Exception e) {
throw new ConverterException(MessageFactory.getMessage(
context, STRING_ID, value,
MessageFactory.getLabel(context, component)), e);
}
}
// --------------------------------------------------------- Private Methods
/**
* Return a DateFormat
instance to use for formatting
* and parsing in this {@link Converter}.
*
* @param locale The Locale
used to select formatting
* and parsing conventions
* @throws ConverterException if no instance can be created
*/
private DateFormat getDateFormat(Locale locale) {
// PENDING(craigmcc) - Implement pooling if needed for performance?
if (pattern == null && type == null) {
throw new IllegalArgumentException("Either pattern or type must" +
" be specified.");
}
DateFormat df;
if (pattern != null) {
df = new SimpleDateFormat(pattern, locale);
} else if (type.equals("both")) {
df = DateFormat.getDateTimeInstance
(getStyle(dateStyle), getStyle(timeStyle), locale);
} else if (type.equals("date")) {
df = DateFormat.getDateInstance(getStyle(dateStyle), locale);
} else if (type.equals("time")) {
df = DateFormat.getTimeInstance(getStyle(timeStyle), locale);
} else {
// PENDING(craigmcc) - i18n
throw new IllegalArgumentException("Invalid type: " + type);
}
df.setLenient(false);
return (df);
}
/**
* Return the Locale
we will use for localizing our
* formatting and parsing processing.
*
* @param context The {@link FacesContext} for the current request
*/
private Locale getLocale(FacesContext context) {
// PENDING(craigmcc) - JSTL localization context?
Locale locale = this.locale;
if (locale == null) {
locale = context.getViewRoot().getLocale();
}
return (locale);
}
/**
* Return the style constant for the specified style name.
*
* @param name Name of the style for which to return a constant
* @throws ConverterException if the style name is not valid
*/
private static int getStyle(String name) {
if ("default".equals(name)) {
return (DateFormat.DEFAULT);
} else if ("short".equals(name)) {
return (DateFormat.SHORT);
} else if ("medium".equals(name)) {
return (DateFormat.MEDIUM);
} else if ("long".equals(name)) {
return (DateFormat.LONG);
} else if ("full".equals(name)) {
return (DateFormat.FULL);
} else {
// PENDING(craigmcc) - i18n
throw new ConverterException("Invalid style '" + name + '\'');
}
}
// ----------------------------------------------------- StateHolder Methods
public Object saveState(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!initialStateMarked()) {
Object values[] = new Object[6];
values[0] = dateStyle;
values[1] = locale;
values[2] = pattern;
values[3] = timeStyle;
values[4] = timeZone;
values[5] = type;
return (values);
}
return null;
}
public void restoreState(FacesContext context, Object state) {
if (context == null) {
throw new NullPointerException();
}
if (state != null) {
Object values[] = (Object[]) state;
dateStyle = (String) values[0];
locale = (Locale) values[1];
pattern = (String) values[2];
timeStyle = (String) values[3];
timeZone = (TimeZone) values[4];
type = (String) values[5];
}
}
private boolean transientFlag = false;
public boolean isTransient() {
return (transientFlag);
}
public void setTransient(boolean transientFlag) {
this.transientFlag = transientFlag;
}
private boolean initialState;
public void markInitialState() {
initialState = true;
}
public boolean initialStateMarked() {
return initialState;
}
public void clearInitialState() {
initialState = false;
}
}