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

io.github.mtrevisan.boxon.internal.reflection.Reflections Maven / Gradle / Ivy

/**
 * Copyright (c) 2020 Mauro Trevisan
 *
 * 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.
 */
package io.github.mtrevisan.boxon.internal.reflection;

import io.github.mtrevisan.boxon.internal.JavaHelper;
import io.github.mtrevisan.boxon.internal.reflection.exceptions.ReflectionsException;
import io.github.mtrevisan.boxon.internal.reflection.helpers.ClasspathHelper;
import io.github.mtrevisan.boxon.internal.reflection.helpers.ReflectionHelper;
import io.github.mtrevisan.boxon.internal.reflection.scanners.AbstractScanner;
import io.github.mtrevisan.boxon.internal.reflection.scanners.ScannerInterface;
import io.github.mtrevisan.boxon.internal.reflection.scanners.SubTypesScanner;
import io.github.mtrevisan.boxon.internal.reflection.scanners.TypeAnnotationsScanner;
import io.github.mtrevisan.boxon.internal.reflection.vfs.VFSDirectory;
import io.github.mtrevisan.boxon.internal.reflection.vfs.VFSException;
import io.github.mtrevisan.boxon.internal.reflection.vfs.VFSFile;
import io.github.mtrevisan.boxon.internal.reflection.vfs.VirtualFileSystem;
import org.slf4j.Logger;

import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;


/**
 * @see Reflections
 */
public final class Reflections{

	private static final Logger LOGGER = JavaHelper.getLoggerFor(Reflections.class);

	private static final String DOT_CLASS = ".class";

	private static final TypeAnnotationsScanner TYPE_ANNOTATIONS_SCANNER = new TypeAnnotationsScanner();
	private static final SubTypesScanner SUB_TYPES_SCANNER = new SubTypesScanner();
	private static final ScannerInterface[] SCANNERS = {TYPE_ANNOTATIONS_SCANNER, SUB_TYPES_SCANNER};


	public static Reflections create(final URL... urls){
		return new Reflections(false, urls);
	}

	public static Reflections create(final Class... classes){
		final URL[] urls = extractDistinctURLs(classes);
		return new Reflections(false, urls);
	}

	public static Reflections createExpandSuperTypes(final Class... classes){
		final URL[] urls = extractDistinctURLs(classes);
		return new Reflections(true, urls);
	}

