/*
GASH 2
DateDBField.java
The GANYMEDE object storage system.
Created: 2 July 1996
Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2013
The University of Texas at Austin
Ganymede is a registered trademark of The University of Texas at Austin
Contact information
Web site: http://www.arlut.utexas.edu/gash2
Author Email: ganymede_author@arlut.utexas.edu
Email mailing list: ganymede@arlut.utexas.edu
US Mail:
Computer Science Division
Applied Research Laboratories
The University of Texas at Austin
PO Box 8029, Austin TX 78713-8029
Telephone: (512) 835-3200
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package arlut.csd.ganymede.server;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Vector;
import arlut.csd.ganymede.common.ReturnVal;
import arlut.csd.ganymede.rmi.date_field;
import arlut.csd.Util.TranslationService;
/*------------------------------------------------------------------------------
class
DateDBField
------------------------------------------------------------------------------*/
/**
* <p>DateDBField is a subclass of {@link
* arlut.csd.ganymede.server.DBField DBField} for the storage and
* handling of Date fields in the {@link
* arlut.csd.ganymede.server.DBStore DBStore} on the Ganymede
* server.</p>
*
* <p>The Ganymede client talks to DateDBFields through the {@link
* arlut.csd.ganymede.rmi.date_field date_field} RMI interface.</p>
*
* <p>Ganymede uses the standard Java Date class, which can encode
* dates from roughly 300 million years B.C. to 300 million years
* A.D., with millisecond resolution. No Y2k problems here. ;-)</p>
*/
public class DateDBField extends DBField implements date_field {
static DateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss",
java.util.Locale.US);
/**
* TranslationService object for handling string localization in
* the Ganymede server.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.DateDBField");
// ---
/**
* <p>Receive constructor. Used to create a DateDBField from a
* {@link arlut.csd.ganymede.server.DBStore DBStore}/{@link
* arlut.csd.ganymede.server.DBJournal DBJournal} DataInput
* stream.</p>
*/
DateDBField(DBObject owner, DataInput in, DBObjectBaseField definition) throws IOException
{
super(owner, definition.getID());
this.value = null;
receive(in, definition);
}
/**
* <p>No-value constructor. Allows the construction of a
* 'non-initialized' field, for use where the {@link
* arlut.csd.ganymede.server.DBObjectBase DBObjectBase} definition
* indicates that a given field may be present, but for which no
* value has been stored in the {@link
* arlut.csd.ganymede.server.DBStore DBStore}.</p>
*
* <p>Used to provide the client a template for 'creating' this
* field if so desired.</p>
*/
DateDBField(DBObject owner, DBObjectBaseField definition)
{
super(owner, definition.getID());
this.value = null;
}
/**
*
* Copy constructor.
*
*/
public DateDBField(DBObject owner, DateDBField field)
{
super(owner, field.getID());
if (field.value instanceof Date)
{
this.value = new Date(((Date) field.value).getTime());
}
else
{
this.value = field.value;
}
}
/**
*
* Scalar value constructor.
*
*/
public DateDBField(DBObject owner, Date value, DBObjectBaseField definition)
{
super(owner, definition.getID());
this.value = value;
}
/**
*
* Vector value constructor.
*
*/
public DateDBField(DBObject owner, Vector values, DBObjectBaseField definition)
{
super(owner, definition.getID());
throw new IllegalArgumentException("vector constructor called on scalar field");
}
@Override public Object clone() throws CloneNotSupportedException
{
throw new CloneNotSupportedException();
}
@Override void emit(DataOutput out) throws IOException
{
out.writeLong(((Date) value).getTime());
}
@Override void receive(DataInput in, DBObjectBaseField definition) throws IOException
{
value = new Date(in.readLong());
}
/**
* <p>This method is used when the database is being dumped, to write
* out this field to disk.</p>
*/
@Override synchronized void emitXML(XMLDumpContext xmlOut) throws IOException
{
xmlOut.startElementIndent(this.getXMLName());
emitDateXML(xmlOut, value());
xmlOut.endElement(this.getXMLName());
}
public void emitDateXML(XMLDumpContext xmlOut, Date value) throws IOException
{
xmlOut.startElement("date");
synchronized (formatter)
{
xmlOut.attribute("val", formatter.format(value));
}
xmlOut.attribute("timecode", java.lang.Long.toString(value.getTime()));
xmlOut.endElement("date");
}
// ****
//
// type-specific accessor methods
//
// ****
public Date value()
{
if (value == null)
{
return null;
}
return new Date(((Date) value).getTime()); // return a defensive copy
}
public Date value(int index)
{
throw new IllegalArgumentException("vector accessor called on scalar");
}
/**
* This method returns a text encoded value for this DBField
* without checking permissions.
*/
@Override public synchronized String getValueString()
{
/* -- */
if (value == null)
{
return "";
}
// pass the date through the localized default formatter rather
// than using the toString() method.
// "{0,date,EEE, MMM d yyyy hh:mm:ss aaa zz}"
return ts.l("getValueString.date", this.value);
}
/**
* Date fields need a special encoding that can be easily
* reversed to obtain a date object on the client for sorting
* and selection purposes.
*/
@Override public synchronized String getEncodingString()
{
if (value == null)
{
return "";
}
return Long.toString(((Date) this.value).getTime());
}
/**
* <p>Returns a String representing the change in value between this
* field and orig. This String is intended for logging and email,
* not for any sort of programmatic activity. The format of the
* generated string is not defined, but is intended to be suitable
* for inclusion in a log entry and in an email message.</p>
*
* <p>If there is no change in the field, null will be returned.</p>
*/
@Override public String getDiffString(DBField orig)
{
if (!(orig instanceof DateDBField))
{
throw new IllegalArgumentException("bad field comparison");
}
DateDBField origD = (DateDBField) orig;
if (origD.value().equals(this.value()))
{
return null;
}
StringBuilder result = new StringBuilder();
// "\tOld: {0,date,EEE, MMM d yyyy hh:mm:ss aaa zz}\n"
result.append(ts.l("getDiffString.old", origD.value));
// "\tNew: {0,date,EEE, MMM d yyyy hh:mm:ss aaa zz}\n"
result.append(ts.l("getDiffString.new", this.value));
return result.toString();
}
// ****
//
// date_field methods
//
// ****
/**
* <p>Returns true if this date field has a minimum and/or maximum date
* set.</p>
*
* <p>We depend on our owner's
* {@link arlut.csd.ganymede.server.DBEditObject#isDateLimited(arlut.csd.ganymede.server.DBField) isDateLimited()}
* method to tell us whether this Date field should be limited or not
* in this editing context.</p>
*
* @see arlut.csd.ganymede.rmi.date_field
*/
public boolean limited()
{
DBEditObject eObj;
/* -- */
if (!isEditable(true))
{
throw new IllegalArgumentException("not applicable to a non-editable field/object");
}
eObj = (DBEditObject) this.owner;
return eObj.isDateLimited(this);
}
/**
* <p>Returns the earliest date acceptable for this field</p>
*
* <p>We depend on our owner's {@link
* arlut.csd.ganymede.server.DBEditObject#minDate(arlut.csd.ganymede.server.DBField)
* minDate()} method to tell us what the earliest acceptable Date
* for this field is.</p>
*
* @see arlut.csd.ganymede.rmi.date_field
*/
public Date minDate()
{
DBEditObject eObj;
/* -- */
if (!isEditable(true))
{
throw new IllegalArgumentException("not applicable to a non-editable field/object");
}
eObj = (DBEditObject) this.owner;
return eObj.minDate(this);
}
/**
* <p>Returns the latest date acceptable for this field</p>
*
* <p>We depend on our owner's {@link
* arlut.csd.ganymede.server.DBEditObject#maxDate(arlut.csd.ganymede.server.DBField)
* maxDate()} method to tell us what the earliest acceptable Date
* for this field is.</p>
*
* @see arlut.csd.ganymede.rmi.date_field
*/
public Date maxDate()
{
DBEditObject eObj;
/* -- */
if (!isEditable(true))
{
throw new IllegalArgumentException("not applicable to a non-editable field/object");
}
eObj = (DBEditObject) this.owner;
return eObj.maxDate(this);
}
// ****
//
// Overridable methods for implementing intelligent behavior
//
// ****
@Override public boolean verifyTypeMatch(Object o)
{
return ((o == null) || (o instanceof Date));
}
@Override public ReturnVal verifyNewValue(Object o)
{
DBEditObject eObj;
Date d, d2;
/* -- */
if (!isEditable(true))
{
// "Date Field Error"
// "Don''t have permission to edit field {0} in object {1}."
return Ganymede.createErrorDialog(this.getGSession(),
ts.l("verifyNewValue.error_title"),
ts.l("verifyNewValue.bad_perm", getName(), this.owner.getLabel()));
}
eObj = (DBEditObject) this.owner;
if (!verifyTypeMatch(o))
{
// "Date Field Error"
// "Type error. Submitted value {0} is not a Date! Major client error while trying to edit field {1} in object {2}."
return Ganymede.createErrorDialog(this.getGSession(),
ts.l("verifyNewValue.error_title"),
ts.l("verifyNewValue.bad_type", o, getName(), this.owner.getLabel()));
}
if (o == null)
{
return eObj.verifyNewValue(this, null); // FindBugs explicit
}
d = (Date) o;
if (limited())
{
d2 = minDate();
if (d2 != null)
{
if (d.before(d2))
{
// "Date Field Error"
// "Submitted Date {0,date,EEE, MMM d yyyy hh:mm:ss aaa zz} is out of range for field {1} in object {2}.\n
// This field will not accept dates before {3,date,EEE, MMM d yyyy hh:mm:ss aaa zz}."
return Ganymede.createErrorDialog(this.getGSession(),
ts.l("verifyNewValue.error_title"),
ts.l("verifyNewValue.under_range", d, getName(), this.owner.getLabel(), d2));
}
}
d2 = maxDate();
if (d2 != null)
{
if (d.after(d2))
{
// "Date Field Error"
// "Submitted Date {0,date,EEE, MMM d yyyy hh:mm:ss aaa zz} is out of range for field {1} in object {2}.\n
// This field will not accept dates after {3,date,EEE, MMM d yyyy hh:mm:ss aaa zz}."
return Ganymede.createErrorDialog(this.getGSession(),
ts.l("verifyNewValue.error_title"),
ts.l("verifyNewValue.over_range", d, getName(), this.owner.getLabel(), d2));
}
}
}
// have our parent make the final ok on the value
return eObj.verifyNewValue(this, o);
}
}