/* * 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; } } }