/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
package org.beanfabrics.model;
import java.text.DateFormat;
import java.text.Format;
import java.text.ParsePosition;
import java.util.Date;
import java.util.Locale;
import java.util.ResourceBundle;
import org.beanfabrics.util.ResourceBundleFactory;
import org.beanfabrics.validation.ValidationRule;
import org.beanfabrics.validation.ValidationState;
/**
* The {@link DatePM} is a {@link PresentationModel} that contains a
* {@link Date} value.
* <p>
* The date format used for formatting and pasring can be set by calling
* {@link #setFormat(DateFormat)}. The default text format is {@link Locale}
* dependent.
*
* @author Michael Karneim
* @author Max Gensthaler
*/
public class DatePM extends TextPM implements IDatePM {
protected static final String KEY_MESSAGE_INVALID_DATE = "message.invalidDate";
private final ResourceBundle resourceBundle = ResourceBundleFactory.getBundle(DatePM.class);
/**
* The {@link DateFormatProvider} is a factory for the default
* {@link DateFormat} used by newly created {@link DatePM} instances.
*
* @see DatePM#setDefaultDateFormatProvider(DateFormatProvider)
*/
public static class DateFormatProvider {
/**
* Creates and returns a new {@link DateFormat}
*
* @return a new {@link DateFormat}
*/
public DateFormat getDateFormat() {
DateFormat format = DateFormat.getDateInstance();
format.setLenient(false);
return format;
}
}
private static DateFormatProvider DEFAULT_DATE_FORMAT_PROVIDER = new DateFormatProvider();
/**
* Returns the {@link DateFormatProvider} that is used to create the default
* {@link DateFormat} used by newly created {@link DatePM} instances.
*
* @return the {@link DateFormatProvider}
*/
public static synchronized DateFormatProvider getDefaultDateFormatProvider() {
return DEFAULT_DATE_FORMAT_PROVIDER;
}
/**
* Sets the {@link DateFormatProvider} that is used to create the default
* {@link DateFormat} used by newly created {@link DatePM} instances.
*
* @param provider
*/
public static synchronized void setDefaultDateFormatProvider(DateFormatProvider provider) {
DEFAULT_DATE_FORMAT_PROVIDER = provider;
}
private DateFormat format;
/**
* Constructs a {@link DatePM} using the default format.
*
* @see #getDefaultFormat()
*/
public DatePM() {
setFormat(getDefaultFormat());
// Please note: to disable default validation rules just call getValidator().clear();
getValidator().add(new DateValidationRule());
}
/**
* Returns a {@link DateFormat} for formatting a {@link Date} to a
* {@link String} and converting a <code>String</code> to a
* <code>Date</code>.
*
* @return a localized {@link DateFormat} for formatting and parsing this
* PM's value
*/
protected DateFormat getDefaultFormat() {
return getDefaultDateFormatProvider().getDateFormat();
}
/** {@inheritDoc} */
public DateFormat getFormat() {
return format;
}
/** {@inheritDoc} */
public void setFormat(DateFormat newFormat) {
Format oldFormat = format;
if (oldFormat == newFormat) {
return;
}
boolean doReformat;
Date oldValue = null;
try {
oldValue = getDate();
doReformat = true;
} catch (ConversionException ex) {
doReformat = false;
}
format = (DateFormat)newFormat.clone();
getPropertyChangeSupport().firePropertyChange("format", oldFormat, newFormat); //$NON-NLS-1$
revalidate();
if (doReformat) {
setDate(oldValue);
}
}
/** {@inheritDoc} */
public Date getDate()
throws ConversionException {
if (isEmpty()) {
return null;
}
String str = getText();
ParsePosition posInOut = new ParsePosition(0);
Date result = (Date)format.parseObject(str, posInOut);
if (posInOut.getIndex() == str.length()) {
return result;
} else {
throw new ConversionException();
}
}
/** {@inheritDoc} */
public void setDate(Date date) {
if (date == null) {
setText(null);
} else {
String str = format.format(date);
setText(str);
}
}
/**
* Sets the default value of this PM to the given {@link Date} value.
*
* @param date the default value
*/
public void setDefaultDate(Date date) {
if (date == null) {
setDefaultText(null);
} else {
setDefaultText(format.format(date));
}
}
/** {@inheritDoc} */
@Override
public Comparable<?> getComparable() {
return new DateComparable();
}
/**
* The {@link DateComparable} delegates the comparison to the PM's date
* value.
*
* @author Michael Karneim
*/
private class DateComparable extends TextComparable {
private Long time;
/**
* Constructs a {@link DateComparable}.
*/
public DateComparable() {
if (!isEmpty()) {
try {
Date date = getDate();
time = date.getTime();
} catch (ConversionException ex) {
// ignore
}
}
}
/** {@inheritDoc} */
@Override
public int compareTo(Object o) {
if (o == null) {
throw new IllegalArgumentException("o == null");
}
if (!(o instanceof DateComparable)) {
throw new IllegalArgumentException("incompatible comparable class");
}
DateComparable oc = (DateComparable)o;
if (time == null) {
if (oc.time == null) {
return super.compareTo(o);
} else {
return -1;
}
} else {
if (oc.time == null) {
return 1;
} else {
return time.compareTo(oc.time);
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!super.equals(o)) {
return false;
}
if (o == null) {
return false;
}
if (o.getClass() != getClass()) {
return false;
}
DateComparable castedObj = (DateComparable)o;
return ((time == null ? castedObj.time == null : time.equals(castedObj.time)));
}
@Override
public int hashCode() {
return time.hashCode();
}
}
/**
* This rule evaluates to invalid if the PM's value can't be converted into
* a {@link Date}.
*
* @author Michael Karneim
*/
public class DateValidationRule implements ValidationRule {
/** {@inheritDoc} */
public ValidationState validate() {
if (isEmpty()) {
return null;
}
try {
getDate(); // try to convert to date
return null;
} catch (ConversionException ex) {
String message = resourceBundle.getString(KEY_MESSAGE_INVALID_DATE);
return new ValidationState(message);
}
}
}
}