/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.core;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.jcr.ItemNotFoundException;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.jackrabbit.core.id.ItemId;
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.state.ChildNodeEntry;
import org.apache.jackrabbit.core.state.ItemState;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.ItemStateManager;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.core.state.NodeStateListener;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
import org.apache.jackrabbit.spi.commons.name.PathBuilder;
import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
import org.apache.jackrabbit.spi.commons.name.PathMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of a <code>HierarchyManager</code> that caches paths of
* items.
*/
public class CachingHierarchyManager extends HierarchyManagerImpl
implements NodeStateListener {
/**
* Default upper limit of cached states
*/
public static final int DEFAULT_UPPER_LIMIT = 10000;
private static final int MAX_UPPER_LIMIT =
Integer.getInteger("org.apache.jackrabbit.core.CachingHierarchyManager.cacheSize", DEFAULT_UPPER_LIMIT);
private static final int CACHE_STATISTICS_LOG_INTERVAL_MILLIS =
Integer.getInteger("org.apache.jackrabbit.core.CachingHierarchyManager.logInterval", 60000);
/**
* Logger instance
*/
private static Logger log = LoggerFactory.getLogger(CachingHierarchyManager.class);
/**
* Mapping of paths to children in the path map
*/
private final PathMap<LRUEntry> pathCache = new PathMap<LRUEntry>();
/**
* Mapping of item ids to <code>LRUEntry</code> in the path map
*/
private final ReferenceMap idCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.HARD);
/**
* Cache monitor object
*/
private final Object cacheMonitor = new Object();
/**
* Upper limit
*/
private final int upperLimit;
/**
* Object collecting and logging statistics about the idCache
*/
private final CacheStatistics idCacheStatistics;
/**
* Head of LRU
*/
private LRUEntry head;
/**
* Tail of LRU
*/
private LRUEntry tail;
/**
* Flag indicating whether consistency checking is enabled.
*/
private boolean consistencyCheckEnabled;
/**
* Log interval for item state exceptions.
*/
private static final int ITEM_STATE_EXCEPTION_LOG_INTERVAL_MILLIS = 60 * 1000;
/**
* Last time-stamp item state exception was logged with a stacktrace.
*/
private long itemStateExceptionLogTimestamp = 0;
/**
* Create a new instance of this class.
*
* @param rootNodeId root node id
* @param provider item state manager
*/
public CachingHierarchyManager(NodeId rootNodeId,
ItemStateManager provider) {
super(rootNodeId, provider);
upperLimit = MAX_UPPER_LIMIT;
idCacheStatistics = new CacheStatistics();
if (log.isTraceEnabled()) {
log.trace("CachingHierarchyManager initialized. Max cache size = {}", upperLimit, new Exception());
} else {
log.debug("CachingHierarchyManager initialized. Max cache size = {}", upperLimit);
}
}
/**
* Enable or disable consistency checks in this instance.
*
* @param enable <code>true</code> to enable consistency checks;
* <code>false</code> to disable
*/
public void enableConsistencyChecks(boolean enable) {
this.consistencyCheckEnabled = enable;
}
//-------------------------------------------------< base class overrides >
/**
* {@inheritDoc}
*/
protected ItemId resolvePath(Path path, int typesAllowed)
throws RepositoryException {
Path pathToNode = path;
if ((typesAllowed & RETURN_NODE) == 0) {
// if we must not return a node, pass parent path
// (since we only cache nodes)
pathToNode = path.getAncestor(1);
}
PathMap.Element<LRUEntry> element = map(pathToNode);
if (element == null) {
// not even intermediate match: call base class
return super.resolvePath(path, typesAllowed);
}
LRUEntry entry = element.get();
if (element.hasPath(path)) {
// exact match: return answer
synchronized (cacheMonitor) {
entry.touch();
}
return entry.getId();
}
Path.Element[] elements = path.getElements();
try {
return resolvePath(elements, element.getDepth() + 1, entry.getId(), typesAllowed);
} catch (ItemStateException e) {
String msg = "failed to retrieve state of intermediary node for entry: "
+ entry.getId() + ", path: " + path.getString();
logItemStateException(msg, e);
log.debug(msg);
// probably stale cache entry -> evict
evictAll(entry.getId(), true);
}
// JCR-3617: fall back to super class in case of ItemStateException
return super.resolvePath(path, typesAllowed);
}
/**
* {@inheritDoc}
*/
protected void pathResolved(ItemId id, PathBuilder builder)
throws MalformedPathException {
if (id.denotesNode()) {
cache((NodeId) id, builder.getPath());
}
}
/**
* {@inheritDoc}
* <p>
* Overridden method tries to find a mapping for the intermediate item
* <code>state</code> and add its path elements to the builder currently
* being used. If no mapping is found, the item is cached instead after
* the base implementation has been invoked.
*/
protected void buildPath(
PathBuilder builder, ItemState state, CycleDetector detector)
throws ItemStateException, RepositoryException {
if (state.isNode()) {
PathMap.Element<LRUEntry> element = get(state.getId());
if (element != null) {
try {
Path.Element[] elements = element.getPath().getElements();
for (int i = elements.length - 1; i >= 0; i--) {
builder.addFirst(elements[i]);
}
return;
} catch (MalformedPathException mpe) {
String msg = "Failed to build path of " + state.getId();
log.debug(msg);
throw new RepositoryException(msg, mpe);
}
}
}
super.buildPath(builder, state, detector);
if (state.isNode()) {
try {
cache(((NodeState) state).getNodeId(), builder.getPath());
} catch (MalformedPathException mpe) {
log.warn("Failed to build path of " + state.getId());
}
}
}
//-----------------------------------------------------< HierarchyManager >
/**
* {@inheritDoc}
* <p>
* Overridden method simply checks whether we have an item matching the id
* and returns its path, otherwise calls base implementation.
*/
public Path getPath(ItemId id)
throws ItemNotFoundException, RepositoryException {
if (id.denotesNode()) {
PathMap.Element<LRUEntry> element = get(id);
if (element != null) {
try {
return element.getPath();
} catch (MalformedPathException mpe) {
String msg = "Failed to build path of " + id;
log.debug(msg);
throw new RepositoryException(msg, mpe);
}
}
}
return super.getPath(id);
}
/**
* {@inheritDoc}
*/
public Name getName(ItemId id)
throws ItemNotFoundException, RepositoryException {
if (id.denotesNode()) {
PathMap.Element<LRUEntry> element = get(id);
if (element != null) {
return element.getName();
}
}
return super.getName(id);
}
/**
* {@inheritDoc}
*/
public int getDepth(ItemId id)
throws ItemNotFoundException, RepositoryException {
if (id.denotesNode()) {
PathMap.Element<LRUEntry> element = get(id);
if (element != null) {
return element.getDepth();
}
}
return super.getDepth(id);
}
/**
* {@inheritDoc}
*/
public boolean isAncestor(NodeId nodeId, ItemId itemId)
throws ItemNotFoundException, RepositoryException {
if (itemId.denotesNode()) {
PathMap.Element<LRUEntry> element = get(nodeId);
if (element != null) {
PathMap.Element<LRUEntry> child = get(itemId);
if (child != null) {
return element.isAncestorOf(child);
}
}
}
return super.isAncestor(nodeId, itemId);
}
//----------------------------------------------------< ItemStateListener >
/**
* {@inheritDoc}
*/
public void stateCreated(ItemState created) {
}
/**
* {@inheritDoc}
*/
public void stateModified(ItemState modified) {
if (modified.isNode()) {
nodeModified((NodeState) modified);
}
}
/**
* {@inheritDoc}
*
* If path information is cached for <code>modified</code>, this iterates
* over all child nodes in the path map, evicting the ones that do not
* (longer) exist in the underlying <code>NodeState</code>.
*/
public void nodeModified(NodeState modified) {
synchronized (cacheMonitor) {
for (PathMap.Element<LRUEntry> element
: getCachedPaths(modified.getNodeId())) {
for (PathMap.Element<LRUEntry> child : element.getChildren()) {
ChildNodeEntry cne = modified.getChildNodeEntry(
child.getName(), child.getNormalizedIndex());
if (cne == null) {
// Item does not exist, remove
evict(child, true);
} else {
LRUEntry childEntry = child.get();
if (childEntry != null
&& !cne.getId().equals(childEntry.getId())) {
// Different child item, remove
evict(child, true);
}
}
}
}
checkConsistency();
}
}
private List<PathMap.Element<LRUEntry>> getCachedPaths(NodeId id) {
// JCR-2720: Handle the root path as a special case
if (rootNodeId.equals(id)) {
return Collections.singletonList(pathCache.map(
PathFactoryImpl.getInstance().getRootPath(), true));
}
LRUEntry entry = (LRUEntry) idCache.get(id);
if (entry != null) {
return Arrays.asList(entry.getElements());
} else {
return Collections.emptyList();
}
}
/**
* {@inheritDoc}
*/
public void stateDestroyed(ItemState destroyed) {
evictAll(destroyed.getId(), true);
}
/**
* {@inheritDoc}
*/
public void stateDiscarded(ItemState discarded) {
if (discarded.isTransient() && !discarded.hasOverlayedState()
&& discarded.getStatus() == ItemState.STATUS_NEW) {
// a new node has been discarded -> remove from cache
evictAll(discarded.getId(), true);
} else if (provider.hasItemState(discarded.getId())) {
evictAll(discarded.getId(), false);
} else {
evictAll(discarded.getId(), true);
}
}
/**
* {@inheritDoc}
*/
public void nodeAdded(NodeState state, Name name, int index, NodeId id) {
synchronized (cacheMonitor) {
if (idCache.containsKey(state.getNodeId())) {
// Optimization: ignore notifications for nodes that are not in the cache
try {
Path path = PathFactoryImpl.getInstance().create(getPath(state.getNodeId()), name, index, true);
nodeAdded(state, path, id);
checkConsistency();
} catch (PathNotFoundException e) {
log.warn("Unable to get path of node " + state.getNodeId()
+ ", event ignored.");
} catch (MalformedPathException e) {
log.warn("Unable to create path of " + id, e);
} catch (ItemNotFoundException e) {
log.warn("Unable to find item " + state.getNodeId(), e);
} catch (ItemStateException e) {
log.warn("Unable to find item " + id, e);
} catch (RepositoryException e) {
log.warn("Unable to get path of " + state.getNodeId(), e);
}
} else if (state.getParentId() == null && idCache.containsKey(id)) {
// A top level node was added
evictAll(id, true);
}
}
}
/**
* {@inheritDoc}
* <p>
* Iterate over all cached children of this state and verify each
* child's position.
*/
public void nodesReplaced(NodeState state) {
synchronized (cacheMonitor) {
LRUEntry entry = (LRUEntry) idCache.get(state.getNodeId());
if (entry == null) {
return;
}
for (PathMap.Element<LRUEntry> parent : entry.getElements()) {
HashMap<Path.Element, PathMap.Element<LRUEntry>> newChildrenOrder =
new HashMap<Path.Element, PathMap.Element<LRUEntry>>();
boolean orderChanged = false;
for (PathMap.Element<LRUEntry> child : parent.getChildren()) {
LRUEntry childEntry = (LRUEntry) child.get();
if (childEntry == null) {
// Child has no associated UUID information: we're
// therefore unable to determine if this child's
// position is still accurate and have to assume
// the worst and remove it.
evict(child, false);
} else {
NodeId childId = childEntry.getId();
ChildNodeEntry cne = state.getChildNodeEntry(childId);
if (cne == null) {
// Child no longer in parent node, so remove it
evict(child, false);
} else {
// Put all children into map of new children order
// - regardless whether their position changed or
// not - as we might need to reorder them later on.
Path.Element newNameIndex =
PathFactoryImpl.getInstance().createElement(
cne.getName(), cne.getIndex());
newChildrenOrder.put(newNameIndex, child);
if (!newNameIndex.equals(child.getPathElement())) {
orderChanged = true;
}
}
}
}
if (orderChanged) {
/* If at least one child changed its position, reorder */
parent.setChildren(newChildrenOrder);
}
}
checkConsistency();
}
}
/**
* {@inheritDoc}
*/
public void nodeRemoved(NodeState state, Name name, int index, NodeId id) {
synchronized (cacheMonitor) {
if (idCache.containsKey(state.getNodeId())) {
// Optimization: ignore notifications for nodes that are not in the cache
try {
Path path = PathFactoryImpl.getInstance().create(getPath(state.getNodeId()), name, index, true);
nodeRemoved(state, path, id);
checkConsistency();
} catch (PathNotFoundException e) {
log.warn("Unable to get path of node " + state.getNodeId()
+ ", event ignored.");
} catch (MalformedPathException e) {
log.warn("Unable to create path of " + id, e);
} catch (ItemStateException e) {
log.warn("Unable to find item " + id, e);
} catch (ItemNotFoundException e) {
log.warn("Unable to get path of " + state.getNodeId(), e);
} catch (RepositoryException e) {
log.warn("Unable to get path of " + state.getNodeId(), e);
}
} else if (state.getParentId() == null && idCache.containsKey(id)) {
// A top level node was removed
evictAll(id, true);
}
}
}
//------------------------------------------------------< private methods >
/**
* Return the first cached path that is mapped to given id.
*
* @param id node id
* @return cached element, <code>null</code> if not found
*/
private PathMap.Element<LRUEntry> get(ItemId id) {
synchronized (cacheMonitor) {
LRUEntry entry = (LRUEntry) idCache.get(id);
if (entry != null) {
entry.touch();
return entry.getElements()[0];
}
return null;
}
}
/**
* Return the nearest cached element in the path map, given a path.
* The returned element is guaranteed to have an associated object that
* is not <code>null</code>.
*
* @param path path
* @return cached element, <code>null</code> if not found
*/
private PathMap.Element<LRUEntry> map(Path path) {
synchronized (cacheMonitor) {
PathMap.Element<LRUEntry> element = pathCache.map(path, false);
while (element != null) {
LRUEntry entry = element.get();
if (entry != null) {
entry.touch();
return element;
}
element = element.getParent();
}
return null;
}
}
/**
* Cache an item in the hierarchy given its id and path.
*
* @param id node id
* @param path path to item
*/
private void cache(NodeId id, Path path) {
synchronized (cacheMonitor) {
if (isCached(id, path)) {
return;
}
if (idCache.size() >= upperLimit) {
idCacheStatistics.log();
/**
* Remove least recently used item. Scans the LRU list from
* head to tail and removes the first item that has no children.
*/
LRUEntry entry = head;
while (entry != null) {
PathMap.Element<LRUEntry>[] elements = entry.getElements();
int childrenCount = 0;
for (int i = 0; i < elements.length; i++) {
childrenCount += elements[i].getChildrenCount();
}
if (childrenCount == 0) {
evictAll(entry.getId(), false);
return;
}
entry = entry.getNext();
}
}
PathMap.Element<LRUEntry> element = pathCache.put(path);
if (element.get() != null) {
if (!id.equals(((LRUEntry) element.get()).getId())) {
log.debug("overwriting PathMap.Element");
}
}
LRUEntry entry = (LRUEntry) idCache.get(id);
if (entry == null) {
entry = new LRUEntry(id, element);
idCache.put(id, entry);
} else {
entry.addElement(element);
}
element.set(entry);
checkConsistency();
}
}
/**
* Return a flag indicating whether a certain node and/or path is cached.
* If <code>path</code> is <code>null</code>, check whether the item is
* cached at all. If <code>path</code> is <b>not</b> <code>null</code>,
* check whether the item is cached with that path.
*
* @param id item id
* @param path path, may be <code>null</code>
* @return <code>true</code> if the item is already cached;
* <code>false</code> otherwise
*/
boolean isCached(NodeId id, Path path) {
synchronized (cacheMonitor) {
LRUEntry entry = (LRUEntry) idCache.get(id);
if (entry == null) {
return false;
}
if (path == null) {
return true;
}
PathMap.Element<LRUEntry>[] elements = entry.getElements();
for (int i = 0; i < elements.length; i++) {
if (elements[i].hasPath(path)) {
return true;
}
}
return false;
}
}
/**
* Return a flag indicating whether a certain path is cached.
*
* @param path item path
* @return <code>true</code> if the item is already cached;
* <code>false</code> otherwise
*/
boolean isCached(Path path) {
synchronized (cacheMonitor) {
PathMap.Element<LRUEntry> element = pathCache.map(path, true);
if (element != null) {
return element.get() != null;
}
return false;
}
}
/**
* Remove all path mapping for a given item id. Removes the associated
* <code>LRUEntry</code> and the <code>PathMap.Element</code> with it.
* Indexes of same name sibling elements are shifted!
*
* @param id item id
*/
private void evictAll(ItemId id, boolean shift) {
synchronized (cacheMonitor) {
LRUEntry entry = (LRUEntry) idCache.get(id);
if (entry != null) {
PathMap.Element<LRUEntry>[] elements = entry.getElements();
for (int i = 0; i < elements.length; i++) {
evict(elements[i], shift);
}
}
checkConsistency();
}
}
/**
* Evict path map element from cache. This will traverse all children
* of this element and remove the objects associated with them.
* Index of same name sibling items are shifted!
*
* @param element path map element
*/
private void evict(PathMap.Element<LRUEntry> element, boolean shift) {
// assert: synchronized (cacheMonitor)
element.traverse(new PathMap.ElementVisitor<LRUEntry>() {
public void elementVisited(PathMap.Element<LRUEntry> element) {
LRUEntry entry = (LRUEntry) element.get();
if (entry.removeElement(element) == 0) {
idCache.remove(entry.getId());
entry.remove();
}
}
}, false);
element.remove(shift);
}
/**
* Invoked when a notification about a child node addition has been received.
*
* @param state node state where child was added
* @param path path to child node
* @param id child node id
*
* @throws PathNotFoundException if the path was not found
* @throws RepositoryException If the path's direct ancestor cannot be determined.
* @throws ItemStateException If the id cannot be resolved to a NodeState.
*/
private void nodeAdded(NodeState state, Path path, NodeId id)
throws RepositoryException, ItemStateException {
// assert: synchronized (cacheMonitor)
PathMap.Element<LRUEntry> element = null;
LRUEntry entry = (LRUEntry) idCache.get(id);
if (entry != null) {
// child node already cached: this can have the following
// reasons:
// 1) node was moved, cached path is outdated
// 2) node was cloned, cached path is still valid
NodeState child = null;
if (hasItemState(id)) {
child = (NodeState) getItemState(id);
}
if (child == null || !child.isShareable()) {
PathMap.Element<LRUEntry>[] elements = entry.getElements();
element = elements[0];
for (int i = 0; i < elements.length; i++) {
elements[i].remove();
}
}
}
PathMap.Element<LRUEntry> parent = pathCache.map(path.getAncestor(1), true);
if (parent != null) {
parent.insert(path.getNameElement());
}
if (element != null) {
// store remembered element at new position
pathCache.put(path, element);
}
}
/**
* Invoked when a notification about a child node removal has been received.
*
* @param state node state
* @param path node path
* @param id node id
*
* @throws PathNotFoundException if the path was not found.
* @throws RepositoryException If the path's direct ancestor cannot be determined.
* @throws ItemStateException If the id cannot be resolved to a NodeState.
*/
private void nodeRemoved(NodeState state, Path path, NodeId id)
throws RepositoryException, ItemStateException {
// assert: synchronized (cacheMonitor)
PathMap.Element<LRUEntry> parent =
pathCache.map(path.getAncestor(1), true);
if (parent == null) {
return;
}
PathMap.Element<LRUEntry> element =
parent.getDescendant(path.getLastElement(), true);
if (element != null) {
// with SNS, this might evict a child that is NOT the one
// having <code>id</code>, check first whether item has
// the id passed as argument
LRUEntry entry = (LRUEntry) element.get();
if (entry != null && !entry.getId().equals(id)) {
return;
}
// if item is shareable, remove this path only, otherwise
// every path this item has been mapped to
NodeState child = null;
if (hasItemState(id)) {
child = (NodeState) getItemState(id);
}
if (child == null || !child.isShareable()) {
evictAll(id, true);
} else {
evict(element, true);
}
} else {
// element itself is not cached, but removal might cause SNS
// index shifting
parent.remove(path.getNameElement());
}
}
/**
* Dump contents of path map and elements included to a string.
*/
public String toString() {
final StringBuilder builder = new StringBuilder();
synchronized (cacheMonitor) {
pathCache.traverse(new PathMap.ElementVisitor<LRUEntry>() {
public void elementVisited(PathMap.Element<LRUEntry> element) {
for (int i = 0; i < element.getDepth(); i++) {
builder.append("--");
}
builder.append(element.getName());
int index = element.getIndex();
if (index != 0 && index != 1) {
builder.append('[');
builder.append(index);
builder.append(']');
}
builder.append(" ");
builder.append(element.get());
builder.append("\n");
}
}, true);
}
return builder.toString();
}
/**
* Check consistency.
*/
private void checkConsistency() throws IllegalStateException {
// assert: synchronized (cacheMonitor)
if (!consistencyCheckEnabled) {
return;
}
int elementsInCache = 0;
Iterator iter = idCache.values().iterator();
while (iter.hasNext()) {
LRUEntry entry = (LRUEntry) iter.next();
elementsInCache += entry.getElements().length;
}
class PathMapElementCounter implements PathMap.ElementVisitor<LRUEntry> {
int count;
public void elementVisited(PathMap.Element<LRUEntry> element) {
LRUEntry mappedEntry = (LRUEntry) element.get();
LRUEntry cachedEntry = (LRUEntry) idCache.get(mappedEntry.getId());
if (cachedEntry == null) {
String msg = "Path element (" + element +
" ) cached in path map, associated id (" +
mappedEntry.getId() + ") isn't.";
throw new IllegalStateException(msg);
}
if (cachedEntry != mappedEntry) {
String msg = "LRUEntry associated with element (" + element +
" ) in path map is not equal to cached LRUEntry (" +
cachedEntry.getId() + ").";
throw new IllegalStateException(msg);
}
PathMap.Element<LRUEntry>[] elements = cachedEntry.getElements();
for (int i = 0; i < elements.length; i++) {
if (elements[i] == element) {
count++;
return;
}
}
String msg = "Element (" + element +
") cached in path map, but not in associated LRUEntry (" +
cachedEntry.getId() + ").";
throw new IllegalStateException(msg);
}
}
PathMapElementCounter counter = new PathMapElementCounter();
pathCache.traverse(counter, false);
if (counter.count != elementsInCache) {
String msg = "PathMap element and cached element count don't match (" +
counter.count + " != " + elementsInCache + ")";
throw new IllegalStateException(msg);
}
}
/**
* Helper method to log item state exception with stack trace every so often.
*
* @param logMessage log message
* @param e item state exception
*/
private void logItemStateException(String logMessage, ItemStateException e) {
long now = System.currentTimeMillis();
if ((now - itemStateExceptionLogTimestamp) >= ITEM_STATE_EXCEPTION_LOG_INTERVAL_MILLIS) {
itemStateExceptionLogTimestamp = now;
log.debug(logMessage, e);
} else {
log.debug(logMessage);
}
}
/**
* Entry in the LRU list
*/
private class LRUEntry {
/**
* Previous entry
*/
private LRUEntry previous;
/**
* Next entry
*/
private LRUEntry next;
/**
* Node id
*/
private final NodeId id;
/**
* Elements in path map
*/
private PathMap.Element<LRUEntry>[] elements;
/**
* Create a new instance of this class
*
* @param id node id
* @param element the path map element for this entry
*/
public LRUEntry(NodeId id, PathMap.Element<LRUEntry> element) {
this.id = id;
this.elements = new PathMap.Element[] { element };
append();
}
/**
* Append entry to end of LRU list
*/
public void append() {
if (tail == null) {
head = this;
tail = this;
} else {
previous = tail;
tail.next = this;
tail = this;
}
}
/**
* Remove entry from LRU list
*/
public void remove() {
if (previous != null) {
previous.next = next;
}
if (next != null) {
next.previous = previous;
}
if (head == this) {
head = next;
}
if (tail == this) {
tail = previous;
}
previous = null;
next = null;
}
/**
* Touch entry. Removes it from its current position in the LRU list
* and moves it to the end.
*/
public void touch() {
remove();
append();
}
/**
* Return next LRU entry
*
* @return next LRU entry
*/
public LRUEntry getNext() {
return next;
}
/**
* Return node ID
*
* @return node ID
*/
public NodeId getId() {
return id;
}
/**
* Return elements in path map that are mapped to <code>id</code>. If
* this entry is a shareable node or one of its descendant, it can
* be reached by more than one path.
*
* @return element in path map
*/
public PathMap.Element<LRUEntry>[] getElements() {
return elements;
}
/**
* Add a mapping to some element.
*/
public void addElement(PathMap.Element<LRUEntry> element) {
PathMap.Element<LRUEntry>[] tmp =
new PathMap.Element[elements.length + 1];
System.arraycopy(elements, 0, tmp, 0, elements.length);
tmp[elements.length] = element;
elements = tmp;
}
/**
* Remove a mapping to some element from this entry.
*
* @return number of mappings left
*/
public int removeElement(PathMap.Element<LRUEntry> element) {
boolean found = false;
for (int i = 0; i < elements.length; i++) {
if (found) {
elements[i - 1] = elements[i];
} else if (elements[i] == element) {
found = true;
}
}
if (found) {
PathMap.Element<LRUEntry>[] tmp =
new PathMap.Element[elements.length - 1];
System.arraycopy(elements, 0, tmp, 0, tmp.length);
elements = tmp;
}
return elements.length;
}
/**
* {@inheritDoc}
*/
public String toString() {
return id.toString();
}
}
private final class CacheStatistics {
private final String id;
private final ReferenceMap cache;
private long timeStamp = 0;
public CacheStatistics() {
this.id = cacheMonitor.toString();
this.cache = idCache;
}
public void log() {
if (log.isDebugEnabled()) {
long now = System.currentTimeMillis();
final String msg = "Cache id = {};size = {};max = {}";
if (log.isTraceEnabled()) {
log.trace(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception());
} else if (now > timeStamp + CACHE_STATISTICS_LOG_INTERVAL_MILLIS) {
timeStamp = now;
log.debug(msg, new Object[]{id, this.cache.size(), upperLimit}, new Exception());
}
}
}
}
}