All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cedarsoftware.util.ClassValueSet Maven / Gradle / Ivy

The newest version!
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> 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> 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> 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); } }; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy