/*
* 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.jcr2spi.nodetype;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.jackrabbit.spi.Name;
/**
* Implements an effective node type cache that uses a bit set for storing the
* information about participating node types in a set.
*/
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 ConcurrentHashMap<Name, Integer> nameIndex = new ConcurrentHashMap<Name, Integer>();
/**
* 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>();
}
//---------------------------------------------< EffectiveNodeTypeCache >---
/**
* @see EffectiveNodeTypeCache#getKey(Name[])
*/
public Key getKey(Name[] ntNames) {
return new BitsetKey(ntNames, nameIndex.size() + ntNames.length);
}
/**
* @see EffectiveNodeTypeCache#put(EffectiveNodeType)
*/
public void put(EffectiveNodeType ent) {
put(getKey(ent.getMergedNodeTypes()), ent);
}
/**
* @see EffectiveNodeTypeCache#put(Key, EffectiveNodeType)
*/
public void put(Key key, EffectiveNodeType ent) {
aggregates.put(key, ent);
sortedKeys.add(key);
}
/**
* @see EffectiveNodeTypeCache#findBest(Key)
*/
public Key findBest(Key key) {
// quick check for already cached key
if (contains(key)) {
return key;
}
for (Key k : sortedKeys) {
if (key.contains(k)) {
return k;
}
}
return null;
}
/**
* @see EffectiveNodeTypeCache#invalidate(Name)
*/
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 (Key k : keys) {
EffectiveNodeType ent = get(k);
if (ent.includesNodeType(name)) {
remove(k);
}
}
}
/**
* @see EffectiveNodeTypeCache#contains(Key)
*/
public boolean contains(Key key) {
return aggregates.containsKey(key);
}
/**
* @see EffectiveNodeTypeCache#get(Key)
*/
public EffectiveNodeType get(Key key) {
return aggregates.get(key);
}
/**
* @see EffectiveNodeTypeCache#clear()
*/
public void clear() {
sortedKeys.clear();
aggregates.clear();
nameIndex.clear();
}
//------------------------------------------------------------< private >---
/**
* 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 = nameIndex.get(name);
if (i == null) {
synchronized (nameIndex) {
i = nameIndex.get(name);
if (i == null) {
int idx = nameIndex.size();
i = 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;
}
/**
* 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;
}
//----------------------------------------------------------< Cloneable >---
/**
* @see Cloneable#clone()
*/
@Override
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;
}
//-------------------------------------------------------------< Object >---
/**
* Returns the the state of this instance in a human readable format.
*/
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("EffectiveNodeTypeCache (" + super.toString() + ")\n");
builder.append("EffectiveNodeTypes in cache:\n");
for (Key k : sortedKeys) {
builder.append(k);
builder.append('\n');
}
return builder.toString();
}
//----------------------------------------------------------------< Key >---
/**
* 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 suite all our needs. 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 hashcode, only calculated once
*/
private final int hashCode;
/**
* Creates a ew bitset key.
* @param names the node type names
* @param maxBit the approximate 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 new bitset key.
* @param bits the array if 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();
}
/**
* 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;
}
/**
* Calculates the hashcode.
* @return the calculated hashcode
*/
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);
}
//------------------------------------------------------------< Key >---
/**
* @see Key#getNames()
*/
public Name[] getNames() {
return names;
}
/**
* @see Key#contains(Key)
*/
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;
}
/**
* @see Key#subtract(Key)
*/
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);
}
//-----------------------------------------------------< Comparable >---
/**
* {@inheritDoc}
*
* This compares 1. the cardinality (number of set bits) and 2. the
* numeric value of the bitsets in descending order.
*
* @see Comparable#compareTo(Object)
*/
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 & 0x0ffffffffL;
h2 = w2 & 0x0ffffffffL;
}
return Long.signum(h2 - h1);
}
adr--;
}
}
return res;
}
//---------------------------------------------------------< Object >---
/**
* @see Object#equals(Object)
*/
@Override
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;
}
/**
* @see Object#hashCode()
*/
@Override
public int hashCode() {
return hashCode;
}
/**
* @see Object#toString()
*/
@Override
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();
}
}
}