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

org.joinfaces.autoconfigure.ClasspathScanUtil Maven / Gradle / Ivy

/*
 * Copyright 2016-2022 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
 *
 *      https://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.joinfaces.autoconfigure;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

import org.springframework.util.StringUtils;

/**
 * Utility class for handling classpath scan results.
 *
 * @author Lars Grefer
 */
@Slf4j
@UtilityClass
public class ClasspathScanUtil {

	public static Optional>> readClassSet(String resourceName, ClassLoader classLoader) {
		return readClasses(
				resourceName, classLoader,
				ClasspathScanUtil::readClassSet
		);
	}

	public static Optional, Set>>> readClassMap(String resourceName, ClassLoader classLoader) {
		return readClasses(
				resourceName, classLoader,
				ClasspathScanUtil::readClassMap
		);
	}

	@SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "https://github.com/spotbugs/spotbugs/issues/259")
	private static  Optional readClasses(String resourceName, ClassLoader classLoader, BiFunction function) {
		InputStream resourceAsStream = classLoader.getResourceAsStream(resourceName);

		if (resourceAsStream == null) {
			log.debug("No prepared scan result {} found.", resourceName);
			return Optional.empty();
		}

		long start = System.nanoTime();
		try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8))) {
			T result = function.apply(bufferedReader, classLoader);
			double ms = (System.nanoTime() - start) / 1_000_000d;
			log.info("Loading prepared scan result took {}ms", ms);
			return Optional.ofNullable(result);
		}
		catch (IOException e) {
			log.warn("Failed to read prepared scan-result {}", resourceName, e);
			return Optional.empty();
		}
	}

	static Set> readClassSet(BufferedReader bufferedReader, ClassLoader classLoader) {
		return getClasses(bufferedReader.lines(), classLoader);
	}

	static Map, Set>> readClassMap(BufferedReader bufferedReader, ClassLoader classLoader) {
		Map, Set>> classes = new HashMap<>();

		bufferedReader.lines().forEach(line -> {
			String[] split = line.split("=", 2);
			String annotationName = split[0];
			String classNameList = split[1];

			Class annotation;
			try {
				annotation = (Class) classLoader.loadClass(annotationName);
			}
			catch (ClassNotFoundException | LinkageError e) {
				log.warn("Failed to load annotation class {}", annotationName, e);
				return;
			}
			Set> classSet;

			if (StringUtils.hasText(classNameList)) {
				classSet = getClasses(Arrays.stream(classNameList.split(",")), classLoader);
			}
			else {
				classSet = Collections.emptySet();
			}

			classes.put(annotation, classSet);
		});
		return classes;

	}

	static Set> getClasses(Stream classNames, ClassLoader classLoader) {
		AtomicInteger missingClasses = new AtomicInteger();
		AtomicInteger missingDependentClasses = new AtomicInteger();

		Set> collect = classNames
				.map(className -> {
					try {
						return classLoader.loadClass(className);
					}
					catch (ClassNotFoundException e) {
						missingClasses.incrementAndGet();
						log.debug("Failed to load class {} although it's listed in the prepared scan result.", className);
						log.trace("Stacktrace", e);
					}
					catch (NoClassDefFoundError e) {
						missingDependentClasses.incrementAndGet();
						log.debug("Failed to load class {} because it's dependency {} is missing.", className, e.getMessage());
						log.trace("Stacktrace", e);
					}
					catch (LinkageError e) {
						log.warn("Failed to load class {} from prepared scan result", className, e);
					}
					return null;
				})
				.filter(Objects::nonNull)
				.collect(Collectors.toSet());

		if (missingClasses.get() > 0) {
			log.warn("{} classes listed in the prepared scan result could not be found. Set the log-level to debug for more information.", missingClasses.get());
		}
		if (missingDependentClasses.get() > 0) {
			log.info("{} classes failed to load, because some of their dependencies are missing. Set the log-level to debug for more information.", missingDependentClasses.get());
		}
		return collect;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy