1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package net.sourceforge.sannotations.utils;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.net.URL;
26 import java.net.URLDecoder;
27 import java.security.AccessControlException;
28 import java.util.ArrayList;
29 import java.util.Enumeration;
30 import java.util.HashSet;
31 import java.util.Set;
32 import java.util.zip.ZipEntry;
33 import java.util.zip.ZipException;
34 import java.util.zip.ZipFile;
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 /***
38 * Utility class that is used to scan the classpath for classes within the configured exclude and include patterns.<br/>
39 * The scanner will look for all jars with the specified resource name in the root, and for all open directories in the classpath.
40 * @author Urubatan
41 */
42 public class Scanner {
43 private static final Log log = LogFactory.getLog(Scanner.class);
44 private final String resourceName;
45 private final ClassLoader classLoader;
46 private ArrayList<String> excludePackages = new ArrayList<String>();
47 private ArrayList<String> includePackages = new ArrayList<String>();
48 private boolean scanDirs = true;
49
50 /***
51 * Default constructor, the resourceName will be "to.properties" and the class loader will be Thread.currentThread().getContextClassLoader()
52 */
53 public Scanner() {
54 this("to.properties", Thread.currentThread().getContextClassLoader());
55 }
56
57
58 public Scanner(final String resourceName, final ClassLoader classLoader) {
59 this.resourceName = resourceName;
60 this.classLoader = classLoader;
61 }
62
63 public Scanner(boolean scanDirs, ArrayList<String> includePackages, ArrayList<String> excludePackages) {
64 this("to.properties", Thread.currentThread().getContextClassLoader());
65 this.scanDirs = scanDirs;
66 this.includePackages = includePackages;
67 this.excludePackages = excludePackages;
68 }
69 /***
70 * method used to convert a file name to a class name, it replaces all '/' or '\' with a '.' and removes the '.class' ending
71 * @param filename file name to convert
72 * @return converted class name
73 */
74 public static String filenameToClassname(final String filename) {
75 return filename.substring(0, filename.lastIndexOf(".class")).replace('/', '.').replace('//', '.');
76 }
77
78
79 public Set<Class<?>> getClasses() {
80 final Set<Class<?>> result = new HashSet<Class<?>>();
81 Enumeration<URL> urls;
82 final ArrayList<String> dirs = new ArrayList<String>();
83 try {
84 urls = this.classLoader.getResources(this.resourceName);
85 } catch (final IOException ioe) {
86 Scanner.log.warn("could not read: " + this.resourceName, ioe);
87 return result;
88 }
89 try {
90 if (this.scanDirs) {
91
92 try {
93 final Enumeration<URL> urls2 = this.classLoader.getResources(".");
94 while (urls2.hasMoreElements()) {
95 String urlPath = urls2.nextElement().getFile();
96 urlPath = URLDecoder.decode(urlPath, "UTF-8");
97 if (urlPath.startsWith("file:")) {
98
99
100
101 urlPath = urlPath.substring(5);
102 }
103 if (urlPath.indexOf('!') > 0) {
104 urlPath = urlPath.substring(0, urlPath.indexOf('!'));
105 }
106 Scanner.log.debug("scanning: " + urlPath);
107 final File file = new File(urlPath);
108 if (file.isDirectory()) {
109 dirs.add(file.toString());
110 }
111 }
112 } catch (final IOException ioe) {
113 Scanner.log.warn("could not read entries", ioe);
114 }
115
116
117
118
119
120
121 }
122 } catch (final AccessControlException e) {
123 }
124 while (urls.hasMoreElements()) {
125 try {
126 String urlPath = urls.nextElement().getFile();
127 urlPath = URLDecoder.decode(urlPath, "UTF-8");
128 if (urlPath.startsWith("file:")) {
129
130
131
132 urlPath = urlPath.substring(5);
133 }
134 if (urlPath.indexOf('!') > 0) {
135 urlPath = urlPath.substring(0, urlPath.indexOf('!'));
136 } else {
137 urlPath = new File(urlPath).getParent();
138 }
139 Scanner.log.debug("scanning: " + urlPath);
140 final File file = new File(urlPath);
141 if (file.isDirectory()) {
142 if (dirs.contains(urlPath)) {
143 dirs.remove(urlPath);
144 }
145 this.handleDirectory(result, file, null);
146 } else {
147 this.handleArchive(result, file);
148 }
149 } catch (final IOException ioe) {
150 Scanner.log.warn("could not read entries", ioe);
151 }
152 }
153 for (final String urlPath : dirs) {
154 final File file = new File(urlPath + "/");
155 this.handleDirectory(result, file, null);
156 }
157 return result;
158 }
159
160 private void handleArchive(final Set<Class<?>> result, final File file) throws ZipException, IOException {
161 Scanner.log.debug("archive: " + file);
162 final ZipFile zip = new ZipFile(file);
163 final Enumeration<? extends ZipEntry> entries = zip.entries();
164 while (entries.hasMoreElements()) {
165 final ZipEntry entry = entries.nextElement();
166 final String name = entry.getName();
167 Scanner.log.debug("found: " + name);
168 this.handleItem(result, name);
169 }
170 }
171
172 private void handleDirectory(final Set<Class<?>> result, final File file, final String path) {
173 Scanner.log.debug("directory: " + file);
174 for (final File child : file.listFiles()) {
175 final String newPath = path == null ? child.getName() : path + '/' + child.getName();
176 if (child.isDirectory()) {
177 this.handleDirectory(result, child, newPath);
178 } else {
179 this.handleItem(result, newPath);
180 }
181 }
182 }
183
184 private void handleItem(final Set<Class<?>> result, final String name) {
185 if (name.endsWith(".class")) {
186 if (!this.includePackages.isEmpty()) {
187 boolean proceed = false;
188 for (final String regexp : this.includePackages) {
189 if (name.matches(regexp)) {
190 proceed = true;
191 continue;
192 }
193 }
194 if (!proceed) {
195 return;
196 }
197 }
198 if (!this.excludePackages.isEmpty()) {
199 boolean proceed = true;
200 for (final String regexp : this.excludePackages) {
201 if (name.matches(regexp)) {
202 proceed = false;
203 continue;
204 }
205 }
206 if (!proceed) {
207 return;
208 }
209 }
210 final String classname = Scanner.filenameToClassname(name);
211 try {
212 result.add(this.classLoader.loadClass(classname));
213 } catch (final ClassNotFoundException cnfe) {
214 Scanner.log.debug("could not load class: " + classname, cnfe);
215 } catch (final NoClassDefFoundError cnfe) {
216 Scanner.log.debug("could not load class: " + classname, cnfe);
217 }
218 }
219 }
220 }