View Javadoc

1   package ch.elca.el4j.services.persistence.hibernate.entityfinder;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.net.JarURLConnection;
6   import java.net.URL;
7   import java.util.ArrayList;
8   import java.util.Enumeration;
9   import java.util.List;
10  import java.util.jar.JarEntry;
11  import java.util.jar.JarFile;
12  
13  import org.springframework.util.Assert;
14  
15  /**
16   * Utility to locate classes from the classpath.
17   *
18   * @svnLink $Revision: 3873 $;$Date: 2009-08-04 13:59:45 +0200 (Di, 04. Aug 2009) $;$Author: swismer $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/hibernate/src/main/java/ch/elca/el4j/services/persistence/hibernate/entityfinder/ClassLocator.java $
19   */
20  public class ClassLocator {
21  	/**
22  	 * Are the package names the found classes' package must start with.
23  	 */
24  	protected final String[] packageNames;
25  
26  	/**
27  	 * Is the used classloader to lookup classes.
28  	 */
29  	protected final ClassLoader classLoader;
30  
31  	/**
32  	 * Locates all classes that are in one of the given packages (inclusive sub-packages).
33  	 *
34  	 * @param packageNames
35  	 *            Are the package names the found classes' package must start with.
36  	 * @throws ClassNotFoundException
37  	 *             If the context classloader of the current thread could not be fetched.
38  	 */
39  	public ClassLocator(String... packageNames) throws ClassNotFoundException {
40  		this(Thread.currentThread().getContextClassLoader(), packageNames);
41  	}
42  
43  	/**
44  	 * Locates all classes that are in one of the given packages (inclusive sub-packages).
45  	 *
46  	 * @param classLoader
47  	 *            Is the used classloader to lookup classes.
48  	 * @param packageNames
49  	 *            Are the package names the found classes' package must start with.
50  	 */
51  	public ClassLocator(ClassLoader classLoader, String... packageNames) {
52  		Assert.notNull(classLoader);
53  		Assert.notNull(packageNames);
54  		this.classLoader = classLoader;
55  		this.packageNames = packageNames;
56  	}
57  
58  	/**
59  	 * Returns all located classes where the found classes' package starts with one of the given
60  	 * package names.
61  	 *
62  	 * @return Returns the found class locations.
63  	 * @throws ClassNotFoundException If no class could be found for one of the given packages.
64  	 * @throws IOException If there was a general IO problem.
65  	 */
66  	public List<ClassLocation> getAllClassLocations() throws ClassNotFoundException, IOException {
67  		List<ClassLocation> classLocations = new ArrayList<ClassLocation>();
68  
69  		for (String packageName : packageNames) {
70  			String path = packageName.replace('.', '/');
71  			Enumeration<URL> resources = classLoader.getResources(path);
72  			if (null == resources || !resources.hasMoreElements()) {
73  				throw new ClassNotFoundException("No resource for " + path);
74  			}
75  
76  			while (resources.hasMoreElements()) {
77  				URL resource = resources.nextElement();
78  				if (resource.getProtocol().equalsIgnoreCase("FILE")) {
79  					loadDirectory(packageName, resource, classLocations);
80  				} else if (resource.getProtocol().equalsIgnoreCase("JAR")) {
81  					loadJar(packageName, resource, classLocations);
82  				} else if (resource.getProtocol().equalsIgnoreCase("ZIP")) {
83  					// Weblogic 10.3x uses zip: instead of jar:
84  					resource = new URL("jar:file://" + resource.getFile().replaceAll("\\\\", "/"));
85  					loadJar(packageName, resource, classLocations);
86  				} else {
87  					throw new ClassNotFoundException("Unknown protocol on class resource: "
88  							+ resource.toExternalForm());
89  				}
90  			}
91  		}
92  		return classLocations;
93  	}
94  
95  	/**
96  	 * Tries to fill the given class location list with classes from the given package that are
97  	 * saved in a jar file.
98  	 *
99  	 * @param packageName Is the name of the package the class's package has to start with.
100 	 * @param resource Is the real location of the given package name.
101 	 * @param classLocations Are the already found class locations.
102 	 * @throws IOException If there was a general IO problem.
103 	 */
104 	private void loadJar(String packageName, URL resource, List<ClassLocation> classLocations)
105 		throws IOException {
106 		JarURLConnection conn = (JarURLConnection) resource.openConnection();
107 		JarFile jarFile = conn.getJarFile();
108 		Enumeration<JarEntry> entries = jarFile.entries();
109 		String packagePath = packageName.replace('.', '/');
110 
111 		while (entries.hasMoreElements()) {
112 			JarEntry entry = entries.nextElement();
113 			if ((entry.getName().startsWith(packagePath) || entry.getName().startsWith(
114 					"WEB-INF/classes/" + packagePath))
115 					&& entry.getName().endsWith(".class")) {
116 				URL url = new URL("jar:"
117 						+ new URL("file", null, jarFile.getName())
118 								.toExternalForm() + "!/" + entry.getName());
119 
120 				String className = entry.getName();
121 				if (className.startsWith("/")) {
122 					className = className.substring(1);
123 				}
124 				className = className.replace('/', '.');
125 
126 				className = className.substring(0, className.length() - ".class".length());
127 
128 				ClassLocation classLocation = new ClassLocation(classLoader, className, url);
129 				addClassLocation(classLocation, classLocations);
130 			}
131 		}
132 
133 	}
134 
135 	/**
136 	 * Does the same as {@link #loadJar(String, URL, List)} but the class is directly saved on
137 	 * the file system.
138 	 *
139 	 * @see #loadJar(String, URL, List)
140 	 */
141 	private void loadDirectory(String packageName, URL resource,
142 		List<ClassLocation> classLocations) throws IOException {
143 		loadDirectory(packageName, resource.getFile(), classLocations);
144 
145 	}
146 
147 	/**
148 	 * The same as {@link #loadDirectory(String, URL, List)} but with the full "class" path instead
149 	 * of the url.
150 	 *
151 	 * @see #loadDirectory(String, URL, List)
152 	 */
153 	private void loadDirectory(String packageName, String fullPath,
154 		List<ClassLocation> classLocations) throws IOException {
155 		File directory = new File(fullPath);
156 		if (!directory.isDirectory()) {
157 			throw new IOException("Invalid directory " + directory.getAbsolutePath());
158 		}
159 
160 		File[] files = directory.listFiles();
161 		for (File file : files) {
162 			if (file.isDirectory()) {
163 				loadDirectory(packageName + '.' + file.getName(), file.getAbsolutePath(),
164 					classLocations);
165 			} else if (file.getName().endsWith(".class")) {
166 				String simpleName = file.getName();
167 				simpleName = simpleName.substring(0, simpleName.length() - ".class".length());
168 				String className = String.format("%s.%s", packageName, simpleName);
169 				ClassLocation location = new ClassLocation(classLoader, className, new URL("file",
170 						null, file.getAbsolutePath()));
171 				addClassLocation(location, classLocations);
172 			}
173 		}
174 	}
175 
176 	/**
177 	 * Adds the given class location to the given class location list if this list does not
178 	 * already contains it.
179 	 *
180 	 * @param classLocation Is the class location to add.
181 	 * @param classLocations Are the already found class locations.
182 	 * @throws IOException If the given class location is already in the given list.
183 	 */
184 	private void addClassLocation(ClassLocation classLocation, List<ClassLocation> classLocations)
185 		throws IOException {
186 		if (classLocations.contains(classLocation)) {
187 			throw new IOException("Duplicate location found for: " + classLocation.getClassName());
188 		}
189 		classLocations.add(classLocation);
190 	}
191 }