org.libj.util.Classes Maven / Gradle / Ivy
Show all versions of util Show documentation
/* Copyright (c) 2006 LibJ
*
* 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.
*
* You should have received a copy of The MIT License (MIT) along with this
* program. If not, see .
*/
package org.libj.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericSignatureFormatError;
import java.lang.reflect.MalformedParameterizedTypeException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
/**
* Utility providing implementations of methods missing from the API of
* {@link Class}.
*/
public final class Classes {
/**
* Returns the name of the declaring class of the specified class name.
*
* - If the specified class name represents an inner class, the name of the
* declaring class will be returned.
* - If the specified class name represents a regular class, the specified
* class name will be returned.
*
*
*
* Examples
* className returns
* {@code foo.bar.One} {@code foo.bar.One}
* {@code foo.bar.One$Two} {@code foo.bar.One}
* {@code foo.bar.One$Two$Three} {@code foo.bar.One$Two}
* {@code foo.bar.One.$Two$} {@code foo.bar.One.$Two$}
* {@code foo.bar.One.$Two$$Three$$$Four} {@code foo.bar.One.$Two$$Three}
* {@code foo.bar.One.$Two.$$Three$} {@code foo.bar.One.$Two.$$Three$}
*
*
*
* @param className The class name for which to return the name of the
* declaring class.
* @return The name of the declaring class of the specified class name.
* @throws IllegalArgumentException If {@code className} is not a valid
* Java
* Identifier.
* @throws NullPointerException If {@code className} is null.
*/
public static String getDeclaringClassName(final String className) {
if (!Identifiers.isValid(className))
throw new IllegalArgumentException("Not a valid java identifier: " + className);
int index = className.length() - 1;
for (char ch; (index = className.lastIndexOf('$', index - 1)) > 1 && ((ch = className.charAt(index - 1)) == '.' || ch == '$'););
return index <= 0 ? className : className.substring(0, index);
}
/**
* Returns the name of the root declaring class for the specified class name.
*
* - If the specified class name represents an inner class of an inner class
* of an inner class, the name of the root declaring class will be returned
* (i.e. the name of the class corresponding to the name of the {@code .java}
* file in which the inner class is defined).
* - If the specified class name represents a regular class, the specified
* class name will be returned.
*
*
*
* Examples
* className returns
* {@code foo.bar.One} {@code foo.bar.One}
* {@code foo.bar.One$Two} {@code foo.bar.One}
* {@code foo.bar.One$Two$Three} {@code foo.bar.One}
* {@code foo.bar.One.$Two$} {@code foo.bar.One.$Two$}
* {@code foo.bar.One.$Two$$Three$$$Four} {@code foo.bar.One.$Two}
* {@code foo.bar.One.$Two.$$Three$} {@code foo.bar.One.$Two.$$Three$}
*
*
*
* @param className The class name for which to return the name of the root
* declaring class.
* @return The name of the root declaring class for the specified class name.
* @throws IllegalArgumentException If {@code className} is not a valid
* Java
* Identifier.
* @throws NullPointerException If {@code className} is null.
*/
public static String getRootDeclaringClassName(final String className) {
if (!Identifiers.isValid(className))
throw new IllegalArgumentException("Not a valid java identifier: " + className);
final int limit = className.length() - 1;
int index = 0;
while ((index = className.indexOf('$', index + 1)) > 1) {
final char ch = className.charAt(index - 1);
if (index == limit)
return className;
if (ch != '.' && ch != '$')
break;
}
return index == -1 ? className : className.substring(0, index);
}
/**
* Returns the canonical name of the specified class name, as defined by the Java
* Language Specification.
*
*
* Examples
* className returns
* {@code foo.bar.One} {@code foo.bar.One}
* {@code foo.bar.One$Two} {@code foo.bar.One.Two}
* {@code foo.bar.One$Two$Three} {@code foo.bar.One.Two.Three}
* {@code foo.bar.One.$Two$} {@code foo.bar.One.$Two$}
* {@code foo.bar.One.$Two$$Three$$$Four} {@code foo.bar.One.$Two.$Three.$$Four}
* {@code foo.bar.One.$Two.$$Three$} {@code foo.bar.One.$Two.$$Three$}
*
*
*
* @param className The class name.
* @return The canonical name of the underlying specified class name.
* @throws IllegalArgumentException If {@code className} is not a valid
* Java
* Identifier.
* @throws NullPointerException If {@code className} is null.
* @see 6.7.
* Fully Qualified Names and Canonical Names
*/
public static String toCanonicalClassName(final String className) {
if (!Identifiers.isValid(className))
throw new IllegalArgumentException("Not a valid java identifier: " + className);
final StringBuilder builder = new StringBuilder();
builder.append(className.charAt(0));
builder.append(className.charAt(1));
char last = '\0';
for (int i = 2; i < className.length() - 1; ++i) {
final char ch = className.charAt(i);
builder.append(last != '.' && last != '$' && ch == '$' ? '.' : ch);
last = ch;
}
if (className.length() > 2)
builder.append(className.charAt(className.length() - 1));
return builder.toString();
}
/**
* Returns the "Compound Name" of the class or interface represented by
* {@code cls}.
*
* The "Compound Name" is the fully qualified name of a class
* ({@link Class#getName()} with its package name
* ({@link Class#getPackage()}.getName()) removed.
*
* For example:
*
* - The "Compound Name" of {@code java.lang.String} is {@code String}.
* - The "Compound Name" of {@code java.lang.Map.Entry} is
* {@code Map$Entry}.
*
*
* @param cls The class or interface.
* @return The "Compound Name" of the class or interface represented by
* {@code cls}.
* @throws NullPointerException If {@code cls} is null.
*/
public static String getCompoundName(final Class> cls) {
final String pkg = cls.getPackage().getName();
return pkg.length() == 0 ? cls.getName() : cls.getName().substring(pkg.length() + 1);
}
/**
* Returns the canonical "Compound Name" of the class or interface represented
* by {@code cls}.
*
* The canonical "Compound Name" is the fully qualified name of a class
* ({@link Class#getCanonicalName()} with its package name
* ({@link Class#getPackage()}.getName()) removed.
*
* For example:
*
* - The canonical "Compound Name" of {@code java.lang.String} is
* {@code String}.
* - The canonical "Compound Name" of {@code java.lang.Map.Entry} is
* {@code Map.Entry}.
*
*
* @param cls The class or interface.
* @return The canonical "Compound Name" of the class or interface represented
* by {@code cls}.
* @throws NullPointerException If {@code cls} is null.
*/
public static String getCanonicalCompoundName(final Class> cls) {
final String pkg = cls.getPackage().getName();
return pkg.length() == 0 ? cls.getCanonicalName() : cls.getCanonicalName().substring(pkg.length() + 1);
}
/**
* Returns the {@code Class} array most accurately reflecting the actual type
* parameters used in the source code for the generic superclass of the
* specified {@code Class}, or {@code null} if no generic superclass exist.
*
* @param cls The {@code Class}.
* @return The {@code Class} array most accurately reflecting the actual type
* parameters used in the source code for the generic superclass of
* the specified {@code Class}, or {@code null} if no generic
* superclass exist.
* @throws GenericSignatureFormatError If the generic class signature does not
* conform to the format specified in The Java™ Virtual
* Machine Specification.
* @throws TypeNotPresentException If the generic superclass refers to a
* non-existent type declaration.
* @throws MalformedParameterizedTypeException If the generic superclass
* refers to a parameterized type that cannot be instantiated for
* any reason.
* @throws NullPointerException If {@code cls} is null.
*/
public static Type[] getSuperclassGenericTypes(final Class> cls) {
return cls.getGenericSuperclass() instanceof ParameterizedType ? ((ParameterizedType)cls.getGenericSuperclass()).getActualTypeArguments() : null;
}
/**
* Traverses and returns the class hierarchy of the specified {@code Class}.
* This method visits the superclasses and superinterfaces with Breadth First
* Search.
*
* @param cls The {@code Class}.
* @param forEach The {@link Predicate} called for each visited superclass and
* superinterface. If the {@link Predicate} returns {@code false},
* traversal will terminate, and the method will return the set of
* classes that had been visited before termination.
* @return The class hierarchy of the specified {@code Class}.
* @throws NullPointerException If {@code cls} or {@code forEach} is null.
*/
public static Set> getClassHierarchy(Class> cls, final Predicate> forEach) {
final Set> visited = new LinkedHashSet<>();
final Queue> queue = new LinkedList<>();
if (!visitSuperclass(cls, queue, visited, forEach))
return visited;
do {
if (!visitSuperclass(cls.getSuperclass(), queue, visited, forEach))
return visited;
for (final Class> superInterface : cls.getInterfaces())
if (!visitSuperclass(superInterface, queue, visited, forEach))
return visited;
}
while ((cls = queue.poll()) != null);
return visited;
}
private static boolean visitSuperclass(final Class> cls, final Queue> queue, final Set> visited, final Predicate> forEach) {
if (cls == null || !visited.add(cls))
return true;
if (!forEach.test(cls))
return false;
queue.add(cls);
return true;
}
/**
* Returns the array of generic type classes for the specified field. If the
* field is not a parameterized type, this method will return an empty array.
*
* @param field The {@code Field}
* @return The array of generic type classes for the specified field.
* @throws NullPointerException If {@code field} is null.
*/
public static Class>[] getGenericClasses(final Field field) {
final Type genericType = field.getGenericType();
if (!(genericType instanceof ParameterizedType))
return new Class[0];
final Type[] types = ((ParameterizedType)genericType).getActualTypeArguments();
final Class>[] classes = new Class[types.length];
for (int i = 0; i < classes.length; ++i) {
if (types[i] instanceof Class)
classes[i] = (Class>)types[i];
else if (types[i] instanceof ParameterizedType)
classes[i] = (Class>)((ParameterizedType)types[i]).getRawType();
}
return classes;
}
private static Field getField(final Class> cls, final String fieldName, final boolean declared) {
final Field[] fields = declared ? cls.getDeclaredFields() : cls.getFields();
for (final Field field : fields)
if (fieldName.equals(field.getName()))
return field;
return null;
}
/**
* Returns a {@code Field} object that reflects the specified public member
* field of the class or interface represented by {@code cls} (including
* inherited fields). The {@code name} parameter is a {@code String}
* specifying the simple name of the desired field.
*
* The field to be reflected is determined by the algorithm that follows. Let
* C be the class or interface represented by this object:
*
* - If C declares a public field with the name specified, that is the field
* to be reflected.
* - If no field was found in step 1 above, this algorithm is applied
* recursively to each direct superinterface of C. The direct superinterfaces
* are searched in the order they were declared.
* - If no field was found in steps 1 and 2 above, and C has a superclass S,
* then this algorithm is invoked recursively upon S. If C has no superclass,
* then this method returns null.
*
*
* If this {@code Class} object represents an array type, then this method
* does not find the {@code length} field of the array type.
*
* This method differentiates itself from {@link Class#getField(String)} by
* returning null when a field is not found, instead of throwing
* {@link NoSuchFieldException}.
*
* @param cls The class in which to find the public field.
* @param name The field name.
* @return A {@code Field} object that reflects the specified public member
* field of the class or interface represented by {@code cls}
* (including inherited fields). The {@code name} parameter is a
* {@code String} specifying the simple name of the desired field.
* @throws NullPointerException If {@code cls} or {@code name} is null.
* @throws SecurityException If a security manager, s, is present and
* the caller's class loader is not the same as or an ancestor of
* the class loader for the current class and invocation of
* {@link SecurityManager#checkPackageAccess s.checkPackageAccess()}
* denies access to the package of this class.
*/
public static Field getField(final Class> cls, final String name) {
return Classes.getField(cls, name, false);
}
/**
* Returns a {@code Field} object that reflects the specified declared member
* field of the class or interface represented by {@code cls} (excluding
* inherited fields). The {@code name} parameter is a {@code String}
* specifying the simple name of the desired field.
*
* Declared fields include public, protected, default (package) access,
* and private visibility.
*
* If this {@code Class} object represents an array type, then this method
* does not find the {@code length} field of the array type.
*
* This method differentiates itself from {@link Class#getDeclaredField(String)} by
* returning null when a field is not found, instead of throwing
* {@link NoSuchFieldException}.
*
* @param cls The class in which to find the declared field.
* @param name The field name.
* @return A {@code Field} object that reflects the specified public member
* field of the class or interface represented by {@code cls}
* (excluding inherited fields). The {@code name} parameter is a
* {@code String} specifying the simple name of the desired field.
* @throws NullPointerException If {@code cls} or {@code name} is null.
* @throws SecurityException If a security manager, s, is present and
* the caller's class loader is not the same as or an ancestor of
* the class loader for the current class and invocation of
* {@link SecurityManager#checkPackageAccess s.checkPackageAccess()}
* denies access to the package of this class.
*/
public static Field getDeclaredField(final Class> cls, final String name) {
return Classes.getField(cls, name, true);
}
/**
* Returns a {@code Field} object that reflects the specified declared member
* field of the class or interface represented by {@code cls} (including
* inherited fields). The {@code name} parameter is a {@code String}
* specifying the simple name of the desired field.
*
* Declared fields include public, protected, default (package) access,
* and private visibility.
*
* If this {@code Class} object represents an array type, then this method
* does not find the {@code length} field of the array type.
*
* This method differentiates itself from {@link Class#getDeclaredField(String)} by
* returning null when a field is not found, instead of throwing
* {@link NoSuchFieldException}.
*
* @param cls The class in which to find the declared field.
* @param name The field name.
* @return A {@code Field} object that reflects the specified public member
* field of the class or interface represented by {@code cls}
* (including inherited fields). The {@code name} parameter is a
* {@code String} specifying the simple name of the desired field.
* @throws NullPointerException If {@code cls} or {@code name} is null.
* @throws SecurityException If a security manager, s, is present and
* the caller's class loader is not the same as or an ancestor of
* the class loader for the current class and invocation of
* {@link SecurityManager#checkPackageAccess s.checkPackageAccess()}
* denies access to the package of this class.
*/
public static Field getDeclaredFieldDeep(Class> cls, final String name) {
Field field;
do
field = getField(cls, name, true);
while (field == null && (cls = cls.getSuperclass()) != null);
return field;
}
/**
* Returns a Constructor object that reflects the specified public constructor
* of the class or interface represented by {@code cls} (including inherited
* constructors), or null if the constructor is not found.
*
* The {@code parameterTypes} parameter is an array of Class objects that
* identify the constructor's formal parameter types, in declared order. If
* {@code cls} represents an inner class declared in a non-static context, the
* formal parameter types include the explicit enclosing instance as the first
* parameter.
*
* This method differentiates itself from
* {@link Class#getConstructor(Class...)} by returning null when a method is
* not found, instead of throwing {@link NoSuchMethodException}.
*
* @param cls The class in which to find the public constructor.
* @param parameterTypes The parameter array.
* @return A Constructor object that reflects the specified public constructor
* of the class or interface represented by {@code cls} (including
* inherited constructors), or null if the constructor is not found.
* @throws NullPointerException If {@code cls} is null.
*/
public static Constructor> getConstructor(final Class> cls, final Class> ... parameterTypes) {
final Constructor>[] constructors = cls.getConstructors();
for (final Constructor> constructor : constructors)
if (Arrays.equals(constructor.getParameterTypes(), parameterTypes))
return constructor;
return null;
}
/**
* Returns a Constructor object that reflects the specified declared
* constructor of the class or interface represented by {@code cls} (excluding
* inherited constructors), or null if the constructor is not found.
*
* Declared constructors include public, protected, default (package) access,
* and private visibility.
*
* The {@code parameterTypes} parameter is an array of Class objects that
* identify the constructor's formal parameter types, in declared order. If
* {@code cls} represents an inner class declared in a non-static context, the
* formal parameter types include the explicit enclosing instance as the first
* parameter.
*
* This method differentiates itself from
* {@link Class#getDeclaredConstructor(Class...)} by returning null when a
* method is not found, instead of throwing {@link NoSuchMethodException}.
*
* @param cls The class in which to find the declared constructor.
* @param parameterTypes The parameter array.
* @return A Constructor object that reflects the specified declared
* constructor of the class or interface represented by {@code cls}
* (excluding inherited constructors), or null if the constructor is
* not found.
* @throws NullPointerException If {@code cls} is null.
*/
public static Constructor> getDeclaredConstructor(final Class> cls, final Class> ... parameterTypes) {
final Constructor>[] constructors = cls.getDeclaredConstructors();
for (final Constructor> constructor : constructors)
if (Arrays.equals(constructor.getParameterTypes(), parameterTypes))
return constructor;
return null;
}
/**
* Changes the annotation value for {@code key} in {@code annotation} to
* {@code newValue}, and returns the previous value.
*
* @param Type parameter of the value.
* @param annotation The annotation.
* @param key The key.
* @param newValue The new value.
* @return The previous value assigned to {@code key}.
* @throws IllegalArgumentException If {@code newValue} does not match the
* required type of the value for {@code key}.
* @throws NullPointerException If {@code annotation}, {@code key}, or
* {@code newValue} is null.
*/
@SuppressWarnings("unchecked")
public static T setAnnotationValue(final Annotation annotation, final String key, final T newValue) {
final Object handler = Proxy.getInvocationHandler(annotation);
Objects.requireNonNull(key);
Objects.requireNonNull(newValue);
final Field field;
final Map memberValues;
try {
field = handler.getClass().getDeclaredField("memberValues");
field.setAccessible(true);
memberValues = (Map)field.get(handler);
}
catch (final IllegalArgumentException | IllegalAccessException | NoSuchFieldException e) {
throw new IllegalStateException(e);
}
final T oldValue = (T)memberValues.get(key);
if (oldValue == null)
throw new IllegalArgumentException(key + " is not a valid key");
if (newValue.getClass() != oldValue.getClass())
throw new IllegalArgumentException(newValue.getClass().getName() + " does not match the required type " + oldValue.getClass().getName());
memberValues.put(key, newValue);
return oldValue;
}
private interface SuperclassRecurser extends Repeat.Recurser,M,A> {
@Override
default Class> next(final Class> container) {
return container.getSuperclass();
}
}
private interface DeclaredFieldRecurser extends SuperclassRecurser {
@Override
default Field[] members(final Class> container) {
return container.getDeclaredFields();
}
}
private interface DeclaredMethodRecurser extends SuperclassRecurser {
@Override
default Method[] members(final Class> container) {
return container.getDeclaredMethods();
}
}
private interface DeclaredClassRecurser extends SuperclassRecurser,A> {
@Override
default Class>[] members(final Class> container) {
return container.getDeclaredClasses();
}
}
private static final Repeat.Recurser,Field,Object> declaredFieldRecurser = new DeclaredFieldRecurser