DirectoryEntryMap.java
package emissary.directory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nullable;
/**
* Keep a map of DataID to DirectoryEntryList for the Directory Extensible to use other things for the key if desired,
* just override the methods that figure out the key automatically from the DirectoryEntry or DirectoryEntryList.
*/
public class DirectoryEntryMap extends ConcurrentHashMap<String, DirectoryEntryList> {
// Serializable
static final long serialVersionUID = 9097156614421373808L;
private static final Logger logger = LoggerFactory.getLogger(DirectoryEntryMap.class);
/** Value of DEEP_COPY flag */
public static final boolean DEEP_COPY = true;
/** Value of SHALLOW_COPY flag */
public static final boolean SHALLOW_COPY = false;
/**
* No arg ctor supplies our tuned defaults to the super ctor
*/
public DirectoryEntryMap() {
super(1024, 0.8f, 3);
}
/**
* Capacity ctor
*
* @param initialCapacity initial capacity for map
*/
public DirectoryEntryMap(final int initialCapacity) {
super(initialCapacity, 0.8f, 3);
}
/**
* Full ctor
*
* @param initialCapacity initial capacity for map
* @param loadFactor how loaded before rehash required
* @param concurrencyLevel how many threads can update at once
*/
public DirectoryEntryMap(final int initialCapacity, final float loadFactor, final int concurrencyLevel) {
super(initialCapacity, loadFactor, concurrencyLevel);
}
/**
* Create a new map from an old one. Makes a shallow copy
*
* @param map the map to copy
*/
public DirectoryEntryMap(final DirectoryEntryMap map) {
this(map, SHALLOW_COPY);
}
/**
* Create a new map from an old one.
*
* @param map the map to copy
* @param deepCopy true if should be a deep copy
*/
public DirectoryEntryMap(@Nullable final DirectoryEntryMap map, final boolean deepCopy) {
this();
if (map != null) {
for (final Map.Entry<String, DirectoryEntryList> entry : map.entrySet()) {
if (deepCopy) {
final DirectoryEntryList dl = new DirectoryEntryList(entry.getValue(), DirectoryEntryList.DEEP_COPY);
this.put(entry.getKey(), dl);
} else {
this.put(entry.getKey(), entry.getValue());
}
}
}
}
/**
* Add a directory entry to the appropriate DirectoryEntryList If it is a duplicate entry in all parts except cost, only
* the lowest cost entry is kept. Either this entry or the one already in the list will be discarded.
*
* @param d the entry to add
*/
public void addEntry(final DirectoryEntry d) {
final String dataId = KeyManipulator.getDataId(d.getKey());
addEntry(dataId, d);
}
/**
* Add the directory entry to the specified list
*
* @param key the key to this map
* @param d the entry to add
*/
protected void addEntry(final String key, final DirectoryEntry d) {
DirectoryEntryList list = get(key);
if (list == null) {
list = new DirectoryEntryList();
put(key, list);
}
final int beforeSize = list.size();
list.add(d);
final int afterSize = list.size();
if (logger.isDebugEnabled()) {
// This check could be wrong since nothing is synchronized.
// Its understood and ok since it's just debug stuff
if (afterSize > beforeSize) {
logger.debug("Appears to have added entry {} list is now size {}", d.getKey(), afterSize);
} else {
logger.debug("Appear to have dropped or replaced entry {} list is still size {}", d.getKey(), afterSize);
}
}
}
/**
* Builds a super list of all the entries currently in all buckets of the map
*
* @return list of all directory entries
*/
public List<DirectoryEntry> allEntries() {
final List<DirectoryEntry> list = new ArrayList<>();
for (final DirectoryEntryList d : values()) {
list.addAll(d);
}
return list;
}
/**
* Builds a super list of all the entry keys currently in all buckets of the map
*
* @return list of all directory entry keys
*/
public List<String> allEntryKeys() {
final List<String> list = new ArrayList<>();
for (final DirectoryEntryList d : values()) {
for (final DirectoryEntry e : d) {
list.add(e.getFullKey());
}
}
return list;
}
/**
* Perform a super count of all the DirectoryEntry objects on all of the lists
*
* @return count of all DirectoryEntry contained in map
*/
public int entryCount() {
int count = 0;
for (final DirectoryEntryList d : values()) {
count += d.size();
}
return count;
}
/**
* Remove a directory entry from the map
*
* @param entryKey for the entry to remove, not wildcarded
* @return the removed entry or null if not found
*/
public DirectoryEntry removeEntry(final String entryKey) {
final String dataId = KeyManipulator.getDataId(entryKey);
return removeEntry(dataId, entryKey);
}
/**
* Remove the directory entry specified by entry key from the list specified by key
*
* @param key the key to this map
* @param entryKey the key of the DirectoryEntry to remove
* @return the removed object
*/
protected DirectoryEntry removeEntry(final String key, final String entryKey) {
DirectoryEntry removed = null;
final DirectoryEntryList list = get(key);
if (list != null) {
// NB: cannot remove from DirectoryEntryList through iterator
for (int i = 0; i < list.size(); i++) {
final DirectoryEntry entry = list.get(i);
if (entry.getKey().equals(entryKey)) {
removed = entry;
list.remove(i);
break;
}
}
// Remove the mapping if it is empty
if (list.isEmpty()) {
this.remove(key);
}
}
return removed;
}
/**
* Remove all entries that match the key returning them as a list
*
* @param key the key that must be matched (can be wildcarded)
* @return list of entries that were removed from the map
*/
public List<DirectoryEntry> removeAllMatching(final String key) {
return removeAllMatching(key, Long.MAX_VALUE);
}
/**
* Remove all entries that match the key returning them as a list
*
* @param key the key that must be matched (can be wildcarded)
* @param checkpoint only remove entries older than this value, use Long.MAX_VALUE to have all matching entries removed
* without regard to their age
* @return list of entries that were removed from the map
*/
public List<DirectoryEntry> removeAllMatching(final String key, final long checkpoint) {
final List<DirectoryEntry> removed = new ArrayList<>();
for (final DirectoryEntryList list : values()) {
// NB: cannot remove from DirectoryEntryList through iterator
// Need to mark and sweep
for (int i = 0; i < list.size(); i++) {
final DirectoryEntry entry = list.get(i);
if (KeyManipulator.gmatch(entry.getKey(), key) && entry.getAge() < checkpoint) {
removed.add(entry);
}
}
}
for (final DirectoryEntry d : removed) {
removeEntry(d.getKey());
}
return removed;
}
/**
* Find and remove all entries on a directory
*
* @param key the key of the directory
* @return list of all entries removed
*/
public List<DirectoryEntry> removeAllOnDirectory(final String key) {
// Wildcard the key so we can just use gmatch
final String wckey = KeyManipulator.getHostMatchKey(key);
return removeAllMatching(wckey);
}
/**
* Find and remove all entries on a directory
*
* @param key the key of the directory
* @param checkpoint only remove entries older than this
* @return list of all entries removed
*/
public List<DirectoryEntry> removeAllOnDirectory(final String key, final long checkpoint) {
// Wildcard the key so we can just use gmatch
final String wckey = KeyManipulator.getHostMatchKey(key);
return removeAllMatching(wckey, checkpoint);
}
/**
* Collect all entries that match the key returning them as a list
*
* @param key the key that must be matched (can be wildcarded)
* @return list of entries that were matches, still live in the directory map
*/
public List<DirectoryEntry> collectAllMatching(final String key) {
final List<DirectoryEntry> match = new ArrayList<>();
final String dataId = KeyManipulator.getDataId(key);
if (dataId.contains("*") || dataId.contains("?")) {
for (final DirectoryEntryList list : values()) {
for (final DirectoryEntry entry : list) {
if (KeyManipulator.gmatch(entry.getKey(), key)) {
match.add(entry);
}
}
}
} else {
final DirectoryEntryList list = this.get(dataId);
if (list != null) {
for (final DirectoryEntry entry : list) {
if (KeyManipulator.gmatch(entry.getKey(), key)) {
match.add(entry);
}
}
}
}
return match;
}
/**
* Find all entries on a directory
*
* @param key the key of the directory
* @return list of all entries matched, still live in directory map
*/
public List<DirectoryEntry> collectAllOnDirectory(final String key) {
// Wildcard the key so we can just use gmatch
final String wckey = KeyManipulator.getHostMatchKey(key);
return collectAllMatching(wckey);
}
/**
* Count all entries that match the key
*
* @param key the key that must be matched (can be wildcarded)
* @return count of entries that were matches
*/
public int countAllMatching(final String key) {
int count = 0;
for (final DirectoryEntryList list : values()) {
for (DirectoryEntry entry : list) {
if (KeyManipulator.gmatch(entry.getKey(), key)) {
count++;
}
}
}
return count;
}
/**
* Count all entries on a directory
*
* @param key the key of the directory
* @return count of all entries matched
*/
public int countAllOnDirectory(final String key) {
// Wildcard the key so we can just use gmatch
final String wckey = KeyManipulator.getHostMatchKey(key);
return countAllMatching(wckey);
}
/**
* Merge new entries in our local entry map
*
* @param entryList the list of entries to merge
*/
public void addEntries(@Nullable final List<DirectoryEntry> entryList) {
if (entryList != null) {
for (final DirectoryEntry d : entryList) {
addEntry(d);
}
}
}
/**
* Merge new non-local peer entries into our local entry map.
*
* @param that the new entries
*/
public void addEntries(@Nullable final DirectoryEntryMap that) {
if (that != null) {
for (final Map.Entry<String, DirectoryEntryList> entry : that.entrySet()) {
// Optimized add since already grouped by same key
DirectoryEntryList list = this.get(entry.getKey());
if (list == null) {
list = new DirectoryEntryList();
put(entry.getKey(), list);
}
list.addAll(entry.getValue());
}
}
}
/**
* Change cost on matching entries
*
* @param key the key to match
* @param increment cost increment
* @return List of string entry keys changed
*/
public List<String> addCostToMatching(final String key, final int increment) {
final List<DirectoryEntry> list = collectAllMatching(key);
final List<String> ret = new ArrayList<>();
for (final DirectoryEntry e : list) {
e.addCost(increment);
ret.add(e.getFullKey());
}
// Put them back in order if something changed
if (!ret.isEmpty()) {
sort();
}
return ret;
}
/**
* Force a sort on all the directory entry lists due to some external factors
*/
public void sort() {
for (final DirectoryEntryList list : values()) {
list.sort();
}
}
}