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

org.bbottema.javareflection.ClassUtils Maven / Gradle / Ivy

/*
 * Copyright (C) ${project.inceptionYear} Benny Bottema ([email protected])
 *
 * 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 org.bbottema.javareflection;

import lombok.experimental.UtilityClass;
import org.bbottema.javareflection.model.MethodModifier;
import org.bbottema.javareflection.util.ExternalClassLoader;
import org.bbottema.javareflection.valueconverter.IncompatibleTypeException;
import org.bbottema.javareflection.valueconverter.ValueConversionHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static org.bbottema.javareflection.LookupCaches.CLASS_CACHE;
import static org.bbottema.javareflection.util.MiscUtil.trustedNullableCast;

/**
 * Utility with convenience methods that operate on the class level.
 * 
    *
  • With this helper class you can locate and/or load classes. An advanced Class lookup ({@link #locateClass(String, boolean, * ClassLoader)}), that allows a full scan (to try all * packages known) and an optional {@link ExternalClassLoader} instance that is able to actually compile a .java file on the fly and load its compile * .class file
  • *
  • create a new instance while handling all the exceptions
  • *
  • find fields or assign values to fields, using casting, autoboxing or type conversions
  • *
  • simply give back a list of field / method names
  • *
*/ @UtilityClass public final class ClassUtils { /** * Searches the JVM and optionally all of its packages * * @param className The name of the class to locate. * @param fullscan Whether a full scan through all available java packages is required. * @param classLoader Optional user-provided classloader. * @return The Class reference if found or null otherwise. */ @Nullable @SuppressWarnings({"WeakerAccess", "unchecked"}) public static Class locateClass(final String className, final boolean fullscan, @Nullable final ClassLoader classLoader) { final String cacheKey = className + fullscan; if (CLASS_CACHE.containsKey(cacheKey)) { return (Class) CLASS_CACHE.get(cacheKey); } final Class _class; if (fullscan) { _class = locateClass(className, null, classLoader); } else { // try standard package used for most common classes _class = locateClass(className, "java.lang", classLoader); } CLASS_CACHE.put(cacheKey, _class); return (Class) _class; } @Nullable @SuppressWarnings({"WeakerAccess", "unchecked"}) public static Class locateClass(final String className, @Nullable final String inPackage, @Nullable final ClassLoader classLoader) { final String cacheKey = className + inPackage; if (CLASS_CACHE.containsKey(cacheKey)) { return (Class) CLASS_CACHE.get(cacheKey); } Class _class = locateClass(className, classLoader); if (_class == null) { _class = PackageUtils.scanPackagesForClass(className, inPackage, classLoader); } CLASS_CACHE.put(cacheKey, _class); return (Class) _class; } /** * This function dynamically tries to locate a class. First it searches the class-cache list, then it tries to get it from the Virtual Machine * using {@code Class.forName(String)}. * * @param fullClassName The Class that needs to be found. * @param classLoader Optional user-provided classloader. * @return The {@code Class} object found from cache or VM. */ @SuppressWarnings({"WeakerAccess", "unchecked"}) @Nullable public static Class locateClass(final String fullClassName, @Nullable final ClassLoader classLoader) { try { Class _class = null; if (classLoader != null) { _class = classLoader.loadClass(fullClassName); } if (_class == null) { _class = Class.forName(fullClassName); } return (Class) _class; } catch (final ClassNotFoundException e) { return null; } } /** * Simply calls {@link Class#newInstance()} and hides the exception handling boilerplate code. * * @param _class The datatype for which we need to create a new instance of. * @param Type used to parameterize the return instance. * @return A new parameterized instance of the given type. */ @NotNull @SuppressWarnings("WeakerAccess") public static T newInstanceSimple(final Class _class) { try { return _class.getConstructor().newInstance(); } catch (SecurityException e) { throw new RuntimeException("unable to invoke parameterless constructor; security problem", e); } catch (InstantiationException e) { throw new RuntimeException("unable to complete instantiation of object", e); } catch (IllegalAccessException e) { throw new RuntimeException("unable to access parameterless constructor", e); } catch (InvocationTargetException e) { throw new RuntimeException("unable to invoke parameterless constructor", e); } catch (NoSuchMethodException e) { throw new RuntimeException("unable to find parameterless constructor (not public?)", e); } } /** * Gets value from the field returned by {@link #solveField(Object, String)}.; */ @Nullable @SuppressWarnings("WeakerAccess") public static T solveFieldValue(final Object object, final String fieldName) { final Field field = solveField(object, fieldName); if (field == null) { throw new RuntimeException(new NoSuchFieldException()); } field.setAccessible(true); try { return trustedNullableCast(field.get(object)); } catch (IllegalAccessException e) { throw new RuntimeException("Was unable to retrieve value from field %s", e); } } /** * Delegates to {@link #solveField(Class, String)} by using the class of given object object or if object itself if it ss a class. */ @Nullable @SuppressWarnings("WeakerAccess") public static Field solveField(final Object object, final String fieldName) { return object.getClass().equals(Class.class) ? solveField((Class) object, fieldName) // Java static field : solveField(object.getClass(), fieldName); // Java instance field } /** * Returns a field from the given Class that goes by the name of fieldName. Will search for fields on implemented interfaces and superclasses. * * @param _class The reference to the Class to fetch the field from. * @param fieldName The identifier or name of the member field/property. * @return The value of the Field. */ @Nullable @SuppressWarnings("WeakerAccess") public static Field solveField(final Class _class, final String fieldName) { Field resolvedField = null; try { resolvedField = _class.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { for (int i = 0; resolvedField == null && i < _class.getInterfaces().length; i++) { resolvedField = solveField(_class.getInterfaces()[i], fieldName); } for (Class superclass = _class.getSuperclass(); resolvedField == null && superclass != null; superclass = superclass.getSuperclass()) { resolvedField = solveField(superclass, fieldName); } } return resolvedField; } /** * Assigns a value to a field id on the given object o. If a simple assignment fails, a common conversion will be * attempted. * * @param o The object to find the field on. * @param property The name of the field we're assigning the value to. * @param value The value to assign to the field, may be converted to the field's type through common conversion. * @return The actual value that was assigned (the original or the converted value). * @throws IllegalAccessException Thrown by {@link Field#set(Object, Object)} * @throws NoSuchFieldException Thrown if the {@link Field} could not be found, even after trying to convert the value to the target type. * @see ValueConversionHelper#convert(Object, Class) */ @Nullable @SuppressWarnings("WeakerAccess") public static Object assignToField(final Object o, final String property, final Object value) throws IllegalAccessException, NoSuchFieldException { final Field field = solveField(o, property); if (field != null) { field.setAccessible(true); Object assignedValue = value; try { field.set(o, value); } catch (final IllegalArgumentException ie) { try { assignedValue = ValueConversionHelper.convert(value, field.getType()); } catch (IncompatibleTypeException e) { throw new NoSuchFieldException(e.getMessage()); } field.set(o, assignedValue); } return assignedValue; } else { throw new NoSuchFieldException(); } } /** * Returns a list of names that represent the fields on an Object. * * @param subject The Object who's properties/fields need to be reflected. * @return A list of names that represent the fields on the given Object. */ @NotNull @SuppressWarnings("WeakerAccess") public static Collection collectPropertyNames(final Object subject) { final Collection properties = new LinkedHashSet<>(); final Field[] fields = subject.getClass().getFields(); for (final Field f : fields) { properties.add(f.getName()); } return properties; } /** * @return Returns the result of {@link #collectMethods(Class, Class, EnumSet)} mapped to the method names. */ @SuppressWarnings("WeakerAccess") public static Set collectMethodNames(Class dataType, Class boundaryMarker, EnumSet methodModifiers) { Set methodNames = new HashSet<>(); for (Method m : collectMethods(dataType, boundaryMarker, methodModifiers)) { methodNames.add(m.getName()); } return methodNames; } /** * @return The result of {@link #collectMethodsMappingToName(Class, Class, EnumSet)} filtered on method name. */ @SuppressWarnings("WeakerAccess") public static List collectMethodsByName(final Class type, Class boundaryMarker, EnumSet methodModifiers, final String methodName) { return collectMethodsMappingToName(type, boundaryMarker, methodModifiers).get(methodName); } /** * @return Whether {@link #collectMethodsMappingToName(Class, Class, EnumSet)} contains a method with the given name. */ @SuppressWarnings("WeakerAccess") public static boolean hasMethodByName(final Class type, Class boundaryMarker, EnumSet methodModifiers, final String methodName) { LinkedHashMap> methodsByName = collectMethodsMappingToName(type, boundaryMarker, methodModifiers); return methodsByName.containsKey(methodName) && !methodsByName.get(methodName).isEmpty(); } /** * @return The first result of {@link #collectMethodsByName(Class, Class, EnumSet, String)}. * Note: methods are ordered in groups (see {@link #collectMethods(Class, Class, EnumSet)})). */ @Nullable @SuppressWarnings("WeakerAccess") public static Method findFirstMethodByName(final Class type, Class boundaryMarker, EnumSet methodModifiers, final String methodName) { List methods = collectMethodsByName(type, boundaryMarker, methodModifiers, methodName); return methods.isEmpty() ? null : methods.iterator().next(); } /** * @return The result of {@link #collectMethods(Class, Class, EnumSet)} filtered on method name, * ordered in groups (see {@link #collectMethods(Class, Class, EnumSet)})). */ @SuppressWarnings("WeakerAccess") public static LinkedHashMap> collectMethodsMappingToName(Class type, Class boundaryMarker, EnumSet methodModifiers) { LinkedHashMap> methodsMappedToName = new LinkedHashMap<>(); for (Method method : collectMethods(type, boundaryMarker, methodModifiers)) { if (!methodsMappedToName.containsKey(method.getName())) { methodsMappedToName.put(method.getName(), new ArrayList()); } methodsMappedToName.get(method.getName()).add(method); } return methodsMappedToName; } /** * Returns a list of names that represent the methods on an Object. *

* Methods are ordered by their declaring type in the inheritance chain, but unordered for methods of the same type. * In other words, considering type A, B and C each with methods 1, 2 and 3, the methods might be ordered as follows: * {@code [A2,A1,B1,B2,C2,C1]}. * * @param methodModifiers List of method modifiers that will match any method that has one of them. * @param boundaryMarker Optional type to limit (including) how far back up the inheritance chain we go for discovering methods. * @return Returns a list with methods, either {@link Method}s. */ @SuppressWarnings("WeakerAccess") public static List collectMethods(Class dataType, Class boundaryMarker, EnumSet methodModifiers) { final List allMethods = new ArrayList<>(); for (Method declaredMethod : dataType.getDeclaredMethods()) { if (MethodModifier.meetsModifierRequirements(declaredMethod, methodModifiers)) { allMethods.add(declaredMethod); } } for (Class implementedInterface : dataType.getInterfaces()) { allMethods.addAll(collectMethods(implementedInterface, boundaryMarker, methodModifiers)); } if (dataType != boundaryMarker && dataType.getSuperclass() != null) { allMethods.addAll(collectMethods(dataType.getSuperclass(), boundaryMarker, methodModifiers)); } return allMethods; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy