/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2010 Sun Microsystems, Inc.
* Portions copyright 2011 ForgeRock AS
*/
package org.opends.server.api;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.opends.server.types.DN;
/**
* The DITCacheMap class implements custom Map for structural
* storage of arbitrary objects in Directory Information Tree
* (DIT) like structure.
*
* This Map intended usage is for caching various server
* objects which can be subject to subtree operations
* like retrieval or removal of all objects under a
* specific DN. While using a regular Map it would
* require the entire Map iteration to achieve, this Map
* implementation maintains such internal structure that
* subtree operations are more efficient and do not
* require iterations over the entire map, instead
* additional subtree operations methods are provided by
* this Map to do just that.
*
* API wise it behaves exactly like a regular Map
* implementation except for providing additional
* subtree methods. All required linkage and
* structuring is performed within this Map
* implementation itself and not exposed via the
* API in any way. For example, putting these
* key/value pairs
*
* cn=Object1,ou=Objects,dc=example,dc=com : object1
* cn=Object2,ou=Objects,dc=example,dc=com : object2
* cn=Object3,ou=Objects,dc=example,dc=com : object3
*
* then invoking a subtree method on this Map with
* any of these keys
*
* ou=Objects,dc=example,dc=com
* dc=example,dc=com
* dc=com
*
* would bring all three objects previously stored in
* this map into subtree operation scope. Standard
* Map API methods can only work with the objects
* previously stored in this map explicitly.
*
* Note that this Map implementation is not
* synchronized.
*
* @param <T> arbitrary object type.
*/
public final class DITCacheMap<T> extends AbstractMap<DN,T>
{
/**
* Node class for object storage and
* linking to any subordinate nodes.
* @param <T> arbitrary storage object.
*/
private static final class Node<T>
{
// Node DN.
DN dn;
// Storage object or null if this node exist
// only to support the DIT like structuring.
T element;
// Parent.
Node<T> parent;
// First child.
Node<T> child;
// Next sibling.
Node<T> next;
// Previous sibling.
Node<T> previous;
/**
* {@inheritDoc}
*/
public String toString()
{
if (element == null)
{
return "glue";
}
else
{
return "node(" + String.valueOf(element) + ")";
}
}
}
// Map size reflecting only nodes
// containing non empty elements.
private int size = 0;
// Backing Map implementation.
private Map<DN,Node<T>> ditCacheMap;
/**
* Default constructor.
*/
public DITCacheMap()
{
ditCacheMap = new HashMap<DN,Node<T>>();
}
/**
* Constructs a new DITCacheMap from a given Map.
* @param m existing Map to construct new
* DITCacheMap from.
*/
public DITCacheMap(Map<? extends DN, ? extends T> m)
{
ditCacheMap = new HashMap<DN,Node<T>>();
this.putAll(m);
}
/**
* {@inheritDoc}
*/
@Override
public int size()
{
return size;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isEmpty()
{
return ditCacheMap.isEmpty();
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsKey(Object key)
{
if (get((DN) key) != null)
{
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsValue(Object value)
{
for (Node<T> node : ditCacheMap.values())
{
if ((node.element != null) &&
node.element.equals(value))
{
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public T get(Object key)
{
Node<T> node = ditCacheMap.get((DN)key);
if (node != null)
{
return node.element;
}
return null;
}
/**
* Returns a set of stored objects
* subordinate to subtree DN.
* @param key subtree DN.
* @return collection of stored objects
* subordinate to subtree DN.
*/
public Collection<T> getSubtree(DN key)
{
return new DITSubtreeSet(key);
}
/**
* {@inheritDoc}
*/
@Override
public T put(DN key, T value)
{
final Node<T> existingNode = ditCacheMap.get(key);
if (existingNode != null)
{
final T returnValue = existingNode.element;
existingNode.element = value;
return returnValue;
}
Node<T> node = new Node<T>();
node.dn = key;
node.element = value;
node.parent = null;
node.child = null;
node.next = null;
node.previous = null;
ditCacheMap.put(key, node);
size++;
// Update parent hierarchy.
for (DN parentDN = key.getParent();
parentDN != null;
parentDN = parentDN.getParent())
{
final Node<T> parentNode = ditCacheMap.get(parentDN);
if (parentNode == null)
{
// Add glue node.
final Node<T> newParentNode = new Node<T>();
newParentNode.dn = parentDN;
newParentNode.element = null;
newParentNode.parent = null;
newParentNode.child = node;
newParentNode.next = null;
newParentNode.previous = null;
ditCacheMap.put(parentDN, newParentNode);
node.parent = newParentNode;
node = newParentNode;
}
else
{
if (parentNode.child != null)
{
Node<T> lastNode = parentNode.child;
while (lastNode.next != null)
{
lastNode = lastNode.next;
}
node.previous = lastNode;
lastNode.next = node;
}
else
{
parentNode.child = node;
}
node.parent = parentNode;
break;
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public T remove(Object key)
{
final DN dn = (DN) key;
final Node<T> node = ditCacheMap.get(dn);
if (node == null)
{
return null;
}
final T returnValue = node.element;
if (returnValue == null)
{
return null;
}
// Remove element from DIT.
size--;
node.element = null;
if (node.child == null)
{
// This node is now glue, so remove it completely and fix up
// any other nodes which reference it.
ditCacheMap.remove(dn);
fixNodeReferences(node);
}
return returnValue;
}
/**
* Remove references to a node after it has been removed.
* @param node The node which has just been removed.
*/
private void fixNodeReferences(Node<T> node)
{
while (true)
{
// Fix siblings.
final Node<T> nextNode = node.next;
final Node<T> previousNode = node.previous;
if (nextNode != null)
{
nextNode.previous = previousNode;
}
if (previousNode != null)
{
previousNode.next = nextNode;
}
// Fix parent, if it exists.
final Node<T> parentNode = node.parent;
if (parentNode == null)
{
// Reached the top of the DIT, so no parents to fix.
break;
}
if (parentNode.child != node)
{
// The parent references another sibling and does not need
// fixing.
break;
}
if (nextNode != null)
{
// Update child pointer and return.
parentNode.child = nextNode;
break;
}
if (parentNode.element != null)
{
// Parent node is still needed as it contains data.
break;
}
// The parent node is glue so remove it.
ditCacheMap.remove(parentNode.dn);
// Smash pointers.
node.parent = null;
node.previous = null;
node.next = null;
// Continue up tree.
node = parentNode;
}
}
/**
* Removes a set of stored objects subordinate to subtree DN.
* @param key subtree DN.
* @param values collection for removed objects subordinate
* to subtree DN or <code>null</code>.
* @return <code>true</code> on success or
* <code>false</code> otherwise.
*/
public boolean removeSubtree(DN key, Collection<? super T> values)
{
final Node<T> node = ditCacheMap.remove(key);
if (node != null)
{
// Fix up sibling and parent references.
fixNodeReferences(node);
// Collect all elements and update the size.
adjustSizeAndCollectElements(node, values);
return true;
}
else
{
return false;
}
}
/**
* Iterate through detached subtree counting and collecting any
* elements.
*
* @param node
* The node to be collected.
* @param values
* Collection in which to put elemenets.
*/
private void adjustSizeAndCollectElements(final Node<T> node,
Collection<? super T> values)
{
if (node.element != null)
{
if (values != null)
{
values.add(node.element);
}
node.element = null;
size--;
}
// Repeat for each child.
Node<T> child = node.child;
while (child != null)
{
final Node<T> next = child.next;
adjustSizeAndCollectElements(child, values);
child = next;
}
// Smash pointers.
node.parent = null;
node.child = null;
node.previous = null;
node.next = null;
// Remove node from map.
ditCacheMap.remove(node.dn);
}
/**
* {@inheritDoc}
*/
@Override
public void putAll(Map<? extends DN, ? extends T> m)
{
for (Entry<? extends DN, ? extends T> entry : m.entrySet())
{
put(entry.getKey(), entry.getValue());
}
}
/**
* {@inheritDoc}
*/
@Override
public void clear()
{
ditCacheMap.clear();
size = 0;
}
/**
* {@inheritDoc}
*/
public Set<Entry<DN, T>> entrySet()
{
return new DITCacheEntrySet();
}
/**
* EntrySet class implementation for the DITCacheMap.
*/
private class DITCacheEntrySet extends AbstractSet<Entry<DN, T>>
{
/**
* Iterator class implementation for the DITCacheEntrySet.
*/
private class EntryIterator implements Iterator<Entry<DN, T>>
{
private Iterator<Entry<DN, Node<T>>> ditCacheMapIterator;
private Entry<DN, Node<T>> currentEntry;
private Entry<DN, Node<T>> nextEntry;
private boolean hasNext;
/**
* Default constructor.
*/
public EntryIterator()
{
ditCacheMapIterator = ditCacheMap.entrySet().iterator();
currentEntry = null;
nextEntry = null;
hasNext = false;
}
/**
* {@inheritDoc}
*/
public boolean hasNext()
{
if (hasNext)
{
return true;
}
while (ditCacheMapIterator.hasNext())
{
Entry<DN, Node<T>> entry = ditCacheMapIterator.next();
Node<T> node = entry.getValue();
if ((node != null) && (node.element != null))
{
nextEntry = entry;
hasNext = true;
return true;
}
}
nextEntry = null;
return false;
}
/**
* {@inheritDoc}
*/
public Entry<DN, T> next()
{
if (nextEntry != null)
{
Node<T> node = nextEntry.getValue();
currentEntry = nextEntry;
nextEntry = null;
hasNext = false;
return new DITCacheMapEntry(node.dn, node.element);
}
while (ditCacheMapIterator.hasNext())
{
Entry<DN, Node<T>> entry = ditCacheMapIterator.next();
Node<T> node = entry.getValue();
if ((node != null) && (node.element != null))
{
currentEntry = entry;
hasNext = false;
return new DITCacheMapEntry(node.dn, node.element);
}
}
throw new NoSuchElementException();
}
/**
* {@inheritDoc}
*/
public void remove()
{
if (currentEntry != null)
{
Entry<DN, Node<T>> oldIteratorEntry = null;
if (hasNext())
{
oldIteratorEntry = nextEntry;
}
if (DITCacheMap.this.remove(currentEntry.getKey()) != null)
{
ditCacheMapIterator = ditCacheMap.entrySet().iterator();
currentEntry = null;
nextEntry = null;
hasNext = false;
while (hasNext())
{
Entry<DN, T> newIteratorEntry = next();
if ((oldIteratorEntry != null) &&
oldIteratorEntry.getKey().equals(
newIteratorEntry.getKey()) &&
oldIteratorEntry.getValue().element.equals(
newIteratorEntry.getValue()))
{
nextEntry = currentEntry;
hasNext = true;
return;
}
}
currentEntry = null;
nextEntry = null;
hasNext = false;
return;
}
}
throw new IllegalStateException();
}
}
/**
* {@inheritDoc}
*/
public int size()
{
return DITCacheMap.this.size();
}
/**
* {@inheritDoc}
*/
public Iterator<Entry<DN, T>> iterator()
{
return new EntryIterator();
}
}
/**
* Map.Entry class implementation for the DITCacheMap.
*/
private class DITCacheMapEntry implements Map.Entry<DN, T>
{
private DN key;
private T value;
/**
* Constructs a new DITCacheMapEntry
* with given key and value.
* @param key Map.Entry key.
* @param value Map.Entry value.
*/
public DITCacheMapEntry(DN key, T value)
{
this.key = key;
this.value = value;
}
/**
* {@inheritDoc}
*/
public DN getKey()
{
return key;
}
/**
* {@inheritDoc}
*/
public T getValue()
{
return value;
}
/**
* {@inheritDoc}
*/
public T setValue(T value)
{
Node<T> node = ditCacheMap.get(key);
T oldValue = this.value;
node.element = value;
this.value = value;
return oldValue;
}
}
/**
* SubtreeSet class implementation.
*/
private class DITSubtreeSet extends AbstractSet<T>
{
// Set key.
private DN key;
/**
* Keyed constructor.
* @param key to construct
* this set from.
*/
public DITSubtreeSet(DN key)
{
this.key = key;
}
/**
* Iterator class implementation for SubtreeSet.
*/
private class SubtreeSetIterator implements Iterator<T>
{
// Iterator key.
private DN key;
// Iterator root node.
private Node<T> rootNode;
// Iterator current node.
private Node<T> node;
/**
* Default constructor.
*/
public SubtreeSetIterator()
{
this.key = DITSubtreeSet.this.key;
rootNode = ditCacheMap.get(this.key);
node = rootNode;
}
/**
* Keyed constructor.
* @param key to cue this
* iterator from.
*/
public SubtreeSetIterator(DN key)
{
this.key = key;
rootNode = ditCacheMap.get(this.key);
node = rootNode;
}
/**
* {@inheritDoc}
*/
public boolean hasNext()
{
if (rootNode != null)
{
if (node == rootNode)
{
if (rootNode.element != null)
{
return true;
}
}
while (node != null)
{
if (node.element != null)
{
return true;
}
if (node.child != null)
{
node = node.child;
}
else
{
while ((node.next == null) &&
(node.parent != rootNode))
{
node = node.parent;
}
node = node.next;
}
}
}
return false;
}
/**
* {@inheritDoc}
*/
public T next()
{
T element = null;
if (rootNode != null)
{
if (node == rootNode)
{
node = rootNode.child;
if (rootNode.element != null)
{
return rootNode.element;
}
}
while (node != null)
{
if (node.element != null)
{
element = node.element;
}
else
{
element = null;
}
if (node.child != null)
{
node = node.child;
}
else
{
while ((node.next == null) &&
(node.parent != rootNode))
{
node = node.parent;
}
node = node.next;
}
if (element != null)
{
return element;
}
}
}
throw new NoSuchElementException();
}
/**
* {@inheritDoc}
*/
public void remove()
{
throw new UnsupportedOperationException();
}
}
/**
* {@inheritDoc}
*/
public Iterator<T> iterator()
{
return new SubtreeSetIterator();
}
/**
* {@inheritDoc}
*/
public int size()
{
int size = 0;
Iterator<T> iterator = new SubtreeSetIterator(this.key);
while (iterator.hasNext())
{
iterator.next();
size++;
}
return size;
}
}
/**
* Returns the size of the internal map. Used for testing purposes
* only.
*
* @return The size of the internal map.
*/
int getMapSize()
{
return ditCacheMap.size();
}
}