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

com.tngtech.archunit.junit.internal.ClassCache Maven / Gradle / Ivy

Go to download

A Java architecture test library, to specify and assert architecture rules in plain Java - Module 'archunit-junit5-engine'

The newest version!
/*
 * Copyright 2018 TNG Technology Consulting GmbH
 *
 * 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 com.tngtech.archunit.junit.internal;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.core.importer.Location;
import com.tngtech.archunit.core.importer.Locations;
import com.tngtech.archunit.junit.CacheMode;
import com.tngtech.archunit.junit.LocationProvider;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.tngtech.archunit.junit.CacheMode.FOREVER;
import static com.tngtech.archunit.junit.internal.ReflectionUtils.newInstanceOf;
import static java.util.Arrays.stream;
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toSet;

/**
 * The {@link ClassCache} takes care of caching {@link JavaClasses} between test runs. On the one hand,
 * it caches {@link JavaClasses} between different test runs,
 * on the other hand, it caches {@link JavaClasses} between different test classes,
 * i.e. if two test classes ATest and BTest
 * import the same locations (e.g. packages, URLs, etc.), the imported {@link JavaClasses} from ATest will be
 * reused for BTest. This behavior can be controlled by the supplied {@link CacheMode}.
 * 

* Important information regarding performance: The cache uses soft references, meaning that a small heap * may dramatically reduce performance, if multiple test classes are executed. * The cache will hold imported classes as long as there is sufficient memory, and reuse them, if the same * locations (i.e. URLs) are imported. */ class ClassCache { @VisibleForTesting final Map, JavaClasses> cachedByTest = new ConcurrentHashMap<>(); @VisibleForTesting final LoadingCache cachedByLocations = CacheBuilder.newBuilder().softValues().build(new CacheLoader() { @Override public LazyJavaClasses load(LocationsKey key) { return new LazyJavaClasses(key.locations, key.importOptionTypes); } }); @SuppressWarnings("FieldMayBeFinal") // We want to change this in tests private CacheClassFileImporter cacheClassFileImporter = new CacheClassFileImporter(); JavaClasses getClassesToAnalyzeFor(Class testClass, ClassAnalysisRequest classAnalysisRequest) { checkNotNull(testClass); checkNotNull(classAnalysisRequest); if (cachedByTest.containsKey(testClass)) { return cachedByTest.get(testClass); } LocationsKey locations = RequestedLocations.by(classAnalysisRequest, testClass).asKey(); JavaClasses classes = classAnalysisRequest.getCacheMode() == FOREVER ? cachedByLocations.getUnchecked(locations).get() : new LazyJavaClasses(locations.locations, locations.importOptionTypes).get(); cachedByTest.put(testClass, classes); return classes; } void clear(Class testClass) { cachedByTest.remove(testClass); } private class LazyJavaClasses { private final Set locations; private final Set> importOptionTypes; private volatile JavaClasses javaClasses; private LazyJavaClasses(Set locations, Set> importOptionTypes) { this.locations = locations; this.importOptionTypes = importOptionTypes; } public JavaClasses get() { if (javaClasses == null) { initialize(); } return javaClasses; } private synchronized void initialize() { if (javaClasses == null) { Set importOptions = new HashSet<>(); for (Class optionClass : importOptionTypes) { importOptions.add(newInstanceOf(optionClass)); } javaClasses = cacheClassFileImporter.importClasses(importOptions, locations); } } } // Used for testing -> that's also the reason it's declared top level static class CacheClassFileImporter { JavaClasses importClasses(Set importOptions, Collection locations) { return new ClassFileImporter(importOptions).importLocations(locations); } } private static class LocationsKey { private final Set> importOptionTypes; private final Set locations; private LocationsKey(Class[] importOptionTypes, Set locations) { this.importOptionTypes = ImmutableSet.copyOf(importOptionTypes); this.locations = locations; } @Override public int hashCode() { return Objects.hash(importOptionTypes, locations); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } LocationsKey other = (LocationsKey) obj; return Objects.equals(this.importOptionTypes, other.importOptionTypes) && Objects.equals(this.locations, other.locations); } } private abstract static class RequestedLocations { abstract LocationsKey asKey(); private static class AllInTestPackage extends RequestedLocations { private final Class[] importOptions; private final Set locationsOfTestPackage; private AllInTestPackage(Class[] importOptions, Class testClass) { this.importOptions = importOptions; locationsOfTestPackage = Locations.ofPackage(testClass.getPackage().getName()); } @Override LocationsKey asKey() { return new LocationsKey(importOptions, locationsOfTestPackage); } } private static class Specific extends RequestedLocations { private final Class[] importOptions; private final Set declaredLocations; private Specific(ClassAnalysisRequest classAnalysisRequest, Class testClass) { importOptions = classAnalysisRequest.getImportOptions(); declaredLocations = ImmutableSet.builder() .addAll(getLocationsOfPackages(classAnalysisRequest)) .addAll(getLocationsOfProviders(classAnalysisRequest, testClass)) .addAll(classAnalysisRequest.scanWholeClasspath() ? Locations.inClassPath() : emptySet()) .build(); } private Set getLocationsOfPackages(ClassAnalysisRequest classAnalysisRequest) { Set packages = ImmutableSet.builder() .add(classAnalysisRequest.getPackageNames()) .addAll(toPackageStrings(classAnalysisRequest.getPackageRoots())) .build(); return locationsOf(packages); } private Set getLocationsOfProviders(ClassAnalysisRequest classAnalysisRequest, Class testClass) { return stream(classAnalysisRequest.getLocationProviders()) .flatMap(providerClass -> tryCreate(providerClass).get(testClass).stream()) .collect(toSet()); } private LocationProvider tryCreate(Class providerClass) { try { return newInstanceOf(providerClass); } catch (RuntimeException e) { String message = String.format( "Failed to create %s. It must be accessible and provide a public default constructor", providerClass.getSimpleName()); throw new ArchTestExecutionException(message, e); } } private Set toPackageStrings(Class[] classes) { ImmutableSet.Builder result = ImmutableSet.builder(); for (Class clazz : classes) { result.add(clazz.getPackage().getName()); } return result.build(); } private Set locationsOf(Set packages) { return packages.stream().flatMap(pkg -> Locations.ofPackage(pkg).stream()).collect(toSet()); } @Override LocationsKey asKey() { return new LocationsKey(importOptions, declaredLocations); } } public static RequestedLocations by(ClassAnalysisRequest classAnalysisRequest, Class testClass) { return noSpecificLocationRequested(classAnalysisRequest) ? new AllInTestPackage(classAnalysisRequest.getImportOptions(), testClass) : new Specific(classAnalysisRequest, testClass); } private static boolean noSpecificLocationRequested(ClassAnalysisRequest classAnalysisRequest) { return classAnalysisRequest.getPackageNames().length == 0 && classAnalysisRequest.getPackageRoots().length == 0 && classAnalysisRequest.getLocationProviders().length == 0 && !classAnalysisRequest.scanWholeClasspath(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy