From 5c62ddeb419dccc7c4768d4382019c47917a6e45 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Mon, 9 Dec 2024 15:50:47 +0100 Subject: SONAR-21354 Revert to use the sonar-classloader library --- sonar-core/build.gradle | 1 + .../java/org/sonar/classloader/ClassRealm.java | 206 ------ .../org/sonar/classloader/ClassloaderBuilder.java | 246 ------- .../java/org/sonar/classloader/ClassloaderRef.java | 53 -- .../sonar/classloader/DefaultClassloaderRef.java | 69 -- .../src/main/java/org/sonar/classloader/Mask.java | 208 ------ .../org/sonar/classloader/NullClassloaderRef.java | 46 -- .../org/sonar/classloader/ParentFirstStrategy.java | 64 -- .../org/sonar/classloader/SelfFirstStrategy.java | 66 -- .../main/java/org/sonar/classloader/Strategy.java | 35 - .../org/sonar/classloader/StrategyContext.java | 52 -- .../java/org/sonar/classloader/package-info.java | 24 - .../sonar/classloader/ClassloaderBuilderTest.java | 787 --------------------- .../test/java/org/sonar/classloader/MaskTest.java | 170 ----- 14 files changed, 1 insertion(+), 2026 deletions(-) delete mode 100644 sonar-core/src/main/java/org/sonar/classloader/ClassRealm.java delete mode 100644 sonar-core/src/main/java/org/sonar/classloader/ClassloaderBuilder.java delete mode 100644 sonar-core/src/main/java/org/sonar/classloader/ClassloaderRef.java delete mode 100644 sonar-core/src/main/java/org/sonar/classloader/DefaultClassloaderRef.java delete mode 100644 sonar-core/src/main/java/org/sonar/classloader/Mask.java delete mode 100644 sonar-core/src/main/java/org/sonar/classloader/NullClassloaderRef.java delete mode 100644 sonar-core/src/main/java/org/sonar/classloader/ParentFirstStrategy.java delete mode 100644 sonar-core/src/main/java/org/sonar/classloader/SelfFirstStrategy.java delete mode 100644 sonar-core/src/main/java/org/sonar/classloader/Strategy.java delete mode 100644 sonar-core/src/main/java/org/sonar/classloader/StrategyContext.java delete mode 100644 sonar-core/src/main/java/org/sonar/classloader/package-info.java delete mode 100644 sonar-core/src/test/java/org/sonar/classloader/ClassloaderBuilderTest.java delete mode 100644 sonar-core/src/test/java/org/sonar/classloader/MaskTest.java (limited to 'sonar-core') diff --git a/sonar-core/build.gradle b/sonar-core/build.gradle index 5c922dc1ae8..91f196e5a85 100644 --- a/sonar-core/build.gradle +++ b/sonar-core/build.gradle @@ -14,6 +14,7 @@ dependencies { api 'org.slf4j:slf4j-api' api 'org.sonarsource.api.plugin:sonar-plugin-api' api 'org.sonarsource.update-center:sonar-update-center-common' + api 'org.sonarsource.classloader:sonar-classloader' api 'org.springframework:spring-context' api project(':sonar-plugin-api-impl') api project(':sonar-ws') diff --git a/sonar-core/src/main/java/org/sonar/classloader/ClassRealm.java b/sonar-core/src/main/java/org/sonar/classloader/ClassRealm.java deleted file mode 100644 index 1fa3a4d601c..00000000000 --- a/sonar-core/src/main/java/org/sonar/classloader/ClassRealm.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import javax.annotation.CheckForNull; - -class ClassRealm extends URLClassLoader implements StrategyContext { - - private final String key; - private Mask mask = Mask.ALL; - private Mask exportMask = Mask.ALL; - private ClassloaderRef parentRef = NullClassloaderRef.INSTANCE; - private List siblingRefs = new ArrayList<>(); - private Strategy strategy; - - ClassRealm(String key, ClassLoader baseClassloader) { - super(new URL[0], baseClassloader); - this.key = key; - } - - String getKey() { - return key; - } - - ClassRealm setMask(Mask mask) { - this.mask = mask; - return this; - } - - Mask getExportMask() { - return exportMask; - } - - ClassRealm setExportMask(Mask exportMask) { - this.exportMask = exportMask; - return this; - } - - ClassRealm setParent(ClassloaderRef parentRef) { - this.parentRef = parentRef; - return this; - } - - ClassRealm addSibling(ClassloaderRef ref) { - this.siblingRefs.add(ref); - return this; - } - - ClassRealm setStrategy(Strategy strategy) { - this.strategy = strategy; - return this; - } - - ClassRealm addConstituent(URL url) { - super.addURL(url); - return this; - } - - @Override - public Class loadClass(String name) throws ClassNotFoundException { - return loadClass(name, false); - } - - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - if (mask.acceptClass(name)) { - try { - // first, try loading bootstrap classes - return super.loadClass(name, resolve); - } catch (ClassNotFoundException ignored) { - // next, try loading via siblings, self and parent as controlled by strategy - return strategy.loadClass(this, name); - } - } - throw new ClassNotFoundException(String.format("Class %s is not accepted in classloader %s", name, this)); - } - - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - // not supposed to be used. Replaced by loadClassFromSelf(String) - throw new ClassNotFoundException(name); - } - - @CheckForNull - @Override - public URL getResource(String name) { - if (mask.acceptResource(name)) { - return strategy.getResource(this, name); - } - return null; - } - - @Override - public Enumeration getResources(String name) throws IOException { - // Important note: do not use java.util.Set as equals and hashCode methods of - // java.net.URL perform domain name resolution. This can result in a big performance hit. - List resources = new ArrayList<>(); - if (mask.acceptResource(name)) { - strategy.getResources(this, name, resources); - } - return Collections.enumeration(resources); - } - - @Override - public Class loadClassFromSelf(String name) { - Class clazz = findLoadedClass(name); - if (clazz == null) { - try { - return super.findClass(name); - } catch (ClassNotFoundException ignored) { - // return null when class is not found, so that loading strategy - // can try parent or sibling classloaders. - } - } - return clazz; - } - - @Override - public Class loadClassFromSiblings(String name) { - for (ClassloaderRef siblingRef : siblingRefs) { - Class clazz = siblingRef.loadClassIfPresent(name); - if (clazz != null) { - return clazz; - } - } - return null; - } - - @Override - public Class loadClassFromParent(String name) { - return parentRef.loadClassIfPresent(name); - } - - @Override - public URL loadResourceFromSelf(String name) { - return super.findResource(name); - } - - @Override - public URL loadResourceFromSiblings(String name) { - for (ClassloaderRef siblingRef : siblingRefs) { - URL url = siblingRef.loadResourceIfPresent(name); - if (url != null) { - return url; - } - } - return null; - } - - @Override - public URL loadResourceFromParent(String name) { - return parentRef.loadResourceIfPresent(name); - } - - @Override - public void loadResourcesFromSelf(String name, Collection appendTo) { - try { - appendTo.addAll(Collections.list(super.findResources(name))); - } catch (IOException e) { - throw new IllegalStateException(String.format("Fail to load resources named '%s' from classloader %s", name, toString()), e); - } - } - - @Override - public void loadResourcesFromSiblings(String name, Collection appendTo) { - for (ClassloaderRef siblingRef : siblingRefs) { - siblingRef.loadResources(name, appendTo); - } - } - - @Override - public void loadResourcesFromParent(String name, Collection appendTo) { - parentRef.loadResources(name, appendTo); - } - - @Override - public String toString() { - return String.format("ClassRealm{%s}", key); - } -} diff --git a/sonar-core/src/main/java/org/sonar/classloader/ClassloaderBuilder.java b/sonar-core/src/main/java/org/sonar/classloader/ClassloaderBuilder.java deleted file mode 100644 index 5a843212fc9..00000000000 --- a/sonar-core/src/main/java/org/sonar/classloader/ClassloaderBuilder.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import java.net.URL; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static java.util.Collections.emptyList; - -/** - * @since 0.1 - */ -public class ClassloaderBuilder { - private final Map previouslyCreatedClassLoaders; - - private final Map newRealmsByKey = new HashMap<>(); - - public ClassloaderBuilder() { - this(emptyList()); - } - - /** - * Creates a new classloader builder that can use a collection of previously created - * classloaders as parent or siblings when building the new classloaders. - * - * @param previouslyCreatedClassLoaders Collection of classloaders that can be used as a - * parent or sibling. Must be of type {@link ClassRealm}. - */ - public ClassloaderBuilder(Collection previouslyCreatedClassLoaders) { - this.previouslyCreatedClassLoaders = new HashMap<>(); - for (ClassLoader cl : previouslyCreatedClassLoaders) { - if (!(cl instanceof ClassRealm)) { - throw new IllegalArgumentException("classloader not of type ClassRealm: " + cl); - } - ClassRealm classRealm = (ClassRealm) cl; - this.previouslyCreatedClassLoaders.put(classRealm.getKey(), classRealm); - } - } - - public enum LoadingOrder { - /** - * Order: siblings, then parent, then self - */ - PARENT_FIRST(ParentFirstStrategy.INSTANCE), - - /** - * Order: siblings, then self, then parent - */ - SELF_FIRST(SelfFirstStrategy.INSTANCE); - - private final Strategy strategy; - - LoadingOrder(Strategy strategy) { - this.strategy = strategy; - } - } - - /** - * Wrapper of {@link ClassRealm} as long as associations are not fully - * defined - */ - private static class NewRealm { - private final ClassRealm realm; - - // key of the optional parent classloader - private String parentKey; - - private final List siblingKeys = new ArrayList<>(); - private final Map associatedMasks = new HashMap<>(); - - private NewRealm(ClassRealm realm) { - this.realm = realm; - } - } - - /** - * Declares a new classloader based on system classloader. - */ - public ClassloaderBuilder newClassloader(String key) { - return newClassloader(key, getSystemClassloader()); - } - - /** - * Declares a new classloader based on a given parent classloader. Key must be unique. An - * {@link IllegalArgumentException} is thrown if the key is already referenced. - *

- * Default loading order is {@link LoadingOrder#PARENT_FIRST}. - */ - public ClassloaderBuilder newClassloader(final String key, final ClassLoader baseClassloader) { - if (newRealmsByKey.containsKey(key)) { - throw new IllegalStateException(String.format("The classloader '%s' already exists. Can not create it twice.", key)); - } - if (previouslyCreatedClassLoaders.containsKey(key)) { - throw new IllegalStateException(String.format("The classloader '%s' already exists in the list of previously created classloaders." - + " Can not create it twice.", key)); - } - ClassRealm realm = AccessController.doPrivileged((PrivilegedAction) () -> new ClassRealm(key, baseClassloader)); - realm.setStrategy(LoadingOrder.PARENT_FIRST.strategy); - newRealmsByKey.put(key, new NewRealm(realm)); - return this; - } - - public ClassloaderBuilder setParent(String key, String parentKey, Mask mask) { - NewRealm newRealm = getOrFail(key); - newRealm.parentKey = parentKey; - newRealm.associatedMasks.put(parentKey, mask); - return this; - } - - public ClassloaderBuilder setParent(String key, ClassLoader parent, Mask mask) { - NewRealm newRealm = getOrFail(key); - newRealm.realm.setParent(new DefaultClassloaderRef(parent, mask)); - return this; - } - - public ClassloaderBuilder addSibling(String key, String siblingKey, Mask mask) { - NewRealm newRealm = getOrFail(key); - newRealm.siblingKeys.add(siblingKey); - newRealm.associatedMasks.put(siblingKey, mask); - return this; - } - - public ClassloaderBuilder addSibling(String key, ClassLoader sibling, Mask mask) { - NewRealm newRealm = getOrFail(key); - newRealm.realm.addSibling(new DefaultClassloaderRef(sibling, mask)); - return this; - } - - public ClassloaderBuilder addURL(String key, URL url) { - getOrFail(key).realm.addConstituent(url); - return this; - } - - public ClassloaderBuilder setMask(String key, Mask mask) { - getOrFail(key).realm.setMask(mask); - return this; - } - - public ClassloaderBuilder setExportMask(String key, Mask mask) { - getOrFail(key).realm.setExportMask(mask); - return this; - } - - public ClassloaderBuilder setLoadingOrder(String key, LoadingOrder order) { - getOrFail(key).realm.setStrategy(order.strategy); - return this; - } - - /** - * Returns the new classloaders, grouped by keys. The parent and sibling classloaders - * that are already existed (see {@link #setParent(String, ClassLoader, Mask)} - * and {@link #addSibling(String, ClassLoader, Mask)} are not included into result. - */ - public Map build() { - Map result = new HashMap<>(); - - // all the classloaders are created. Associations can now be resolved. - for (Map.Entry entry : newRealmsByKey.entrySet()) { - NewRealm newRealm = entry.getValue(); - if (newRealm.parentKey != null) { - ClassRealm parent = getNewOrPreviousClassloader(newRealm.parentKey); - Mask parentMask = newRealm.associatedMasks.get(newRealm.parentKey); - parentMask = mergeWithExportMask(parentMask, newRealm.parentKey); - newRealm.realm.setParent(new DefaultClassloaderRef(parent, parentMask)); - } - for (String siblingKey : newRealm.siblingKeys) { - ClassRealm sibling = getNewOrPreviousClassloader(siblingKey); - Mask siblingMask = newRealm.associatedMasks.get(siblingKey); - siblingMask = mergeWithExportMask(siblingMask, siblingKey); - newRealm.realm.addSibling(new DefaultClassloaderRef(sibling, siblingMask)); - } - result.put(newRealm.realm.getKey(), newRealm.realm); - } - return result; - } - - private Mask mergeWithExportMask(Mask mask, String exportKey) { - NewRealm newRealm = newRealmsByKey.get(exportKey); - if (newRealm != null) { - return Mask.builder().copy(mask).merge(newRealm.realm.getExportMask()).build(); - } - ClassRealm realm = previouslyCreatedClassLoaders.get(exportKey); - if (realm != null) { - return Mask.builder().copy(mask).merge(realm.getExportMask()).build(); - } - return mask; - } - - private NewRealm getOrFail(String key) { - NewRealm newRealm = newRealmsByKey.get(key); - if (newRealm == null) { - throw new IllegalStateException(String.format("The classloader '%s' does not exist", key)); - } - return newRealm; - } - - private ClassRealm getNewOrPreviousClassloader(String key) { - NewRealm newRealm = newRealmsByKey.get(key); - if (newRealm != null) { - return newRealm.realm; - } - ClassRealm previousClassloader = previouslyCreatedClassLoaders.get(key); - if (previousClassloader != null) { - return previousClassloader; - } - - throw new IllegalStateException(String.format("The classloader '%s' does not exist", key)); - } - - /** - * JRE system classloader. In Oracle JVM: - * - ClassLoader.getSystemClassLoader() is sun.misc.Launcher$AppClassLoader. It contains app classpath. - * - ClassLoader.getSystemClassLoader().getParent() is sun.misc.Launcher$ExtClassLoader. It is the JRE core classloader. - */ - private static ClassLoader getSystemClassloader() { - ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); - ClassLoader systemParent = systemClassLoader.getParent(); - if (systemParent != null) { - systemClassLoader = systemParent; - } - return systemClassLoader; - } -} diff --git a/sonar-core/src/main/java/org/sonar/classloader/ClassloaderRef.java b/sonar-core/src/main/java/org/sonar/classloader/ClassloaderRef.java deleted file mode 100644 index 0443f31db7e..00000000000 --- a/sonar-core/src/main/java/org/sonar/classloader/ClassloaderRef.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import java.net.URL; -import java.util.Collection; -import javax.annotation.CheckForNull; - -interface ClassloaderRef { - - /** - * Does not throw {@link java.lang.ClassNotFoundException} but returns null - * when class is not found - * - * @param name name of class, for example "org.foo.Bar" - */ - @CheckForNull - Class loadClassIfPresent(String name); - - /** - * Searches for a resource. Returns null if not found. - * - * @param name name of resource, for example "org/foo/Bar.class" or "org/foo/config.xml" - */ - @CheckForNull - URL loadResourceIfPresent(String name); - - /** - * Searches for all the occurrences of a resource from hierarchy of classloaders. - * Results are appended to the parameter "appendTo". Order of resources is given by the - * hierarchy order of classloaders. - * - * @see #loadResourceIfPresent(String) for the format of resource name - */ - void loadResources(String name, Collection appendTo); -} diff --git a/sonar-core/src/main/java/org/sonar/classloader/DefaultClassloaderRef.java b/sonar-core/src/main/java/org/sonar/classloader/DefaultClassloaderRef.java deleted file mode 100644 index 722964f880b..00000000000 --- a/sonar-core/src/main/java/org/sonar/classloader/DefaultClassloaderRef.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import java.io.IOException; -import java.net.URL; -import java.util.Collection; -import java.util.Enumeration; - -class DefaultClassloaderRef implements ClassloaderRef { - private final Mask mask; - private final ClassLoader classloader; - - DefaultClassloaderRef(ClassLoader classloader, Mask mask) { - this.classloader = classloader; - this.mask = mask; - } - - @Override - public Class loadClassIfPresent(String classname) { - if (mask.acceptClass(classname)) { - try { - return classloader.loadClass(classname); - } catch (ClassNotFoundException ignored) { - // excepted behavior. Return null if class does not exist in this classloader - } - } - return null; - } - - @Override - public URL loadResourceIfPresent(String name) { - if (mask.acceptResource(name)) { - return classloader.getResource(name); - } - return null; - } - - @Override - public void loadResources(String name, Collection appendTo) { - if (mask.acceptResource(name)) { - try { - Enumeration resources = classloader.getResources(name); - while (resources.hasMoreElements()) { - appendTo.add(resources.nextElement()); - } - } catch (IOException e) { - throw new IllegalStateException(String.format("Fail to load resources named '%s'", name), e); - } - } - } -} diff --git a/sonar-core/src/main/java/org/sonar/classloader/Mask.java b/sonar-core/src/main/java/org/sonar/classloader/Mask.java deleted file mode 100644 index e20e5756bfa..00000000000 --- a/sonar-core/src/main/java/org/sonar/classloader/Mask.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import javax.annotation.Nullable; - -/** - * A mask restricts access of a classloader to resources through inclusion and exclusion patterns. - * By default all resources/classes are visible. - *

- * Format of inclusion/exclusion patterns is the file path separated by slashes, for example - * "org/foo/Bar.class" or "org/foo/config.xml". Wildcard patterns are not supported. Directories must end with - * slash, for example "org/foo/" for excluding package org.foo and its sub-packages. Add the - * exclusion "/" to exclude everything. - * - * @since 0.1 - */ -public class Mask { - - private static final String ROOT = "/"; - - /** - * Accepts everything - * - * @since 1.1 - */ - public static final Mask ALL = Mask.builder().build(); - - /** - * Accepts nothing - * - * @since 1.1 - */ - public static final Mask NONE = Mask.builder().exclude(ROOT).build(); - - private final Set inclusions; - private final Set exclusions; - - private Mask(Builder builder) { - this.inclusions = Collections.unmodifiableSet(builder.inclusions); - this.exclusions = Collections.unmodifiableSet(builder.exclusions); - } - - /** - * Create a {@link Builder} for building immutable instances of {@link Mask} - * - * @since 1.1 - */ - public static Builder builder() { - return new Builder(); - } - - public Set getInclusions() { - return inclusions; - } - - public Set getExclusions() { - return exclusions; - } - - boolean acceptClass(String classname) { - if (inclusions.isEmpty() && exclusions.isEmpty()) { - return true; - } - return acceptResource(classToResource(classname)); - } - - boolean acceptResource(String name) { - boolean ok = true; - if (!inclusions.isEmpty()) { - ok = false; - for (String include : inclusions) { - if (matchPattern(name, include)) { - ok = true; - break; - } - } - } - if (ok) { - for (String exclude : exclusions) { - if (matchPattern(name, exclude)) { - ok = false; - break; - } - } - } - return ok; - } - - private static boolean matchPattern(String name, String pattern) { - return pattern.equals(ROOT) || (pattern.endsWith("/") && name.startsWith(pattern)) || pattern.equals(name); - } - - private static String classToResource(String classname) { - return classname.replace('.', '/') + ".class"; - } - - - public static class Builder { - private final Set inclusions = new HashSet<>(); - private final Set exclusions = new HashSet<>(); - - private Builder() { - } - - public Builder include(String path, String... others) { - doInclude(path); - for (String other : others) { - doInclude(other); - } - return this; - } - - public Builder exclude(String path, String... others) { - doExclude(path); - for (String other : others) { - doExclude(other); - } - return this; - } - - public Builder copy(Mask with) { - this.inclusions.addAll(with.inclusions); - this.exclusions.addAll(with.exclusions); - return this; - } - - public Builder merge(Mask with) { - List lowestIncludes = new ArrayList<>(); - - if (inclusions.isEmpty()) { - lowestIncludes.addAll(with.inclusions); - } else if (with.inclusions.isEmpty()) { - lowestIncludes.addAll(inclusions); - } else { - for (String include : inclusions) { - for (String fromInclude : with.inclusions) { - overlappingInclude(include, fromInclude) - .ifPresent(lowestIncludes::add); - } - } - } - inclusions.clear(); - inclusions.addAll(lowestIncludes); - exclusions.addAll(with.exclusions); - return this; - } - - private static Optional overlappingInclude(String include, String fromInclude) { - if (fromInclude.equals(include)) { - return Optional.of(fromInclude); - } else if (fromInclude.startsWith(include)) { - return Optional.of(fromInclude); - } else if (include.startsWith(fromInclude)) { - return Optional.of(include); - } - return Optional.empty(); - } - - public Mask build() { - return new Mask(this); - } - - private void doInclude(@Nullable String path) { - this.inclusions.add(validatePath(path)); - } - - private void doExclude(@Nullable String path) { - this.exclusions.add(validatePath(path)); - } - - private static String validatePath(@Nullable String path) { - if (path == null) { - throw new IllegalArgumentException("Mask path must not be null"); - } - if (path.startsWith("/") && path.length() > 1) { - throw new IllegalArgumentException("Mask path must not start with slash: "); - } - if (path.contains("*")) { - throw new IllegalArgumentException("Mask path is not a wildcard pattern and should not contain star characters (*): " + path); - } - return path; - } - } -} diff --git a/sonar-core/src/main/java/org/sonar/classloader/NullClassloaderRef.java b/sonar-core/src/main/java/org/sonar/classloader/NullClassloaderRef.java deleted file mode 100644 index 68e481754bd..00000000000 --- a/sonar-core/src/main/java/org/sonar/classloader/NullClassloaderRef.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import java.net.URL; -import java.util.Collection; - -class NullClassloaderRef implements ClassloaderRef { - - public static final NullClassloaderRef INSTANCE = new NullClassloaderRef(); - - private NullClassloaderRef() { - } - - @Override - public Class loadClassIfPresent(String classname) { - return null; - } - - @Override - public URL loadResourceIfPresent(String name) { - return null; - } - - @Override - public void loadResources(String name, Collection appendTo) { - // do nothing - } -} diff --git a/sonar-core/src/main/java/org/sonar/classloader/ParentFirstStrategy.java b/sonar-core/src/main/java/org/sonar/classloader/ParentFirstStrategy.java deleted file mode 100644 index 54962da829b..00000000000 --- a/sonar-core/src/main/java/org/sonar/classloader/ParentFirstStrategy.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import java.net.URL; -import java.util.Collection; - -class ParentFirstStrategy implements Strategy { - static final Strategy INSTANCE = new ParentFirstStrategy(); - - private ParentFirstStrategy() { - } - - @Override - public Class loadClass(StrategyContext context, String name) throws ClassNotFoundException { - Class clazz = context.loadClassFromSiblings(name); - if (clazz == null) { - clazz = context.loadClassFromParent(name); - if (clazz == null) { - clazz = context.loadClassFromSelf(name); - if (clazz == null) { - throw new ClassNotFoundException(name); - } - } - } - return clazz; - } - - @Override - public URL getResource(StrategyContext context, String name) { - URL url = context.loadResourceFromSiblings(name); - if (url == null) { - url = context.loadResourceFromParent(name); - if (url == null) { - url = context.loadResourceFromSelf(name); - } - } - return url; - } - - @Override - public void getResources(StrategyContext context, String name, Collection appendTo) { - context.loadResourcesFromSiblings(name, appendTo); - context.loadResourcesFromParent(name, appendTo); - context.loadResourcesFromSelf(name, appendTo); - } -} diff --git a/sonar-core/src/main/java/org/sonar/classloader/SelfFirstStrategy.java b/sonar-core/src/main/java/org/sonar/classloader/SelfFirstStrategy.java deleted file mode 100644 index 1c990e95417..00000000000 --- a/sonar-core/src/main/java/org/sonar/classloader/SelfFirstStrategy.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import java.net.URL; -import java.util.Collection; - -class SelfFirstStrategy implements Strategy { - - static final SelfFirstStrategy INSTANCE = new SelfFirstStrategy(); - - private SelfFirstStrategy() { - // singleton instance - } - - @Override - public Class loadClass(StrategyContext context, String name) throws ClassNotFoundException { - Class clazz = context.loadClassFromSiblings(name); - if (clazz == null) { - clazz = context.loadClassFromSelf(name); - if (clazz == null) { - clazz = context.loadClassFromParent(name); - if (clazz == null) { - throw new ClassNotFoundException(name); - } - } - } - return clazz; - } - - @Override - public URL getResource(StrategyContext context, String name) { - URL url = context.loadResourceFromSiblings(name); - if (url == null) { - url = context.loadResourceFromSelf(name); - if (url == null) { - url = context.loadResourceFromParent(name); - } - } - return url; - } - - @Override - public void getResources(StrategyContext context, String name, Collection appendTo) { - context.loadResourcesFromSiblings(name, appendTo); - context.loadResourcesFromSelf(name, appendTo); - context.loadResourcesFromParent(name, appendTo); - } -} diff --git a/sonar-core/src/main/java/org/sonar/classloader/Strategy.java b/sonar-core/src/main/java/org/sonar/classloader/Strategy.java deleted file mode 100644 index a7790b174a9..00000000000 --- a/sonar-core/src/main/java/org/sonar/classloader/Strategy.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import java.net.URL; -import java.util.Collection; -import javax.annotation.CheckForNull; - -interface Strategy { - - Class loadClass(StrategyContext context, String name) throws ClassNotFoundException; - - @CheckForNull - URL getResource(StrategyContext context, String name); - - void getResources(StrategyContext context, String name, Collection urls); - -} diff --git a/sonar-core/src/main/java/org/sonar/classloader/StrategyContext.java b/sonar-core/src/main/java/org/sonar/classloader/StrategyContext.java deleted file mode 100644 index 31b3b34e04c..00000000000 --- a/sonar-core/src/main/java/org/sonar/classloader/StrategyContext.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import java.net.URL; -import java.util.Collection; -import javax.annotation.CheckForNull; - -interface StrategyContext { - - @CheckForNull - Class loadClassFromSiblings(String name); - - @CheckForNull - Class loadClassFromSelf(String name); - - @CheckForNull - Class loadClassFromParent(String name); - - @CheckForNull - URL loadResourceFromSiblings(String name); - - @CheckForNull - URL loadResourceFromSelf(String name); - - @CheckForNull - URL loadResourceFromParent(String name); - - void loadResourcesFromSiblings(String name, Collection appendTo); - - void loadResourcesFromSelf(String name, Collection appendTo); - - void loadResourcesFromParent(String name, Collection appendTo); - -} diff --git a/sonar-core/src/main/java/org/sonar/classloader/package-info.java b/sonar-core/src/main/java/org/sonar/classloader/package-info.java deleted file mode 100644 index ccb9c13cb74..00000000000 --- a/sonar-core/src/main/java/org/sonar/classloader/package-info.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -@ParametersAreNonnullByDefault -package org.sonar.classloader; - -import javax.annotation.ParametersAreNonnullByDefault; - diff --git a/sonar-core/src/test/java/org/sonar/classloader/ClassloaderBuilderTest.java b/sonar-core/src/test/java/org/sonar/classloader/ClassloaderBuilderTest.java deleted file mode 100644 index 08e67eec07c..00000000000 --- a/sonar-core/src/test/java/org/sonar/classloader/ClassloaderBuilderTest.java +++ /dev/null @@ -1,787 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import java.io.File; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.apache.commons.io.IOUtils; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; - -public class ClassloaderBuilderTest { - - ClassloaderBuilder sut = new ClassloaderBuilder(); - - @Test - public void minimal_system_classloader() throws Exception { - // create a classloader based on system classloader - // -> access only to JRE - Map classloaders = sut.newClassloader("example").build(); - - assertThat(classloaders).hasSize(1); - ClassLoader classloader = classloaders.get("example"); - assertThat(classloader).hasToString("ClassRealm{example}"); - assertThat(canLoadClass(classloader, HashMap.class.getName())).isTrue(); - assertThat(canLoadClass(classloader, Test.class.getName())).isFalse(); - assertThat(canLoadClass(classloader, "A")).isFalse(); - assertThat(canLoadResource(classloader, "a.txt")).isFalse(); - } - - @Test - public void previous_classloader_not_returned_again() throws Exception { - Map classloaders1 = sut.newClassloader("example1").build(); - Map classloaders2 = new ClassloaderBuilder(classloaders1.values()) - .newClassloader("example2").build(); - - assertThat(classloaders2).containsOnlyKeys("example2"); - } - - @Test - public void fail_if_setting_attribute_to_previously_loaded_classloader() throws Exception { - Map classloaders1 = sut.newClassloader("example1").build(); - ClassloaderBuilder builder = new ClassloaderBuilder(classloaders1.values()) - .newClassloader("example2"); - - try { - builder.setMask("example1", Mask.ALL); - fail(); - } catch (IllegalStateException e) { - // ok - } - } - - /** - * Classloader based on another one (the junit env in this example). No parent-child hierarchy. - */ - @Test - public void base_classloader() throws Exception { - // - Map classloaders = sut.newClassloader("example", getClass().getClassLoader()).build(); - - assertThat(classloaders).hasSize(1); - ClassLoader classloader = classloaders.get("example"); - assertThat(canLoadClass(classloader, HashMap.class.getName())).isTrue(); - assertThat(canLoadClass(classloader, Test.class.getName())).isTrue(); - assertThat(canLoadClass(classloader, "A")).isFalse(); - assertThat(canLoadResource(classloader, "a.txt")).isFalse(); - } - - @Test - public void classloader_constituents() throws Exception { - Map classloaders = sut - .newClassloader("the-cl") - .addURL("the-cl", new File("tester/a.jar").toURL()) - .addURL("the-cl", new File("tester/b.jar").toURL()) - .build(); - - assertThat(classloaders).hasSize(1); - ClassLoader self = classloaders.get("the-cl"); - assertThat(canLoadClass(self, "A")).isTrue(); - assertThat(canLoadResource(self, "a.txt")).isTrue(); - assertThat(canLoadClass(self, "B")).isTrue(); - assertThat(canLoadResource(self, "b.txt")).isTrue(); - assertThat(canLoadClass(self, "C")).isFalse(); - assertThat(canLoadResource(self, "c.txt")).isFalse(); - } - - /** - * Parent -> child -> grand-child classloaders. Default order strategy is parent-first - */ - @Test - public void parent_child_relation() throws Exception { - // parent contains class A -> access to only A - // child contains class B -> access to A and B - // grand-child contains class C -> access to A, B and C - Map classloaders = sut - .newClassloader("the-parent") - .addURL("the-parent", new File("tester/a.jar").toURL()) - - // order of declaration is not important -> declare grand-child before child - .newClassloader("the-grand-child") - .addURL("the-grand-child", new File("tester/c.jar").toURL()) - .setParent("the-grand-child", "the-child", Mask.ALL) - - .newClassloader("the-child") - .addURL("the-child", new File("tester/b.jar").toURL()) - .setParent("the-child", "the-parent", Mask.ALL) - - .build(); - - assertThat(classloaders).hasSize(3); - - ClassLoader parent = classloaders.get("the-parent"); - assertThat(canLoadClass(parent, "A")).isTrue(); - assertThat(canLoadClass(parent, "B")).isFalse(); - assertThat(canLoadClass(parent, "C")).isFalse(); - assertThat(canLoadResource(parent, "a.txt")).isTrue(); - assertThat(canLoadResource(parent, "b.txt")).isFalse(); - assertThat(canLoadResource(parent, "c.txt")).isFalse(); - - ClassLoader child = classloaders.get("the-child"); - assertThat(canLoadClass(child, "A")).isTrue(); - assertThat(canLoadClass(child, "B")).isTrue(); - assertThat(canLoadClass(child, "C")).isFalse(); - assertThat(canLoadResource(child, "a.txt")).isTrue(); - assertThat(canLoadResource(child, "b.txt")).isTrue(); - assertThat(canLoadResource(child, "c.txt")).isFalse(); - - ClassLoader grandChild = classloaders.get("the-grand-child"); - assertThat(canLoadClass(grandChild, "A")).isTrue(); - assertThat(canLoadClass(grandChild, "B")).isTrue(); - assertThat(canLoadClass(grandChild, "C")).isTrue(); - assertThat(canLoadResource(grandChild, "a.txt")).isTrue(); - assertThat(canLoadResource(grandChild, "b.txt")).isTrue(); - assertThat(canLoadResource(grandChild, "c.txt")).isTrue(); - } - - /** - * Parent classloader can be created outside {@link ClassloaderBuilder}. - * Default ordering strategy is parent-first. - */ - @Test - public void existing_parent() throws Exception { - // parent contains JUnit - // child contains class A -> access to A and JUnit - ClassLoader parent = getClass().getClassLoader(); - Map newClassloaders = sut - .newClassloader("the-child") - .addURL("the-child", new File("tester/a.jar").toURL()) - .setParent("the-child", parent, Mask.ALL) - .build(); - - assertThat(newClassloaders).hasSize(1); - assertThat(canLoadClass(parent, Test.class.getName())).isTrue(); - assertThat(canLoadClass(parent, "A")).isFalse(); - ClassLoader child = newClassloaders.get("the-child"); - assertThat(canLoadClass(child, Test.class.getName())).isTrue(); - assertThat(canLoadClass(child, "A")).isTrue(); - } - - @Test - public void parent_first_ordering() throws Exception { - // parent contains version 1 of A - // child contains version 2 of A - Map newClassloaders = sut - .newClassloader("the-parent") - .addURL("the-parent", new File("tester/a.jar").toURL()) - - .newClassloader("the-child") - .addURL("the-child", new File("tester/a_v2.jar").toURL()) - .setParent("the-child", "the-parent", Mask.ALL) - .build(); - - ClassLoader parent = newClassloaders.get("the-parent"); - assertThat(canLoadMethod(parent, "A", "version1")).isTrue(); - assertThat(canLoadMethod(parent, "A", "version2")).isFalse(); - assertThat(IOUtils.toString(parent.getResource("a.txt"))).startsWith("version 1 of a.txt"); - - ClassLoader child = newClassloaders.get("the-child"); - assertThat(canLoadMethod(child, "A", "version1")).isTrue(); - assertThat(canLoadMethod(child, "A", "version2")).isFalse(); - assertThat(IOUtils.toString(child.getResource("a.txt"))).startsWith("version 1 of a.txt"); - } - - /** - * - parent contains B and version 1 of A - * - child contains version 2 of A -> sees B and version 2 of A - */ - @Test - public void self_first_ordering() throws Exception { - - Map newClassloaders = sut - .newClassloader("the-parent") - .addURL("the-parent", new File("tester/a.jar").toURL()) - .addURL("the-parent", new File("tester/b.jar").toURL()) - - .newClassloader("the-child") - .addURL("the-child", new File("tester/a_v2.jar").toURL()) - .setParent("the-child", "the-parent", Mask.ALL) - .setLoadingOrder("the-child", ClassloaderBuilder.LoadingOrder.SELF_FIRST) - .build(); - - ClassLoader parent = newClassloaders.get("the-parent"); - assertThat(canLoadMethod(parent, "A", "version1")).isTrue(); - assertThat(canLoadMethod(parent, "A", "version2")).isFalse(); - assertThat(IOUtils.toString(parent.getResource("a.txt"))).startsWith("version 1 of a.txt"); - - ClassLoader child = newClassloaders.get("the-child"); - assertThat(canLoadClass(child, "B")).isTrue(); - assertThat(canLoadMethod(child, "A", "version1")).isFalse(); - assertThat(canLoadMethod(child, "A", "version2")).isTrue(); - assertThat(IOUtils.toString(child.getResource("a.txt"))).startsWith("version 2 of a.txt"); - assertThat(Collections.list(child.getResources("b.txt"))).hasSize(1); - ArrayList resources = Collections.list(child.getResources("a.txt")); - assertThat(resources).hasSize(2); - assertThat(IOUtils.toString(resources.get(0))).startsWith("version 2 of a.txt"); - assertThat(IOUtils.toString(resources.get(1))).startsWith("version 1 of a.txt"); - } - - /** - * Prevent a classloader from loading some resources that are available in its own constituents. - */ - @Test - public void self_mask() throws Exception { - Map classloaders = sut - .newClassloader("the-cl") - .addURL("the-cl", new File("tester/a.jar").toURL()) - .addURL("the-cl", new File("tester/b.jar").toURL()) - .setMask("the-cl", Mask.builder().exclude("A.class", "a.txt").build()) - .build(); - - ClassLoader cl = classloaders.get("the-cl"); - assertThat(canLoadClass(cl, "A")).isFalse(); - assertThat(canLoadClass(cl, "B")).isTrue(); - assertThat(canLoadResource(cl, "a.txt")).isFalse(); - assertThat(canLoadResource(cl, "b.txt")).isTrue(); - } - - /** - * Partial inheritance of parent classloader - */ - @Test - public void parent_mask() throws Exception { - Map newClassloaders = sut - .newClassloader("the-parent") - .addURL("the-parent", new File("tester/a.jar").toURL()) - .addURL("the-parent", new File("tester/b.jar").toURL()) - - .newClassloader("the-child") - .addURL("the-child", new File("tester/c.jar").toURL()) - .setParent("the-child", "the-parent", Mask.builder().exclude("A.class", "a.txt").build()) - .build(); - - ClassLoader parent = newClassloaders.get("the-parent"); - assertThat(canLoadClass(parent, "A")).isTrue(); - assertThat(canLoadClass(parent, "B")).isTrue(); - assertThat(canLoadClass(parent, "C")).isFalse(); - assertThat(canLoadResource(parent, "a.txt")).isTrue(); - assertThat(canLoadResource(parent, "b.txt")).isTrue(); - assertThat(canLoadResource(parent, "c.txt")).isFalse(); - - ClassLoader child = newClassloaders.get("the-child"); - assertThat(canLoadClass(child, "A")).isFalse(); - assertThat(canLoadClass(child, "B")).isTrue(); - assertThat(canLoadClass(child, "C")).isTrue(); - assertThat(canLoadResource(child, "a.txt")).isFalse(); - assertThat(canLoadResource(child, "b.txt")).isTrue(); - assertThat(canLoadResource(child, "c.txt")).isTrue(); - } - - /** - * Parent classloader contains A and B, but exports only B to its children - */ - @Test - public void export_mask() throws Exception { - Map newClassloaders = sut - .newClassloader("the-parent") - .addURL("the-parent", new File("tester/a.jar").toURL()) - .addURL("the-parent", new File("tester/b.jar").toURL()) - .setExportMask("the-parent", Mask.builder().exclude("A.class", "a.txt").build()) - - .newClassloader("the-child") - .setParent("the-child", "the-parent", Mask.ALL) - .build(); - - ClassLoader parent = newClassloaders.get("the-parent"); - assertThat(canLoadClass(parent, "A")).isTrue(); - assertThat(canLoadClass(parent, "B")).isTrue(); - assertThat(canLoadResource(parent, "a.txt")).isTrue(); - assertThat(canLoadResource(parent, "b.txt")).isTrue(); - - ClassLoader child = newClassloaders.get("the-child"); - assertThat(canLoadClass(child, "A")).isFalse(); - assertThat(canLoadClass(child, "B")).isTrue(); - assertThat(canLoadResource(child, "a.txt")).isFalse(); - assertThat(canLoadResource(child, "b.txt")).isTrue(); - } - - /** - * Parent classloader contains A, B and C, but exports only B and C to its children. - * On the other side child classloader excludes B from its parent, so it benefits - * only from C - */ - @Test - public void mix_of_import_and_export_masks() throws Exception { - Map newClassloaders = sut - .newClassloader("the-parent") - .addURL("the-parent", new File("tester/a.jar").toURL()) - .addURL("the-parent", new File("tester/b.jar").toURL()) - .addURL("the-parent", new File("tester/c.jar").toURL()) - .setExportMask("the-parent", Mask.builder().exclude("A.class", "a.txt").build()) - - .newClassloader("the-child") - .setParent("the-child", "the-parent", Mask.builder().exclude("B.class", "b.txt").build()) - .build(); - - ClassLoader parent = newClassloaders.get("the-parent"); - assertThat(canLoadClass(parent, "A")).isTrue(); - assertThat(canLoadClass(parent, "B")).isTrue(); - assertThat(canLoadClass(parent, "C")).isTrue(); - assertThat(canLoadResource(parent, "a.txt")).isTrue(); - assertThat(canLoadResource(parent, "b.txt")).isTrue(); - assertThat(canLoadResource(parent, "c.txt")).isTrue(); - - ClassLoader child = newClassloaders.get("the-child"); - assertThat(canLoadClass(child, "A")).isFalse(); - assertThat(canLoadClass(child, "B")).isFalse(); - assertThat(canLoadClass(child, "C")).isTrue(); - assertThat(canLoadResource(child, "a.txt")).isFalse(); - assertThat(canLoadResource(child, "b.txt")).isFalse(); - assertThat(canLoadResource(child, "c.txt")).isTrue(); - } - - @Test - public void fail_to_create_the_same_classloader_twice() throws Exception { - sut.newClassloader("the-cl"); - try { - sut.newClassloader("the-cl"); - fail(); - } catch (IllegalStateException e) { - assertThat(e).hasMessage("The classloader 'the-cl' already exists. Can not create it twice."); - } - } - - @Test - public void fail_to_create_the_same_previous_classloader_twice() throws Exception { - Map classloaders1 = sut.newClassloader("the-cl").build(); - ClassloaderBuilder classloaderBuilder = new ClassloaderBuilder(classloaders1.values()); - try { - classloaderBuilder.newClassloader("the-cl"); - fail(); - } catch (IllegalStateException e) { - assertThat(e).hasMessage("The classloader 'the-cl' already exists in the list of previously created classloaders. " + - "Can not create it twice."); - } - } - - @Test - public void fail_if_missing_declaration() throws Exception { - sut.newClassloader("the-cl"); - sut.setParent("the-cl", "missing", Mask.ALL); - try { - sut.build(); - fail(); - } catch (IllegalStateException e) { - assertThat(e).hasMessage("The classloader 'missing' does not exist"); - } - } - - @Test - public void sibling() throws Exception { - // sibling1 contains A - // sibling2 contains B - // child contains C -> see A, B and C - Map newClassloaders = sut - .newClassloader("sib1") - .addURL("sib1", new File("tester/a.jar").toURL()) - - .newClassloader("sib2") - .addURL("sib2", new File("tester/b.jar").toURL()) - - .newClassloader("the-child") - .addURL("the-child", new File("tester/c.jar").toURL()) - .addSibling("the-child", "sib1", Mask.ALL) - .addSibling("the-child", "sib2", Mask.ALL) - .build(); - - ClassLoader sib1 = newClassloaders.get("sib1"); - assertThat(canLoadClass(sib1, "A")).isTrue(); - assertThat(canLoadClass(sib1, "B")).isFalse(); - assertThat(canLoadClass(sib1, "C")).isFalse(); - assertThat(canLoadResource(sib1, "a.txt")).isTrue(); - assertThat(canLoadResource(sib1, "b.txt")).isFalse(); - assertThat(canLoadResource(sib1, "c.txt")).isFalse(); - - ClassLoader sib2 = newClassloaders.get("sib2"); - assertThat(canLoadClass(sib2, "A")).isFalse(); - assertThat(canLoadClass(sib2, "B")).isTrue(); - assertThat(canLoadClass(sib2, "C")).isFalse(); - assertThat(canLoadResource(sib2, "a.txt")).isFalse(); - assertThat(canLoadResource(sib2, "b.txt")).isTrue(); - assertThat(canLoadResource(sib2, "c.txt")).isFalse(); - - ClassLoader child = newClassloaders.get("the-child"); - assertThat(canLoadClass(child, "A")).isTrue(); - assertThat(canLoadClass(child, "B")).isTrue(); - assertThat(canLoadClass(child, "C")).isTrue(); - assertThat(canLoadResource(child, "a.txt")).isTrue(); - assertThat(canLoadResource(child, "b.txt")).isTrue(); - assertThat(canLoadResource(child, "c.txt")).isTrue(); - } - - /** - * Sibling classloader can be created outside {@link ClassloaderBuilder}. - */ - @Test - public void existing_sibling() throws Exception { - // sibling1 contains JUnit - // child contains A -> see JUnit and A - Map newClassloaders = sut - .newClassloader("the-child") - .addURL("the-child", new File("tester/a.jar").toURL()) - .addSibling("the-child", getClass().getClassLoader(), Mask.ALL) - .build(); - - ClassLoader child = newClassloaders.get("the-child"); - assertThat(canLoadClass(child, Test.class.getName())).isTrue(); - assertThat(canLoadClass(child, "A")).isTrue(); - } - - /** - * - sibling contains A and B - * - child contains C and excludes A from sibling -> sees only B and C - */ - @Test - public void sibling_mask() throws Exception { - Map newClassloaders = sut - .newClassloader("sib1") - .addURL("sib1", new File("tester/a.jar").toURL()) - .addURL("sib1", new File("tester/b.jar").toURL()) - - .newClassloader("the-child") - .addURL("the-child", new File("tester/c.jar").toURL()) - .addSibling("the-child", "sib1", Mask.builder().exclude("A.class", "a.txt").build()) - .build(); - - ClassLoader sib1 = newClassloaders.get("sib1"); - assertThat(canLoadClass(sib1, "A")).isTrue(); - assertThat(canLoadClass(sib1, "B")).isTrue(); - assertThat(canLoadResource(sib1, "a.txt")).isTrue(); - assertThat(canLoadResource(sib1, "b.txt")).isTrue(); - - ClassLoader child = newClassloaders.get("the-child"); - assertThat(canLoadClass(child, "A")).isFalse(); - assertThat(canLoadClass(child, "B")).isTrue(); - assertThat(canLoadClass(child, "C")).isTrue(); - assertThat(canLoadResource(child, "a.txt")).isFalse(); - assertThat(canLoadResource(child, "b.txt")).isTrue(); - assertThat(canLoadResource(child, "c.txt")).isTrue(); - assertThat(Collections.list(child.getResources("a.txt"))).isEmpty(); - assertThat(Collections.list(child.getResources("b.txt"))).hasSize(1); - assertThat(Collections.list(child.getResources("c.txt"))).hasSize(1); - } - - /** - * - sibling contains A and B but exports only B - * - child contains C -> sees only B and C - */ - @Test - public void sibling_export_mask() throws Exception { - Map newClassloaders = sut - .newClassloader("sib1") - .addURL("sib1", new File("tester/a.jar").toURL()) - .addURL("sib1", new File("tester/b.jar").toURL()) - .setExportMask("sib1", Mask.builder().include("B.class", "b.txt").build()) - - .newClassloader("the-child") - .addURL("the-child", new File("tester/c.jar").toURL()) - .addSibling("the-child", "sib1", Mask.ALL) - .build(); - - ClassLoader sib1 = newClassloaders.get("sib1"); - assertThat(canLoadClass(sib1, "A")).isTrue(); - assertThat(canLoadClass(sib1, "B")).isTrue(); - assertThat(canLoadResource(sib1, "a.txt")).isTrue(); - assertThat(canLoadResource(sib1, "b.txt")).isTrue(); - - ClassLoader child = newClassloaders.get("the-child"); - assertThat(canLoadClass(child, "A")).isFalse(); - assertThat(canLoadClass(child, "B")).isTrue(); - assertThat(canLoadClass(child, "C")).isTrue(); - assertThat(canLoadResource(child, "a.txt")).isFalse(); - assertThat(canLoadResource(child, "b.txt")).isTrue(); - assertThat(canLoadResource(child, "c.txt")).isTrue(); - assertThat(Collections.list(child.getResources("a.txt"))).isEmpty(); - assertThat(Collections.list(child.getResources("b.txt"))).hasSize(1); - assertThat(Collections.list(child.getResources("c.txt"))).hasSize(1); - } - - /** - * Sibling classloader is loaded previously self: - * - sibling has version 1 of A - * - self has version 2 of A -> sees version 1 - */ - @Test - public void sibling_prevails_over_self() throws Exception { - Map newClassloaders = sut - .newClassloader("sib") - .addURL("sib", new File("tester/a.jar").toURL()) - - .newClassloader("self") - .addURL("self", new File("tester/a_v2.jar").toURL()) - .addSibling("self", "sib", Mask.ALL) - .build(); - - ClassLoader sib = newClassloaders.get("sib"); - assertThat(canLoadMethod(sib, "A", "version1")).isTrue(); - assertThat(canLoadMethod(sib, "A", "version2")).isFalse(); - assertThat(IOUtils.toString(sib.getResource("a.txt"))).startsWith("version 1 of a.txt"); - - ClassLoader self = newClassloaders.get("self"); - assertThat(canLoadMethod(self, "A", "version1")).isTrue(); - assertThat(canLoadMethod(self, "A", "version2")).isFalse(); - assertThat(IOUtils.toString(self.getResource("a.txt"))).startsWith("version 1 of a.txt"); - } - - /** - * Sibling classloader is always loaded previously self, even if self-first strategy: - * - sibling has version 1 of A - * - self has version 2 of A -> sees version 1 - */ - @Test - public void sibling_prevails_over_self_even_if_self_first() throws Exception { - Map newClassloaders = sut - .newClassloader("sib") - .addURL("sib", new File("tester/a.jar").toURL()) - - .newClassloader("self") - .addURL("self", new File("tester/a_v2.jar").toURL()) - .addSibling("self", "sib", Mask.ALL) - .setLoadingOrder("self", ClassloaderBuilder.LoadingOrder.SELF_FIRST) - .build(); - - ClassLoader sib = newClassloaders.get("sib"); - assertThat(canLoadMethod(sib, "A", "version1")).isTrue(); - assertThat(canLoadMethod(sib, "A", "version2")).isFalse(); - assertThat(IOUtils.toString(sib.getResource("a.txt"))).startsWith("version 1 of a.txt"); - - ClassLoader self = newClassloaders.get("self"); - assertThat(canLoadMethod(self, "A", "version1")).isTrue(); - assertThat(canLoadMethod(self, "A", "version2")).isFalse(); - assertThat(IOUtils.toString(self.getResource("a.txt"))).startsWith("version 1 of a.txt"); - } - - /** - * https://github.com/SonarSource/sonar-classloader/issues/1 - */ - @Test - public void cycle_of_siblings() throws Exception { - Map newClassloaders = sut - .newClassloader("a") - .addURL("a", new File("tester/a.jar").toURL()) - - .newClassloader("b") - .addURL("b", new File("tester/b.jar").toURL()) - .addSibling("a", "b", Mask.builder().include("B.class", "b.txt").build()) - .addSibling("b", "a", Mask.builder().include("A.class", "a.txt").build()) - .build(); - - ClassLoader a = newClassloaders.get("a"); - assertThat(canLoadClass(a, "A")).isTrue(); - assertThat(canLoadClass(a, "B")).isTrue(); - assertThat(IOUtils.toString(a.getResource("a.txt"))).isNotEmpty(); - assertThat(IOUtils.toString(a.getResource("b.txt"))).isNotEmpty(); - - ClassLoader b = newClassloaders.get("b"); - assertThat(canLoadClass(b, "A")).isTrue(); - assertThat(canLoadClass(b, "B")).isTrue(); - assertThat(IOUtils.toString(b.getResource("a.txt"))).isNotEmpty(); - assertThat(IOUtils.toString(b.getResource("b.txt"))).isNotEmpty(); - } - - @Test - public void getResources_from_parent_and_siblings() throws Exception { - Map newClassloaders = sut - .newClassloader("the-parent") - .addURL("the-parent", new File("tester/a.jar").toURL()) - - .newClassloader("the-sib") - .addURL("the-sib", new File("tester/b.jar").toURL()) - - .newClassloader("the-child") - .addURL("the-child", new File("tester/c.jar").toURL()) - .setParent("the-child", "the-parent", Mask.ALL) - .addSibling("the-child", "the-sib", Mask.ALL) - .build(); - - ClassLoader parent = newClassloaders.get("the-parent"); - assertThat(Collections.list(parent.getResources("a.txt"))).hasSize(1); - assertThat(Collections.list(parent.getResources("b.txt"))).isEmpty(); - assertThat(Collections.list(parent.getResources("c.txt"))).isEmpty(); - - ClassLoader child = newClassloaders.get("the-child"); - assertThat(Collections.list(child.getResources("a.txt"))).hasSize(1); - assertThat(Collections.list(child.getResources("b.txt"))).hasSize(1); - assertThat(Collections.list(child.getResources("c.txt"))).hasSize(1); - } - - @Test - public void getResources_from_previously_loaded_parent() throws Exception { - Map classloaders1 = sut - .newClassloader("the-parent") - .addURL("the-parent", new File("tester/a.jar").toURL()) - .build(); - - - Map classloaders2 = new ClassloaderBuilder(classloaders1.values()) - .newClassloader("the-child") - .addURL("the-child", new File("tester/b.jar").toURL()) - .setParent("the-child", "the-parent", Mask.ALL) - .build(); - - ClassLoader parent = classloaders1.get("the-parent"); - assertThat(Collections.list(parent.getResources("a.txt"))).hasSize(1); - assertThat(Collections.list(parent.getResources("b.txt"))).isEmpty(); - - ClassLoader child = classloaders2.get("the-child"); - assertThat(Collections.list(child.getResources("a.txt"))).hasSize(1); - assertThat(Collections.list(child.getResources("b.txt"))).hasSize(1); - } - - @Test - public void getResources_from_previously_loaded_sibling_based_on_export_mask() throws Exception { - Map classloaders1 = sut - .newClassloader("the-sib") - .addURL("the-sib", new File("tester/a.jar").toURL()) - .setExportMask("the-sib", Mask.builder().include("A.java").build()) - .build(); - - Map classloaders2 = new ClassloaderBuilder(classloaders1.values()) - .newClassloader("the-child") - .addURL("the-child", new File("tester/b.jar").toURL()) - .addSibling("the-child", "the-sib", Mask.ALL) - .build(); - - ClassLoader parent = classloaders1.get("the-sib"); - assertThat(Collections.list(parent.getResources("a.txt"))).hasSize(1); - assertThat(Collections.list(parent.getResources("A.java"))).hasSize(1); - assertThat(Collections.list(parent.getResources("b.txt"))).isEmpty(); - - ClassLoader child = classloaders2.get("the-child"); - assertThat(Collections.list(child.getResources("a.txt"))).isEmpty(); - assertThat(Collections.list(parent.getResources("A.java"))).hasSize(1); - assertThat(Collections.list(child.getResources("b.txt"))).hasSize(1); - } - - @Test - public void getResources_from_previously_loaded_sibling() throws Exception { - Map classloaders1 = sut - .newClassloader("the-sib") - .addURL("the-sib", new File("tester/a.jar").toURL()) - .build(); - - Map classloaders2 = new ClassloaderBuilder(classloaders1.values()) - .newClassloader("the-child") - .addURL("the-child", new File("tester/b.jar").toURL()) - .addSibling("the-child", "the-sib", Mask.ALL) - .build(); - - ClassLoader parent = classloaders1.get("the-sib"); - assertThat(Collections.list(parent.getResources("a.txt"))).hasSize(1); - assertThat(Collections.list(parent.getResources("b.txt"))).isEmpty(); - - ClassLoader child = classloaders2.get("the-child"); - assertThat(Collections.list(child.getResources("a.txt"))).hasSize(1); - assertThat(Collections.list(child.getResources("b.txt"))).hasSize(1); - } - - @Test - public void getResources_multiple_versions_with_parent_first_strategy() throws Exception { - Map newClassloaders = sut - .newClassloader("the-parent") - .addURL("the-parent", new File("tester/a.jar").toURL()) - - .newClassloader("the-child") - .addURL("the-child", new File("tester/a_v2.jar").toURL()) - .setParent("the-child", "the-parent", Mask.ALL) - .build(); - - ClassLoader parent = newClassloaders.get("the-parent"); - assertThat(Collections.list(parent.getResources("a.txt"))).hasSize(1); - - ClassLoader child = newClassloaders.get("the-child"); - List childResources = Collections.list(child.getResources("a.txt")); - assertThat(childResources).hasSize(2); - assertThat(IOUtils.toString(childResources.get(0))).startsWith("version 1 of a.txt"); - assertThat(IOUtils.toString(childResources.get(1))).startsWith("version 2 of a.txt"); - } - - @Test - public void resource_not_found_in_parent_first_strategy() throws Exception { - Map newClassloaders = sut - .newClassloader("the-parent") - .addURL("the-parent", new File("tester/a.jar").toURL()) - - .newClassloader("the-child") - .addURL("the-child", new File("tester/a_v2.jar").toURL()) - .setParent("the-child", "the-parent", Mask.ALL) - .build(); - - ClassLoader parent = newClassloaders.get("the-child"); - assertThat(parent.getResource("missing")).isNull(); - try { - parent.loadClass("missing"); - fail(); - } catch (ClassNotFoundException e) { - // ok - } - } - - @Test - public void resource_not_found_in_self_first_strategy() throws Exception { - Map newClassloaders = sut - .newClassloader("the-parent") - .addURL("the-parent", new File("tester/a.jar").toURL()) - - .newClassloader("the-child") - .addURL("the-child", new File("tester/a_v2.jar").toURL()) - .setParent("the-child", "the-parent", Mask.ALL) - .setLoadingOrder("the-child", ClassloaderBuilder.LoadingOrder.SELF_FIRST) - .build(); - - ClassLoader parent = newClassloaders.get("the-child"); - assertThat(parent.getResource("missing")).isNull(); - try { - parent.loadClass("missing"); - fail(); - } catch (ClassNotFoundException e) { - // ok - } - } - - private boolean canLoadClass(ClassLoader classloader, String classname) { - try { - classloader.loadClass(classname); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - - private boolean canLoadMethod(ClassLoader classloader, String classname, String methodName) { - try { - Class clazz = classloader.loadClass(classname); - return clazz.getMethod(methodName) != null; - } catch (Exception e) { - return false; - } - } - - private boolean canLoadResource(ClassLoader classloader, String name) { - return classloader.getResource(name) != null; - } -} diff --git a/sonar-core/src/test/java/org/sonar/classloader/MaskTest.java b/sonar-core/src/test/java/org/sonar/classloader/MaskTest.java deleted file mode 100644 index 85a72e0fdad..00000000000 --- a/sonar-core/src/test/java/org/sonar/classloader/MaskTest.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.classloader; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class MaskTest { - - @Test - void ALL_accepts_everything() throws Exception { - assertThat(Mask.ALL.acceptClass("org.sonar.Bar")).isTrue(); - assertThat(Mask.ALL.acceptClass("Bar")).isTrue(); - } - - @Test - void NONE_accepts_nothing() throws Exception { - assertThat(Mask.NONE.acceptClass("org.sonar.Bar")).isFalse(); - assertThat(Mask.NONE.acceptClass("Bar")).isFalse(); - } - - @Test - void include_class() throws Exception { - Mask mask = Mask.builder().include("org/sonar/Bar.class").build(); - assertThat(mask.acceptClass("org.sonar.Bar")).isTrue(); - assertThat(mask.acceptClass("org.sonar.qube.Bar")).isFalse(); - assertThat(mask.acceptClass("org.sonar.Foo")).isFalse(); - assertThat(mask.acceptClass("Bar")).isFalse(); - } - - @Test - void include_class_of_root_package() throws Exception { - Mask mask = Mask.builder().include("Bar.class").build(); - assertThat(mask.acceptClass("Bar")).isTrue(); - assertThat(mask.acceptClass("Foo")).isFalse(); - } - - @Test - void include_resource() throws Exception { - Mask mask = Mask.builder().include("org/sonar/Bar.class").build(); - assertThat(mask.acceptResource("org/sonar/Bar.class")).isTrue(); - assertThat(mask.acceptResource("org/sonar/qube/Bar.class")).isFalse(); - assertThat(mask.acceptResource("org/sonar/Foo.class")).isFalse(); - assertThat(mask.acceptResource("Bar.class")).isFalse(); - } - - @Test - void include_package() throws Exception { - Mask mask = Mask.builder().include("org/sonar/", "org/other/").build(); - assertThat(mask.acceptClass("Foo")).isFalse(); - assertThat(mask.acceptClass("org.sonar.Bar")).isTrue(); - assertThat(mask.acceptClass("org.sonarqube.Foo")).isFalse(); - assertThat(mask.acceptClass("org.sonar.qube.Foo")).isTrue(); - assertThat(mask.acceptClass("Bar")).isFalse(); - } - - @Test - void exclude_class() throws Exception { - Mask mask = Mask.builder().exclude("org/sonar/Bar.class").build(); - assertThat(mask.acceptClass("org.sonar.Bar")).isFalse(); - assertThat(mask.acceptClass("org.sonar.qube.Bar")).isTrue(); - assertThat(mask.acceptClass("org.sonar.Foo")).isTrue(); - assertThat(mask.acceptClass("Bar")).isTrue(); - } - - @Test - void exclude_package() throws Exception { - Mask mask = Mask.builder().exclude("org/sonar/", "org/other/").build(); - assertThat(mask.acceptClass("Foo")).isTrue(); - assertThat(mask.acceptClass("org.sonar.Bar")).isFalse(); - assertThat(mask.acceptClass("org.sonarqube.Foo")).isTrue(); - assertThat(mask.acceptClass("org.sonar.qube.Foo")).isFalse(); - assertThat(mask.acceptClass("Bar")).isTrue(); - } - - @Test - void exclusion_is_subset_of_inclusion() throws Exception { - Mask mask = Mask.builder() - .include("org/sonar/") - .exclude("org/sonar/qube/") - .build(); - assertThat(mask.acceptClass("org.sonar.Foo")).isTrue(); - assertThat(mask.acceptClass("org.sonar.Qube")).isTrue(); - assertThat(mask.acceptClass("org.sonar.qube.Foo")).isFalse(); - } - - @Test - void inclusion_is_subset_of_exclusion() throws Exception { - Mask mask = Mask.builder() - .include("org/sonar/qube/") - .exclude("org/sonar/") - .build(); - assertThat(mask.acceptClass("org.sonar.Foo")).isFalse(); - assertThat(mask.acceptClass("org.sonar.Qube")).isFalse(); - assertThat(mask.acceptClass("org.sonar.qube.Foo")).isFalse(); - } - - @Test - void exclude_everything() throws Exception { - Mask mask = Mask.builder().exclude("/").build(); - assertThat(mask.acceptClass("org.sonar.Foo")).isFalse(); - assertThat(mask.acceptClass("Foo")).isFalse(); - assertThat(mask.acceptResource("config.xml")).isFalse(); - assertThat(mask.acceptResource("org/config.xml")).isFalse(); - } - - @Test - void include_everything() throws Exception { - Mask mask = Mask.builder().include("/").build(); - assertThat(mask.acceptClass("org.sonar.Foo")).isTrue(); - assertThat(mask.acceptClass("Foo")).isTrue(); - assertThat(mask.acceptResource("config.xml")).isTrue(); - assertThat(mask.acceptResource("org/config.xml")).isTrue(); - } - - @Test - void merge_with_ALL() throws Exception { - Mask mask = Mask.builder() - .include("org/foo/") - .exclude("org/bar/") - .merge(Mask.ALL) - .build(); - - assertThat(mask.getInclusions()).containsOnly("org/foo/"); - assertThat(mask.getExclusions()).containsOnly("org/bar/"); - } - - @Test - void merge_exclusions() throws Exception { - Mask with = Mask.builder().exclude("bar/").build(); - Mask mask = Mask.builder().exclude("org/foo/").merge(with).build(); - - assertThat(mask.getExclusions()).containsOnly("org/foo/", "bar/"); - } - - @Test - void should_not_merge_disjoined_inclusions() throws Exception { - Mask with = Mask.builder().include("org/bar/").build(); - Mask mask = Mask.builder().include("org/foo/").merge(with).build(); - - assertThat(mask.getInclusions()).isEmpty(); - // TODO does that mean that merge result accepts everything ? - } - - @Test - void merge_inclusions() throws Exception { - Mask with = Mask.builder().include("org/foo/sub/", "org/bar/").build(); - Mask mask = Mask.builder().include("org/foo/", "org/bar/sub/").merge(with).build(); - - assertThat(mask.getInclusions()).containsOnly("org/foo/sub/", "org/bar/sub/"); - } -} -- cgit v1.2.3