/* * Part of the CCNx Java Library. * * Copyright (C) 2011-2012 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.profiles.versioning; import org.ccnx.ccn.profiles.VersionMissingException; import org.ccnx.ccn.profiles.VersioningProfile; import org.ccnx.ccn.protocol.CCNTime; import org.ccnx.ccn.protocol.ContentName; /** * Represent the version used in a CCNx name. * * The core of this is based on a CCNTime, but CCNTime has two different * uses (getTime as long since epoch and binaryTime), which ends up being * confusing. This class should eventually be updated to just operate on * the raw byte array. * * A version number is an immutable object. * * This class is currently only used in the versioning package, not in * the VersioningProfile or elsewhere in the code. */ public class VersionNumber implements Comparable<VersionNumber>, ContentName.ComponentProvider { /** * Create a VersionNUmber with the current timestamp from * the system clock. */ public VersionNumber() { _version = CCNTime.now(); _versionComponent = VersioningProfile.timeToVersionComponent(_version); _binaryTime = _version.toBinaryTimeAsLong(); } /** * Create a VersionNumber based on the binaryTime of #version * @param version */ public VersionNumber(CCNTime version) { _version = new CCNTime(version); _versionComponent = VersioningProfile.timeToVersionComponent(_version); _binaryTime = _version.toBinaryTimeAsLong(); } /** * Create a version number from the milliseconds since epoch * (as per System.currentTimeMillis or CCNTime.getTime). * @param msecSinceEpoch */ public VersionNumber(long msecSinceEpoch) { _version = new CCNTime(msecSinceEpoch); _versionComponent = VersioningProfile.timeToVersionComponent(_version); _binaryTime = _version.toBinaryTimeAsLong(); } /** * Given a versioned ContentName, extract the version number * from the end. * @param versionedNamed * @exception IllegalArgumentException if the name is unversioned * @throws VersionMissingException */ public VersionNumber(ContentName versionedNamed) throws VersionMissingException { _version = VersioningProfile.getLastVersionAsTimestamp(versionedNamed); _versionComponent = VersioningProfile.timeToVersionComponent(_version); _binaryTime = _version.toBinaryTimeAsLong(); } /** * Create a VersionNumber from a byte array, such as from * a ContentName component. * @param versionComponent */ public VersionNumber(byte [] versionComponent) { _version = VersioningProfile.getVersionComponentAsTimestamp(versionComponent); _versionComponent = copyOf(versionComponent, versionComponent.length); _binaryTime = _version.toBinaryTimeAsLong(); } public VersionNumber(VersionNumber version1a) { this(version1a.getAsTime()); } /** * Return the byte array corresponding to this version, to * be used in the construction of a ContentName. Includes the version * marker %FD. * @return A copy of the internal byte array. */ public byte [] getVersionBytes() { return copyOf(_versionComponent, _versionComponent.length); } /** * Returns a new CCNTime object representing the version */ public CCNTime getAsTime() { return new CCNTime(_version); } /** * A representation of the version as milli-seconds since the epoch * (as per System.currentTimeMillis). * @return */ public long getAsMillis() { return _version.getTime(); } // in case someone really wants it, we could do this.... // /** // * Internal binary representation. Try not to use. // * @return // */ // public long getAsBinaryTime() { // return _version.toBinaryTimeAsLong(); // } /** * Add (or subtract if negative) #arg from the current version and * return a new object. The caller should understand that the * value is used as an unsigned long. * @return */ public VersionNumber addAndReturn(long count) { long binaryTime = _version.toBinaryTimeAsLong(); binaryTime += count; return new VersionNumber(CCNTime.fromBinaryTimeAsLong(binaryTime)); } /** * Add (or subtract) the given number of milliseconds from the * version and return a new object. * * @param msec * @return */ public VersionNumber addMillisecondsAndreturn(long msec) { long result = _version.getTime() + msec; return new VersionNumber(result); } public int compareTo(VersionNumber other) throws ClassCastException { if( null == other ) throw new ClassCastException("Null value"); if( isLessThanUnsigned(_binaryTime, other._binaryTime) ) return -1; if( isLessThanUnsigned(other._binaryTime, _binaryTime) ) return +1; return 0; } @Override public boolean equals(Object obj) throws ClassCastException { if( null == obj ) throw new ClassCastException("Null value"); if( !(obj instanceof VersionNumber) ) throw new ClassCastException("Not a VersionNumber"); VersionNumber other = (VersionNumber) obj; return (_binaryTime == other._binaryTime); } @Override public int hashCode() { return _version.hashCode(); } /** * return the URI-encoded representation and the msec representation as: * "%s (%d)" */ @Override public String toString() { if( null == _asString ) { synchronized(this) { _asString = String.format("%s (%d)", VersioningProfile.printAsVersionComponent(_version), _version.getTime()); } } return _asString; } /** * Print the URI-encoded representation * @return */ public String printAsVersionComponent() { return VersioningProfile.printAsVersionComponent(_version); } public static VersionNumber now() { return new VersionNumber(CCNTime.now()); } // ======================================== // static methods protected final static VersionNumber minVersionNumber = new VersionNumber(VersioningProfile.MIN_VERSION_MARKER); protected final static VersionNumber maxVersionNumber = new VersionNumber(VersioningProfile.MAX_VERSION_MARKER); public static VersionNumber getMaximumVersion() { return maxVersionNumber; } public static VersionNumber getMinimumVersion() { return minVersionNumber; } /** * To compare versions, we need unsigned math * @param n1 * @param n2 * @return true if n1 < n2 unsigned */ protected static boolean isLessThanUnsigned(long n1, long n2) { // see http://www.javamex.com/java_equivalents/unsigned_arithmetic.shtml return (n1 < n2) ^ ((n1 < 0) != (n2 < 0)); } /** * @param time * @return true if this version is strictly less than #time */ public boolean before(VersionNumber time) { return compareTo(time) < 0; } /** * @param time * @return true if this version is strictly greater than #time */ public boolean after(VersionNumber time) { return compareTo(time) > 0; } /** * @param time * @return true if this version is strictly less than #time */ public boolean before(CCNTime time) { return _version.compareTo(time) < 0; } /** * @param time * @return true if this version is strictly greater than #time */ public boolean after(CCNTime time) { return _version.compareTo(time) > 0; } // ========================================= // Internal implementation protected final CCNTime _version; protected final byte [] _versionComponent; protected final long _binaryTime; protected String _asString = null; /** * Copy #input to a new array of #length, padding with 0's as necessary * * This is necessary because Java 1.5 does not support Arrays.copyof() * * @param input * @param length * @return */ protected static byte [] copyOf(byte [] input, int length) { byte [] output = new byte[length]; int min = (input.length < length) ? input.length : length; for(int i = 0; i < min; i++) output[i] = input[i]; for(int i = input.length; i < length; i++) output[i] = (byte) 0; return output; } public byte[] getComponent() { return _versionComponent; } }