/*
* The contents of this file are subject to the Open Software License
* Version 3.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.opensource.org/licenses/osl-3.0.txt
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*/
package org.mulgara.store.stringpool.xa;
import java.net.URI;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import org.apache.log4j.Logger;
import org.mulgara.store.stringpool.*;
import org.mulgara.util.Constants;
import org.mulgara.util.Timezone;
/**
* Holds constants used by the SPG dateTime types.
*
* @created Sep 19, 2008
* @author Paula Gearon
* @copyright © 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
* @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
*/
public abstract class AbstractSPGDateTime extends AbstractSPTypedLiteral {
private final static Logger logger = Logger.getLogger(AbstractSPGDateTime.class);
/** The arbitrary default year to use for a type that has no year. */
public static final int D_YEAR = 2000;
/** The arbitrary default month to use for a type that has no month. */
public static final int D_MONTH = 0;
/** The arbitrary default day to use for a type that has no day. */
public static final int D_DAY = 1;
/** The arbitrary default hour to use for a types with no hour. */
public static final int D_HOUR_OF_DAY = 0;
/** The arbitrary default minute to use for a type that has no minutes. */
public static final int D_MINUTE = 0;
/** The arbitrary default seconds to use for a type that has no seconds. */
public static final int D_SECOND = 0;
/** The arbitrary default milliseconds to use for a type that has no milliseconds. */
public static final int D_MILLISECOND = 0;
/** Bit 0 indicates a timezone. */
static final byte NO_TZ_BIT = 1;
/** The date representation for the data. */
protected Calendar gDate;
/** The timezone info. */
protected Timezone tz = null;
/**
* Constructs a new GDay representation using a calendar object representation.
*
* @param dayDate The gDay object represented as a date
*/
AbstractSPGDateTime(int typeID, URI typeURI, Date dayDate) {
super(typeID, typeURI);
// Initialise the calendar object
gDate = Calendar.getInstance();
gDate.setTime(dayDate);
}
/**
* Constructs a new GDay representation using a calendar object representation.
*
* @param dayCalendar The gDay object represented as a calendar
*/
AbstractSPGDateTime(int typeID, URI typeURI, Calendar dayCalendar, Timezone tz) {
super(typeID, typeURI);
// Store the day date as a calendar
gDate = dayCalendar;
this.tz = tz;
}
/**
* Creates a new gDay representation using a long value of the day
* and creating a Date object from it.
*
* @param dayLong The day as a long
*/
AbstractSPGDateTime(int typeID, URI typeURI, long dayLong) {
// Use the date constructor to create a new instance
this(typeID, typeURI, new Date(dayLong));
}
/**
*
* Constructs a gDay object which reads the day value from a byte buffer as
* an integer.
*
* @param data The byte buffer storing the day as an integer
*/
AbstractSPGDateTime(int typeID, URI typeURI, ByteBuffer data) {
// Call the constructor using a long for the date
this(typeID, typeURI, data.getLong());
// update the timezone
setTzByte(data.get());
}
/**
* Retrieve the size of the buffer needed for this object. This is the size of the buffer
* returned by {@link #getData()} or used by {@link #AbstractSPGDateTime(int, URI, ByteBuffer)}.
* @return A buffer size in bytes.
*/
public static int getBufferSize() {
return Constants.SIZEOF_LONG + 1;
}
/**
* Converts this gDate/Time object to a buffer of byte data.
* @return The byte representation of this gDate/Time object
*/
public ByteBuffer getData() {
// Create a new byte buffer that can hold a long object
ByteBuffer data = ByteBuffer.allocate(getBufferSize());
// Store the date as a long value
data.putLong(gDate.getTimeInMillis());
data.put(getTzByte());
// Prepare the buffer for reading
data.flip();
return data;
}
/**
* Compares this gDay representation to another object to see if they are
* the same values. First the typing is checked and then the value.
* @param object The object we are comparing against
* @return Whether the gDay value is greater than (> 0), less than (< 0), or equal to (0) this value
*/
public int compareTo(SPObject object) {
// Compare types.
int comparison = super.compareTo(object);
if (comparison != 0) return comparison;
// Compare the dates lexically
return getLexicalForm().compareTo(((AbstractSPGDateTime)object).getLexicalForm());
}
/** @see java.lang.Object#hashCode() */
public int hashCode() {
return gDate.hashCode();
}
/**
* Determines whether the object is equal to the one passed in, in both type
* and value. This is different to the compareTo(Object) method in the
* respect that it does a direct comparison, not a ranking comparison.
* @param object The object to compare this one to
* @return Whether the object is the same as this one
*/
public boolean equals(Object object) {
if (object == null) return false;
return object.getClass().isInstance(this) && ((AbstractSPGDateTime)object).getLexicalForm().equals(getLexicalForm());
}
/**
* Create a new comparator for comparison operations.
* @return The comparator to be used for comparisons
*/
public SPComparator getSPComparator() {
return SPGDateTimeComparator.getInstance();
}
/**
* Convert the gDay representation to a lexical string as defined by XSD datatypes.
* @return The lexical form of the gDay object
*/
public String getLexicalForm() {
// Create a formatter to output the date
SimpleDateFormat formatter = new SimpleDateFormat(getFormatString());
if (tz != null) formatter.setTimeZone(tz.asJavaTimeZone());
// Apply the formatting
String lexical = formatter.format(gDate.getTime());
// append the timezone if needed
if (tz != null) lexical += tz.toString();
return lexical;
}
/**
* Get the format string used for lexical form.
* @return A format string used by {@linkplain java.text.SimpleDateFormat}.
*/
abstract protected String getFormatString();
/**
* Set up a calendar object for the "arbitrary" year/month/time used by this type.
* @param cal The calendar to configure
*/
protected static void configureCalendar(Calendar cal) {
cal.set(Calendar.YEAR, D_YEAR);
cal.set(Calendar.MONTH, D_MONTH);
cal.set(Calendar.DAY_OF_MONTH, D_DAY);
cal.set(Calendar.HOUR_OF_DAY, D_HOUR_OF_DAY);
cal.set(Calendar.MINUTE, D_MINUTE);
cal.set(Calendar.SECOND, D_SECOND);
cal.set(Calendar.MILLISECOND, D_MILLISECOND);
}
/**
* Scan a lexical form for a timezone, and return an appropriate timezone.
* @param str The lexical form of the day value.
* @param maxDashes The number of dashes used when no timezone is present.
* @return a new Timezone built from the string, or null if none found.
*/
protected static Timezone scanForTimezone(String str, int maxDashes) {
if (str.indexOf('Z') > 1) return Timezone.newZuluTimezone();
// look for indicators of a negative or a positive timezone,
// and set the start of the timezone string
int startPos = str.indexOf('+') + 1;
if (startPos == 0) {
int splitLen = str.split("-").length;
// Add a dash to check for negative timezones, and one more for the number of fields.
if (splitLen > maxDashes + 2) throw new IllegalArgumentException("Bad timezone. Too many dashes in: " + str);
if (splitLen == maxDashes + 2) startPos = str.lastIndexOf("-");
} else {
// positive offset, so there should be no dashes after this point
if (str.lastIndexOf('-') >= startPos) throw new IllegalArgumentException("Bad timezone characters in: " + str);
}
Timezone result = null;
// If the timezone string is there, then pull it out and set the object
if (startPos > 1) {
String[] zone = str.substring(startPos).split(":");
if (zone.length < 2) throw new IllegalArgumentException("Bad timezone characters in: " + str.substring(startPos));
result = buildTimezone(zone[0], zone[1]);
}
return result;
}
/**
* Creates a "Standard" calendar object, with a default date and time, in the given timezone.
* @param tz The timezone for the calendar.
* @return A new default calendar in the given timezone.
*/
protected static Calendar createCalendar(Timezone tz) {
// Get the date/time object as a calendar
Calendar calendar = tz == null ? new GregorianCalendar() : new GregorianCalendar(tz.asJavaTimeZone());
configureCalendar(calendar);
if (logger.isDebugEnabled()) {
logger.debug("Calendar lexical string is: " +
calendar.get(Calendar.YEAR) + "-" +
calendar.get(Calendar.MONTH) + "-" +
calendar.get(Calendar.DAY_OF_MONTH));
logger.debug("TimeZone of calendar is: " + calendar.getTimeZone());
logger.debug("Calendar as date: " + calendar.getTime());
}
return calendar;
}
/**
* Creates a Timezone for an object
* @param hr The hour offset of the timezone as a string. May be negative.
* @param min The minute offset of the timezone as a string.
*/
private static Timezone buildTimezone(String hr, String min) {
return new Timezone(Integer.parseInt(hr), Integer.parseInt(min));
}
/**
* Calculate an encoding byte for the timezone.
* @return A code for the presence of a timezone.
*/
private byte getTzByte() {
return (tz == null) ? NO_TZ_BIT : tz.getCode();
}
/**
* Calculate an encoding byte for the timezone.
* Sets a code for the presence of a timezone.
*/
private void setTzByte(byte tzData) {
tz = null;
if ((tzData & NO_TZ_BIT) == 0) tz = new Timezone(tzData);
}
/**
* Implementation of an SPComparator which compares the binary representations of GDateTime objects.
*/
public static class SPGDateTimeComparator implements SPComparator {
/** Singleton instance of the comparator */
private static final SPGDateTimeComparator INSTANCE = new SPGDateTimeComparator();
/**
* Retrieves the singleton instance of this comparator.
*
* @return The comparator singleton instance
*/
public static SPGDateTimeComparator getInstance() {
return INSTANCE;
}
/**
* Gives the comparator an opportunity to return an ordering where only the
* prefix of the binary representation of one or both SPObjects is available.
* If the comparator does not support this method or if an ordering can not
* be determined from the available data then zero (0) should be returned.
*
* @param d1 The first gDay's byte buffer
* @param d2 The second gDay's byte buffer
* @param d2Size The number of bytes to compare
*
* @return Whether the first prefix is greater than (> 0), less than (< 0),
* or equal to (0) the other
*/
public int comparePrefix(ByteBuffer d1, ByteBuffer d2, int d2Size) {
return 0;
}
/**
* Compares the content of a byte buffer to the other and determines whether
* they are equal or not.
*
* @param d1 The first byte buffer
* @param d2 The second byte buffer
* @return Whether the first buffer's content is greater than (> 0), less
* than (< 0), or equal to (0) the other
*/
public int compare(ByteBuffer d1, int st1, ByteBuffer d2, int st2) {
int c = AbstractSPObject.compare(d1.getLong(), d2.getLong());
if (c != 0) return c;
// if equal then compare the timezone bytes
return AbstractSPObject.compare(d1.get(), d2.get());
}
}
}