/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008-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.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.Interest;
/**
* Table of Interests, holding an arbitrary value for any
* Interest or ContentName. This is conceptually like a Map<Interest, V> except it supports
* duplicate entries and has operations for access based on CCN
* matching. An InterestTable may be used to hold real Interests, or merely
* ContentNames only, though mixing the two in the same instance of InterestTable
* is not recommended. InterestTables are synchronized using _contents as a synchronization
* object
*
* Since interests can be reexpressed we could end up with duplicate
* interests in the table. To avoid that an LRU algorithm is
* optionally implemented to keep the table from growing without
* bounds.
*/
public class InterestTable<V> {
public interface Entry<T> {
/**
* Get the ContentName of this entry. All table entries have non-null
* ContentName.
* @return
*/
public ContentName name();
/**
* Get the Interest of this entry. If a name is entered in the table
* then the Interest will be null.
* @return Interest if present, null otherwise
*/
public Interest interest();
/**
* Get the value of this entry. A value may be null.
* @return
*/
public T value();
}
protected final static class LongestFirstComparator implements Comparator<ContentName>{
public int compare(ContentName o1, ContentName o2) {
if (o1 == o2)
return 0;
int thisCount = o1.count();
int oCount = o2.count();
if (thisCount == oCount)
return o1.compareTo(o2);
return (oCount - thisCount);
}
}
protected SortedMap<ContentName,List<Holder<V>>> _contents = new TreeMap<ContentName,List<Holder<V>>>(new LongestFirstComparator()) {
private static final long serialVersionUID = -2774858588706066528L;
@Override
public String toString() {
StringBuffer s = new StringBuffer();
for(ContentName n : keySet() )
s.append(n.toString() + " ");
return s.toString();
}
};
protected List<ContentName> _contentNamesLRU = null;
protected Integer _capacity = null; // For LRU size control - default is none
protected abstract class Holder<T> implements Entry<T> {
protected T value;
public Holder(T v) {
value= v;
}
public T value() {
return value;
}
}
protected class NameHolder<T> extends Holder<T> {
protected ContentName name;
public NameHolder(ContentName n, T v) {
super(v);
name = n;
}
public ContentName name() {
return name;
}
public Interest interest() {
return null;
}
}
protected class InterestHolder<T> extends Holder<T> {
protected Interest interest;
public InterestHolder(Interest i, T v) {
super(v);
interest = i;
}
public ContentName name() {
return interest.name();
}
public Interest interest() {
return interest;
}
}
/**
* Set capacity for LRU size control. Defaults to
* no size control
*
* @param capacity
*/
public void setCapacity(int capacity) {
synchronized (_contents) {
_capacity = capacity;
_contentNamesLRU = new LinkedList<ContentName>();
}
}
/**
* Gets the current capacity for LRU size control
* @return the capacity. null if not set
*/
public Integer getCapacity() {
synchronized (_contents) {
return _capacity;
}
}
/**
* Add a value associated with an interest to the table
*
* @param interest the interest
* @param value associated object
*/
public void add(Interest interest, V value) {
if (null == interest) {
throw new NullPointerException("InterestTable may not contain null Interest");
}
if (null == interest.name()) {
throw new NullPointerException("InterestTable may not contain Interest with null name");
}
if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "adding interest {0}", interest);
Holder<V> holder = new InterestHolder<V>(interest, value);
add(holder);
}
/**
* Add a value associated with content to the table
*
* @param name name of the content
* @param value associated object
*/
public void add(ContentName name, V value) {
if (null == name) {
throw new NullPointerException("InterestTable may not contain null name");
}
if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "adding name {0}", name);
Holder<V> holder = new NameHolder<V>(name, value);
add(holder);
}
/**
* Add a value holder - could be interest or content
*
* @param holder
*/
protected void add(Holder<V> holder) {
ContentName name = holder.name();
synchronized (_contents) {
if (_contents.containsKey(name)) {
List<Holder<V>> list = _contents.get(name);
list.add(holder);
if (null != _capacity) {
// Have to update our LRUness
_contentNamesLRU.remove(name);
_contentNamesLRU.add(name);
}
} else {
ArrayList<Holder<V>> list = new ArrayList<Holder<V>>(1);
list.add(holder);
if (null != _capacity) {
if ( _contents.size() >= _capacity) {
// The LRU is the first key in the LRU list. So remove the contents
// corresponding to that one.
// XXX - should we care about whether the key has multiple
// interests attached?
if (Log.isLoggable(Log.FAC_ENCODING, Level.INFO)) {
Log.info(Log.FAC_ENCODING, "removing entry associated with name {0}", _contentNamesLRU.get(0));
}
_contents.remove(_contentNamesLRU.get(0));
_contentNamesLRU.remove(0);
}
_contentNamesLRU.add(name);
}
_contents.put(name, list);
}
}
}
protected Holder<V> getMatchByName(ContentName name, ContentObject target) {
List<Holder<V>> list;
synchronized (_contents) {
list = _contents.get(name);
if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "name: {0} target: {1} possible matches: {2}", name, target.name(), ((null == list) ? 0 : list.size()));
if (null != list) {
for (Iterator<Holder<V>> holdIt = list.iterator(); holdIt.hasNext(); ) {
Holder<V> holder = holdIt.next();
if (null != holder.interest()) {
if (holder.interest().matches(target)) {
return holder;
}
}
}
}
}
return null;
}
/**
* Internal: return all the entries having exactly the specified name,
* useful once you have found the matching names to collect entries from them
*
* @param name
* @param target
* @return
*/
protected List<Holder<V>> getAllMatchByName(ContentName name, ContentObject target) {
if(Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "name: {0} target: {1}", name, target.name());
List<Holder<V>> matches = new ArrayList<Holder<V>>();
List<Holder<V>> list;
synchronized (_contents) {
list = _contents.get(name);
if (null != list) {
for (Iterator<Holder<V>> holdIt = list.iterator(); holdIt.hasNext(); ) {
Holder<V> holder = holdIt.next();
if (null != holder.interest()) {
if (holder.interest().matches(target)) {
matches.add(holder);
}
}
}
}
}
return matches;
}
protected Holder<V> removeMatchByName(ContentName name, ContentObject target) {
if(Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "name: {0} target: {1}", name, target.name());
synchronized (_contents) {
List<Holder<V>> list = _contents.get(name);
if (null != list) {
for (Iterator<Holder<V>> holdIt = list.iterator(); holdIt.hasNext(); ) {
Holder<V> holder = holdIt.next();
if (null != holder.interest()) {
if (holder.interest().matches(target)) {
holdIt.remove();
if (list.size() == 0) {
_contents.remove(name);
}
return holder;
}
}
}
}
}
return null;
}
/**
* Remove first exact match entry (both name and value match).
*
* @param name ContentName of name
* @param value associated value
*
* @return the matching entry or null if none found
*/
public Entry<V> remove(ContentName name, V value) {
Holder<V> result = null;
synchronized (_contents) {
List<Holder<V>> list = _contents.get(name);
if (null != list) {
for (Iterator<Holder<V>> holdIt = list.iterator(); holdIt.hasNext(); ) {
Holder<V> holder = holdIt.next();
if (null == holder.value()) {
if (null == value) {
holdIt.remove();
result = holder;
}
} else {
if (holder.value().equals(value)) {
holdIt.remove();
result = holder;
}
}
}
if (list.size() == 0) {
synchronized (_contents) {
_contents.remove(name);
}
}
}
}
return result;
}
/**
* Remove first exact match entry (both interest and value match)
*
* @param interest Interest to match
* @param value associated value
* @return the matching entry or null if none found
*/
public Entry<V> remove(Interest interest, V value) {
Holder<V> result = null;
ContentName name = interest.name();
synchronized (_contents) {
List<Holder<V>> list = _contents.get(name);
if (null != list) {
for (Iterator<Holder<V>> holdIt = list.iterator(); holdIt.hasNext(); ) {
Holder<V> holder = holdIt.next();
if (interest.equals(holder.interest())) {
if (null == holder.value()) {
if (null == value) {
holdIt.remove();
result = holder;
}
} else {
if (holder.value().equals(value)) {
holdIt.remove();
result = holder;
}
}
}
}
if (list.size() == 0) {
_contents.remove(name);
}
}
}
return result;
}
protected List<Holder<V>> removeAllMatchByName(ContentName name, ContentObject target) {
List<Holder<V>> matches = new ArrayList<Holder<V>>();
synchronized (_contents) {
List<Holder<V>> list = _contents.get(name);
if (null != list) {
for (Iterator<Holder<V>> holdIt = list.iterator(); holdIt.hasNext(); ) {
Holder<V> holder = holdIt.next();
if (null != holder.interest()) {
if (holder.interest().matches(target)) {
holdIt.remove();
matches.add(holder);
}
}
}
if (list.size() == 0) {
_contents.remove(name);
}
}
}
return matches;
}
/**
* Get value of longest matching Interest for a ContentObject, where longest is defined
* as longest ContentName. Any ContentName entries in the table will be
* ignored by this operation. If there are multiple matches, first is returned.
*
* @param target - desired ContentObject
* @return Entry of longest match if any, null if no match
*/
public V getValue(ContentObject target) {
Entry<V> match = getMatch(target);
if (null != match) {
return match.value();
} else {
return null;
}
}
/**
* Get longest matching Interest for a ContentObject. This is the same as
* getValue() except that the Entry is returned so the matching item
* may be retrieved and null value may be detected. The Entry returned will have a
* non-null interest because this method matches only Interests in the table.
*
* @param target - desired ContentObject
* @return Entry of longest match if any, null if no match
*/
public Entry<V> getMatch(ContentObject target) {
if(Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "target: {0}", target.name());
Entry<V> match = null;
Set<ContentName> names;
synchronized (_contents) {
names = _contents.keySet();
for (ContentName name : names) {
match = getMatchByName(name, target);
if (null != match)
break;
}
}
return match;
}
/**
* Get values of all matching Interests for a ContentObject.
* Any ContentName entries in the table will be
* ignored by this operation and any null values will be ignored.
*
* @param target target ContentObject
* @return list of all matching values
*/
public List<V> getValues(ContentObject target) {
if(Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "target: {0}", target.name());
List<V> result = new ArrayList<V>();
List<Entry<V>> matches = getMatches(target);
for (Entry<V> entry : matches) {
if (null != entry.value()) {
result.add(entry.value());
}
}
return result;
}
/**
* Get all matching Interests for a ContentObject.
* Any ContentName entries in the table will be
* ignored by this operation, so every Entry returned will have a
* non-null interest. This is the same as getValues() except that
* Entry objects are returned.
*
* @param target - desired ContentObject
* @return List of matches, empty if no match
*/
public List<Entry<V>> getMatches(ContentObject target) {
if(Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "target object name: {0}", target.name());
List<Entry<V>> matches = new ArrayList<Entry<V>>();
if (null != target) {
synchronized (_contents) {
for (ContentName name : _contents.keySet()) {
// Name match - is there an interest match here?
matches.addAll(getAllMatchByName(name, target));
}
}
}
return matches;
}
/**
* Get value of longest matching Interest for a ContentName, where longest is defined
* as longest ContentName. If there are multiple matches, first is returned.
* This will return a mix of ContentName and Interest entries if they exist
* (and match) in the table, i.e. the Interest of an Entry may be null in some cases.
*
* @param target desired ContentName
* @return Entry of longest match if any, null if no match
*/
public V getValue(ContentName target) {
if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "target: {0}", target);
Entry<V> match = getMatch(target);
if (null != match) {
return match.value();
} else {
return null;
}
}
/**
* Get longest matching Interest. This method is the same as getValue()
* except that the Entry is returned so the matching item may be retrieved
* and null value may be detected.
*
* @param target desired ContentName
* @return longest matching entry or null if none found
*/
public Entry<V> getMatch(ContentName target) {
if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "target: {0}", target);
Entry<V> match = null;
synchronized (_contents) {
for (ContentName name : _contents.keySet()) {
if (name.isPrefixOf(target)) {
match = _contents.get(name).get(0);
break;
}
}
}
return match;
}
/**
* Get values matching a target ContentName
*
* @param target the desired ContentName
* @return list of values associated with this ContentName
*/
public List<V> getValues(ContentName target) {
if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "target: {0}", target);
List<V> result = new ArrayList<V>();
List<Entry<V>> matches = getMatches(target);
for (Entry<V> entry : matches) {
if (null != entry.value()) {
result.add(entry.value());
}
}
return result;
}
/**
* Get all matching entries for a ContentName.
* This will return a mix of ContentName and Interest entries if they exist
* (and match) in the table, i.e. the Interest of an Entry may be null in some cases.
*
* @param target desired ContentName
* @return List of matches ordered from longest match to shortest, empty if no match
*/
public List<Entry<V>> getMatches(ContentName target) {
if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "target: {0}", target);
List<Entry<V>> matches = new ArrayList<Entry<V>>();
synchronized (_contents) {
for (ContentName name : _contents.keySet()) {
if (name.isPrefixOf(target)) {
matches.addAll(_contents.get(name));
}
}
}
return matches;
}
/**
* Get all entries. This will return a mix of ContentName and Interest entries
* if they exist in the table, i.e. the Interest of an Entry may be null in some cases.
*
* @return Collection of entries in arbitrary order
*/
public Collection<Entry<V>> values() {
List<Entry<V>> results = new ArrayList<Entry<V>>();
synchronized (_contents) {
for (Iterator<ContentName> keyIt = _contents.keySet().iterator(); keyIt.hasNext();) {
ContentName name = keyIt.next();
List<Holder<V>> list = _contents.get(name);
results.addAll(list);
}
}
return results;
}
/**
* Remove and return value of the longest matching Interest for a ContentObject, where best is defined
* as longest ContentName. Any ContentName entries in the table will be
* ignored by this operation, as will null values.
*
* @param target - desired ContentObject
* @return value of longest match if any, null if no match
*/
public V removeValue(ContentObject target) {
Entry<V> match = removeMatch(target);
if (null != match) {
return match.value();
} else {
return null;
}
}
/**
* Remove and return the longest matching Interest for a ContentObject, where best is defined
* as longest ContentName. Any ContentName entries in the table will be
* ignored by this operation, so the Entry returned will have a
* non-null interest.
*
* @param target - desired ContentObject
* @return Entry of longest match if any, null if no match
*/
public Entry<V> removeMatch(ContentObject target) {
Entry<V> match = null;
if (null != target) {
ContentName matchName = null;
if(Log.isLoggable(Log.FAC_ENCODING, Level.FINEST))
Log.finest(Log.FAC_ENCODING, "removeMatch: looking for match to target {0} among {1} possibilities.", target.name(), _contents.keySet().size());
Set<ContentName> names;
synchronized (_contents) {
names = _contents.keySet();
for (ContentName name : names) {
match = getMatchByName(name, target);
if (null != match) {
matchName = name;
break;
}
// Do not remove here -- need to find best match and avoid disturbing iterator
}
if (null != match) {
return removeMatchByName(matchName, target);
}
}
}
return match;
}
/**
* Remove and return values for all matching Interests for a ContentObject.
* Any ContentName entries in the table will be
* ignored by this operation. Null values will not be represented in returned
* list though their Interests will have been removed if any.
*
* @param target - desired ContentObject
* @return List of matches ordered from longest match to shortest, empty if no match
*/
public List<V> removeValues(ContentObject target) {
List<V> result = new ArrayList<V>();
List<Entry<V>> matches = removeMatches(target);
for (Entry<V> entry : matches) {
if (null != entry.value()) {
result.add(entry.value());
}
}
return result;
}
/**
* Remove and return all matching Interests for a ContentObject.
* Any ContentName entries in the table will be
* ignored by this operation, so every Entry returned will have a
* non-null interest.
*
* @param target - desired ContentObject
* @return List of matches ordered from longest match to shortest, empty if no match
*/
public List<Entry<V>> removeMatches(ContentObject target) {
List<Entry<V>> matches = new ArrayList<Entry<V>>();
List<ContentName> names = new ArrayList<ContentName>();
Set<ContentName> LFCnames;
synchronized (_contents) {
LFCnames = _contents.keySet();
for (ContentName name : LFCnames) {
if (name.isPrefixOf(target.name())) {
// Name match - is there an interest match here?
matches.addAll(getAllMatchByName(name, target));
names.add(name);
}
}
if (matches.size() != 0) {
for (ContentName contentName : names) {
removeAllMatchByName(contentName, target);
}
}
}
return matches;
}
/**
* Get the number of distinct entries in the table. Note that duplicate entries
* are fully supported, so the number of entries may be much larger than the
* number of ContentNames (sizeNames()).
*
* @return the number of entries in the table
*/
public int size() {
int result = 0;
synchronized (_contents) {
for (Iterator<ContentName> nameIt = _contents.keySet().iterator(); nameIt.hasNext();) {
ContentName name = nameIt.next();
List<Holder<V>> list = _contents.get(name);
result += list.size();
}
}
return result;
}
/**
* Get the number of distinct ContentNames in the table. Note that duplicate
* entries are fully supported, so the number of ContentNames may be much smaller
* than the number of entries (size()).
*
* @return the number of ContentNames in the table
*/
public int sizeNames() {
synchronized (_contents) {
return _contents.size();
}
}
/**
* Clear the table
*/
public void clear() {
synchronized (_contents) {
_contents.clear();
}
}
}