/*
* 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 java.io.Serializable;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.logging.Level;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.impl.support.TreeSet6;
import org.ccnx.ccn.profiles.VersioningProfile;
import org.ccnx.ccn.protocol.Component;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.Exclude;
import org.ccnx.ccn.protocol.ExcludeAny;
import org.ccnx.ccn.protocol.ExcludeComponent;
import org.ccnx.ccn.protocol.Interest;
/**
* Stores state about a specific Interest on the wire. This class does not
* do any network transactions, it only stores state about a specific interest
* and will generate a new Interest message based on its current start, stop,
* and exclusion list.
*
*/
public class InterestData {
// VersioningInterestManager uses an ordering by _startTime.
// For a data structure like TreeSet to be serializable, its comparator must be serializable
public static class StartTimeComparator implements Comparator<InterestData>, Serializable {
private static final long serialVersionUID = -1033458449981547213L;
/**
* Order by startTime using UNSIGNED COMPARISON
*/
public int compare(InterestData arg0, InterestData arg1) {
synchronized(arg0) {
synchronized(arg1) {
return(arg0._startTime.compareTo(arg1._startTime));
}
}
}
}
/**
* An Interest with unbounded timespan
* @param basename
*/
public InterestData(ContentName basename) {
this(basename, VersionNumber.getMinimumVersion(), VersionNumber.getMaximumVersion());
}
/**
* An Interest with only a lower bound
* @param basename
* @param startTime
*/
public InterestData(ContentName basename, VersionNumber startTime) {
this(basename, startTime, VersionNumber.getMaximumVersion());
}
/**
* @param startTime minimum version to include.
* @param stopTime maximum version to include.
*/
public InterestData(ContentName basename, VersionNumber startTime, VersionNumber stopTime) {
_name = basename;
setStartTime(startTime);
setStopTime(stopTime);
}
public synchronized int size() {
return _excludedVersions.size();
}
/**
* Implement equals based on name, startTime, stopTime, and the excluded version numbers
*/
@Override
public synchronized boolean equals(Object obj) throws ClassCastException {
if( null == obj )
throw new ClassCastException("Null value");
if( !(obj instanceof InterestData) )
throw new ClassCastException("Not a InterestData");
synchronized(obj) {
InterestData other = (InterestData) obj;
if( _name.equals(other._name) ) {
if(_startTime.equals(other._startTime) && _stopTime.equals(other._stopTime) ) {
if( _excludedVersions.equals(other._excludedVersions) ) {
return true;
}
}
}
return false;
}
}
/**
* Implement hashCode() so we can ensure consistency with equals
*/
@Override
public synchronized int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + _name.hashCode();
result = prime * result + _startTime.hashCode();
result = prime * result + _stopTime.hashCode();
// this is the cumulative hashcode of everything in the set
result = prime * result + _excludedVersions.hashCode();
return result;
}
/**
* Dont do this while in a sorted set, as the sort order will break.
* Start time is the minimum version to include.
* in milliseconds (not binarytime)
*
@param startTime minimum version to include (milliseconds). The earliest time is NO_START_TIME.
* if a startTime < NO_START_TIME is given (e.g. 0), NO_START_TIME is used.
*/
public synchronized void setStartTime(VersionNumber startTime) {
if( VersionNumber.getMinimumVersion().after(startTime) )
startTime = VersionNumber.getMinimumVersion();
_startTime = startTime;
_dirty = true;
}
/**
* stopTime is the maximum version to include.
* use NO_STOP_TIME for infinity. If a greater value (unsigned comparison) is
* given, NO_STOP_TIME is used.
* in milliseconds (not binarytime)
*/
public synchronized void setStopTime(VersionNumber stopTime) {
if( VersionNumber.getMaximumVersion().before(stopTime) )
stopTime = VersionNumber.getMaximumVersion();
_stopTime = stopTime;
_dirty = true;
}
/**
* Returns false if too many excludes in this Interest
* @param version
* @return
*/
public synchronized boolean addExclude(VersionNumber version) {
if( _excludedVersions.size() >= VersioningInterestManager.MAX_FILL ) {
if( Log.isLoggable(Log.FAC_ENCODING, Level.FINE) )
Log.fine(Log.FAC_ENCODING, "addExclude full, not adding {0}", version);
return false;
}
addExcludeUnbounded(version);
return true;
}
public synchronized Interest buildInterest() {
if( !_dirty )
return _interest;
ArrayList<Exclude.Element> components = new ArrayList<Exclude.Element>();
byte [] startTimeMinusOneComponent;
if( VersionNumber.getMinimumVersion().before(_startTime) ) {
VersionNumber startTimeMinusOne = _startTime.addAndReturn(-1);
startTimeMinusOneComponent = startTimeMinusOne.getVersionBytes();
} else {
// if we're starring at 0, exclude below that. We actually cannot
// express this as a CCNTime
startTimeMinusOneComponent = VersioningProfile.BOTTOM_EXCLUDE_VERSION_MARKER;
}
components.add(new ExcludeAny());
components.add(new ExcludeComponent(startTimeMinusOneComponent));
if( Log.isLoggable(Log.FAC_ENCODING, Level.FINEST) )
Log.finest(Log.FAC_ENCODING, "Exclusion: start version {0}",
Component.printURI(startTimeMinusOneComponent));
// Now add the specific exclusions
ExcludeComponent lastComponentExcluded = null;
// TreeSet is sorted, so this is in right order for the exclusion filter
Iterator<VersionNumber> i = _excludedVersions.iterator();
while( i.hasNext() ) {
VersionNumber elem = i.next();
lastComponentExcluded = new ExcludeComponent(elem.getVersionBytes());
components.add(lastComponentExcluded);
}
// Now exclude everything after stop time
ExcludeComponent exStop = null;
byte [] stopTimePlusOneComponent;
if( VersionNumber.getMaximumVersion().after(_stopTime))
stopTimePlusOneComponent = _stopTime.addAndReturn(1).getVersionBytes();
else
stopTimePlusOneComponent = VersioningProfile.TOP_EXCLUDE_VERSION_MARKER;
exStop = new ExcludeComponent(stopTimePlusOneComponent);
// It could happen that our stop time is exactly equal to the version of an
// exclusion we already made. if that's the case, don't add a duplicate
if( null == lastComponentExcluded || ! lastComponentExcluded.equals(exStop) )
components.add(exStop);
components.add(new ExcludeAny());
if( Log.isLoggable(Log.FAC_ENCODING, Level.FINEST) )
Log.finest(Log.FAC_ENCODING, "Exclusion: stop version {0}", exStop.toString());
Exclude exclude;
try {
exclude = new Exclude(components);
} catch(InvalidParameterException ipe) {
// all of this verbosity was to catch a bug in the startTime component.
// That's fixed now, but if this ever comes up, its something serious.
ipe.printStackTrace();
Log.severe(Log.FAC_ENCODING, this.toString());
StringBuilder sb_asVersion = new StringBuilder();
StringBuilder sb_asLongs = new StringBuilder();
for(VersionNumber vn : _excludedVersions) {
sb_asVersion.append(String.format("%s, ", vn.printAsVersionComponent()));
sb_asLongs.append( String.format("%d, ", vn.getAsMillis()));
}
Log.severe(Log.FAC_ENCODING, "Versions : " + sb_asVersion.toString());
Log.severe(Log.FAC_ENCODING, "Longs : " + sb_asLongs.toString());
Log.severe(Log.FAC_ENCODING, "Parameters: " + components.toString());
throw ipe;
}
if( Log.isLoggable(Log.FAC_ENCODING, Level.FINEST) )
Log.finest(Log.FAC_ENCODING, "Exclusion: {0}", exclude.toString());
Interest interest = Interest.last(
_name,
exclude,
(Integer) _name.count(),
(Integer) 3, // dont want anything beyond version/segment
(Integer) 2, // version, segment
null // publisher
);
// recompute density too. This is the only place
// where we set _dirty to false
getDensity();
_dirty = false;
_interest = interest;
return _interest;
}
/**
* return the last interest built.
*/
public Interest getLastInterest() {
return _interest;
}
/**
* Is #version contained in [startTime, stopTime]?
* Uses UNSIGNED COMPARISON
* @param version
* @return
*/
public synchronized boolean contains(VersionNumber version) {
if( _startTime.compareTo(version) <= 0 &&
_stopTime.compareTo(version) >= 0 )
return true;
return false;
}
public String toString() {
return String.format("InterestData(%s, %s, %s, %d)", _name, _startTime, _stopTime, _excludedVersions.size());
}
public String dumpContents() {
StringBuilder sb = new StringBuilder();
for( VersionNumber vn : _excludedVersions ) {
sb.append(vn.printAsVersionComponent());
sb.append(", ");
}
return sb.toString();
}
/**
* Split this object to the left, transferring #count elements
*/
public synchronized InterestData splitLeft(int count) {
// create a pristine object
InterestData left = new InterestData(_name, _startTime, _stopTime);
if( count > 0 )
transferLeft(left, count);
return left;
}
/**
* Split this object to the right, transferring #count elements
*/
public synchronized InterestData splitRight(int count) {
// create a pristine object
InterestData right = new InterestData(_name, _startTime, _stopTime);
if( count > 0 )
transferRight(right, count);
return right;
}
/**
* transfer #count items from head of exclusion list to #left. Caller
* has verified that #count items will fit in #left.
* @param left
* @param count
*/
public void transferLeft(InterestData left, int count) {
if( count <= 0 )
return;
// walk from the left
Iterator<VersionNumber> iter = _excludedVersions.iterator();
// this is a redundant condition
VersionNumber lastversion = null;
while( count-- > 0 && iter.hasNext() ) {
VersionNumber vn = iter.next();
lastversion = vn;
left.addExcludeUnbounded(lastversion);
iter.remove();
}
// now fixup the start and stop times
left.setStopTime(lastversion);
// add 1 tick
this.setStartTime(lastversion.addAndReturn(1));
if( Log.isLoggable(Log.FAC_ENCODING, Level.FINE) )
Log.fine(Log.FAC_ENCODING, String.format("TransferLeft to %s from %s", left.toString(), this.toString()));
}
/**
* transfer #count items from tail of exclusion list to #right. Caller
* has verified that #count items will fit in #right.
* @param right
* @param count
*/
public void transferRight(InterestData right, int count) {
if( count <= 0 )
return;
// walk from the right
Iterator<VersionNumber> iter = _excludedVersions.descendingIteratorCompatible();
// this is a redundant condition
VersionNumber lastversion = null;
while( count-- > 0 && iter.hasNext() ) {
VersionNumber vn = iter.next();
lastversion = vn;
right.addExcludeUnbounded(lastversion);
iter.remove();
}
// now fixup the start and stop times
right.setStartTime(lastversion);
// subtract 1 tick
this.setStopTime(lastversion.addAndReturn(-1));
if( Log.isLoggable(Log.FAC_ENCODING, Level.FINE) )
Log.fine(Log.FAC_ENCODING, String.format("TransferRight from %s to %s", this.toString(), right.toString()));
}
public synchronized VersionNumber getStartVersion() {
return _startTime;
}
public synchronized VersionNumber getStopVersion() {
return _stopTime;
}
/**
* @return stopTime - startTime + 1
*/
public synchronized long getWidth() {
return _stopTime.getAsMillis() - _startTime.getAsMillis() + 1;
}
public synchronized double getDensity() {
if( ! _dirty )
return _density;
_density = (double) size() / getWidth();
return _density;
}
/**
* Sanity check that all the excluded versions fall between
* [start, stop] inclusive, using unsigned comparison.
* @return
*/
public synchronized boolean validate() {
for(VersionNumber vn : _excludedVersions) {
if( ! contains(vn) )
return false;
}
return true;
}
// ===========================
private final TreeSet6<VersionNumber> _excludedVersions = new TreeSet6<VersionNumber>();
private final ContentName _name;
private VersionNumber _startTime;
private VersionNumber _stopTime;
private boolean _dirty = true;
private Interest _interest = null;
private double _density;
/**
* Used internally. Sometimes we want to intentionally overflow
* @param version
*/
protected synchronized void addExcludeUnbounded(VersionNumber version) {
if( Log.isLoggable(Log.FAC_ENCODING, Level.FINER) )
Log.finer(Log.FAC_ENCODING, String.format("addExcludeUnbounded %s", version.toString()));
_excludedVersions.add(version);
_dirty = true;
}
// ===================================
// Inner classes
// // public for testing
// public static class TimeElement implements Comparable<TimeElement> {
// public final CCNTime version;
// public final byte [] versionComponent;
//
// public TimeElement(CCNTime version) {
// this.version = version;
// this.versionComponent = VersioningProfile.timeToVersionComponent(version);
// }
//
// // So it is sortable
// @Override
// public int compareTo(TimeElement other) {
// return version.compareTo(other.version);
// }
// }
}