• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.testng.internal;
2 
3 import java.io.File;
4 import java.io.FileFilter;
5 import java.io.IOException;
6 import java.io.UnsupportedEncodingException;
7 import java.lang.reflect.Method;
8 import java.net.JarURLConnection;
9 import java.net.URL;
10 import java.net.URLConnection;
11 import java.net.URLDecoder;
12 import java.util.Enumeration;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.Vector;
16 import java.util.jar.JarEntry;
17 import java.util.jar.JarFile;
18 import java.util.regex.Pattern;
19 
20 import org.testng.TestNG;
21 import org.testng.collections.Lists;
22 
23 /**
24  * Utility class that finds all the classes in a given package.
25  *
26  * Created on Feb 24, 2006
27  * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
28  */
29 public class PackageUtils {
30   private static String[] s_testClassPaths;
31 
32   /** The additional class loaders to find classes in. */
33   private static final List<ClassLoader> m_classLoaders = new Vector<>();
34 
35   /** Add a class loader to the searchable loaders. */
addClassLoader(final ClassLoader loader)36   public static void addClassLoader(final ClassLoader loader) {
37     m_classLoaders.add(loader);
38   }
39 
40   /**
41    *
42    * @param packageName
43    * @return The list of all the classes inside this package
44    * @throws IOException
45    */
findClassesInPackage(String packageName, List<String> included, List<String> excluded)46   public static String[] findClassesInPackage(String packageName,
47       List<String> included, List<String> excluded)
48     throws IOException
49   {
50     String packageOnly = packageName;
51     boolean recursive = false;
52     if (packageName.endsWith(".*")) {
53       packageOnly = packageName.substring(0, packageName.lastIndexOf(".*"));
54       recursive = true;
55     }
56 
57     List<String> vResult = Lists.newArrayList();
58     String packageDirName = packageOnly.replace('.', '/') + (packageOnly.length() > 0 ? "/" : "");
59 
60 
61     Vector<URL> dirs = new Vector<>();
62     // go through additional class loaders
63     Vector<ClassLoader> allClassLoaders = new Vector<>();
64     ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
65     if (contextClassLoader != null) {
66       allClassLoaders.add(contextClassLoader);
67     }
68     if (m_classLoaders != null) {
69       allClassLoaders.addAll(m_classLoaders);
70     }
71 
72     int count = 0;
73     for (ClassLoader classLoader : allClassLoaders) {
74       ++count;
75       if (null == classLoader) {
76         continue;
77       }
78       Enumeration<URL> dirEnumeration = classLoader.getResources(packageDirName);
79       while(dirEnumeration.hasMoreElements()){
80         URL dir = dirEnumeration.nextElement();
81         dirs.add(dir);
82       }
83     }
84 
85     Iterator<URL> dirIterator = dirs.iterator();
86     while (dirIterator.hasNext()) {
87       URL url = dirIterator.next();
88       String protocol = url.getProtocol();
89       if(!matchTestClasspath(url, packageDirName, recursive)) {
90         continue;
91       }
92 
93       if ("file".equals(protocol)) {
94         findClassesInDirPackage(packageOnly, included, excluded,
95                                 URLDecoder.decode(url.getFile(), "UTF-8"),
96                                 recursive,
97                                 vResult);
98       }
99       else if ("jar".equals(protocol)) {
100         JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
101         Enumeration<JarEntry> entries = jar.entries();
102         while (entries.hasMoreElements()) {
103           JarEntry entry = entries.nextElement();
104           String name = entry.getName();
105           if (name.charAt(0) == '/') {
106             name = name.substring(1);
107           }
108           if (name.startsWith(packageDirName)) {
109             int idx = name.lastIndexOf('/');
110             if (idx != -1) {
111               packageName = name.substring(0, idx).replace('/', '.');
112             }
113 
114             if (recursive || packageName.equals(packageOnly)) {
115               //it's not inside a deeper dir
116               Utils.log("PackageUtils", 4, "Package name is " + packageName);
117               if (name.endsWith(".class") && !entry.isDirectory()) {
118                 String className = name.substring(packageName.length() + 1, name.length() - 6);
119                 Utils.log("PackageUtils", 4, "Found class " + className + ", seeing it if it's included or excluded");
120                 includeOrExcludeClass(packageName, className, included, excluded, vResult);
121               }
122             }
123           }
124         }
125       }
126       else if ("bundleresource".equals(protocol)) {
127         try {
128           Class params[] = {};
129           // BundleURLConnection
130           URLConnection connection = url.openConnection();
131           Method thisMethod = url.openConnection().getClass()
132               .getDeclaredMethod("getFileURL", params);
133           Object paramsObj[] = {};
134           URL fileUrl = (URL) thisMethod.invoke(connection, paramsObj);
135           findClassesInDirPackage(packageOnly, included, excluded,
136               URLDecoder.decode(fileUrl.getFile(), "UTF-8"), recursive, vResult);
137         } catch (Exception ex) {
138           // ignore - probably not an Eclipse OSGi bundle
139         }
140       }
141     }
142 
143     String[] result = vResult.toArray(new String[vResult.size()]);
144     return result;
145   }
146 
getTestClasspath()147   private static String[] getTestClasspath() {
148     if (null != s_testClassPaths) {
149       return s_testClassPaths;
150     }
151 
152     String testClasspath = System.getProperty(TestNG.TEST_CLASSPATH);
153     if (null == testClasspath) {
154       return null;
155     }
156 
157     String[] classpathFragments= Utils.split(testClasspath, File.pathSeparator);
158     s_testClassPaths= new String[classpathFragments.length];
159 
160     for(int i= 0; i < classpathFragments.length; i++)  {
161       String path= null;
162       if(classpathFragments[i].toLowerCase().endsWith(".jar") || classpathFragments[i].toLowerCase().endsWith(".zip")) {
163         path= classpathFragments[i] + "!/";
164       }
165       else {
166         if(classpathFragments[i].endsWith(File.separator)) {
167           path= classpathFragments[i];
168         }
169         else {
170           path= classpathFragments[i] + "/";
171         }
172       }
173 
174       s_testClassPaths[i]= path.replace('\\', '/');
175     }
176 
177     return s_testClassPaths;
178   }
179 
matchTestClasspath(URL url, String lastFragment, boolean recursive)180   private static boolean matchTestClasspath(URL url, String lastFragment, boolean recursive) {
181     String[] classpathFragments= getTestClasspath();
182     if(null == classpathFragments) {
183       return true;
184     }
185 
186     String fileName= null;
187     try {
188       fileName= URLDecoder.decode(url.getFile(), "UTF-8");
189     }
190     catch(UnsupportedEncodingException ueex) {
191       ; // ignore. should never happen
192     }
193 
194     for(String classpathFrag: classpathFragments) {
195       String path=  classpathFrag + lastFragment;
196       int idx= fileName.indexOf(path);
197       if((idx == -1) || (idx > 0 && fileName.charAt(idx-1) != '/')) {
198         continue;
199       }
200 
201       if(fileName.endsWith(classpathFrag + lastFragment)
202           || (recursive && fileName.charAt(idx + path.length()) == '/')) {
203         return true;
204       }
205     }
206 
207     return false;
208   }
209 
findClassesInDirPackage(String packageName, List<String> included, List<String> excluded, String packagePath, final boolean recursive, List<String> classes)210   private static void findClassesInDirPackage(String packageName,
211                                               List<String> included,
212                                               List<String> excluded,
213                                               String packagePath,
214                                               final boolean recursive,
215                                               List<String> classes) {
216     File dir = new File(packagePath);
217 
218     if (!dir.exists() || !dir.isDirectory()) {
219       return;
220     }
221 
222     File[] dirfiles = dir.listFiles(new FileFilter() {
223           @Override
224           public boolean accept(File file) {
225             return (recursive && file.isDirectory())
226               || (file.getName().endsWith(".class"))
227               || (file.getName().endsWith(".groovy"));
228           }
229         });
230 
231     Utils.log("PackageUtils", 4, "Looking for test classes in the directory: " + dir);
232     for (File file : dirfiles) {
233       if (file.isDirectory()) {
234         findClassesInDirPackage(makeFullClassName(packageName, file.getName()),
235                                 included,
236                                 excluded,
237                                 file.getAbsolutePath(),
238                                 recursive,
239                                 classes);
240       }
241       else {
242         String className = file.getName().substring(0, file.getName().lastIndexOf("."));
243         Utils.log("PackageUtils", 4, "Found class " + className
244             + ", seeing it if it's included or excluded");
245         includeOrExcludeClass(packageName, className, included, excluded, classes);
246       }
247     }
248   }
249 
makeFullClassName(String pkg, String cls)250   private static String makeFullClassName(String pkg, String cls) {
251     return pkg.length() > 0 ? pkg + "." + cls : cls;
252   }
253 
includeOrExcludeClass(String packageName, String className, List<String> included, List<String> excluded, List<String> classes)254   private static void includeOrExcludeClass(String packageName, String className,
255       List<String> included, List<String> excluded, List<String> classes)
256   {
257     if (isIncluded(packageName, included, excluded)) {
258       Utils.log("PackageUtils", 4, "... Including class " + className);
259       classes.add(makeFullClassName(packageName, className));
260     }
261     else {
262       Utils.log("PackageUtils", 4, "... Excluding class " + className);
263     }
264   }
265 
266   /**
267    * @return true if name should be included.
268    */
isIncluded(String name, List<String> included, List<String> excluded)269   private static boolean isIncluded(String name,
270       List<String> included, List<String> excluded)
271   {
272     boolean result = false;
273 
274     //
275     // If no includes nor excludes were specified, return true.
276     //
277     if (included.size() == 0 && excluded.size() == 0) {
278       result = true;
279     }
280     else {
281       boolean isIncluded = PackageUtils.find(name, included);
282       boolean isExcluded = PackageUtils.find(name, excluded);
283       if (isIncluded && !isExcluded) {
284         result = true;
285       }
286       else if (isExcluded) {
287         result = false;
288       }
289       else {
290         result = included.size() == 0;
291       }
292     }
293     return result;
294   }
295 
find(String name, List<String> list)296   private static boolean find(String name, List<String> list) {
297     for (String regexpStr : list) {
298       if (Pattern.matches(regexpStr, name)) {
299         return true;
300       }
301     }
302     return false;
303   }
304 }
305