/*
* 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.nodetype;
import org.apache.jackrabbit.core.nodetype.EffectiveNodeTypeCache.Key;
import org.apache.jackrabbit.spi.Name;
import java.util.TreeSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ArrayList;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
/**
* Implements an effective node type cache that uses a bit set for storing the
* information about participating node types in a set.
*/
public class BitSetENTCacheImpl implements EffectiveNodeTypeCache {
/**
* constant for bits-per-word
*/
private static final int BPW = 64;
/**
* OR mask for bit set
*/
private static final long[] OR_MASK = new long[BPW];
static {
for (int i = 0; i < BPW; i++) {
OR_MASK[i] = 1L << i;
}
}
/**
* An ordered set of the keys. This is used for {@link #findBest(Key)}.
*/
private final TreeSet<Key> sortedKeys;
/**
* cache of pre-built aggregations of node types
*/
private final HashMap<Key, EffectiveNodeType> aggregates;
/**
* A lookup table for bit numbers for a given name.
*
* Note: further performance improvements could be made if this index would
* be stored in the node type registry since only registered node type names
* are allowed in the keys.
*/
private final ConcurrentReaderHashMap nameIndex = new ConcurrentReaderHashMap();
/**
* The reverse lookup table for bit numbers to names
*/
private Name[] names = new Name[1024];
/**
* Creates a new bitset effective node type cache
*/
BitSetENTCacheImpl() {
sortedKeys = new TreeSet<Key>();
aggregates = new HashMap<Key, EffectiveNodeType>();
}
/**
* {@inheritDoc}
*/
public Key getKey(Name[] ntNames) {
return new BitSetKey(ntNames, nameIndex.size() + ntNames.length);
}
/**
* {@inheritDoc}
*/
public void put(EffectiveNodeType ent) {
put(getKey(ent.getMergedNodeTypes()), ent);
}
/**
* {@inheritDoc}
*/
public void put(Key key, EffectiveNodeType ent) {
aggregates.put(key, ent);
sortedKeys.add(key);
}
/**
* {@inheritDoc}
*/
public Key findBest(Key key) {
// quick check for already cached key
if (contains(key)) {
return key;
}
Iterator<Key> iter = sortedKeys.iterator();
while (iter.hasNext()) {
Key k = iter.next();
if (key.contains(k)) {
return k;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public void invalidate(Name name) {
/**
* remove all affected effective node types from aggregates cache
* (copy keys first to prevent ConcurrentModificationException)
*/
ArrayList<Key> keys = new ArrayList<Key>(aggregates.keySet());
for (Iterator<Key> keysIter = keys.iterator(); keysIter.hasNext();) {
Key k = keysIter.next();
EffectiveNodeType ent = get(k);
if (ent.includesNodeType(name)) {
remove(k);
}
}
}
/**
* {@inheritDoc}
*/
public boolean contains(Key key) {
return aggregates.containsKey(key);
}
/**
* {@inheritDoc}
*/
public EffectiveNodeType get(Key key) {
return aggregates.get(key);
}
/**
* Returns the bit number for the given name. If the name does not exist
* a new new bit number for that name is created.
*
* @param name the name to lookup
* @return the bit number for the given name
*/
private int getBitNumber(Name name) {
Integer i = (Integer) nameIndex.get(name);
if (i == null) {
synchronized (nameIndex) {
i = (Integer) nameIndex.get(name);
if (i == null) {
int idx = nameIndex.size();
i = new Integer(idx);
nameIndex.put(name, i);
if (idx >= names.length) {
Name[] newNames = new Name[names.length * 2];
System.arraycopy(names, 0, newNames, 0, names.length);
names = newNames;
}
names[idx] = name;
}
}
}
return i.intValue();
}
/**
* Returns the node type name for a given bit number.
* @param n the bit number to lookup
* @return the node type name
*/
private Name getName(int n) {
return names[n];
}
/**
* Removes the effective node type for the given key from the cache.
*
* @param key the key of the effective node type to remove
* @return the removed effective node type or <code>null</code> if it was
* never cached.
*/
private EffectiveNodeType remove(Key key) {
EffectiveNodeType removed = aggregates.remove(key);
if (removed != null) {
// other than the original implementation, the weights in the
// treeset are now the same as in the given keys. so we can use
// the normal remove method
sortedKeys.remove(key);
}
return removed;
}
/**
* {@inheritDoc}
*/
public Object clone() {
BitSetENTCacheImpl clone = new BitSetENTCacheImpl();
clone.sortedKeys.addAll(sortedKeys);
clone.aggregates.putAll(aggregates);
clone.names = new Name[names.length];
System.arraycopy(names, 0, clone.names, 0, names.length);
clone.nameIndex.putAll(nameIndex);
return clone;
}
/**
* {@inheritDoc}
*/
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("BitSetENTCacheImpl (" + super.toString() + ")\n");
builder.append("EffectiveNodeTypes in cache:\n");
for (Key key : sortedKeys) {
builder.append(key);
builder.append("\n");
}
return builder.toString();
}
/**
* Implements a {@link Key} by storing the node type aggregate information
* in a bit set. We do not use the {@link java.util.BitSet} because it
* does not suit all our requirements. Every node type is represented by a bit
* in the set. This key is immutable.
*/
private class BitSetKey implements Key {
/**
* The names of the node types that form this key.
*/
private final Name[] names;
/**
* The array of longs that hold the bit information.
*/
private final long[] bits;
/**
* the hash code, only calculated once
*/
private final int hashCode;
/**
* Creates a new bit set key.
* @param names the node type names
* @param maxBit the approximative number of the greatest bit
*/
public BitSetKey(Name[] names, int maxBit) {
this.names = names;
bits = new long[maxBit / BPW + 1];
for (int i = 0; i < names.length; i++) {
int n = getBitNumber(names[i]);
bits[n / BPW] |= OR_MASK[n % BPW];
}
hashCode = calcHashCode();
}
/**
* Creates a new bit set key.
* @param bits the array of bits
* @param numBits the number of bits that are '1' in the given bits
*/
private BitSetKey(long[] bits, int numBits) {
this.bits = bits;
names = new Name[numBits];
int i = nextSetBit(0);
int j = 0;
while (i >= 0) {
names[j++] = BitSetENTCacheImpl.this.getName(i);
i = nextSetBit(i + 1);
}
hashCode = calcHashCode();
}
/**
* {@inheritDoc}
*/
public Name[] getNames() {
return names;
}
/**
* {@inheritDoc}
*/
public boolean contains(Key otherKey) {
/*
* 0 - 0 => 0
* 0 - 1 => 1
* 1 - 0 => 0
* 1 - 1 => 0
* !a and b
*/
BitSetKey other = (BitSetKey) otherKey;
int len = Math.max(bits.length, other.bits.length);
for (int i = 0; i < len; i++) {
long w1 = i < bits.length ? bits[i] : 0;
long w2 = i < other.bits.length ? other.bits[i] : 0;
long r = ~w1 & w2;
if (r != 0) {
return false;
}
}
return true;
}
/**
* {@inheritDoc}
*/
public Key subtract(Key otherKey) {
/*
* 0 - 0 => 0
* 0 - 1 => 0
* 1 - 0 => 1
* 1 - 1 => 0
* a and !b
*/
BitSetKey other = (BitSetKey) otherKey;
int len = Math.max(bits.length, other.bits.length);
long[] newBits = new long[len];
int numBits = 0;
for (int i = 0; i < len; i++) {
long w1 = i < bits.length ? bits[i] : 0;
long w2 = i < other.bits.length ? other.bits[i] : 0;
newBits[i] = w1 & ~w2;
numBits += bitCount(newBits[i]);
}
return new BitSetKey(newBits, numBits);
}
/**
* Returns the bit number of the next bit that is set, starting at
* <code>fromIndex</code> inclusive.
*
* @param fromIndex the bit position to start the search
* @return the bit position of the bit or -1 if none found.
*/
private int nextSetBit(int fromIndex) {
int addr = fromIndex / BPW;
int off = fromIndex % BPW;
while (addr < bits.length) {
if (bits[addr] != 0) {
while (off < BPW) {
if ((bits[addr] & OR_MASK[off]) != 0) {
return addr * BPW + off;
}
off++;
}
off = 0;
}
addr++;
}
return -1;
}
/**
* Returns the number of bits set in val.
* For a derivation of this algorithm, see
* "Algorithms and data structures with applications to
* graphics and geometry", by Jurg Nievergelt and Klaus Hinrichs,
* Prentice Hall, 1993.
*
* @param val the value to calculate the bit count for
* @return the number of '1' bits in the value
*/
private int bitCount(long val) {
val -= (val & 0xaaaaaaaaaaaaaaaaL) >>> 1;
val = (val & 0x3333333333333333L) + ((val >>> 2) & 0x3333333333333333L);
val = (val + (val >>> 4)) & 0x0f0f0f0f0f0f0f0fL;
val += val >>> 8;
val += val >>> 16;
return ((int) (val) + (int) (val >>> 32)) & 0xff;
}
/**
* {@inheritDoc}
*
* This compares 1. the cardinality (number of set bits) and 2. the
* numeric value of the bit sets in descending order.
*/
public int compareTo(Key other) {
BitSetKey o = (BitSetKey) other;
int res = o.names.length - names.length;
if (res == 0) {
int adr = Math.max(bits.length, o.bits.length) - 1;
while (adr >= 0) {
long w1 = adr < bits.length ? bits[adr] : 0;
long w2 = adr < o.bits.length ? o.bits[adr] : 0;
if (w1 != w2) {
// some signed arithmetic here
long h1 = w1 >>> 32;
long h2 = w2 >>> 32;
if (h1 == h2) {
h1 = w1 & 0x0ffffL;
h2 = w2 & 0x0ffffL;
}
return Long.signum(h2 - h1);
}
adr--;
}
}
return res;
}
/**
* {@inheritDoc}
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof BitSetKey) {
BitSetKey o = (BitSetKey) obj;
if (names.length != o.names.length) {
return false;
}
int adr = Math.max(bits.length, o.bits.length) - 1;
while (adr >= 0) {
long w1 = adr < bits.length ? bits[adr] : 0;
long w2 = adr < o.bits.length ? o.bits[adr] : 0;
if (w1 != w2) {
return false;
}
adr--;
}
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public int hashCode() {
return hashCode;
}
/**
* Calculates the hash code.
* @return the calculated hash code
*/
private int calcHashCode() {
long h = 1234;
int addr = bits.length - 1;
while (addr >= 0 && bits[addr] == 0) {
addr--;
}
while (addr >= 0) {
h ^= bits[addr] * (addr + 1);
addr--;
}
return (int) ((h >> 32) ^ h);
}
/**
* {@inheritDoc}
*/
public String toString() {
StringBuffer buf = new StringBuffer("w=");
buf.append(names.length);
int i = nextSetBit(0);
while (i >= 0) {
buf.append(", ").append(i).append("=");
buf.append(BitSetENTCacheImpl.this.getName(i));
i = nextSetBit(i + 1);
}
return buf.toString();
}
}
}