
com.cedarsoftware.util.ClassValueSet Maven / Gradle / Ivy
package com.cedarsoftware.util;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Objects;
/**
* A Set implementation for Class objects that leverages a ClassValue cache for extremely
* fast membership tests. This specialized collection is designed for scenarios where you
* frequently need to check if a Class is a member of a set.
*
* Performance Advantages
*
* ClassValueSet provides significantly faster {@code contains()} operations compared to standard
* Set implementations:
*
* - 2-10x faster than HashSet for membership checks
* - 3-15x faster than ConcurrentHashMap.keySet() for concurrent access patterns
* - The performance advantage increases with contention (multiple threads)
* - Most significant when checking the same classes repeatedly
*
*
* How It Works
*
* The implementation utilizes Java's {@link ClassValue} mechanism, which is specially optimized
* in the JVM through:
*
* - Thread-local caching for reduced contention
* - Identity-based lookups (faster than equality checks)
* - Special VM support that connects directly to Class metadata structures
* - Optimized memory layout that can reduce cache misses
*
*
* Ideal Use Cases
*
* ClassValueSet is ideal for:
*
* - High read-to-write ratio scenarios (read-mostly workloads)
* - Relatively static sets of classes that are checked frequently
* - Performance-critical operations in hot code paths
* - Security blocklists (checking if a class is forbidden)
* - Feature flags or capability testing based on class membership
* - Type handling in serialization/deserialization frameworks
*
*
* Trade-offs
*
* The performance benefits come with some trade-offs:
*
* - Higher memory usage (maintains both a backing set and ClassValue cache)
* - Write operations (add/remove) aren't faster and may be slightly slower
* - Only Class objects benefit from the optimized lookups
*
*
* Thread Safety
*
* This implementation is thread-safe for all operations.
*
*
Usage Example
* {@code
* // Create a set of blocked classes for security checks
* ClassValueSet blockedClasses = ClassValueSet.of(
* ClassLoader.class,
* Runtime.class,
* ProcessBuilder.class
* );
*
* // Fast membership check in a security-sensitive context
* public void verifyClass(Class> clazz) {
* if (blockedClasses.contains(clazz)) {
* throw new SecurityException("Access to " + clazz.getName() + " is not allowed");
* }
* }
* }
*
* Important Performance Warning
*
* Wrapping this class with standard collection wrappers like {@code Collections.unmodifiableSet()}
* will destroy the {@code ClassValue} performance benefits. Always use the raw {@code ClassValueSet} directly
* or use the provided {@code unmodifiableView()} method if immutability is required.
*
* @see ClassValue
* @see Set
*
* @author John DeRegnaucourt ([email protected])
*
* Copyright (c) Cedar Software LLC
*
* Licensed 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
*
* License
*
* 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.
*/
public class ClassValueSet extends AbstractSet> {
// Backing set for storage and iteration
private final Set> backingSet = ConcurrentHashMap.newKeySet();
// Flag for null element
private final AtomicBoolean containsNull = new AtomicBoolean(false);
// ClassValue for fast contains checks
private final ClassValue membershipCache = new ClassValue() {
@Override
protected Boolean computeValue(Class> type) {
return backingSet.contains(type);
}
};
/**
* Creates an empty ClassValueSet.
*/
public ClassValueSet() {
}
/**
* Creates a ClassValueSet containing the elements of the specified collection.
*
* @param c the collection whose elements are to be placed into this set
*/
public ClassValueSet(Collection extends Class>> c) {
addAll(c);
}
@Override
public boolean contains(Object o) {
if (o == null) {
return containsNull.get();
}
if (!(o instanceof Class)) {
return false;
}
return membershipCache.get((Class>) o);
}
@Override
public boolean add(Class> cls) {
if (cls == null) {
return !containsNull.getAndSet(true);
}
boolean added = backingSet.add(cls);
if (added) {
// Force cache recomputation on next get
membershipCache.remove(cls);
}
return added;
}
@Override
public boolean remove(Object o) {
if (o == null) {
return containsNull.getAndSet(false);
}
if (!(o instanceof Class)) {
return false;
}
Class> clazz = (Class>) o;
boolean changed = backingSet.remove(clazz);
if (changed) {
// Invalidate cache for this class
membershipCache.remove(clazz);
}
return changed;
}
/**
* Removes all classes from this set.
*/
@Override
public void clear() {
// Save keys for cache invalidation
Set> keysToInvalidate = new HashSet<>(backingSet);
backingSet.clear();
containsNull.set(false);
// Invalidate cache for all previous members
for (Class> cls : keysToInvalidate) {
membershipCache.remove(cls);
}
}
@Override
public int size() {
return backingSet.size() + (containsNull.get() ? 1 : 0);
}
@Override
public boolean isEmpty() {
return backingSet.isEmpty() && !containsNull.get();
}
/**
* Returns true if this set equals another object.
* For sets, equality means they contain the same elements.
*/
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Set)) {
return false;
}
Set> other = (Set>) o;
if (other.size() != size()) {
return false;
}
try {
// Check if other set has all our elements
if (containsNull.get() && !other.contains(null)) {
return false;
}
for (Class> cls : backingSet) {
if (!other.contains(cls)) {
return false;
}
}
// Check if we have all other set's elements
for (Object element : other) {
if (element != null) {
if (!(element instanceof Class) || !contains(element)) {
return false;
}
}
}
return true;
} catch (ClassCastException | NullPointerException e) {
return false;
}
}
/**
* Returns the hash code value for this set.
* The hash code of a set is the sum of the hash codes of its elements.
*/
@Override
public int hashCode() {
int h = 0;
for (Class> cls : backingSet) {
h += (cls != null ? cls.hashCode() : 0);
}
if (containsNull.get()) {
h += 0; // null element's hash code is 0
}
return h;
}
/**
* Retains only the elements in this set that are contained in the specified collection.
*
* @param c collection containing elements to be retained in this set
* @return true if this set changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
@Override
public boolean retainAll(Collection> c) {
Objects.requireNonNull(c, "Collection cannot be null");
boolean modified = false;
// Handle null element specially
if (containsNull.get() && !c.contains(null)) {
containsNull.set(false);
modified = true;
}
// Create a set of classes to remove
Set> toRemove = new HashSet<>();
for (Class> cls : backingSet) {
if (!c.contains(cls)) {
toRemove.add(cls);
}
}
// Remove elements and invalidate cache
for (Class> cls : toRemove) {
backingSet.remove(cls);
membershipCache.remove(cls);
modified = true;
}
return modified;
}
@Override
public Iterator> iterator() {
final boolean hasNull = containsNull.get();
// Make a snapshot of the backing set to avoid ConcurrentModificationException
final Iterator> backingIterator = new HashSet<>(backingSet).iterator();
return new Iterator>() {
private boolean nullReturned = !hasNull;
private Class> lastReturned = null;
private boolean canRemove = false;
@Override
public boolean hasNext() {
return !nullReturned || backingIterator.hasNext();
}
@Override
public Class> next() {
if (!nullReturned) {
nullReturned = true;
lastReturned = null;
canRemove = true;
return null;
}
lastReturned = backingIterator.next();
canRemove = true;
return lastReturned;
}
@Override
public void remove() {
if (!canRemove) {
throw new IllegalStateException("next() has not been called, or remove() has already been called after the last call to next()");
}
canRemove = false;
if (lastReturned == null) {
// Removing the null element
containsNull.set(false);
} else {
// Removing a class element
ClassValueSet.this.remove(lastReturned);
}
}
};
}
/**
* Returns a new set containing all elements from this set
*
* @return a new set containing the same elements
*/
public Set> toSet() {
Set> result = new HashSet<>(backingSet);
if (containsNull.get()) {
result.add(null);
}
return result;
}
/**
* Factory method to create a ClassValueSet from an existing Collection
*
* @param collection the source collection
* @return a new ClassValueSet containing the same elements
*/
public static ClassValueSet from(Collection extends Class>> collection) {
return new ClassValueSet(collection);
}
/**
* Factory method that creates a set using the provided classes
*
* @param classes the classes to include in the set
* @return a new ClassValueSet containing the provided classes
*/
public static ClassValueSet of(Class>... classes) {
ClassValueSet set = new ClassValueSet();
if (classes != null) {
Collections.addAll(set, classes);
}
return set;
}
/**
* Returns an unmodifiable view of this set that preserves ClassValue performance benefits.
* Unlike Collections.unmodifiableSet(), this method returns a view that maintains
* the fast membership-testing performance for Class elements.
*
* @return an unmodifiable view of this set with preserved performance characteristics
*/
public Set> unmodifiableView() {
final ClassValueSet thisSet = this;
return new AbstractSet>() {
@Override
public Iterator> iterator() {
final Iterator> originalIterator = thisSet.iterator();
return new Iterator>() {
@Override
public boolean hasNext() {
return originalIterator.hasNext();
}
@Override
public Class> next() {
return originalIterator.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Cannot modify an unmodifiable set");
}
};
}
@Override
public int size() {
return thisSet.size();
}
@Override
public boolean contains(Object o) {
return thisSet.contains(o); // Preserves ClassValue optimization
}
@Override
public boolean containsAll(Collection> c) {
return thisSet.containsAll(c);
}
@Override
public boolean isEmpty() {
return thisSet.isEmpty();
}
@Override
public Object[] toArray() {
return thisSet.toArray();
}
@Override
public T[] toArray(T[] a) {
return thisSet.toArray(a);
}
// All mutator methods throw UnsupportedOperationException
@Override
public boolean add(Class> e) {
throw new UnsupportedOperationException("Cannot modify an unmodifiable set");
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException("Cannot modify an unmodifiable set");
}
@Override
public boolean addAll(Collection extends Class>> c) {
throw new UnsupportedOperationException("Cannot modify an unmodifiable set");
}
@Override
public boolean removeAll(Collection> c) {
throw new UnsupportedOperationException("Cannot modify an unmodifiable set");
}
@Override
public boolean retainAll(Collection> c) {
throw new UnsupportedOperationException("Cannot modify an unmodifiable set");
}
@Override
public void clear() {
throw new UnsupportedOperationException("Cannot modify an unmodifiable set");
}
@Override
public String toString() {
return thisSet.toString();
}
@Override
public int hashCode() {
return thisSet.hashCode();
}
@Override
public boolean equals(Object obj) {
return this == obj || thisSet.equals(obj);
}
};
}
}