From e5ae7f77c053e12cd7b1c3f5082025305facc7a8 Mon Sep 17 00:00:00 2001 From: Krishnan Mahadevan Date: Thu, 3 Nov 2022 20:18:03 +0530 Subject: [PATCH] Refactoring PackageUtils Broke down the implementation into multiple Child classes powered by a factory method. --- .../org/testng/internal/PackageUtils.java | 207 ++---------------- .../protocols/BundledResourceProcessor.java | 44 ++++ .../internal/protocols/FileProcessor.java | 20 ++ .../org/testng/internal/protocols/Input.java | 96 ++++++++ .../internal/protocols/JarProcessor.java | 71 ++++++ .../internal/protocols/NoOpProcessor.java | 13 ++ .../testng/internal/protocols/Processor.java | 121 ++++++++++ 7 files changed, 387 insertions(+), 185 deletions(-) create mode 100644 testng-core-api/src/main/java/org/testng/internal/protocols/BundledResourceProcessor.java create mode 100644 testng-core-api/src/main/java/org/testng/internal/protocols/FileProcessor.java create mode 100644 testng-core-api/src/main/java/org/testng/internal/protocols/Input.java create mode 100644 testng-core-api/src/main/java/org/testng/internal/protocols/JarProcessor.java create mode 100644 testng-core-api/src/main/java/org/testng/internal/protocols/NoOpProcessor.java create mode 100644 testng-core-api/src/main/java/org/testng/internal/protocols/Processor.java diff --git a/testng-core-api/src/main/java/org/testng/internal/PackageUtils.java b/testng-core-api/src/main/java/org/testng/internal/PackageUtils.java index 278c1ac7b4..8f6e32122c 100644 --- a/testng-core-api/src/main/java/org/testng/internal/PackageUtils.java +++ b/testng-core-api/src/main/java/org/testng/internal/PackageUtils.java @@ -4,19 +4,15 @@ import java.io.File; import java.io.IOException; -import java.lang.reflect.Method; -import java.net.JarURLConnection; import java.net.URL; -import java.net.URLConnection; import java.net.URLDecoder; import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.regex.Pattern; import org.testng.collections.Lists; +import org.testng.internal.protocols.Input; +import org.testng.internal.protocols.Processor; /** * Utility class that finds all the classes in a given package. @@ -26,7 +22,6 @@ * @author Cedric Beust */ public class PackageUtils { - private static final String PACKAGE_UTILS = PackageUtils.class.getSimpleName(); private static String[] testClassPaths; /** The additional class loaders to find classes in. */ @@ -45,21 +40,32 @@ private PackageUtils() { */ public static String[] findClassesInPackage( String packageName, List included, List excluded) throws IOException { - String packageOnly = packageName; + String packageNameWithoutWildCards = packageName; boolean recursive = false; if (packageName.endsWith(".*")) { - packageOnly = packageName.substring(0, packageName.lastIndexOf(".*")); + packageNameWithoutWildCards = packageName.substring(0, packageName.lastIndexOf(".*")); recursive = true; } - List vResult = Lists.newArrayList(); - String packageDirName = packageOnly.replace('.', '/') + (packageOnly.length() > 0 ? "/" : ""); + String packageDirName = + packageNameWithoutWildCards.replace('.', '/') + + (packageNameWithoutWildCards.length() > 0 ? "/" : ""); + + Input input = + Input.Builder.newBuilder() + .forPackageWithoutWildCards(packageNameWithoutWildCards) + .withRecursive(recursive) + .include(included) + .exclude(excluded) + .withPackageName(packageName) + .forPackageDirectory(packageDirName) + .build(); - List dirs = Lists.newArrayList(); // go through additional class loaders List allClassLoaders = ClassHelper.appendContextualClassLoaders(Lists.newArrayList(classLoaders)); + List dirs = Lists.newArrayList(); for (ClassLoader classLoader : allClassLoaders) { if (null == classLoader) { continue; @@ -71,99 +77,19 @@ public static String[] findClassesInPackage( } } + List vResult = Lists.newArrayList(); for (URL url : dirs) { String protocol = url.getProtocol(); if (!matchTestClasspath(url, packageDirName, recursive)) { continue; } - - if ("file".equals(protocol)) { - findClassesInDirPackage( - packageOnly, - included, - excluded, - URLDecoder.decode(url.getFile(), UTF_8), - recursive, - vResult); - } else if ("jar".equals(protocol)) { - processJar( - url, packageDirName, packageName, recursive, packageOnly, included, excluded, vResult); - } else if ("bundleresource".equals(protocol)) - processBundledResources(included, excluded, packageOnly, recursive, vResult, url); + List processed = Processor.newInstance(protocol).process(input, url); + vResult.addAll(processed); } return vResult.toArray(new String[0]); } - private static void processJar( - URL url, - String packageDirName, - String packageName, - boolean recursive, - String packageOnly, - List included, - List excluded, - List vResult) - throws IOException { - JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile(); - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String name = entry.getName(); - if (name.startsWith("module-info") || name.startsWith("META-INF")) { - continue; - } - if (name.charAt(0) == '/') { - name = name.substring(1); - } - if (name.startsWith(packageDirName)) { - int idx = name.lastIndexOf('/'); - if (idx != -1) { - packageName = name.substring(0, idx).replace('/', '.'); - } - - if (recursive || packageName.equals(packageOnly)) { - // it's not inside a deeper dir - Utils.log(PACKAGE_UTILS, 4, "Package name is " + packageName); - if (name.endsWith(".class") && !entry.isDirectory()) { - String className = name.substring(packageName.length() + 1, name.length() - 6); - Utils.log( - PACKAGE_UTILS, - 4, - "Found class " + className + ", seeing it if it's included or excluded"); - includeOrExcludeClass(packageName, className, included, excluded, vResult); - } - } - } - } - } - - private static void processBundledResources( - List included, - List excluded, - String packageOnly, - boolean recursive, - List vResult, - URL url) { - try { - Class[] params = {}; - // BundleURLConnection - URLConnection connection = url.openConnection(); - Method thisMethod = url.openConnection().getClass().getDeclaredMethod("getFileURL", params); - Object[] paramsObj = {}; - URL fileUrl = (URL) thisMethod.invoke(connection, paramsObj); - findClassesInDirPackage( - packageOnly, - included, - excluded, - URLDecoder.decode(fileUrl.getFile(), UTF_8), - recursive, - vResult); - } catch (Exception ex) { - // ignore - probably not an Eclipse OSGi bundle - } - } - private static String[] getTestClasspath() { if (null != testClassPaths) { return testClassPaths; @@ -202,8 +128,7 @@ private static boolean matchTestClasspath(URL url, String lastFragment, boolean return true; } - String fileName = ""; - fileName = URLDecoder.decode(url.getFile(), UTF_8); + String fileName = URLDecoder.decode(url.getFile(), UTF_8); for (String classpathFrag : classpathFragments) { String path = classpathFrag + lastFragment; @@ -217,94 +142,6 @@ private static boolean matchTestClasspath(URL url, String lastFragment, boolean return true; } } - return false; } - - private static void findClassesInDirPackage( - String packageName, - List included, - List excluded, - String packagePath, - final boolean recursive, - List classes) { - File dir = new File(packagePath); - - if (!dir.exists() || !dir.isDirectory()) { - return; - } - - File[] dirfiles = - dir.listFiles( - file -> - (recursive && file.isDirectory()) - || (file.getName().endsWith(".class")) - || (file.getName().endsWith(".groovy"))); - - Utils.log(PACKAGE_UTILS, 4, "Looking for test classes in the directory: " + dir); - if (dirfiles == null) { - return; - } - for (File file : dirfiles) { - if (file.isDirectory()) { - findClassesInDirPackage( - makeFullClassName(packageName, file.getName()), - included, - excluded, - file.getAbsolutePath(), - recursive, - classes); - } else { - String className = file.getName().substring(0, file.getName().lastIndexOf('.')); - Utils.log( - PACKAGE_UTILS, - 4, - "Found class " + className + ", seeing it if it's included or excluded"); - includeOrExcludeClass(packageName, className, included, excluded, classes); - } - } - } - - private static String makeFullClassName(String pkg, String cls) { - return pkg.length() > 0 ? pkg + "." + cls : cls; - } - - private static void includeOrExcludeClass( - String packageName, - String className, - List included, - List excluded, - List classes) { - if (isIncluded(packageName, included, excluded)) { - Utils.log(PACKAGE_UTILS, 4, "... Including class " + className); - classes.add(makeFullClassName(packageName, className)); - } else { - Utils.log(PACKAGE_UTILS, 4, "... Excluding class " + className); - } - } - - /** @return true if name should be included. */ - private static boolean isIncluded(String name, List included, List excluded) { - // - // If no includes nor excludes were specified, return true. - // - if (included.isEmpty() && excluded.isEmpty()) { - return true; - } - boolean isIncluded = PackageUtils.find(name, included); - boolean isExcluded = PackageUtils.find(name, excluded); - boolean result; - if (isIncluded && !isExcluded) { - result = true; - } else if (isExcluded) { - result = false; - } else { - result = included.isEmpty(); - } - return result; - } - - private static boolean find(String name, List list) { - return list.stream().parallel().anyMatch(each -> Pattern.matches(each, name)); - } } diff --git a/testng-core-api/src/main/java/org/testng/internal/protocols/BundledResourceProcessor.java b/testng-core-api/src/main/java/org/testng/internal/protocols/BundledResourceProcessor.java new file mode 100644 index 0000000000..bdcf5808f6 --- /dev/null +++ b/testng-core-api/src/main/java/org/testng/internal/protocols/BundledResourceProcessor.java @@ -0,0 +1,44 @@ +package org.testng.internal.protocols; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.util.List; +import org.testng.collections.Lists; + +public class BundledResourceProcessor extends Processor { + @Override + public List process(Input input, URL url) throws IOException { + return processBundledResources( + url, + input.getIncluded(), + input.getExcluded(), + input.getPackageWithoutWildCards(), + input.isRecursive()); + } + + private static List processBundledResources( + URL url, + List included, + List excluded, + String packageOnly, + boolean recursive) { + try { + Class[] params = {}; + // BundleURLConnection + URLConnection connection = url.openConnection(); + Method thisMethod = url.openConnection().getClass().getDeclaredMethod("getFileURL", params); + Object[] paramsObj = {}; + URL fileUrl = (URL) thisMethod.invoke(connection, paramsObj); + return findClassesInDirPackage( + packageOnly, included, excluded, URLDecoder.decode(fileUrl.getFile(), UTF_8), recursive); + } catch (Exception ex) { + // ignore - probably not an Eclipse OSGi bundle + } + return Lists.newArrayList(); + } +} diff --git a/testng-core-api/src/main/java/org/testng/internal/protocols/FileProcessor.java b/testng-core-api/src/main/java/org/testng/internal/protocols/FileProcessor.java new file mode 100644 index 0000000000..12b93a0e4f --- /dev/null +++ b/testng-core-api/src/main/java/org/testng/internal/protocols/FileProcessor.java @@ -0,0 +1,20 @@ +package org.testng.internal.protocols; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.net.URL; +import java.net.URLDecoder; +import java.util.List; + +class FileProcessor extends Processor { + + @Override + public List process(Input input, URL url) { + return findClassesInDirPackage( + input.getPackageWithoutWildCards(), + input.getIncluded(), + input.getExcluded(), + URLDecoder.decode(url.getFile(), UTF_8), + input.isRecursive()); + } +} diff --git a/testng-core-api/src/main/java/org/testng/internal/protocols/Input.java b/testng-core-api/src/main/java/org/testng/internal/protocols/Input.java new file mode 100644 index 0000000000..18fd4c23b0 --- /dev/null +++ b/testng-core-api/src/main/java/org/testng/internal/protocols/Input.java @@ -0,0 +1,96 @@ +package org.testng.internal.protocols; + +import java.util.Collections; +import java.util.List; + +public class Input { + + private final List included; + private final List excluded; + private final String packageWithoutWildCards; + private final boolean recursive; + private final String packageDirName; + private final String packageName; + + private Input(Builder builder) { + included = Collections.unmodifiableList(builder.included); + excluded = Collections.unmodifiableList(builder.excluded); + packageWithoutWildCards = builder.packageWithoutWildCards; + recursive = builder.recursive; + packageName = builder.packageName; + packageDirName = builder.packageDirName; + } + + public List getIncluded() { + return included; + } + + public List getExcluded() { + return excluded; + } + + public String getPackageWithoutWildCards() { + return packageWithoutWildCards; + } + + public boolean isRecursive() { + return recursive; + } + + public String getPackageDirName() { + return packageDirName; + } + + public String getPackageName() { + return packageName; + } + + public static final class Builder { + private List included; + private List excluded; + private String packageWithoutWildCards; + private boolean recursive; + private String packageDirName; + private String packageName; + + private Builder() {} + + public static Builder newBuilder() { + return new Builder(); + } + + public Builder include(List val) { + included = val; + return this; + } + + public Builder exclude(List val) { + excluded = val; + return this; + } + + public Builder forPackageWithoutWildCards(String val) { + packageWithoutWildCards = val; + return this; + } + + public Builder withRecursive(boolean val) { + recursive = val; + return this; + } + + public Builder forPackageDirectory(String val) { + packageDirName = val; + return this; + } + + public Builder withPackageName(String val) { + packageName = val; + return this; + } + + public Input build() { + return new Input(this); + } + } +} diff --git a/testng-core-api/src/main/java/org/testng/internal/protocols/JarProcessor.java b/testng-core-api/src/main/java/org/testng/internal/protocols/JarProcessor.java new file mode 100644 index 0000000000..c7934001b1 --- /dev/null +++ b/testng-core-api/src/main/java/org/testng/internal/protocols/JarProcessor.java @@ -0,0 +1,71 @@ +package org.testng.internal.protocols; + +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import org.testng.collections.Lists; +import org.testng.internal.Utils; + +public class JarProcessor extends Processor { + @Override + public List process(Input input, URL url) throws IOException { + return processJar( + url, + input.getIncluded(), + input.getExcluded(), + input.getPackageWithoutWildCards(), + input.isRecursive(), + input.getPackageDirName(), + input.getPackageName()); + } + + private static List processJar( + URL url, + List included, + List excluded, + String packageOnly, + boolean recursive, + String packageDirName, + String packageName) + throws IOException { + List vResult = Lists.newArrayList(); + JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile(); + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.startsWith("module-info") || name.startsWith("META-INF")) { + continue; + } + if (name.charAt(0) == '/') { + name = name.substring(1); + } + if (name.startsWith(packageDirName)) { + int idx = name.lastIndexOf('/'); + if (idx != -1) { + packageName = name.substring(0, idx).replace('/', '.'); + } + + if (recursive || packageName.equals(packageOnly)) { + // it's not inside a deeper dir + Utils.log(CLS_NAME, 4, "Package name is " + packageName); + if (name.endsWith(".class") && !entry.isDirectory()) { + String className = name.substring(packageName.length() + 1, name.length() - 6); + Utils.log( + CLS_NAME, + 4, + "Found class " + className + ", seeing it if it's included or excluded"); + List processedList = + includeOrExcludeClass(packageName, className, included, excluded); + vResult.addAll(processedList); + } + } + } + } + return vResult; + } +} diff --git a/testng-core-api/src/main/java/org/testng/internal/protocols/NoOpProcessor.java b/testng-core-api/src/main/java/org/testng/internal/protocols/NoOpProcessor.java new file mode 100644 index 0000000000..526de6f2d1 --- /dev/null +++ b/testng-core-api/src/main/java/org/testng/internal/protocols/NoOpProcessor.java @@ -0,0 +1,13 @@ +package org.testng.internal.protocols; + +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.List; + +public class NoOpProcessor extends Processor { + @Override + public List process(Input input, URL url) throws IOException { + return Collections.emptyList(); + } +} diff --git a/testng-core-api/src/main/java/org/testng/internal/protocols/Processor.java b/testng-core-api/src/main/java/org/testng/internal/protocols/Processor.java new file mode 100644 index 0000000000..4b7c000c28 --- /dev/null +++ b/testng-core-api/src/main/java/org/testng/internal/protocols/Processor.java @@ -0,0 +1,121 @@ +package org.testng.internal.protocols; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.regex.Pattern; +import org.testng.collections.Lists; +import org.testng.internal.Utils; + +public abstract class Processor { + + protected static final String CLS_NAME = Processor.class.getSimpleName(); + + public static Processor newInstance(String protocol) { + Processor instance; + switch (protocol.toLowerCase()) { + case "file": + instance = new FileProcessor(); + break; + case "jar": + instance = new JarProcessor(); + break; + case "bundleresource": + instance = new BundledResourceProcessor(); + break; + default: + instance = new NoOpProcessor(); + } + return instance; + } + + public abstract List process(Input input, URL url) throws IOException; + + protected static List findClassesInDirPackage( + String packageName, + List included, + List excluded, + String packagePath, + final boolean recursive) { + File dir = new File(packagePath); + + if (!dir.exists() || !dir.isDirectory()) { + return Lists.newArrayList(); + } + + File[] dirfiles = + dir.listFiles( + file -> + (recursive && file.isDirectory()) + || (file.getName().endsWith(".class")) + || (file.getName().endsWith(".groovy"))); + + Utils.log(CLS_NAME, 4, "Looking for test classes in the directory: " + dir); + if (dirfiles == null) { + return Lists.newArrayList(); + } + List classes = Lists.newArrayList(); + for (File file : dirfiles) { + if (file.isDirectory()) { + List foundClasses = + findClassesInDirPackage( + makeFullClassName(packageName, file.getName()), + included, + excluded, + file.getAbsolutePath(), + recursive); + classes.addAll(foundClasses); + } else { + String className = file.getName().substring(0, file.getName().lastIndexOf('.')); + Utils.log( + CLS_NAME, 4, "Found class " + className + ", seeing it if it's included or excluded"); + List processedList = + includeOrExcludeClass(packageName, className, included, excluded); + classes.addAll(processedList); + } + } + return classes; + } + + private static String makeFullClassName(String pkg, String cls) { + return pkg.length() > 0 ? pkg + "." + cls : cls; + } + + protected static List includeOrExcludeClass( + String packageName, String className, List included, List excluded) { + List classes = Lists.newArrayList(); + if (isIncluded(packageName, included, excluded)) { + Utils.log(CLS_NAME, 4, "... Including class " + className); + classes.add(makeFullClassName(packageName, className)); + } else { + Utils.log(CLS_NAME, 4, "... Excluding class " + className); + } + return classes; + } + + /** @return true if name should be included. */ + private static boolean isIncluded(String name, List included, List excluded) { + // + // If no includes nor excludes were specified, return true. + // + if (included.isEmpty() && excluded.isEmpty()) { + return true; + } + boolean isIncluded = find(name, included); + boolean isExcluded = find(name, excluded); + boolean result; + if (isIncluded && !isExcluded) { + result = true; + } else if (isExcluded) { + result = false; + } else { + result = included.isEmpty(); + } + return result; + } + + private static boolean find(String name, List list) { + return list.stream().parallel().anyMatch(each -> Pattern.matches(each, name)); + } +}