Skip to content

Commit

Permalink
Refactoring PackageUtils
Browse files Browse the repository at this point in the history
Broke down the implementation into multiple 
Child classes powered by a factory method.
  • Loading branch information
krmahadevan committed Nov 3, 2022
1 parent bc23c47 commit d33a610
Show file tree
Hide file tree
Showing 9 changed files with 424 additions and 211 deletions.
258 changes: 48 additions & 210 deletions testng-core-api/src/main/java/org/testng/internal/PackageUtils.java
Expand Up @@ -4,19 +4,22 @@

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.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.testng.collections.Lists;
import org.testng.internal.protocols.Input;
import org.testng.internal.protocols.Processor;
import org.testng.internal.protocols.UnhandledIOException;

/**
* Utility class that finds all the classes in a given package.
Expand All @@ -26,7 +29,6 @@
* @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
*/
public class PackageUtils {
private static final String PACKAGE_UTILS = PackageUtils.class.getSimpleName();
private static String[] testClassPaths;

/** The additional class loaders to find classes in. */
Expand All @@ -45,123 +47,36 @@ private PackageUtils() {
*/
public static String[] findClassesInPackage(
String packageName, List<String> included, List<String> excluded) throws IOException {
String packageOnly = packageName;
boolean recursive = false;
if (packageName.endsWith(".*")) {
packageOnly = packageName.substring(0, packageName.lastIndexOf(".*"));
recursive = true;
}

List<String> vResult = Lists.newArrayList();
String packageDirName = packageOnly.replace('.', '/') + (packageOnly.length() > 0 ? "/" : "");
String packageNameWithoutWildCards = packageName;
boolean recursive = packageName.endsWith(".*");
if (recursive) {
packageNameWithoutWildCards = packageName.substring(0, packageName.lastIndexOf(".*"));
}

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<URL> dirs = Lists.newArrayList();
// go through additional class loaders
List<ClassLoader> allClassLoaders =
ClassHelper.appendContextualClassLoaders(Lists.newArrayList(classLoaders));

for (ClassLoader classLoader : allClassLoaders) {
if (null == classLoader) {
continue;
}
Enumeration<URL> dirEnumeration = classLoader.getResources(packageDirName);
while (dirEnumeration.hasMoreElements()) {
URL dir = dirEnumeration.nextElement();
dirs.add(dir);
}
}

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);
}

return vResult.toArray(new String[0]);
}

private static void processJar(
URL url,
String packageDirName,
String packageName,
boolean recursive,
String packageOnly,
List<String> included,
List<String> excluded,
List<String> vResult)
throws IOException {
JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
Enumeration<JarEntry> 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<String> included,
List<String> excluded,
String packageOnly,
boolean recursive,
List<String> 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
}
return allClassLoaders.stream()
.filter(Objects::nonNull)
.flatMap(asURLs(packageDirName))
.filter(url -> matchTestClasspath(url, packageDirName, recursive))
.flatMap(url -> Processor.newInstance(url.getProtocol()).process(input, url).stream())
.toArray(String[]::new);
}

private static String[] getTestClasspath() {
Expand Down Expand Up @@ -196,14 +111,25 @@ private static String[] getTestClasspath() {
return testClassPaths;
}

private static Function<ClassLoader, Stream<URL>> asURLs(String packageDir) {
return cl -> {
try {
Iterator<URL> iterator = cl.getResources(packageDir).asIterator();
return StreamSupport.stream(
Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
} catch (IOException e) {
throw new UnhandledIOException(e);
}
};
}

private static boolean matchTestClasspath(URL url, String lastFragment, boolean recursive) {
String[] classpathFragments = getTestClasspath();
if (null == classpathFragments) {
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;
Expand All @@ -217,94 +143,6 @@ private static boolean matchTestClasspath(URL url, String lastFragment, boolean
return true;
}
}

return false;
}

private static void findClassesInDirPackage(
String packageName,
List<String> included,
List<String> excluded,
String packagePath,
final boolean recursive,
List<String> 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<String> included,
List<String> excluded,
List<String> 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<String> included, List<String> 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<String> list) {
return list.stream().parallel().anyMatch(each -> Pattern.matches(each, name));
}
}
@@ -0,0 +1,43 @@
package org.testng.internal.protocols;

import static java.nio.charset.StandardCharsets.UTF_8;

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<String> process(Input input, URL url) {
return processBundledResources(
url,
input.getIncluded(),
input.getExcluded(),
input.getPackageWithoutWildCards(),
input.isRecursive());
}

private static List<String> processBundledResources(
URL url,
List<String> included,
List<String> 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();
}
}
@@ -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<String> process(Input input, URL url) {
return findClassesInDirPackage(
input.getPackageWithoutWildCards(),
input.getIncluded(),
input.getExcluded(),
URLDecoder.decode(url.getFile(), UTF_8),
input.isRecursive());
}
}

0 comments on commit d33a610

Please sign in to comment.