/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008, 2009, 2011 Palo Alto Research Center, Inc.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
* This library 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
* Lesser General Public License for more details. You should have received
* a copy of the GNU Lesser General Public License along with this library;
* if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
* Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.ccnx.ccn.protocol;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.ccnx.ccn.impl.support.DataUtils;
import org.ccnx.ccn.profiles.VersioningProfile;
/**
* CCN has a time representation for versions and signing times that
* is quantized at a granularity of 2^-12 seconds (approximately 1/4 msec).
* Because it does not quantize time evenly on millisecond boundaries,
* this can lead to confusion -- t = System.currentTimeMillis(), when
* used to set a version v, for example, then ends up with v != t as
* v is quantized and t isn't. Simply providing quantization utility
* routines to help developers turned out to be error-prone. So we
* are moving all uses of time in CCN to pre-quantized CCNTime objects,
* which encapsulates CCN time quantization (which could change),
* and gives developers ways to deal with it without having to think
* about it.
*
* CCNTime, all though it implements methods like getNanos, only
* represents time with a granularity equal to the underlying
* CCN wire format -- i.e. ~.25 msec.
*/
public class CCNTime extends Timestamp implements ContentName.ComponentProvider {
private static final long serialVersionUID = -1537142893653443100L;
private byte [] _binarytime = null;
/**
* This is the highest nanos value that doesn't quantize to over the ns limit for Timestamp of 999999999.
*/
public static final int NANOS_MAX = 999877929;
/**
* Create a CCNTime.
* @param msec time in msec
*/
public CCNTime(long msec) {
this((msec/1000) * 1000, (msec % 1000) * 1000000L);
}
/**
* Create a CCNTime
* @param timestamp source timestamp to initialize from, some precision will be lost
*/
public CCNTime(Timestamp timestamp) {
this(timestamp.getTime(), timestamp.getNanos());
}
/**
* Create a CCNTime
* @param time source Date to initialize from, some precision will be lost
* as CCNTime does not round to unitary milliseconds
*/
public CCNTime(Date time) {
this(time.getTime());
}
/**
* Create a CCNTime from its binary encoding
* @param binaryTime12 the binary representation of a CCNTime
*/
public CCNTime(byte [] binaryTime12) {
this(DataUtils.byteArrayToUnsignedLong(binaryTime12), true);
if ((null == binaryTime12) || (binaryTime12.length == 0)) {
throw new IllegalArgumentException("Invalid binary time!");
} else if (binaryTime12.length > 6) {
throw new IllegalArgumentException("Time unacceptably far in the future, can't decode: " + DataUtils.printHexBytes(binaryTime12));
}
}
/**
* Creates a CCNTime initialized to the current time.
*/
public CCNTime() {
this(System.currentTimeMillis());
}
/**
* Creates a CCNTime from a specification of msec and nanos, to ease implementing
* compatibility with Timestamp and Date. Note that there is redundant data here --
* the last 3 significant digits of msec are also represented as the top 3 of nanos.
* We take the version in the nanos. This is derived from Java's slightly odd Timestamp handling.
* @param msec milliseconds
* @param nanos nanoseconds
*/
protected CCNTime(long msec, long nanos) {
this(toBinaryTimeAsLong(msec, nanos), true);
}
/**
* Creates a CCNTime from the internal long representation of the quantized time.
* Equivalent to setFromBinaryTimeAsLong.
* @param binaryTimeAsLong the time in our internal units
* @param unused a marker parameter to separate this from another constructor
*/
protected CCNTime(long binaryTimeAsLong, boolean unused) {
super((binaryTimeAsLong / 4096L) * 1000L);
super.setNanos((int)(((binaryTimeAsLong % 4096L) * 1000000000L) / 4096L));
}
/**
* Factory method to generate a CCNTime from our internal long time representation.
* Make this a static method to avoid confusion; should be little used
* @return
*/
public static CCNTime fromBinaryTimeAsLong(long binaryTimeAsLong) {
return new CCNTime(binaryTimeAsLong, true);
}
/**
* Generate the binary representation of a CCNTime
* @return the binary representation we use for encoding
*/
public byte [] toBinaryTime() {
if( null == _binarytime ) {
byte [] b = DataUtils.unsignedLongToByteArray(toBinaryTimeAsLong());
_binarytime = b;
}
return _binarytime;
}
/**
* Generate the internal long representation of a CCNTime, useful for comparisons
* and used internally
* @return the long representation of this time in our internal units
*/
public long toBinaryTimeAsLong() {
return toBinaryTimeAsLong(getTime(), getNanos());
}
/**
* Static method to convert from milliseconds and nanoseconds to our
* internal long representation.
* Assumes that nanos also contains the integral milliseconds for this
* time. Ignores msec component in msec.
* @param msec milliseconds
* @param nanos nanoseconds
* @return
*/
public static long toBinaryTimeAsLong(long msec, long nanos) {
long timeVal = (msec / 1000) * 4096L + (nanos * 4096L + 500000000L) / 1000000000L;
return timeVal;
}
protected void setFromBinaryTimeAsLong(long binaryTimeAsLong) {
_binarytime = null;
super.setTime((binaryTimeAsLong / 4096L) * 1000L);
super.setNanos((int)(((binaryTimeAsLong % 4096L) * 1000000000L) / 4096L));
}
@Override
public void setTime(long msec) {
_binarytime = null;
long binaryTimeAsLong = toBinaryTimeAsLong((msec/1000) * 1000, (msec % 1000) * 1000000L);
super.setTime((binaryTimeAsLong / 4096L) * 1000L);
super.setNanos((int)(((binaryTimeAsLong % 4096L) * 1000000000L) / 4096L));
}
@Override
public void setNanos(int nanos) {
_binarytime = null;
int quantizedNanos = (int)(((((nanos * 4096L + 500000000L) / 1000000000L)) * 1000000000L) / 4096L);
if ((quantizedNanos < 0) || (quantizedNanos > 999999999)) {
System.out.println("Quantizing nanos " + nanos + " resulted in out of range value " + quantizedNanos + "!");
}
super.setNanos(quantizedNanos);
}
/**
* Note: you have to use a relatively high value of nanos before you get across a quantization
* unit and have an impact. Our units are 2^-12 seconds, or ~250 msec. So 250000 nanos.
* @param nanos
*/
public void addNanos(int nanos) {
_binarytime = null;
setNanos(nanos + getNanos());
}
/**
* A helper method to increment to avoid collisions. Add a number
* of CCNx quantized time units to the time. Synchronize if modifications can
* be performed from multiple threads.
*/
public void increment(int timeUnits) {
_binarytime = null;
long binaryTimeAsLong = toBinaryTimeAsLong();
binaryTimeAsLong += timeUnits;
setFromBinaryTimeAsLong(binaryTimeAsLong);
}
/**
* We handle all comparison functions by quantizing the thing
* we are being compared to, then comparing. The only thing
* this won't catch is if a normal Timestamp or Date's comparison
* method is called with a CCNTime as an argument. This is a
* small risk, and worth it given the convenience of subclassing
* Timestamp and using it for most functionality.
*/
@Override
public boolean equals(Timestamp ts) {
return super.equals(new CCNTime(ts));
}
@Override
public int compareTo(Date o) {
return super.compareTo(new CCNTime(o));
}
@Override
public int compareTo(Timestamp ts) {
return super.compareTo(new CCNTime(ts));
}
@Override
public boolean before(Timestamp ts) {
return super.before(new CCNTime(ts));
}
@Override
public boolean after(Timestamp ts) {
return super.after(new CCNTime(ts));
}
@Override
public boolean before(Date when) {
return super.before(new CCNTime(when));
}
@Override
public boolean after(Date when) {
return super.after(new CCNTime(when));
}
/**
* Create a CCNTime initialized to the current date/time.
* @return the new CCNTime
*/
public static CCNTime now() {
return new CCNTime();
}
/**
* Generate a string representation of a CCNTime containing only date and HH:MM:SS,
* not milliseconds or nanoseconds.
*/
public String toShortString() {
// use . instead of : as URI printer will make it look nicer in the logs
SimpleDateFormat df = new SimpleDateFormat("yy-MM-dd-HH.mm.ss");
return df.format(this);
}
/**
* Convert the time into a version component, so CCNTime objects can be used
* directly in a ContentName builder.
*/
public final byte[] getComponent() {
return VersioningProfile.timeToVersionComponent(this);
}
}