	/**
	 * Sometimes simple calls have unexpected side effects. I wanted to update some plugins, but the update manager was
	 * hanging my UI. Looking at the stack trace reveals:
	 * 

	 * at java.net.Inet4AddressImpl.lookupAllHostAddr(Native Method)
	 * at java.net.InetAddress$1.lookupAllHostAddr(Unknown Source)
	 * at java.net.InetAddress.getAddressFromNameService(Unknown Source)
	 * at java.net.InetAddress.getAllByName0(Unknown Source)
	 * at java.net.InetAddress.getAllByName0(Unknown Source)
	 * at java.net.InetAddress.getAllByName(Unknown Source)
	 * at java.net.InetAddress.getByName(Unknown Source)
	 * at java.net.URLStreamHandler.getHostAddress(Unknown Source)
	 * - locked <0x15ce1280> (a sun.net.www.protocol.http.Handler)
	 * at java.net.URLStreamHandler.hashCode(Unknown Source)
	 * at java.net.URL.hashCode(Unknown Source)
	 * - locked <0x1a3100d0> (a java.net.URL)
	 *
*

Hmm, I must say that it is very dangerous that {@link URL#hashCode()} and {@link URL#equals(Object)} makes an * Internet connection. {@link URL} has the worst equals/hasCode implementation I have ever seen: equality DEPENDS on the state * of the Internet.

*

Do not put {@link URL} into collections unless you can live with the fact that comparing makes calls to the Internet. * Use {@link java.net.URI} instead.

*

URL is an aggressive beast that can slow down and hang your application by making unexpected network traffic.

* * @see java.net.URL.equals and hashCode make (blocking) Internet connections */ private static URL[] extractDistinctURLs(final Class[] classes){ final Set uris = convertIntoURI(classes); final List urls = new ArrayList<>(uris.size()); try{ for(final URI uri : uris) urls.add(uri.toURL()); } catch(final MalformedURLException e){ //cannot happen e.printStackTrace(); } return urls.toArray(URL[]::new); } private static Set convertIntoURI(final Class[] classes){ final Set uris = new HashSet<>(classes.length); for(final Class cls : classes){ final Collection urls = ClasspathHelper.forPackage(cls.getPackageName()); collectURIs(urls, uris); } return uris; } private static void collectURIs(final Collection urls, final Set uris){ for(final URL url : urls){ try{ uris.add(url.toURI()); } catch(final URISyntaxException e){ LOGGER.warn("Invalid URL, cannot convert into URI", e); } } } public static Reflections createExpandSuperTypes(final URL... urls){ return new Reflections(true, urls); } private Reflections(final boolean expandSuperTypes, final URL... urls){ Objects.requireNonNull(urls); if(urls.length == 0) throw new IllegalArgumentException("Packages list cannot be empty"); for(final URL url : urls) scan(url); if(expandSuperTypes) expandSuperTypes(); } private void scan(final URL url){ try(final VFSDirectory directory = VirtualFileSystem.fromURL(url)){ //process each file in the directory for(final VFSFile file : directory.getFiles()) scan(url, directory, file); } catch(final VFSException | ReflectionsException e){ if(LOGGER != null) LOGGER.warn("Could not create VFSDirectory from URL, ignoring the exception and continuing", e); } catch(final Exception e){ if(LOGGER != null) LOGGER.warn("Could not close directory", e); } } private void scan(final URL url, final VFSDirectory directory, final VFSFile file){ final String relativePath = file.getRelativePath(); if(relativePath.endsWith(DOT_CLASS)){ final Object classObject = AbstractScanner.createClassObject(directory, file); for(final ScannerInterface scanner : SCANNERS) scan(scanner, url, relativePath, classObject); } } private void scan(final ScannerInterface scanner, final URL url, final String relativePath, final Object classObject){ try{ scanner.scan(classObject); if(LOGGER != null) LOGGER.trace("Scanned file {} in URL {} with scanner {}", relativePath, url.toExternalForm(), scanner.getClass().getSimpleName()); } catch(final Exception e){ if(LOGGER != null) LOGGER.debug("Could not scan file {} in URL {} with scanner {}", relativePath, url.toExternalForm(), scanner.getClass().getSimpleName(), e); } } /** * Expand super types after scanning (for super types that were not scanned). *

This is helpful in finding the transitive closure without scanning all third party dependencies.

*

It uses {@link ReflectionHelper#getSuperTypes(Class)}.

*

For example, for classes {@code A, B, C} where {@code A} supertype of {@code B}, and {@code B} supertype of {@code C}: *

    *
  • if scanning {@code C} resulted in {@code B} ({@code B -> C} in class store), but {@code A} was not scanned * (although {@code A} supertype of {@code B}) - then {@code getSubTypesOf(A)} will not return {@code C}.
  • *
  • if expanding supertypes, {@code B} will be expanded with {@code A} ({@code A -> B} in class store) - then * {@code getSubTypesOf(A)} will return {@code C}.
  • *
*

*/ private void expandSuperTypes(){ final Set keys = SUB_TYPES_SCANNER.keys(); keys.removeAll(SUB_TYPES_SCANNER.values()); for(final String key : keys){ final Class type = ClasspathHelper.getClassFromName(key); if(type != null) expandSupertypes(SUB_TYPES_SCANNER, key, type); } } private void expandSupertypes(final AbstractScanner scanner, final String key, final Class type){ for(final Class superType : ReflectionHelper.getSuperTypes(type)) if(scanner.put(superType.getName(), key)){ expandSupertypes(scanner, superType.getName(), superType); if(LOGGER != null) LOGGER.trace("Expanded subtype {} into {}", superType.getName(), key); } } /** * Gets all sub types in hierarchy of a given type. * * @param type The type to search for. * @return The set of classes. */ public Set> getSubTypesOf(final Class type){ return ClasspathHelper.getClassFromNames(SUB_TYPES_SCANNER.getAll(type.getName())); } /** * Get types annotated with a given annotation, both classes and annotations. *

{@link Inherited} is not honored by default.

*

When honoring {@link Inherited}, meta-annotation should only effect annotated super classes and its sub types.

*

Note that this ({@link Inherited}) meta-annotation type has no effect if the annotated type is used for * anything other then a class. Also, this meta-annotation causes annotations to be inherited only from superclasses; * annotations on implemented interfaces have no effect.

* * @param annotation The annotation to search for. * @return The set of classes. */ public Set> getTypesAnnotatedWith(final Class annotation){ return getTypesAnnotatedWith(annotation, false); } /** * Get types annotated with a given annotation, both classes and annotations. *

{@link Inherited} is not honored by default.

*

When honoring {@link Inherited}, meta-annotation should only effect annotated super classes and its sub types.

*

When not honoring {@link Inherited}, meta annotation effects all subtypes, including annotations interfaces * and classes.

*

Note that this ({@link Inherited}) meta-annotation type has no effect if the annotated type is used for * anything other then a class. Also, this meta-annotation causes annotations to be inherited only from superclasses; * annotations on implemented interfaces have no effect.

* * @param annotation The annotation to search for. * @return The set of classes. */ public Set> getTypesAnnotatedWithHonorInherited(final Class annotation){ return getTypesAnnotatedWith(annotation, true); } private Set> getTypesAnnotatedWith(final Class annotation, final boolean honorInherited){ final Set annotated = TYPE_ANNOTATIONS_SCANNER.get(annotation.getName()); annotated.addAll(getAllAnnotatedClasses(annotated, annotation, honorInherited)); return ClasspathHelper.getClassFromNames(annotated); } /** * Get types annotated with a given annotation, both classes and annotations, including annotation member values matching. *

{@link Inherited} is not honored by default.

*

When honoring {@link Inherited}, meta-annotation should only effect annotated super classes and its sub types.

*

Note that this ({@link Inherited}) meta-annotation type has no effect if the annotated type is used for * anything other then a class. Also, this meta-annotation causes annotations to be inherited only from superclasses; * annotations on implemented interfaces have no effect.

* * @param annotation The annotation. * @return The set of classes. */ public Set> getTypesAnnotatedWith(final Annotation annotation){ return getTypesAnnotatedWith(annotation, false); } /** * Get types annotated with a given annotation, both classes and annotations, including annotation member values matching. *

{@link Inherited} is not honored by default.

*

When honoring {@link Inherited}, meta-annotation should only effect annotated super classes and its sub types.

*

When not honoring {@link Inherited}, meta annotation effects all subtypes, including annotations interfaces * and classes.

*

Note that this ({@link Inherited}) meta-annotation type has no effect if the annotated type is used for * anything other then a class. Also, this meta-annotation causes annotations to be inherited only from superclasses; * annotations on implemented interfaces have no effect.

* * @param annotation The annotation. * @return The set of classes. */ public Set> getTypesAnnotatedWithHonorInherited(final Annotation annotation){ return getTypesAnnotatedWith(annotation, true); } private Set> getTypesAnnotatedWith(final Annotation annotation, final boolean honorInherited){ final Set annotated = TYPE_ANNOTATIONS_SCANNER.get(annotation.annotationType().getName()); final Set> allAnnotated = JavaHelper.filter(ClasspathHelper.getClassFromNames(annotated), getFilterOnAnnotation(annotation)); final Set filtered = JavaHelper.filter(getAllAnnotatedClasses(ClasspathHelper.getClassNames(allAnnotated), annotation.annotationType(), honorInherited), Predicate.not(annotated::contains)); allAnnotated.addAll(ClasspathHelper.getClassFromNames(filtered)); return allAnnotated; } /** * Returns a predicate that tell if the class is annotated with given {@code annotation}, including member matching. * * @param annotation The annotation. * @return The predicate. * @param The type of the returned predicate. */ private Predicate getFilterOnAnnotation(final Annotation annotation){ return input -> (input != null && input.isAnnotationPresent(annotation.annotationType()) && areAnnotationMembersMatching(input.getAnnotation(annotation.annotationType()), annotation)); } private boolean areAnnotationMembersMatching(final Annotation annotation1, final Annotation annotation2){ final boolean result = (annotation2 != null && annotation1.annotationType() == annotation2.annotationType()); if(result){ try{ for(final Method method : annotation1.annotationType().getDeclaredMethods()) if(!method.invoke(annotation1).equals(method.invoke(annotation2))) return false; } catch(final Exception e){ throw new ReflectionsException("Could not invoke a method on annotation {} or {}", annotation1.annotationType(), annotation2.annotationType()) .withCause(e); } } return result; } private Collection getAllAnnotatedClasses(final Collection annotated, final Class annotation, final boolean honorInherited){ if(!honorInherited){ final Collection subTypes = TYPE_ANNOTATIONS_SCANNER.getAllIncludingKeys(annotated); return SUB_TYPES_SCANNER.getAllIncludingKeys(subTypes); } else if(annotation.isAnnotationPresent(Inherited.class)){ final Set subTypes = SUB_TYPES_SCANNER.get(JavaHelper.filter(annotated, input -> { final Class type = ClasspathHelper.getClassFromName(input); return (type != null && !type.isInterface()); })); return SUB_TYPES_SCANNER.getAllIncludingKeys(subTypes); } else return annotated; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy