/* * Copyright (C) 2016 the original author or 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 ro.pippo.controller.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ro.pippo.core.PippoRuntimeException; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Objects; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.stream.Collectors; /** * Class reflection utility methods. * * @author James Moger */ public class ClassUtils { private final static Logger log = LoggerFactory.getLogger(ClassUtils.class); @SuppressWarnings("unchecked") public static <T> Class<T> getClass(String className) { try { return (Class<T>) Class.forName(className); } catch (Exception e) { throw new PippoRuntimeException("Failed to get class '{}'", className); } } /** * Returns the list of all classes within a package. * * @param packageNames * @return a collection of classes */ public static Collection<Class<?>> getClasses(String... packageNames) { List<Class<?>> classes = new ArrayList<>(); for (String packageName : packageNames) { final String packagePath = packageName.replace('.', '/'); final String packagePrefix = packageName + '.'; List<URL> packageUrls = getResources(packagePath); for (URL packageUrl : packageUrls) { if (packageUrl.getProtocol().equals("jar")) { log.debug("Scanning jar {} for classes", packageUrl); try { String jar = packageUrl.toString().substring("jar:".length()).split("!")[0]; File file = new File(new URI(jar)); try (JarInputStream is = new JarInputStream(new FileInputStream(file))) { JarEntry entry; while ((entry = is.getNextJarEntry()) != null) { if (!entry.isDirectory() && entry.getName().endsWith(".class")) { String className = entry.getName().replace(".class", "").replace('/', '.'); if (className.startsWith(packagePrefix)) { Class<?> aClass = getClass(className); classes.add(aClass); } } } } } catch (URISyntaxException | IOException e) { throw new PippoRuntimeException("Failed to get classes for package '{}'", e, packageName); } } else { log.debug("Scanning filesystem {} for classes", packageUrl); log.debug(packageUrl.getProtocol()); try (InputStream is = packageUrl.openStream()) { Objects.requireNonNull(is, String.format("Package url %s stream is null!", packageUrl)); try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { classes.addAll(reader.lines() .filter(line -> line != null && line.endsWith(".class")) .map(line -> { String className = line.replace(".class", "").replace('/', '.'); try { return getClass(packagePrefix + className); } catch (Exception e) { log.error("Failed to find {}", line, e); } return null; }) .collect(Collectors.toList())); } } catch (IOException e) { throw new PippoRuntimeException("Failed to get classes for package '{}'", e, packageName); } } } } return Collections.unmodifiableCollection(classes); } public static Collection<Class<?>> getAnnotatedClasses(Class<? extends Annotation> annotationClass, String... packageNames) { List<Class<?>> classes = getClasses(packageNames).stream() .filter(aClass -> aClass.isAnnotationPresent(annotationClass)) .collect(Collectors.toList()); return Collections.unmodifiableCollection(classes); } /** * Extract the annotation from the controllerMethod or the declaring class. * * @param method * @param annotationClass * @param <T> * @return the annotation or null */ public static <T extends Annotation> T getAnnotation(Method method, Class<T> annotationClass) { T annotation = method.getAnnotation(annotationClass); if (annotation == null) { annotation = getAnnotation(method.getDeclaringClass(), annotationClass); } return annotation; } public static <T extends Annotation> T getAnnotation(Class<?> objectClass, Class<T> annotationClass) { if (objectClass == null || Object.class == objectClass) { return null; } T annotation = objectClass.getAnnotation(annotationClass); if (annotation != null) { return annotation; } return getAnnotation(objectClass.getSuperclass(), annotationClass); } public static <T extends Annotation> List<T> collectNestedAnnotation(Method method, Class<T> annotationClass) { List<T> list = new ArrayList<>(); for (Annotation annotation : method.getDeclaredAnnotations()) { if (annotation.annotationType().isAnnotationPresent(annotationClass)) { T nestedAnnotation = annotation.annotationType().getAnnotation(annotationClass); list.add(nestedAnnotation); } } list.addAll(collectNestedAnnotation(method.getDeclaringClass(), annotationClass)); return list; } public static <T extends Annotation> List<T> collectNestedAnnotation(Class<?> objectClass, Class<T> annotationClass) { if (objectClass == null || objectClass == Object.class) { return Collections.emptyList(); } List<T> list = new ArrayList<>(); for (Annotation annotation : objectClass.getDeclaredAnnotations()) { if (annotation.annotationType().isAnnotationPresent(annotationClass)) { T nestedAnnotation = annotation.annotationType().getAnnotation(annotationClass); list.add(nestedAnnotation); } } list.addAll(collectNestedAnnotation(objectClass.getSuperclass(), annotationClass)); return list; } @SuppressWarnings("unchecked") public static <T> T executeDeclaredMethod(Object o, String methodName, Object... args) { try { Method method; if (args != null && args.length > 0) { Class<?>[] types = new Class<?>[args.length]; for (int i = 0; i < args.length; i++) { types[i] = args[i].getClass(); } method = o.getClass().getDeclaredMethod(methodName, types); Objects.requireNonNull(method, String.format("Failed to find declared controllerMethod '%s' for args '{}' in type '%s'!", methodName, types, o.getClass().getName())); } else { method = o.getClass().getDeclaredMethod(methodName); Objects.requireNonNull(method, String.format("Failed to find declared controllerMethod '%s' in type '%s'!", methodName, o.getClass().getName())); } return (T) method.invoke(o, args); } catch (Exception e) { throw new PippoRuntimeException("Failed to execute controllerMethod '{}' on object '{}'!", e, methodName); } } public static List<URL> getResources(String name) { List<URL> list = new ArrayList<>(); try { Enumeration<URL> resources = ClassUtils.class.getClassLoader().getResources(name); while (resources.hasMoreElements()) { URL url = resources.nextElement(); list.add(url); } } catch (IOException e) { throw new PippoRuntimeException(e); } return list; } public static boolean isAssignable(Object value, Class<?> type) { if (type.isInstance(value)) { // inheritance return true; } if (boolean.class == type && value instanceof Boolean) { return true; } else if (type.isPrimitive() && value instanceof Number) { return true; } return false; } }