Skip to content

Commit ae741c1

Browse files
committed
Refactoring PackageUtils
Broke down the implementation into multiple Child classes powered by a factory method.
1 parent f3bc377 commit ae741c1

File tree

9 files changed

+422
-194
lines changed

9 files changed

+422
-194
lines changed

testng-core-api/src/main/java/org/testng/internal/PackageUtils.java

Lines changed: 46 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@
44

55
import java.io.File;
66
import java.io.IOException;
7-
import java.lang.reflect.Method;
8-
import java.net.JarURLConnection;
97
import java.net.URL;
10-
import java.net.URLConnection;
118
import java.net.URLDecoder;
129
import java.util.Collection;
13-
import java.util.Enumeration;
10+
import java.util.Iterator;
1411
import java.util.List;
12+
import java.util.Objects;
13+
import java.util.Spliterator;
14+
import java.util.Spliterators;
1515
import java.util.concurrent.ConcurrentLinkedDeque;
16-
import java.util.jar.JarEntry;
17-
import java.util.jar.JarFile;
18-
import java.util.regex.Pattern;
16+
import java.util.function.Function;
17+
import java.util.stream.Stream;
18+
import java.util.stream.StreamSupport;
1919
import org.testng.collections.Lists;
20+
import org.testng.internal.protocols.Input;
21+
import org.testng.internal.protocols.Processor;
22+
import org.testng.internal.protocols.UnhandledIOException;
2023

