Added/moved class utilities.
This commit is contained in:
parent
b70539c18e
commit
23d661d245
|
@ -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;
|
||||
|
190
src/dorkbox/util/classes/ClassHierarchy.java
Normal file
190
src/dorkbox/util/classes/ClassHierarchy.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
/**
|
118
src/dorkbox/util/classes/FastThreadLocal.java
Normal file
118
src/dorkbox/util/classes/FastThreadLocal.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
187
src/dorkbox/util/classes/ReflectionUtils.java
Normal file
187
src/dorkbox/util/classes/ReflectionUtils.java
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user