Added/moved class utilities.

This commit is contained in:
nathan 2020-06-19 15:15:50 +02:00
parent b70539c18e
commit 23d661d245
5 changed files with 497 additions and 2 deletions

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util.generics;
package dorkbox.util.classes;
import java.lang.reflect.Type;

View File

@ -0,0 +1,190 @@
/*
* Copyright 2015 dorkbox, 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
*
* 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 dorkbox.util.classes;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import dorkbox.util.collections.IdentityMap;
/**
* @author dorkbox
* Date: 4/1/15
*/
public final
class ClassHierarchy {
private volatile IdentityMap<Class<?>, Class<?>> arrayCache;
private volatile IdentityMap<Class<?>, Class<?>[]> superClassesCache;
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
private static final AtomicReferenceFieldUpdater<ClassHierarchy, IdentityMap> arrayREF =
AtomicReferenceFieldUpdater.newUpdater(ClassHierarchy.class,
IdentityMap.class,
"arrayCache");
private static final AtomicReferenceFieldUpdater<ClassHierarchy, IdentityMap> superClassesREF =
AtomicReferenceFieldUpdater.newUpdater(ClassHierarchy.class,
IdentityMap.class,
"superClassesCache");
/**
* These data structures are never reset because the class hierarchy doesn't change at runtime. This class uses the "single writer
* principle" for storing data, EVEN THOUGH it's not accessed by a single writer. This DOES NOT MATTER because duplicates DO NOT matter
*/
public
ClassHierarchy(int loadFactor) {
this.arrayCache = new IdentityMap<Class<?>, Class<?>>(32, loadFactor);
this.superClassesCache = new IdentityMap<Class<?>, Class<?>[]>(32, loadFactor);
}
/**
* will return the class + parent classes as an array.
* if parameter clazz is of type array, then the super classes are of array type as well
* <p>
* race conditions will result in DUPLICATE answers, which we don't care if happens
* never returns null
* never reset (class hierarchy never changes during runtime)
*/
public
Class<?>[] getClassAndSuperClasses(final Class<?> clazz) {
// access a snapshot of the subscriptions (single-writer-principle)
final IdentityMap<Class<?>, Class<?>[]> cache = cast(superClassesREF.get(this));
Class<?>[] classes = cache.get(clazz);
// duplicates DO NOT MATTER
if (classes == null) {
// publish all super types of class
final Iterator<Class<?>> superTypesIterator = getSuperTypes(clazz);
final ArrayList<Class<?>> newList = new ArrayList<Class<?>>(16);
Class<?> c;
final boolean isArray = clazz.isArray();
if (isArray) {
// have to add the original class to the front of the list
c = getArrayClass(clazz);
newList.add(c);
while (superTypesIterator.hasNext()) {
c = superTypesIterator.next();
c = getArrayClass(c);
if (c != clazz) {
newList.add(c);
}
}
}
else {
// have to add the original class to the front of the list
newList.add(clazz);
while (superTypesIterator.hasNext()) {
c = superTypesIterator.next();
if (c != clazz) {
newList.add(c);
}
}
}
classes = new Class<?>[newList.size()];
newList.toArray(classes);
cache.put(clazz, classes);
// save this snapshot back to the original (single writer principle)
superClassesREF.lazySet(this, cache);
}
return classes;
}
/**
* race conditions will result in DUPLICATE answers, which we don't care if happens
* never returns null
* never resets (class hierarchy never changes during runtime)
*
* https://bugs.openjdk.java.net/browse/JDK-6525802 (fixed this in 2007, so Array.newInstance is just as fast (via intrinsics) new [])
* Cache is in place to keep GC down.
*/
public
Class<?> getArrayClass(final Class<?> c) {
// access a snapshot of the subscriptions (single-writer-principle)
final IdentityMap<Class<?>, Class<?>> cache = cast(arrayREF.get(this));
Class<?> clazz = cache.get(c);
if (clazz == null) {
// messy, but the ONLY way to do it. Array super types are also arrays
final Object[] newInstance = (Object[]) Array.newInstance(c, 0);
clazz = newInstance.getClass();
cache.put(c, clazz);
// save this snapshot back to the original (single writer principle)
arrayREF.lazySet(this, cache);
}
return clazz;
}
/**
* Collect all directly and indirectly related super types (classes and interfaces) of a given class.
*
* @param from The root class to start with
* @return An array of classes, each representing a super type of the root class
*/
public static
Iterator<Class<?>> getSuperTypes(Class<?> from) {
// This must be a 'set' because there can be duplicates, depending on the object hierarchy
final IdentityMap<Class<?>, Boolean> superclasses = new IdentityMap<Class<?>, Boolean>();
collectInterfaces(from, superclasses);
while (!from.equals(Object.class) && !from.isInterface()) {
superclasses.put(from.getSuperclass(), Boolean.TRUE);
from = from.getSuperclass();
collectInterfaces(from, superclasses);
}
return superclasses.keys();
}
private static
void collectInterfaces(Class<?> from, IdentityMap<Class<?>, Boolean> accumulator) {
for (Class<?> intface : from.getInterfaces()) {
accumulator.put(intface, Boolean.TRUE);
collectInterfaces(intface, accumulator);
}
}
/**
* Clears the caches, should only be called on shutdown
*/
public
void shutdown() {
this.arrayCache.clear();
this.superClassesCache.clear();
}
@SuppressWarnings("unchecked")
private static
<T> T cast(Object obj) {
return (T) obj;
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.util;
package dorkbox.util.classes;
public abstract class ClassResolver {
/**

View File

@ -0,0 +1,118 @@
/*
* Copyright © 2012-2014 Lightweight Java Game Library Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* - Neither the name of 'Light Weight Java Game Library' nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*/
package dorkbox.util.classes;
import java.util.Arrays;
/**
* Fast {@code ThreadLocal} implementation, adapted from the
* <a href="https://github.com/riven8192/LibStruct/blob/master/src/net/indiespot/struct/runtime/FastThreadLocal.java">LibStruct</a> library.
*
* <p>This implementation replaces the {@code ThreadLocalMap} lookup in {@link ThreadLocal} with a simple array access. The big advantage of this method is
* that thread-local accesses are identified as invariant by the JVM, which enables significant code-motion optimizations.</p>
*
* <p>The underlying array contains a slot for each thread that uses the {@link FastThreadLocal} instance. The slot is indexed by {@link Thread#getId()}. The
* array grows if necessary when the {@link #set} method is called.</p>
*
* <p>It is assumed that usages of this class will be read heavy, so any contention/false-sharing issues caused by the {@link #set} method are ignored.</p>
*
* @param <T> the thread-local value type
*
* @author Riven
* @see ThreadLocal
*/
public class FastThreadLocal<T> {
@SuppressWarnings("unchecked")
private T[] threadIDMap = (T[])new Object[1];
/** Creates a thread local variable. */
public
FastThreadLocal() {
}
/**
* Returns the current thread's "initial value" for this thread-local variable.
*
* @see ThreadLocal#initialValue()
*/
public T initialValue() {
return null;
}
/**
* Sets the current thread's copy of this thread-local variable to the specified value.
*
* @param value the value to be stored in the current thread's copy of this thread-local.
*
* @see ThreadLocal#set(T)
*/
public void set(T value) {
int id = (int)Thread.currentThread().getId();
synchronized ( this ) {
int len = threadIDMap.length;
if (len <= id) {
threadIDMap = Arrays.copyOf(threadIDMap, id + 1);
}
threadIDMap[id] = value;
}
}
/**
* Returns the value in the current thread's copy of this thread-local variable.
*
* @see ThreadLocal#get()
*/
public final T get() {
int id = (int)Thread.currentThread().getId();
T[] threadIDMap = this.threadIDMap; // It's OK if the array is resized after this access, will just use the old array.
T value = threadIDMap.length <= id ? null : threadIDMap[id];
if ( value == null ) {
value = initialValue();
set(value);
}
return value;
}
/**
* Removes the current thread's value for this thread-local variable.
*
* @see ThreadLocal#remove()
*/
public void remove() {
set(null);
}
}

View File

@ -0,0 +1,187 @@
/*
* Copyright 2012 Benjamin Diedrichsen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*
* Copyright 2015 dorkbox, 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
*
* 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 dorkbox.util.classes;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import dorkbox.util.collections.IdentityMap;
/**
* @author bennidi
* Date: 2/16/12
* Time: 12:14 PM
* @author dorkbox
* Date: 2/2/15
*/
public final
class ReflectionUtils {
private static final Method[] EMPTY_METHODS = new Method[0];
private
ReflectionUtils() {
}
/**
* Get methods annotated with the specified annotation.
*
* @param target
* @param annotationClass
* @param <A>
* @return
*/
public static
<A extends Annotation> Method[] getMethods(Class<?> target, Class<A> annotationClass) {
ArrayList<Method> methods = new ArrayList<Method>();
getMethods(target, annotationClass, methods);
return methods.toArray(EMPTY_METHODS);
}
private static
<A extends Annotation> void getMethods(Class<?> target, Class<A> annotationClass, ArrayList<Method> methods) {
try {
for (Method method : target.getDeclaredMethods()) {
if (getAnnotation(method, annotationClass) != null) {
methods.add(method);
}
}
} catch (Exception ignored) {
}
// recursively go until root
if (!target.equals(Object.class)) {
getMethods(target.getSuperclass(), annotationClass, methods);
}
}
/**
* Traverses the class hierarchy upwards, starting at the given subclass, looking
* for an override of the given methods -> finds the bottom most override of the given
* method if any exists
*/
public static
Method getOverridingMethod(final Method overridingMethod, final Class<?> subclass) {
Class<?> current = subclass;
while (!current.equals(overridingMethod.getDeclaringClass())) {
try {
return current.getDeclaredMethod(overridingMethod.getName(), overridingMethod.getParameterTypes());
} catch (NoSuchMethodException e) {
current = current.getSuperclass();
}
}
return null;
}
public static
boolean containsOverridingMethod(final Method[] allMethods, final Method methodToCheck) {
final int length = allMethods.length;
Method method;
for (int i = 0; i < length; i++) {
method = allMethods[i];
if (isOverriddenBy(methodToCheck, method)) {
return true;
}
}
return false;
}
/**
* Searches for an Annotation of the given type on the class. Supports meta annotations.
*
* @param from AnnotatedElement (class, method...)
* @param annotationType Annotation class to look for.
* @param <A> Class of annotation type
* @return Annotation instance or null
*/
private static
<A extends Annotation> A getAnnotation(AnnotatedElement from, Class<A> annotationType, IdentityMap<AnnotatedElement, Boolean> visited) {
if (visited.containsKey(from)) {
return null;
}
visited.put(from, Boolean.TRUE);
A ann = from.getAnnotation(annotationType);
if (ann != null) {
return ann;
}
for (Annotation metaAnn : from.getAnnotations()) {
ann = getAnnotation(metaAnn.annotationType(), annotationType, visited);
if (ann != null) {
return ann;
}
}
return null;
}
public static
<A extends Annotation> A getAnnotation(AnnotatedElement from, Class<A> annotationType) {
return getAnnotation(from, annotationType, new IdentityMap<AnnotatedElement, Boolean>());
}
//
private static
boolean isOverriddenBy(final Method superclassMethod, final Method subclassMethod) {
// if the declaring classes are the same or the subclass method is not defined in the subclass
// hierarchy of the given superclass method or the method names are not the same then
// subclassMethod does not override superclassMethod
if (superclassMethod.getDeclaringClass().equals(subclassMethod.getDeclaringClass()) ||
!superclassMethod.getDeclaringClass().isAssignableFrom(subclassMethod.getDeclaringClass()) ||
!superclassMethod.getName().equals(subclassMethod.getName())) {
return false;
}
final Class<?>[] superClassMethodParameters = superclassMethod.getParameterTypes();
final Class<?>[] subClassMethodParameters = subclassMethod.getParameterTypes();
// method must specify the same number of parameters
//the parameters must occur in the exact same order
for (int i = 0; i < subClassMethodParameters.length; i++) {
if (!superClassMethodParameters[i].equals(subClassMethodParameters[i])) {
return false;
}
}
return true;
}
}