| // Copyright 2007, 2008 The Apache Software Foundation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package org.apache.tapestry5.ioc.internal.services; |
| |
| import org.apache.tapestry5.ioc.internal.util.CollectionFactory; |
| import org.apache.tapestry5.ioc.internal.util.InternalUtils; |
| import org.apache.tapestry5.ioc.services.ClassNameLocator; |
| import org.apache.tapestry5.ioc.util.Stack; |
| |
| import java.io.*; |
| import java.net.JarURLConnection; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.util.Collection; |
| import java.util.Enumeration; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| |
| public class ClassNameLocatorImpl implements ClassNameLocator |
| { |
| private static final String CLASS_SUFFIX = ".class"; |
| |
| private final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); |
| |
| static class Queued |
| { |
| final URL packageURL; |
| |
| final String packagePath; |
| |
| public Queued(final URL packageURL, final String packagePath) |
| { |
| this.packageURL = packageURL; |
| this.packagePath = packagePath; |
| } |
| } |
| |
| /** |
| * Synchronization should not be necessary, but perhaps the underlying ClassLoader's are sensitive to threading. |
| */ |
| public synchronized Collection<String> locateClassNames(String packageName) |
| { |
| String packagePath = packageName.replace('.', '/') + "/"; |
| |
| try |
| { |
| |
| return findClassesWithinPath(packagePath); |
| |
| } |
| catch (IOException ex) |
| { |
| throw new RuntimeException(ex); |
| } |
| } |
| |
| private Collection<String> findClassesWithinPath(String packagePath) throws IOException |
| { |
| Collection<String> result = CollectionFactory.newList(); |
| |
| Enumeration<URL> urls = contextClassLoader.getResources(packagePath); |
| |
| while (urls.hasMoreElements()) |
| { |
| URL url = urls.nextElement(); |
| |
| scanURL(packagePath, result, url); |
| } |
| |
| return result; |
| } |
| |
| private void scanURL(String packagePath, Collection<String> componentClassNames, URL url) throws IOException |
| { |
| URLConnection connection = url.openConnection(); |
| |
| JarFile jarFile; |
| |
| if (connection instanceof JarURLConnection) |
| { |
| jarFile = ((JarURLConnection) connection).getJarFile(); |
| } |
| else |
| { |
| jarFile = getAlternativeJarFile(url); |
| } |
| |
| if (jarFile != null) |
| { |
| scanJarFile(packagePath, componentClassNames, jarFile); |
| } |
| else if (supportsDirStream(url)) |
| { |
| Stack<Queued> queue = CollectionFactory.newStack(); |
| |
| queue.push(new Queued(url, packagePath)); |
| |
| while (!queue.isEmpty()) |
| { |
| Queued queued = queue.pop(); |
| |
| scanDirStream(queued.packagePath, queued.packageURL, componentClassNames, queue); |
| } |
| } |
| else |
| { |
| // Try scanning file system. |
| String packageName = packagePath.replace("/", "."); |
| if (packageName.endsWith(".")) |
| { |
| packageName = packageName.substring(0, packageName.length() - 1); |
| } |
| scanDir(packageName, new File(url.getFile()), componentClassNames); |
| } |
| |
| } |
| |
| /** |
| * Check whether container supports opening a stream on a dir/package to get a list of its contents. |
| * |
| * @param packageURL |
| * @return |
| */ |
| private boolean supportsDirStream(URL packageURL) |
| { |
| InputStream is = null; |
| try |
| { |
| is = packageURL.openStream(); |
| return true; |
| } |
| catch (FileNotFoundException ex) |
| { |
| return false; |
| } |
| catch (IOException e) |
| { |
| return false; |
| } |
| finally |
| { |
| InternalUtils.close(is); |
| } |
| } |
| |
| private void scanDirStream(String packagePath, URL packageURL, Collection<String> componentClassNames, |
| Stack<Queued> queue) throws IOException |
| { |
| InputStream is; |
| |
| try |
| { |
| is = new BufferedInputStream(packageURL.openStream()); |
| } |
| catch (FileNotFoundException ex) |
| { |
| // This can happen for certain application servers (JBoss 4.0.5 for example), that |
| // export part of the exploded WAR for deployment, but leave part (WEB-INF/classes) |
| // unexploded. |
| |
| return; |
| } |
| |
| Reader reader = new InputStreamReader(is); |
| LineNumberReader lineReader = new LineNumberReader(reader); |
| |
| String packageName = null; |
| |
| try |
| { |
| while (true) |
| { |
| String line = lineReader.readLine(); |
| |
| if (line == null) break; |
| |
| if (line.contains("$")) continue; |
| |
| if (line.endsWith(CLASS_SUFFIX)) |
| { |
| if (packageName == null) packageName = packagePath.replace('/', '.'); |
| |
| // packagePath ends with '/', packageName ends with '.' |
| |
| String fullClassName = packageName + line.substring(0, line.length() - CLASS_SUFFIX.length()); |
| |
| componentClassNames.add(fullClassName); |
| |
| continue; |
| } |
| |
| // Either a file or a hidden directory (such as .svn) |
| |
| if (line.contains(".")) continue; |
| |
| // The name of a subdirectory. |
| |
| URL newURL = new URL(packageURL.toExternalForm() + line + "/"); |
| String newPackagePath = packagePath + line + "/"; |
| |
| queue.push(new Queued(newURL, newPackagePath)); |
| } |
| |
| lineReader.close(); |
| lineReader = null; |
| } |
| finally |
| { |
| InternalUtils.close(lineReader); |
| } |
| |
| } |
| |
| private void scanJarFile(String packagePath, Collection<String> componentClassNames, JarFile jarFile) |
| { |
| Enumeration<JarEntry> e = jarFile.entries(); |
| |
| while (e.hasMoreElements()) |
| { |
| String name = e.nextElement().getName(); |
| |
| if (!name.startsWith(packagePath)) continue; |
| |
| if (!name.endsWith(CLASS_SUFFIX)) continue; |
| |
| if (name.contains("$")) continue; |
| |
| // Strip off .class and convert the slashes back to periods. |
| |
| String className = name.substring(0, name.length() - CLASS_SUFFIX.length()).replace("/", "."); |
| |
| componentClassNames.add(className); |
| } |
| } |
| |
| /** |
| * Scan a dir for classes. Will recursively look in the supplied directory and all sub directories. |
| * |
| * @param packageName Name of package that this directory corresponds to. |
| * @param dir Dir to scan for clases. |
| * @param componentClassNames List of class names that have been found. |
| */ |
| private void scanDir(String packageName, File dir, Collection<String> componentClassNames) |
| { |
| if (dir.exists() && dir.isDirectory()) |
| { |
| for (File file : dir.listFiles()) |
| { |
| String fileName = file.getName(); |
| if (file.isDirectory()) |
| { |
| scanDir(packageName + "." + fileName, file, componentClassNames); |
| } |
| else if (fileName.endsWith(CLASS_SUFFIX)) |
| { |
| String className = packageName + "." + fileName.substring(0, |
| fileName.length() - CLASS_SUFFIX.length()); |
| componentClassNames.add(className); |
| } |
| } |
| } |
| } |
| |
| /** |
| * For URLs to JARs that do not use JarURLConnection - allowed by the servlet spec - attempt to produce a JarFile |
| * object all the same. Known servlet engines that function like this include Weblogic and OC4J. This is not a full |
| * solution, since an unpacked WAR or EAR will not have JAR "files" as such. |
| * |
| * @param url URL of jar |
| * @return JarFile or null |
| * @throws java.io.IOException If error occurs creating jar file |
| */ |
| private JarFile getAlternativeJarFile(URL url) throws IOException |
| { |
| String urlFile = url.getFile(); |
| // Trim off any suffix - which is prefixed by "!/" on Weblogic |
| int separatorIndex = urlFile.indexOf("!/"); |
| |
| // OK, didn't find that. Try the less safe "!", used on OC4J |
| if (separatorIndex == -1) |
| { |
| separatorIndex = urlFile.indexOf('!'); |
| } |
| if (separatorIndex != -1) |
| { |
| String jarFileUrl = urlFile.substring(0, separatorIndex); |
| // And trim off any "file:" prefix. |
| if (jarFileUrl.startsWith("file:")) |
| { |
| jarFileUrl = jarFileUrl.substring("file:".length()); |
| } |
| return new JarFile(jarFileUrl); |
| } |
| return null; |
| } |
| |
| } |