1/**
2 * Copyright 2006-2017 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.objenesis.tck.search;
17
18import java.io.File;
19import java.io.IOException;
20import java.net.URL;
21import java.util.*;
22import java.util.jar.JarEntry;
23import java.util.jar.JarFile;
24
25/**
26 * Taken and adapted from <a href="https://raw.githubusercontent.com/ddopson/java-class-enumerator/master/src/pro/ddopson/ClassEnumerator.java">here</a>
27 *
28 * @author Henri Tremblay
29 */
30public class ClassEnumerator {
31
32    private static void processDirectory(File directory, String pkgname, SortedSet<String> classes) {
33
34        // Get the list of the files contained in the package
35        String[] files = directory.list();
36
37        for (int i = 0; i < files.length; i++) {
38            String fileName = files[i];
39            // we are only interested in .class files
40            if (fileName.endsWith(".class")) {
41                // removes the .class extension
42                String className = pkgname + '.' + fileName.substring(0, fileName.length() - 6);
43                classes.add(className);
44                continue;
45            }
46
47            File subdir = new File(directory, fileName);
48            if (subdir.isDirectory()) {
49                processDirectory(subdir, pkgname + '.' + fileName, classes);
50            }
51        }
52    }
53
54    private static void processJarfile(URL resource, String pkgname, SortedSet<String> classes) {
55        String relPath = pkgname.replace('.', '/');
56        String resPath = resource.getPath();
57        String jarPath = resPath.replaceFirst("[.]jar[!].*", ".jar").replaceFirst("file:", "");
58
59        JarFile jarFile;
60        try {
61            jarFile = new JarFile(jarPath);
62        } catch (IOException e) {
63            throw new RuntimeException("Unexpected IOException reading JAR File '" + jarPath + "'", e);
64        }
65        Enumeration<JarEntry> entries = jarFile.entries();
66        while(entries.hasMoreElements()) {
67            JarEntry entry = entries.nextElement();
68            String entryName = entry.getName();
69            String className = null;
70            if(entryName.endsWith(".class") && entryName.startsWith(relPath) && entryName.length() > (relPath.length() + "/".length())) {
71                className = entryName.replace('/', '.').replace('\\', '.').replace(".class", "");
72            }
73
74            if (className != null) {
75                classes.add(className);
76            }
77        }
78    }
79
80   /**
81    * Return all the classes in this package recursively. The class loader of the {@code ClassEnumerator} class
82    * is used
83    *
84    * @param pkg the searched package
85    * @return list of full class names
86    */
87    public static SortedSet<String> getClassesForPackage(Package pkg) {
88        return getClassesForPackage(pkg, ClassEnumerator.class.getClassLoader());
89    }
90
91   /**
92    * Return all the classes in this package recursively.
93    *
94    * @param pkg the searched package
95    * @return list of full class names
96    */
97    public static SortedSet<String> getClassesForPackage(Package pkg, ClassLoader classLoader) {
98        SortedSet<String> classes = new TreeSet<String>(new Comparator<String>() {
99           public int compare(String o1, String o2) {
100              String simpleName1 = getSimpleName(o1);
101              String simpleName2 = getSimpleName(o2);
102              return simpleName1.compareTo(simpleName2);
103           }
104
105           private String getSimpleName(String className) {
106              return className.substring(className.lastIndexOf('.'));
107           }
108        });
109
110        String pkgname = pkg.getName();
111        String relPath = pkgname.replace('.', '/');
112
113        // Get a File object for the package
114        Enumeration<URL> resources;
115        try {
116            resources = classLoader.getResources(relPath);
117        } catch (IOException e) {
118            throw new RuntimeException(e);
119        }
120
121        while(resources.hasMoreElements()) {
122            URL resource = resources.nextElement();
123            if (resource.toString().startsWith("jar:")) {
124                processJarfile(resource, pkgname, classes);
125            } else {
126                processDirectory(new File(resource.getPath()), pkgname, classes);
127            }
128        }
129
130        return classes;
131    }
132
133}
134