From 23d661d2452f74a37b531b4ed93079c570dcdf11 Mon Sep 17 00:00:00 2001 From: nathan Date: Fri, 19 Jun 2020 15:15:50 +0200 Subject: [PATCH] Added/moved class utilities. --- .../{generics => classes}/ClassHelper.java | 2 +- src/dorkbox/util/classes/ClassHierarchy.java | 190 ++++++++++++++++++ .../util/{ => classes}/ClassResolver.java | 2 +- src/dorkbox/util/classes/FastThreadLocal.java | 118 +++++++++++ src/dorkbox/util/classes/ReflectionUtils.java | 187 +++++++++++++++++ 5 files changed, 497 insertions(+), 2 deletions(-) rename src/dorkbox/util/{generics => classes}/ClassHelper.java (99%) create mode 100644 src/dorkbox/util/classes/ClassHierarchy.java rename src/dorkbox/util/{ => classes}/ClassResolver.java (98%) create mode 100644 src/dorkbox/util/classes/FastThreadLocal.java create mode 100644 src/dorkbox/util/classes/ReflectionUtils.java diff --git a/src/dorkbox/util/generics/ClassHelper.java b/src/dorkbox/util/classes/ClassHelper.java similarity index 99% rename from src/dorkbox/util/generics/ClassHelper.java rename to src/dorkbox/util/classes/ClassHelper.java index ff1e12b..a68a203 100644 --- a/src/dorkbox/util/generics/ClassHelper.java +++ b/src/dorkbox/util/classes/ClassHelper.java @@ -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; diff --git a/src/dorkbox/util/classes/ClassHierarchy.java b/src/dorkbox/util/classes/ClassHierarchy.java new file mode 100644 index 0000000..eb8bf7d --- /dev/null +++ b/src/dorkbox/util/classes/ClassHierarchy.java @@ -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> arrayCache; + private volatile IdentityMap, Class[]> superClassesCache; + + // Recommended for best performance while adhering to the "single writer principle". Must be static-final + private static final AtomicReferenceFieldUpdater arrayREF = + AtomicReferenceFieldUpdater.newUpdater(ClassHierarchy.class, + IdentityMap.class, + "arrayCache"); + + private static final AtomicReferenceFieldUpdater 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>(32, loadFactor); + this.superClassesCache = new IdentityMap, 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 + *

+ * 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[]> 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> superTypesIterator = getSuperTypes(clazz); + final ArrayList> newList = new ArrayList>(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> 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> getSuperTypes(Class from) { + // This must be a 'set' because there can be duplicates, depending on the object hierarchy + final IdentityMap, Boolean> superclasses = new IdentityMap, 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, 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 cast(Object obj) { + return (T) obj; + } +} diff --git a/src/dorkbox/util/ClassResolver.java b/src/dorkbox/util/classes/ClassResolver.java similarity index 98% rename from src/dorkbox/util/ClassResolver.java rename to src/dorkbox/util/classes/ClassResolver.java index b9233e8..177f01b 100644 --- a/src/dorkbox/util/ClassResolver.java +++ b/src/dorkbox/util/classes/ClassResolver.java @@ -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 { /** diff --git a/src/dorkbox/util/classes/FastThreadLocal.java b/src/dorkbox/util/classes/FastThreadLocal.java new file mode 100644 index 0000000..6155ebb --- /dev/null +++ b/src/dorkbox/util/classes/FastThreadLocal.java @@ -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 + * LibStruct library. + * + *

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.

+ * + *

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.

+ * + *

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.

+ * + * @param the thread-local value type + * + * @author Riven + * @see ThreadLocal + */ +public class FastThreadLocal { + + @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); + } + +} diff --git a/src/dorkbox/util/classes/ReflectionUtils.java b/src/dorkbox/util/classes/ReflectionUtils.java new file mode 100644 index 0000000..9536b54 --- /dev/null +++ b/src/dorkbox/util/classes/ReflectionUtils.java @@ -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 + * @return + */ + public static + Method[] getMethods(Class target, Class annotationClass) { + ArrayList methods = new ArrayList(); + + getMethods(target, annotationClass, methods); + return methods.toArray(EMPTY_METHODS); + } + + private static + void getMethods(Class target, Class annotationClass, ArrayList 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 Class of annotation type + * @return Annotation instance or null + */ + private static + A getAnnotation(AnnotatedElement from, Class annotationType, IdentityMap 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 getAnnotation(AnnotatedElement from, Class annotationType) { + return getAnnotation(from, annotationType, new IdentityMap()); + } + + // + 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; + } +}