2124
/**
2225
* Utility class that finds all the classes in a given package.
@@ -26,7 +29,6 @@
2629
* @author <a href="mailto:[email protected]">Cedric Beust</a>
2730
*/
2831
public class PackageUtils {
29-
private static final String PACKAGE_UTILS = PackageUtils.class.getSimpleName();
3032
private static String[] testClassPaths;
3133

3234
/** The additional class loaders to find classes in. */
@@ -45,101 +47,36 @@ private PackageUtils() {
4547
*/
4648
public static String[] findClassesInPackage(
4749
String packageName, List<String> included, List<String> excluded) throws IOException {
48-
String packageOnly = packageName;
49-
boolean recursive = false;
50-
if (packageName.endsWith(".*")) {
51-
packageOnly = packageName.substring(0, packageName.lastIndexOf(".*"));
52-
recursive = true;
50+
String packageNameWithoutWildCards = packageName;
51+
boolean recursive = packageName.endsWith(".*");
52+
if (recursive) {
53+
packageNameWithoutWildCards = packageName.substring(0, packageName.lastIndexOf(".*"));
5354
}
5455

55-
List<String> vResult = Lists.newArrayList();
56-
String packageDirName = packageOnly.replace('.', '/') + (packageOnly.length() > 0 ? "/" : "");
56+
String packageDirName =
57+
packageNameWithoutWildCards.replace('.', '/')
58+
+ (packageNameWithoutWildCards.length() > 0 ? "/" : "");
59+
60+
Input input =
61+
Input.Builder.newBuilder()
62+
.forPackageWithoutWildCards(packageNameWithoutWildCards)
63+
.withRecursive(recursive)
64+
.include(included)
65+
.exclude(excluded)
66+
.withPackageName(packageName)
67+
.forPackageDirectory(packageDirName)
68+
.build();
5769

58-
List<URL> dirs = Lists.newArrayList();
5970
// go through additional class loaders
6071
List<ClassLoader> allClassLoaders =
6172
ClassHelper.appendContextualClassLoaders(Lists.newArrayList(classLoaders));
6273

63-
for (ClassLoader classLoader : allClassLoaders) {
64-
if (null == classLoader) {
65-
continue;
66-
}
67-
Enumeration<URL> dirEnumeration = classLoader.getResources(packageDirName);
68-
while (dirEnumeration.hasMoreElements()) {
69-
URL dir = dirEnumeration.nextElement();
70-
dirs.add(dir);
71-
}
72-
}
73-
74-
for (URL url : dirs) {
75-
String protocol = url.getProtocol();
76-
if (!matchTestClasspath(url, packageDirName, recursive)) {
77-
continue;
78-
}
79-
80-
if ("file".equals(protocol)) {
81-
findClassesInDirPackage(
82-
packageOnly,
83-
included,
84-
excluded,
85-
URLDecoder.decode(url.getFile(), UTF_8),
86-
recursive,
87-
vResult);
88-
} else if ("jar".equals(protocol)) {
89-
JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
90-
Enumeration<JarEntry> entries = jar.entries();
91-
while (entries.hasMoreElements()) {
92-
JarEntry entry = entries.nextElement();
93-
String name = entry.getName();
94-
if (name.startsWith("module-info") || name.startsWith("META-INF")) {
95-
continue;
96-
}
97-
if (name.charAt(0) == '/') {
98-
name = name.substring(1);
99-
}
100-
if (name.startsWith(packageDirName)) {
101-
int idx = name.lastIndexOf('/');
102-
if (idx != -1) {
103-
packageName = name.substring(0, idx).replace('/', '.');
104-
}
105-
106-
if (recursive || packageName.equals(packageOnly)) {
107-
// it's not inside a deeper dir
108-
Utils.log(PACKAGE_UTILS, 4, "Package name is " + packageName);
109-
if (name.endsWith(".class") && !entry.isDirectory()) {
110-
String className = name.substring(packageName.length() + 1, name.length() - 6);
111-
Utils.log(
112-
PACKAGE_UTILS,
113-
4,
114-
"Found class " + className + ", seeing it if it's included or excluded");
115-
includeOrExcludeClass(packageName, className, included, excluded, vResult);
116-
}
117-
}
118-
}
119-
}
120-
} else if ("bundleresource".equals(protocol)) {
121-
try {
122-
Class<?>[] params = {};
123-
// BundleURLConnection
124-
URLConnection connection = url.openConnection();
125-
Method thisMethod =
126-
url.openConnection().getClass().getDeclaredMethod("getFileURL", params);
127-
Object[] paramsObj = {};
128-
URL fileUrl = (URL) thisMethod.invoke(connection, paramsObj);
129-
findClassesInDirPackage(
130-
packageOnly,
131-
included,
132-
excluded,
133-
URLDecoder.decode(fileUrl.getFile(), UTF_8),
134-
recursive,
135-
vResult);
136-
} catch (Exception ex) {
137-
// ignore - probably not an Eclipse OSGi bundle
138-
}
139-
}
140-
}
141-
142-
return vResult.toArray(new String[0]);
74+
return allClassLoaders.stream()
75+
.filter(Objects::nonNull)
76+
.flatMap(asURLs(packageDirName))
77+
.filter(url -> matchTestClasspath(url, packageDirName, recursive))
78+
.flatMap(url -> Processor.newInstance(url.getProtocol()).process(input, url).stream())
79+
.toArray(String[]::new);
14380
}
14481

14582
private static String[] getTestClasspath() {
@@ -174,14 +111,25 @@ private static String[] getTestClasspath() {
174111
return testClassPaths;
175112
}
176113

114+
private static Function<ClassLoader, Stream<URL>> asURLs(String packageDir) {
115+
return cl -> {
116+
try {
117+
Iterator<URL> iterator = cl.getResources(packageDir).asIterator();
118+
return StreamSupport.stream(
119+
Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
120+
} catch (IOException e) {
121+
throw new UnhandledIOException(e);
122+
}
123+
};
124+
}
125+
177126
private static boolean matchTestClasspath(URL url, String lastFragment, boolean recursive) {
178127
String[] classpathFragments = getTestClasspath();
179128
if (null == classpathFragments) {
180129
return true;
181130
}
182131

183-
String fileName = "";
184-
fileName = URLDecoder.decode(url.getFile(), UTF_8);
132+
String fileName = URLDecoder.decode(url.getFile(), UTF_8);
185133

186134
for (String classpathFrag : classpathFragments) {
187135
String path = classpathFrag + lastFragment;
@@ -195,101 +143,6 @@ private static boolean matchTestClasspath(URL url, String lastFragment, boolean
195143
return true;
196144
}
197145
}
198-
199-
return false;
200-
}
201-
202-
private static void findClassesInDirPackage(
203-
String packageName,
204-
List<String> included,
205-
List<String> excluded,
206-
String packagePath,
207-
final boolean recursive,
208-
List<String> classes) {
209-
File dir = new File(packagePath);
210-
211-
if (!dir.exists() || !dir.isDirectory()) {
212-
return;
213-
}
214-
215-
File[] dirfiles =
216-
dir.listFiles(
217-
file ->
218-
(recursive && file.isDirectory())
219-
|| (file.getName().endsWith(".class"))
220-
|| (file.getName().endsWith(".groovy")));
221-
222-
Utils.log(PACKAGE_UTILS, 4, "Looking for test classes in the directory: " + dir);
223-
if (dirfiles == null) {
224-
return;
225-
}
226-
for (File file : dirfiles) {
227-
if (file.isDirectory()) {
228-
findClassesInDirPackage(
229-
makeFullClassName(packageName, file.getName()),
230-
included,
231-
excluded,
232-
file.getAbsolutePath(),
233-
recursive,
234-
classes);
235-
} else {
236-
String className = file.getName().substring(0, file.getName().lastIndexOf('.'));
237-
Utils.log(
238-
PACKAGE_UTILS,
239-
4,
240-
"Found class " + className + ", seeing it if it's included or excluded");
241-
includeOrExcludeClass(packageName, className, included, excluded, classes);
242-
}
243-
}
244-
}
245-
246-
private static String makeFullClassName(String pkg, String cls) {
247-
return pkg.length() > 0 ? pkg + "." + cls : cls;
248-
}
249-
250-
private static void includeOrExcludeClass(
251-
String packageName,
252-
String className,
253-
List<String> included,
254-
List<String> excluded,
255-
List<String> classes) {
256-
if (isIncluded(packageName, included, excluded)) {
257-
Utils.log(PACKAGE_UTILS, 4, "... Including class " + className);
258-
classes.add(makeFullClassName(packageName, className));
259-
} else {
260-
Utils.log(PACKAGE_UTILS, 4, "... Excluding class " + className);
261-
}
262-
}
263-
264-
/** @return true if name should be included. */
265-
private static boolean isIncluded(String name, List<String> included, List<String> excluded) {
266-
boolean result;
267-
268-
//
269-
// If no includes nor excludes were specified, return true.
270-
//
271-
if (included.isEmpty() && excluded.isEmpty()) {
272-
result = true;
273-
} else {
274-
boolean isIncluded = PackageUtils.find(name, included);
275-
boolean isExcluded = PackageUtils.find(name, excluded);
276-
if (isIncluded && !isExcluded) {
277-
result = true;
278-
} else if (isExcluded) {
279-
result = false;
280-
} else {
281-
result = included.isEmpty();
282-
}
283-
}
284-
return result;
285-
}
286-
287-
private static boolean find(String name, List<String> list) {
288-
for (String regexpStr : list) {
289-
if (Pattern.matches(regexpStr, name)) {
290-
return true;
291-
}
292-
}
293146
return false;
294147
}
295148
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.testng.internal.protocols;
2+
3+
import static java.nio.charset.StandardCharsets.UTF_8;
4+
5+
import java.lang.reflect.Method;
6+
import java.net.URL;
7+
import java.net.URLConnection;
8+
import java.net.URLDecoder;
9+
import java.util.List;
10+
import org.testng.collections.Lists;
11+
12+
class BundledResourceProcessor extends Processor {
13+
@Override
14+
public List<String> process(Input input, URL url) {
15+
return processBundledResources(
16+
url,
17+
input.getIncluded(),
18+
input.getExcluded(),
19+
input.getPackageWithoutWildCards(),
20+
input.isRecursive());
21+
}
22+
23+
private static List<String> processBundledResources(
24+
URL url,
25+
List<String> included,
26+
List<String> excluded,
27+
String packageOnly,
28+
boolean recursive) {
29+
try {
30+
Class<?>[] params = {};
31+
// BundleURLConnection
32+
URLConnection connection = url.openConnection();
33+
Method thisMethod = url.openConnection().getClass().getDeclaredMethod("getFileURL", params);
34+
Object[] paramsObj = {};
35+
URL fileUrl = (URL) thisMethod.invoke(connection, paramsObj);
36+
return findClassesInDirPackage(
37+
packageOnly, included, excluded, URLDecoder.decode(fileUrl.getFile(), UTF_8), recursive);
38+
} catch (Exception ex) {
39+
// ignore - probably not an Eclipse OSGi bundle
40+
}
41+
return Lists.newArrayList();
42+
}
43+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.testng.internal.protocols;
2+
3+
import static java.nio.charset.StandardCharsets.UTF_8;
4+
5+
import java.net.URL;
6+
import java.net.URLDecoder;
7+
import java.util.List;
8+
9+
class FileProcessor extends Processor {
10+
11+
@Override
12+
public List<String> process(Input input, URL url) {
13+
return findClassesInDirPackage(
14+
input.getPackageWithoutWildCards(),
15+
input.getIncluded(),
16+
input.getExcluded(),
17+
URLDecoder.decode(url.getFile(), UTF_8),
18+
input.isRecursive());
19+
}
20+
}

0 commit comments

Comments
 (0)