/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * 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.arakhne.afc.vmutil; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.regex.Pattern; import org.eclipse.xtext.xbase.lib.Inline; import org.eclipse.xtext.xbase.lib.Pure; import org.arakhne.afc.vmutil.asserts.AssertMessages; /** * This utility class provides a way to extend the reflection API and * the Class class with autoboxing-compliant functions. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since since JDK 1.5 */ public final class ReflectionUtil { private ReflectionUtil() { // } /** * Determines if the specified <code>Object</code> is assignment-compatible * with the object represented by the <code>Class</code>. This method extends * {@link Class#isInstance(Object)} with autoboxing support. * * @param type is the class against which the object must be test * @param obj is the object to check * @return <code>true</code> if <code>obj</code> is an instance of the type * @see Class#isInstance(Object) */ @Pure @SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity", "checkstyle:returncount"}) public static boolean isInstance(Class<?> type, Object obj) { assert type != null; if (obj == null) { return false; } // Test according to the Class's behaviour if (type.isInstance(obj)) { return true; } // Test according to autoboxing if (type.isPrimitive() && type != Void.class && type != void.class) { if (type == Boolean.class) { return boolean.class.isInstance(obj); } if (type == boolean.class) { return Boolean.class.isInstance(obj); } if (type == Character.class) { return char.class.isInstance(obj); } if (type == char.class) { return Character.class.isInstance(obj); } if (type == Byte.class) { return byte.class.isInstance(obj); } if (type == byte.class) { return Byte.class.isInstance(obj); } if (type == Short.class) { return short.class.isInstance(obj); } if (type == short.class) { return Short.class.isInstance(obj); } if (type == Integer.class) { return int.class.isInstance(obj); } if (type == int.class) { return Integer.class.isInstance(obj); } if (type == Long.class) { return long.class.isInstance(obj); } if (type == long.class) { return Long.class.isInstance(obj); } if (type == Float.class) { return float.class.isInstance(obj); } if (type == float.class) { return Float.class.isInstance(obj); } if (type == Double.class) { return double.class.isInstance(obj); } if (type == double.class) { return Double.class.isInstance(obj); } if (type == Void.class) { return void.class.isInstance(obj); } if (type == void.class) { return Void.class.isInstance(obj); } assert false : AssertMessages.unsupportedPrimitiveType(); } return false; } /** * Determines if the <code>assignmentTarget</code> object is either the same as, * or is a superclass or superinterface of, the class or interface * represented by the specified * <code>assignementSource</code> parameter. This method extends * {@link Class#isAssignableFrom(Class)} with autoboxing support. * * @param assignementTarget is the class that is tested to be a super class. * @param assignementSource is the class that is tested to be a sub class. * @return <code>true</code> if an object of the {@code assignementSource} type * could be assigned to a variable of {@code assignementTarget} type, * otherwise <code>false</code>. */ @Pure public static boolean isAssignableFrom(Class<?> assignementTarget, Class<?> assignementSource) { assert assignementSource != null; assert assignementTarget != null; // Test according to the Class's behaviour if (assignementTarget.isAssignableFrom(assignementSource)) { return true; } // Test according to autoboxing if (assignementTarget.isPrimitive() && assignementSource.isPrimitive() && assignementTarget != Void.class && assignementTarget != void.class && assignementSource != Void.class && assignementSource != void.class) { return true; } return false; } /** Replies the type that corresponds to the specified class. * If the name corresponds to a primitive type, the low-level type * will be replied. * This method extends * {@link Class#forName(String)} with autoboxing support. * * @param name is the name of the class to load. * @return the loaded class * @throws ClassNotFoundException if name names an * unknown class or primitive */ @Pure @SuppressWarnings({"checkstyle:returncount", "checkstyle:npathcomplexity"}) public static Class<?> forName(String name) throws ClassNotFoundException { if (name == null || "".equals(name) || "null".equals(name) //$NON-NLS-1$ //$NON-NLS-2$ || "void".equals(name)) { //$NON-NLS-1$ return void.class; } if ("boolean".equals(name)) { //$NON-NLS-1$ return boolean.class; } if ("byte".equals(name)) { //$NON-NLS-1$ return byte.class; } if ("char".equals(name)) { //$NON-NLS-1$ return char.class; } if ("double".equals(name)) { //$NON-NLS-1$ return double.class; } if ("float".equals(name)) { //$NON-NLS-1$ return float.class; } if ("int".equals(name)) { //$NON-NLS-1$ return int.class; } if ("long".equals(name)) { //$NON-NLS-1$ return long.class; } if ("short".equals(name)) { //$NON-NLS-1$ return short.class; } return Class.forName(name); } /** Replies the type that corresponds to the specified class. * If the name corresponds to a primitive type, the low-level type * will be replied. * This method extends * {@link Class#forName(String)} with autoboxing support. * * @param name is the name of the class to load. * @param loader is the class loader to use. * @return the loaded class * @throws ClassNotFoundException if name names an * unknown class or primitive */ @Pure @Inline(value = "ReflectionUtil.forName(($1), true, ($2))", imported = {ReflectionUtil.class}) public static Class<?> forName(String name, ClassLoader loader) throws ClassNotFoundException { return forName(name, true, loader); } /** Replies the type that corresponds to the specified class. * If the name corresponds to a primitive type, the low-level type * will be replied. * This method extends * {@link Class#forName(String)} with autoboxing support. * * @param name is the name of the class to load. * @param typeInitialization must be <code>true</code> to initialize the type, <code>false</code> otherwise. * @param loader is the class loader to use. * @return the loaded class * @throws ClassNotFoundException if name names an * unknown class or primitive */ @Pure @SuppressWarnings({"checkstyle:returncount", "checkstyle:npathcomplexity"}) public static Class<?> forName(String name, boolean typeInitialization, ClassLoader loader) throws ClassNotFoundException { if (name == null || "".equals(name) || "null".equals(name) //$NON-NLS-1$ //$NON-NLS-2$ || "void".equals(name)) { //$NON-NLS-1$ return void.class; } if ("boolean".equals(name)) { //$NON-NLS-1$ return boolean.class; } if ("byte".equals(name)) { //$NON-NLS-1$ return byte.class; } if ("char".equals(name)) { //$NON-NLS-1$ return char.class; } if ("double".equals(name)) { //$NON-NLS-1$ return double.class; } if ("float".equals(name)) { //$NON-NLS-1$ return float.class; } if ("int".equals(name)) { //$NON-NLS-1$ return int.class; } if ("long".equals(name)) { //$NON-NLS-1$ return long.class; } if ("short".equals(name)) { //$NON-NLS-1$ return short.class; } return Class.forName(name, typeInitialization, loader); } /** * Replies the list of the classes in the given package. * * @param pkg is the package to explore. * @return the list of classes in the package. */ @Pure @Inline(value = "ReflectionUtil.getPackageClasses(($1).getName())", imported = {ReflectionUtil.class}) public static Collection<Class<?>> getPackageClasses(Package pkg) { return getPackageClasses(pkg.getName()); } /** * Replies the list of the classes in the given package. * * @param packageName is the name of the package to explore. * @return the list of classes in the package. */ @Pure public static Collection<Class<?>> getPackageClasses(String packageName) { final Collection<Class<?>> classes = new ArrayList<>(); final String[] entries = System.getProperty("java.class.path") //$NON-NLS-1$ .split(Pattern.quote(System.getProperty("path.separator"))); //$NON-NLS-1$ String lentry; for (final String path : entries) { lentry = path.toLowerCase(); if (lentry.endsWith(".jar") || lentry.endsWith(".war")) { //$NON-NLS-1$ //$NON-NLS-2$ getPackageClassesFromJar(classes, path, packageName); } else { getPackageClassesFromFileSystem(classes, path, packageName); } } return classes; } private static String basename(String name) { final int idx = name.lastIndexOf('/'); if (idx >= 0 && idx < name.length()) { return name.substring(idx + 1); } return name; } private static String filename(String name) { final String basename = basename(name); final int idx = basename.indexOf('.'); if (idx >= 0 && idx < basename.length()) { return basename.substring(0, idx); } return name; } private static void getPackageClassesFromJar(Collection<Class<?>> classes, String jarFilename, String packageName) { try (JarFile jarFile = new JarFile(jarFilename)) { final String packagePath = packageName.replace(".", "/"); //$NON-NLS-1$ //$NON-NLS-2$ final Enumeration<JarEntry> entries = jarFile.entries(); JarEntry entry; String entryPath; String entryClassname; while (entries.hasMoreElements()) { entry = entries.nextElement(); entryPath = entry.getName(); // In package and not inner class if (entryPath.startsWith(packagePath) && !entryPath.endsWith("/") //$NON-NLS-1$ && !entryPath.contains("$")) { //$NON-NLS-1$ entryClassname = packageName + "." + filename(entryPath); //$NON-NLS-1$ try { classes.add(Class.forName(entryClassname)); } catch (ClassNotFoundException exception) { // } } } } catch (IOException exception) { // } } private static void getPackageClassesFromFileSystem(Collection<Class<?>> classes, String directory, String packageName) { final String packagePath = packageName.replace(".", File.separator); //$NON-NLS-1$ final File packageDirectory = new File(directory, packagePath); String entryClassname; if (packageDirectory.isDirectory()) { for (final String entryPath : packageDirectory.list()) { // In package and not inner class if (!entryPath.contains("$")) { //$NON-NLS-1$ entryClassname = packageName + "." + FileSystem.shortBasename(entryPath); //$NON-NLS-1$ try { classes.add(Class.forName(entryClassname)); } catch (AssertionError e) { throw e; } catch (Throwable exception) { // } } } } } /** * Replies the list of all the subclasses of the given class * in the current classpath. * * @param <T> is the type of the superclass. * @param className is the name of the class to explore. * @return the list of subclasses. */ @Pure public static <T> Collection<Class<? extends T>> getSubClasses(Class<T> className) { final Collection<Class<? extends T>> list = new ArrayList<>(); getSubClasses(className, true, true, true, list); return list; } /** * Replies the list of all the subclasses of the given class * in the current classpath. * * @param <T> is the type of the superclass. * @param className is the name of the class to explore. * @param allowAbstract is <code>true</code> to allow abstract classes to be put in the replied list * @param allowInterface is <code>true</code> to allow interfaces to be put in the replied list. * @param allowEnum is <code>true</code> to allow enumeration to be put in the replied list. * @param result is the list of subclasses which will be filled by this function. */ public static <T> void getSubClasses(Class<T> className, boolean allowAbstract, boolean allowInterface, boolean allowEnum, Collection<Class<? extends T>> result) { final String[] entries = System.getProperty("java.class.path") //$NON-NLS-1$ .split(Pattern.quote(System.getProperty("path.separator"))); //$NON-NLS-1$ String lentry; for (final String path : entries) { lentry = path.toLowerCase(); if (lentry.endsWith(".jar") || lentry.endsWith(".war")) { //$NON-NLS-1$ //$NON-NLS-2$ getSubClassesFromJar(result, path, className, allowAbstract, allowInterface, allowEnum); } else { getSubClassesFromFileSystem(result, path, className, allowAbstract, allowInterface, allowEnum); } } } @SuppressWarnings({"unchecked", "checkstyle:magicnumber"}) private static <T> void getSubClassesFromJar(Collection<Class<? extends T>> classes, String jarFilename, Class<T> className, boolean allowAbstract, boolean allowInterface, boolean allowEnum) { try (JarFile jarFile = new JarFile(jarFilename)) { final String classN = className.getCanonicalName(); if (classN != null) { final Enumeration<JarEntry> entries = jarFile.entries(); JarEntry entry; String entryPath; String entryClassname; while (entries.hasMoreElements()) { entry = entries.nextElement(); entryPath = entry.getName(); // In package and not inner class if (entryPath.endsWith(".class") //$NON-NLS-1$ && !entryPath.contains("$")) { //$NON-NLS-1$ entryClassname = entryPath.substring(0, entryPath.length() - 6).replaceAll( Pattern.quote(File.separator), "."); //$NON-NLS-1$ try { final Class<?> clazz = Class.forName(entryClassname); if ((className.isAssignableFrom(clazz)) && (allowAbstract || !Modifier.isAbstract(clazz.getModifiers())) && (allowInterface || !clazz.isInterface()) && (allowEnum || !clazz.isEnum())) { classes.add((Class<? extends T>) clazz); } } catch (AssertionError e) { throw e; } catch (Throwable exception) { // } } } } } catch (IOException exception) { // } } @SuppressWarnings({"unchecked", "checkstyle:cyclomaticcomplexity"}) private static <T> void getSubClassesFromFileSystem(Collection<Class<? extends T>> classes, String directory, Class<T> className, boolean allowAbstract, boolean allowInterface, boolean allowEnum) { final String classN = className.getCanonicalName(); if (classN != null) { final List<String> directories = new ArrayList<>(); directories.add(""); //$NON-NLS-1$ String ldir; String entryClassname; File dir; File fullFile; while (!directories.isEmpty()) { ldir = directories.remove(0); dir = new File(directory, ldir); if (dir.isDirectory()) { for (final String entryPath : dir.list()) { fullFile = new File(dir, entryPath); if (fullFile.isDirectory()) { if (ldir == null || "".equals(ldir)) { //$NON-NLS-1$ directories.add(entryPath); } else { directories.add(new File(ldir, entryPath).toString()); } } else if (entryPath.endsWith(".class") //$NON-NLS-1$ && !entryPath.contains("$")) { //$NON-NLS-1$ // In package and not inner class assert ldir != null; entryClassname = ldir.replaceAll( Pattern.quote(File.separator), ".") //$NON-NLS-1$ + "." + FileSystem.shortBasename(entryPath); //$NON-NLS-1$ try { final Class<?> clazz = Class.forName(entryClassname); if ((className.isAssignableFrom(clazz)) && (allowAbstract || !Modifier.isAbstract(clazz.getModifiers())) && (allowInterface || !clazz.isInterface()) && (allowEnum || !clazz.isEnum())) { classes.add((Class<? extends T>) clazz); } } catch (AssertionError e) { throw e; } catch (Throwable exception) { // } } } } } } } /** * Determines the interfaces implemented by the classes from the lowest type * to the highestType which are extended the given interfaceType. * * <p>Insteed of {@link Class#getInterfaces()}, this function is exploring * the super classes. This function does not explore super-interfaces * of implemented interfaces. * * <pre><code> * interface IA {} * interface IB extends IA {} * interface IC {} * interface ID extends IB, IC {} * class CA implements IC {} * class CB extends CA {} * class CC extends CB implements IB {} * </code></pre> * This function replies for: * <ul> * <li><code>getAllDirectInterfaces(IA,null,null)</code>=<code>{}</code></li> * <li><code>getAllDirectInterfaces(IB,null,null)</code>=<code>{IA}</code></li> * <li><code>getAllDirectInterfaces(IC,null,null)</code>=<code>{}</code></li> * <li><code>getAllDirectInterfaces(ID,null,null)</code>=<code>{IB,IC}</code></li> * <li><code>getAllDirectInterfaces(CA,null,null)</code>=<code>{IC}</code></li> * <li><code>getAllDirectInterfaces(CB,null,null)</code>=<code>{IC}</code></li> * <li><code>getAllDirectInterfaces(CC,null,null)</code>=<code>{IB,IC}</code></li> * </ul> * * @param <T> is the highest type to explore in type hierarchy. * @param <I> indicates the type of the replied interfaces. * @param lowestType is the lowest type to explore in type hierarchy. * @param highestType is the highest type to explore in type hierarchy. * @param interfaceType indicates the type of the replied interfaces. * @return the implemented interfaces. * @since 5.0 */ @Pure @SuppressWarnings("unchecked") public static <T, I> Set<Class<? extends I>> getAllDirectInterfaces(Class<? extends T> lowestType, Class<T> highestType, Class<I> interfaceType) { assert lowestType != null; final Set<Class<? extends I>> collection = new TreeSet<>(ClassComparator.SINGLETON); Class<?> type = lowestType; boolean cont; do { for (final Class<?> directInterface : type.getInterfaces()) { if (interfaceType == null || interfaceType.isAssignableFrom(directInterface)) { collection.add((Class<? extends I>) directInterface); } } cont = highestType == null || !type.equals(highestType); type = type.getSuperclass(); } while (type != null && cont); return collection; } /** * Determines the interfaces implemented by the classes from the lowest type * to the highestType which are extended the given interfaceType. * * <p>Insteed of {@link Class#getInterfaces()}, this function is exploring * the super classes. This function does not explore super-interfaces * of implemented interfaces. * * <pre><code> * interface IA {} * interface IB extends IA {} * interface IC {} * interface ID extends IB, IC {} * class CA implements IC {} * class CB extends CA {} * class CC extends CB implements IB {} * </code></pre> * This function replies for: * <ul> * <li><code>getAllDirectInterfaces(IA,null,null)</code>=<code>{}</code></li> * <li><code>getAllDirectInterfaces(IB,null,null)</code>=<code>{IA}</code></li> * <li><code>getAllDirectInterfaces(IC,null,null)</code>=<code>{}</code></li> * <li><code>getAllDirectInterfaces(ID,null,null)</code>=<code>{IB,IC}</code></li> * <li><code>getAllDirectInterfaces(CA,null,null)</code>=<code>{IC}</code></li> * <li><code>getAllDirectInterfaces(CB,null,null)</code>=<code>{IC}</code></li> * <li><code>getAllDirectInterfaces(CC,null,null)</code>=<code>{IB,IC}</code></li> * </ul> * * @param <T> is the highest type to explore in type hierarchy. * @param lowestType is the lowest type to explore in type hierarchy. * @param highestType is the highest type to explore in type hierarchy. * @return the implemented interfaces. * @since 5.0 */ public static <T> Set<Class<?>> getAllDirectInterfaces(Class<? extends T> lowestType, Class<T> highestType) { assert lowestType != null; final Set<Class<?>> collection = new TreeSet<>(ClassComparator.SINGLETON); Class<?> type = lowestType; boolean cont; do { for (final Class<?> directInterface : type.getInterfaces()) { collection.add(directInterface); } cont = highestType == null || !type.equals(highestType); type = type.getSuperclass(); } while (type != null && cont); return collection; } /** * Replies the list of all the superclasses of the given class. * * <p>This function does not replies <code>Object.class</code>. * * @param <T> is the type of the lowest class. * @param className is the type of the lowest class. * @return the list of superclasses. * @since 5.0 */ @Pure public static <T> Collection<Class<? super T>> getSuperClasses(Class<T> className) { assert className != null; final Collection<Class<? super T>> list = new ArrayList<>(); Class<? super T> type = className.getSuperclass(); while (type != null && !Object.class.equals(type)) { list.add(type); type = type.getSuperclass(); } return list; } /** Replies the top-most type which is common to both given types. * * @param type1 first type. * @param type2 second type. * @return the top-most type which is common to both given types. * @since 6.0 */ public static Class<?> getCommonType(Class<?> type1, Class<?> type2) { if (type1 == null) { return type2; } if (type2 == null) { return type1; } Class<?> top = type1; while (!top.isAssignableFrom(type2)) { top = top.getSuperclass(); assert top != null; } return top; } /** Replies the top-most type which is common to both given objects. * * @param instance1 first object. * @param instance2 second object. * @return the top-most type which is common to both given objects. * @since 6.0 */ @Pure public static Class<?> getCommonType(Object instance1, Object instance2) { if (instance1 == null) { return instance2 == null ? null : instance2.getClass(); } if (instance2 == null) { return instance1.getClass(); } Class<?> top = instance1.getClass(); while (!top.isInstance(instance2)) { top = top.getSuperclass(); assert top != null; } return top; } /** Replies the outboxing type for the given type. * * @param type the type. * @return the outboxing of the given type. * @since 7.1 */ @Pure @SuppressWarnings({"checkstyle:returncount", "npathcomplexity"}) public static Class<?> getOutboxingType(Class<?> type) { if (void.class.equals(type)) { return Void.class; } if (boolean.class.equals(type)) { return Boolean.class; } if (byte.class.equals(type)) { return Byte.class; } if (char.class.equals(type)) { return Character.class; } if (double.class.equals(type)) { return Double.class; } if (float.class.equals(type)) { return Float.class; } if (int.class.equals(type)) { return Integer.class; } if (long.class.equals(type)) { return Long.class; } if (short.class.equals(type)) { return Short.class; } return type; } /** Replies the outboxing type for the given type. * * @param type the type. * @return the outboxing of the given type. * @since 7.1 */ @Pure @SuppressWarnings({"checkstyle:returncount", "npathcomplexity"}) public static Class<?> getInboxingType(Class<?> type) { if (Void.class.equals(type)) { return void.class; } if (Boolean.class.equals(type)) { return boolean.class; } if (Byte.class.equals(type)) { return byte.class; } if (Character.class.equals(type)) { return char.class; } if (Double.class.equals(type)) { return double.class; } if (Float.class.equals(type)) { return float.class; } if (Integer.class.equals(type)) { return int.class; } if (Long.class.equals(type)) { return long.class; } if (Short.class.equals(type)) { return short.class; } return type; } /** Replies if the formal parameters are matching the given values. * * @param formalParameters the types of the formal parameters. * @param parameterValues the values associated to the paramters. * @return <code>true</code> if the values could be passed to the method. * @since 7.1 */ @Pure public static boolean matchesParameters(Class<?>[] formalParameters, Object... parameterValues) { if (formalParameters == null) { return parameterValues == null; } if (parameterValues != null && formalParameters.length == parameterValues.length) { for (int i = 0; i < formalParameters.length; ++i) { if (!isInstance(formalParameters[i], parameterValues[i])) { return false; } } return true; } return false; } /** Replies if the parameters of the given method are matching the given values. * * @param method the method that contains the types. * @param parameters the objects associated to the paramters. * @return <code>true</code> if the values could be passed to the method. * @since 7.1 */ @Pure @Inline(value = "ReflectionUtil.matchesParameters(($1).getParameterTypes(), ($2))", imported = {ReflectionUtil.class}) public static boolean matchesParameters(Method method, Object... parameters) { return matchesParameters(method.getParameterTypes(), parameters); } /** Replies the string representation of the given object. * * <p>The string representation is based on the values replied by the getters (functions * that are starting by "get" or "is" or "has"). * * @param object the object to analyze. * @return the string representation. */ public static String toString(Object object) { if (object == null) { return ""; //$NON-NLS-1$ } final StringBuilder buffer = new StringBuilder(); buffer.append("{"); //$NON-NLS-1$ boolean isfirst = true; for (final Method method : object.getClass().getMethods()) { try { if (!method.isSynthetic() && !Modifier.isStatic(method.getModifiers()) && method.getParameterCount() == 0 && (method.getReturnType().isPrimitive() || String.class.equals(method.getReturnType()))) { final String name = method.getName(); if (name.startsWith("get")) { //$NON-NLS-1$ if (isfirst) { isfirst = false; } else { buffer.append(','); } buffer.append("\n\t\""); //$NON-NLS-1$ buffer.append(name.substring(3).toLowerCase()); buffer.append("\": "); //$NON-NLS-1$ buffer.append(Objects.toString(method.invoke(object))); } else if (name.startsWith("is")) { //$NON-NLS-1$ if (isfirst) { isfirst = false; } else { buffer.append(','); } buffer.append("\n\t\""); //$NON-NLS-1$ buffer.append(name.substring(2).toLowerCase()); buffer.append("\": "); //$NON-NLS-1$ buffer.append(Objects.toString(method.invoke(object))); } } } catch (Exception e) { // } } buffer.append("\n}\n"); //$NON-NLS-1$ return buffer.toString(); } }