/*
* 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.state;
import org.apache.jackrabbit.spi.Name;
import java.util.Set;
import java.util.Iterator;
import java.util.Collection;
import java.util.HashSet;
/**
* <code>NameSet</code> implements a collection of unique {@link Name}s. The
* methods exposed via the {@link Set} interface are for read only access, which
* means this implementation will throw a {@link UnsupportedOperationException}
* for all modifying methods specified by the {@link Set} interface.
*/
final class NameSet implements Set<Name>, Cloneable {
/**
* The name set cache instance.
*/
private static final NameSetCache CACHE = new NameSetCache();
/**
* The maximum number of names in a set that are cached.
*/
private static final int NUM_NAMES_THRESHOLD = 5;
/**
* The set of property {@link Name}s.
*/
private HashSet names = CACHE.getEmptySet();
/**
* Flag indicating whether the {@link #names} set is shared with another
* {@link NameSet} instance. The initial value is <code>true</code> because
* {@link #names} is initialized with an empty set from the cache.
*/
private boolean shared = true;
/**
* Adds a <code>name</code>.
*
* @param name the name to add.
* @return <code>true</code> if the name is already present,
* <code>false</code> otherwise.
*/
public boolean add(Name name) {
if (names.size() > NUM_NAMES_THRESHOLD) {
ensureModifiable();
return names.add(name);
} else {
int size = names.size();
// get a cached set
names = CACHE.get(names, name, !shared);
// a set from the cache is always shared
shared = true;
return names.size() != size;
}
}
/**
* Removes a <code>name</code>.
*
* @param name the name to remove.
* @return <code>true</code> if the name was removed, <code>false</code>
* if the name was unknown.
*/
boolean remove(Name name) {
ensureModifiable();
return names.remove(name);
}
/**
* Removes all names from this {@link NameSet}.
*/
void removeAll() {
ensureModifiable();
names.clear();
}
/**
* Removes all names currently present and adds all names from
* <code>c</code>.
*
* @param c the {@link Name}s to add.
*/
void replaceAll(Collection c) {
if (c instanceof NameSet) {
NameSet propNames = (NameSet) c;
names = propNames.names;
shared = true;
propNames.shared = true;
} else if (c instanceof HashSet) {
names = CACHE.get((HashSet) c);
shared = true;
} else {
ensureModifiable();
names.clear();
names.addAll(c);
}
}
//------------------------------------------------< unmodifiable Set view >
/**
* {@inheritDoc}
*/
public int size() {
return names.size();
}
/**
* {@inheritDoc}
*/
public boolean isEmpty() {
return names.isEmpty();
}
/**
* {@inheritDoc}
*/
public boolean contains(Object o) {
return names.contains(o);
}
/**
* {@inheritDoc}
* <p>
* The returned iterator will throw a {@link UnsupportedOperationException}
* on {@link Iterator#remove()}.
*/
public Iterator iterator() {
return new Iterator() {
Iterator i = names.iterator();
public boolean hasNext() {
return i.hasNext();
}
public Object next() {
return i.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* {@inheritDoc}
*/
public Object[] toArray() {
return names.toArray();
}
/**
* {@inheritDoc}
*/
public Object[] toArray(Object[] a) {
return names.toArray(a);
}
/**
* @throws UnsupportedOperationException always.
*/
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
public boolean containsAll(Collection c) {
return names.containsAll(c);
}
/**
* @throws UnsupportedOperationException always.
*/
public boolean addAll(Collection c) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always.
*/
public boolean retainAll(Collection c) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always.
*/
public boolean removeAll(Collection c) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always.
*/
public void clear() {
throw new UnsupportedOperationException();
}
//--------------------------------------------------< equals and hashCode >
/**
* {@inheritDoc}
*/
public int hashCode() {
return names.hashCode();
}
/**
* {@inheritDoc}
*/
public boolean equals(Object obj) {
if (obj instanceof NameSet) {
NameSet other = (NameSet) obj;
return this.names.equals(other.names);
}
return false;
}
//----------------------------------------------------< Cloneable support >
/**
* Returns a clone of this <code>PropertyNames</code> instance.
*
* @return a clone of this <code>PropertyNames</code> instance.
*/
public Object clone() {
try {
NameSet propNames = (NameSet) super.clone();
shared = true;
propNames.shared = true;
return propNames;
} catch (CloneNotSupportedException e) {
// will never happen
throw new InternalError();
}
}
//-------------------------------------------------------------< internal >
/**
* Ensures that {@link #names} can be modified (-> not shared).
*/
private void ensureModifiable() {
if (shared) {
names = (HashSet) names.clone();
shared = false;
}
}
/**
* Implements a simple <code>HashSet<Name></code> cache.
* <p>
* Please note that this cache does not ensures that the sets are immutable!
* It is the responsibility of the caller to make sure that sets passed to
* {@link #get} are not modified by multiple threads. Modifying a cached
* set is not a problem in general because it will only cause cache misses.
*/
private static final class NameSetCache {
/**
* Size of the cache (must be a power of two). Note that this is the
* maximum number of objects kept in the cache, but due to hashing it
* can well be that only a part of the cache array is filled even if
* many more distinct objects are being accessed.
*/
private static final int SIZE_POWER_OF_2 = 1024;
/**
* Array of cached hash sets, indexed by their hash codes
* (module size of the array).
*/
private final HashSet[] array = new HashSet[SIZE_POWER_OF_2];
/**
* Returns a set that contains all elements from <code>set</code> and
* <code>obj</code>. If a cached copy of the set already exists, then
* this method returns that copy. Otherwise <code>obj</code> is added
* to the given <code>set</code>, the <code>set</code> is cached and
* then returned.
*
* @param set the initial set.
* @param obj the object to add to <code>set</code>.
* @param modifiable <code>true</code> if <code>set</code> may be modified.
* @return a cached set that contains all elements from <code>set</code>
* and <code>obj</code>.
*/
public HashSet get(HashSet set, Object obj, boolean modifiable) {
if (set.contains(obj)) {
return set;
}
int position = (set.hashCode() + obj.hashCode()) & (SIZE_POWER_OF_2 - 1);
HashSet previous = array[position];
if (previous != null &&
previous.size() == set.size() + 1 &&
previous.containsAll(set) &&
previous.contains(obj)) {
return previous;
} else {
if (modifiable) {
set.add(obj);
} else {
set = (HashSet) set.clone();
set.add(obj);
}
array[position] = set;
return set;
}
}
/**
* If a cached copy of the given set already exists, then this method
* returns that copy. Otherwise the given set is cached and returned.
*
* @param set set to return from the cache
* @return the given set or a previously cached copy
*/
public HashSet get(HashSet set) {
int position = set.hashCode() & (SIZE_POWER_OF_2 - 1);
HashSet previous = array[position];
if (set.equals(previous)) {
return previous;
} else {
array[position] = set;
return set;
}
}
/**
* Returns a cached copy of an empty set.
*
* @return a cached copy of an empty set.
*/
public HashSet getEmptySet() {
HashSet set = array[0];
if (set == null || !set.isEmpty()) {
set = new HashSet();
array[0] = set;
}
return set;
}
}
}