View Javadoc

1   /*
2    Spring-Annotation, an easy way to configre Spring-Framework without all that XML.
3    Copyright (C) 2007 Rodrigo Urubatan Ferreira Jardim (http://www.urubatan.com.br)
4   
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9   
10   This library is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   Lesser General Public License for more details.
14  
15   You should have received a copy of the GNU Lesser General Public
16   License along with this library; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  
19   <a href="http://www.gnu.org/licenses/lgpl.html">http://www.gnu.org/licenses/lgpl.html</a>
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  	//TODO document
58  	public Scanner(final String resourceName, final ClassLoader classLoader) {
59  		this.resourceName = resourceName;
60  		this.classLoader = classLoader;
61  	}
62  	//TODO document
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  	//TODO document
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  				// String cp = System.getProperty("java.class.path");
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  							// On windows urlpath looks like file:/C: on Linux
99  							// file:/home
100 							// substring(5) works for both
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 				// if (cp != null)
116 				// for (String d : cp.split(File.pathSeparator)) {
117 				// File f = new File(d);
118 				// if (f.exists() && f.isDirectory())
119 				// dirs.add(d);
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 					// On windows urlpath looks like file:/C: on Linux
130 					// file:/home
131 					// substring(5) works for both
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 	//TODO document
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 	//TODO document
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 	//TODO document
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 }