Return-Path: X-Original-To: apmail-tapestry-dev-archive@www.apache.org Delivered-To: apmail-tapestry-dev-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id E5B5FDC5C for ; Fri, 5 Oct 2012 21:42:31 +0000 (UTC) Received: (qmail 9567 invoked by uid 500); 5 Oct 2012 21:42:31 -0000 Delivered-To: apmail-tapestry-dev-archive@tapestry.apache.org Received: (qmail 9409 invoked by uid 500); 5 Oct 2012 21:42:31 -0000 Mailing-List: contact commits-help@tapestry.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@tapestry.apache.org Delivered-To: mailing list commits@tapestry.apache.org Received: (qmail 9400 invoked by uid 99); 5 Oct 2012 21:42:31 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 05 Oct 2012 21:42:31 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 681F93A9F0; Fri, 5 Oct 2012 21:42:31 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: hlship@apache.org To: commits@tapestry.apache.org X-Mailer: ASF-Git Admin Mailer Subject: git commit: Split the guts of ClassNameLocator into a new ClasspathScanner service Message-Id: <20121005214231.681F93A9F0@tyr.zones.apache.org> Date: Fri, 5 Oct 2012 21:42:31 +0000 (UTC) Updated Branches: refs/heads/5.4-js-rewrite 0eaeb8bb5 -> c8fd64807 Split the guts of ClassNameLocator into a new ClasspathScanner service Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/c8fd6480 Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/c8fd6480 Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/c8fd6480 Branch: refs/heads/5.4-js-rewrite Commit: c8fd648077b8be794ac78f03c666c4b8576c2869 Parents: 0eaeb8b Author: Howard M. Lewis Ship Authored: Fri Oct 5 14:39:54 2012 -0700 Committer: Howard M. Lewis Ship Committed: Fri Oct 5 14:39:54 2012 -0700 ---------------------------------------------------------------------- .../hibernate/HibernateSessionSourceImplTest.java | 11 +- .../internal/services/ClassNameLocatorImpl.java | 318 ++------------- .../internal/services/ClasspathScannerImpl.java | 314 ++++++++++++++ .../tapestry5/ioc/services/ClassNameLocator.java | 3 +- .../tapestry5/ioc/services/ClasspathMatcher.java | 38 ++ .../tapestry5/ioc/services/ClasspathScanner.java | 38 ++ .../tapestry5/ioc/services/TapestryIOCModule.java | 1 + .../ioc/specs/ClassNameLocatorImplSpec.groovy | 82 ++-- 8 files changed, 481 insertions(+), 324 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c8fd6480/tapestry-hibernate-core/src/test/java/org/apache/tapestry5/internal/hibernate/HibernateSessionSourceImplTest.java ---------------------------------------------------------------------- diff --git a/tapestry-hibernate-core/src/test/java/org/apache/tapestry5/internal/hibernate/HibernateSessionSourceImplTest.java b/tapestry-hibernate-core/src/test/java/org/apache/tapestry5/internal/hibernate/HibernateSessionSourceImplTest.java index 515c076..0ea7d6a 100644 --- a/tapestry-hibernate-core/src/test/java/org/apache/tapestry5/internal/hibernate/HibernateSessionSourceImplTest.java +++ b/tapestry-hibernate-core/src/test/java/org/apache/tapestry5/internal/hibernate/HibernateSessionSourceImplTest.java @@ -1,4 +1,4 @@ -// Copyright 2007, 2008 The Apache Software Foundation +// Copyright 2007, 2008, 2012 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. @@ -18,6 +18,7 @@ import org.apache.tapestry5.hibernate.HibernateConfigurer; import org.apache.tapestry5.hibernate.HibernateEntityPackageManager; import org.apache.tapestry5.hibernate.HibernateSessionSource; import org.apache.tapestry5.ioc.internal.services.ClassNameLocatorImpl; +import org.apache.tapestry5.ioc.internal.services.ClasspathScannerImpl; import org.apache.tapestry5.ioc.internal.services.ClasspathURLConverterImpl; import org.apache.tapestry5.ioc.internal.util.CollectionFactory; import org.apache.tapestry5.ioc.test.IOCTestCase; @@ -47,11 +48,15 @@ public class HibernateSessionSourceImplTest extends IOCTestCase HibernateEntityPackageManager packageManager = newMock(HibernateEntityPackageManager.class); TestBase.expect(packageManager.getPackageNames()).andReturn(packageNames); + ClasspathScannerImpl scanner = new ClasspathScannerImpl(new ClasspathURLConverterImpl()); + ClassNameLocatorImpl classNameLocator = new ClassNameLocatorImpl(scanner); + List filters = Arrays.asList(new DefaultHibernateConfigurer(true), - new PackageNameHibernateConfigurer(packageManager, new ClassNameLocatorImpl( - new ClasspathURLConverterImpl()))); + new PackageNameHibernateConfigurer(packageManager, classNameLocator)); + replay(); + HibernateSessionSource source = new HibernateSessionSourceImpl(log, filters); Session session = source.create(); http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c8fd6480/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClassNameLocatorImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClassNameLocatorImpl.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClassNameLocatorImpl.java index 3738f83..18fb90d 100644 --- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClassNameLocatorImpl.java +++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClassNameLocatorImpl.java @@ -1,4 +1,4 @@ -// Copyright 2007, 2008, 2010 The Apache Software Foundation +// Copyright 2007, 2008, 2010, 2012 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. @@ -14,328 +14,84 @@ 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.func.F; +import org.apache.tapestry5.func.Mapper; import org.apache.tapestry5.ioc.services.ClassNameLocator; -import org.apache.tapestry5.ioc.services.ClasspathURLConverter; -import org.apache.tapestry5.ioc.util.Stack; +import org.apache.tapestry5.ioc.services.ClasspathMatcher; +import org.apache.tapestry5.ioc.services.ClasspathScanner; -import java.io.*; -import java.net.JarURLConnection; -import java.net.URL; -import java.net.URLConnection; +import java.io.IOException; import java.util.Collection; -import java.util.Enumeration; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; import java.util.regex.Pattern; public class ClassNameLocatorImpl implements ClassNameLocator { - private static final String CLASS_SUFFIX = ".class"; - public static final String PACKAGE_INFO = "package-info.class"; - - private final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - - private final ClasspathURLConverter converter; + private final ClasspathScanner scanner; // This matches normal class files but not inner class files (which contain a '$'. private final Pattern CLASS_NAME_PATTERN = Pattern.compile("^\\p{javaJavaIdentifierStart}[\\p{javaJavaIdentifierPart}&&[^\\$]]*\\.class$", Pattern.CASE_INSENSITIVE); - private final Pattern FOLDER_NAME_PATTERN = Pattern.compile("^\\p{javaJavaIdentifierStart}[\\p{javaJavaIdentifierPart}]*$", Pattern.CASE_INSENSITIVE); - - static class Queued - { - final URL packageURL; - - final String packagePath; - - public Queued(final URL packageURL, final String packagePath) - { - this.packageURL = packageURL; - this.packagePath = packagePath; - } - } - - public ClassNameLocatorImpl(ClasspathURLConverter converter) - { - this.converter = converter; - } - /** - * Synchronization should not be necessary, but perhaps the underlying ClassLoader's are sensitive to threading. + * Matches paths that are classes, but not for inner classes, or the package-info.class psuedo-class (used for package-level annotations). */ - public synchronized Collection locateClassNames(String packageName) - { - String packagePath = packageName.replace('.', '/') + "/"; - - try - { - - return findClassesWithinPath(packagePath); - - } catch (IOException ex) - { - throw new RuntimeException(ex); - } - } - - private Collection findClassesWithinPath(String packagePath) throws IOException - { - Collection result = CollectionFactory.newList(); - - Enumeration urls = contextClassLoader.getResources(packagePath); - - while (urls.hasMoreElements()) - { - URL url = urls.nextElement(); - - URL converted = converter.convert(url); - - scanURL(packagePath, result, converted); - } - - return result; - } - - private void scanURL(String packagePath, Collection componentClassNames, URL url) throws IOException + private final ClasspathMatcher CLASS_NAME_MATCHER = new ClasspathMatcher() { - 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)) + @Override + public boolean matches(String packagePath, String fileName) { - Stack queue = CollectionFactory.newStack(); - - queue.push(new Queued(url, packagePath)); - - while (!queue.isEmpty()) + if (!CLASS_NAME_PATTERN.matcher(fileName).matches()) { - 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); + return false; } - 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 componentClassNames, - Stack 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); + // Filter out inner classes. - String packageName = null; - - try - { - while (true) + if (fileName.contains("$") || fileName.equals("package-info.class")) { - String line = lineReader.readLine(); - - if (line == null) break; - - if (CLASS_NAME_PATTERN.matcher(line).matches()) - { - if (packageName == null) - { - packageName = packagePath.replace('/', '.'); - } - - // packagePath ends with '/', packageName ends with '.' - - String fileName = line.substring(0, line.length() - CLASS_SUFFIX.length()); - - if (!fileName.equals("package-info")) - { - String fullClassName = packageName + fileName; - - componentClassNames.add(fullClassName); - } - - continue; - } - - // This should match just directories. It may also match files that have no extension; - // when we read those, none of the lines should look like class files. - - if (FOLDER_NAME_PATTERN.matcher(line).matches()) - { - URL newURL = new URL(packageURL.toExternalForm() + line + "/"); - String newPackagePath = packagePath + line + "/"; - - queue.push(new Queued(newURL, newPackagePath)); - } + return false; } - lineReader.close(); - lineReader = null; - } finally - { - InternalUtils.close(lineReader); - } - - } - - private void scanJarFile(String packagePath, Collection componentClassNames, JarFile jarFile) - { - Enumeration e = jarFile.entries(); - - while (e.hasMoreElements()) - { - String name = e.nextElement().getName(); - - if (!name.startsWith(packagePath)) continue; - - - int lastSlashx = name.lastIndexOf('/'); - - String fileName = name.substring(lastSlashx + 1); - - if (isClassName(fileName)) - { - - // Strip off .class and convert the slashes back to periods. - String className = - name.substring(0, lastSlashx + 1).replace('/', '.') + - fileName.substring(0, fileName.length() - CLASS_SUFFIX.length()); - - - componentClassNames.add(className); - } + return true; } - } + }; /** - * 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 classes. - * @param componentClassNames List of class names that have been found. + * Maps a path name ("foo/bar/Baz.class") to a class name ("foo.bar.Baz"). */ - private void scanDir(String packageName, File dir, Collection componentClassNames) + private final Mapper CLASS_NAME_MAPPER = new Mapper() { - if (dir.exists() && dir.isDirectory()) + @Override + public String map(String element) { - for (File file : dir.listFiles()) - { - String fileName = file.getName(); - if (file.isDirectory()) - { - scanDir(packageName + "." + fileName, file, componentClassNames); - } - // https://issues.apache.org/jira/browse/TAP5-1737 - // Use of package-info.java leaves these package-info.class files around. - else if (isClassName(fileName)) - { - String className = packageName + "." + fileName.substring(0, - fileName.length() - CLASS_SUFFIX.length()); - componentClassNames.add(className); - } - } + return element.substring(0, element.length() - 6).replace('/', '.'); } - } + }; + - private boolean isClassName(String fileName) + public ClassNameLocatorImpl(ClasspathScanner scanner) { - return fileName.endsWith(CLASS_SUFFIX) && !fileName.equals(PACKAGE_INFO) && !fileName.contains("$"); + this.scanner = scanner; } /** - * 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 + * Synchronization should not be necessary, but perhaps the underlying ClassLoader's are sensitive to threading. */ - private JarFile getAlternativeJarFile(URL url) throws IOException + public synchronized Collection locateClassNames(String packageName) { - String urlFile = url.getFile(); - // Trim off any suffix - which is prefixed by "!/" on Weblogic - int separatorIndex = urlFile.indexOf("!/"); + String packagePath = packageName.replace('.', '/') + "/"; - // OK, didn't find that. Try the less safe "!", used on OC4J - if (separatorIndex == -1) + try { - separatorIndex = urlFile.indexOf('!'); - } + Collection matches = scanner.scan(packagePath, CLASS_NAME_MATCHER); - 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 F.flow(matches).map(CLASS_NAME_MAPPER).toSet(); - return new JarFile(jarFileUrl); + } catch (IOException ex) + { + throw new RuntimeException(ex); } - - return null; } + } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c8fd6480/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClasspathScannerImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClasspathScannerImpl.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClasspathScannerImpl.java new file mode 100644 index 0000000..c006c0f --- /dev/null +++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/ClasspathScannerImpl.java @@ -0,0 +1,314 @@ +// Copyright 2012 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.ClasspathMatcher; +import org.apache.tapestry5.ioc.services.ClasspathScanner; +import org.apache.tapestry5.ioc.services.ClasspathURLConverter; +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.Enumeration; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Pattern; + +public class ClasspathScannerImpl implements ClasspathScanner +{ + private final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + + private final ClasspathURLConverter converter; + + private final Pattern FOLDER_NAME_PATTERN = Pattern.compile("^\\p{javaJavaIdentifierStart}[\\p{javaJavaIdentifierPart}]*$", Pattern.CASE_INSENSITIVE); + + public ClasspathScannerImpl(ClasspathURLConverter converter) + { + this.converter = converter; + } + + /** + * Scans the indicated package path for matches. + * + * @param packagePath + * a package path (like a package name, but using '/' instead of '.', and ending with '/') + * @param matcher + * passed a resource path from the package (or a sub-package), returns true if the provided + * path should be included in the returned collection + * @return collection of matching paths, in no specified order + * @throws java.io.IOException + */ + public Set scan(String packagePath, ClasspathMatcher matcher) throws IOException + { + assert packagePath != null && packagePath.endsWith("/"); + assert matcher != null; + + return new Job(matcher).findMatches(packagePath); + } + + /** + * Check whether container supports opening a stream on a dir/package to get a list of its contents. + */ + private boolean supportsDirStream(URL packageURL) + { + InputStream is = null; + + try + { + is = packageURL.openStream(); + + return true; + } catch (FileNotFoundException ex) + { + return false; + } catch (IOException ex) + { + return false; + } finally + { + InternalUtils.close(is); + } + } + + /** + * 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; + } + + static class Queued + { + final URL packageURL; + + final String packagePath; + + public Queued(final URL packageURL, final String packagePath) + { + this.packageURL = packageURL; + this.packagePath = packagePath; + } + } + + class Job + { + final ClasspathMatcher matcher; + + final Set matches = CollectionFactory.newSet(); + + final Stack queue = CollectionFactory.newStack(); + + Job(ClasspathMatcher matcher) + { + this.matcher = matcher; + } + + Set findMatches(String packagePath) throws IOException + { + + Enumeration urls = contextClassLoader.getResources(packagePath); + + while (urls.hasMoreElements()) + { + URL url = urls.nextElement(); + + URL converted = converter.convert(url); + + scanURL(packagePath, converted); + + while (!queue.isEmpty()) + { + Queued queued = queue.pop(); + + scanDirStream(queued.packagePath, queued.packageURL); + } + } + + return matches; + } + + void scanURL(String packagePath, 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, jarFile); + } else if (supportsDirStream(url)) + { + queue.push(new Queued(url, packagePath)); + } else + { + // Try scanning file system. + + scanDir(packagePath, new File(url.getFile())); + } + + } + + /** + * Scan a dir for classes. Will recursively look in the supplied directory and all sub directories. + * + * @param packagePath + * Name of package that this directory corresponds to. + * @param packageDir + * Dir to scan for classes. + */ + private void scanDir(String packagePath, File packageDir) + { + if (packageDir.exists() && packageDir.isDirectory()) + { + for (File file : packageDir.listFiles()) + { + String fileName = file.getName(); + + if (file.isDirectory()) + { + // TODO: A second queue instead of recursion. + + scanDir(packagePath + fileName + "/", file); + } + + if (matcher.matches(packagePath, fileName)) + { + matches.add(packagePath + fileName); + } + } + } + } + + private void scanDirStream(String packagePath, URL packageURL) 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); + + try + { + while (true) + { + String line = lineReader.readLine(); + + if (line == null) break; + + if (matcher.matches(packagePath, line)) + { + matches.add(packagePath + line); + } else + { + + // This should match just directories. It may also match files that have no extension; + // when we read those, none of the lines should look like class files. + + if (FOLDER_NAME_PATTERN.matcher(line).matches()) + { + 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, JarFile jarFile) + { + Enumeration e = jarFile.entries(); + + while (e.hasMoreElements()) + { + String name = e.nextElement().getName(); + + if (!name.startsWith(packagePath)) continue; + + int lastSlashx = name.lastIndexOf('/'); + + String filePackagePath = name.substring(0, lastSlashx + 1); + String fileName = name.substring(lastSlashx + 1); + + if (matcher.matches(filePackagePath, fileName)) + { + matches.add(name); + } + } + } + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c8fd6480/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClassNameLocator.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClassNameLocator.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClassNameLocator.java index 3c9ded8..8b22404 100644 --- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClassNameLocator.java +++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClassNameLocator.java @@ -1,4 +1,4 @@ -// Copyright 2007, 2008 The Apache Software Foundation +// Copyright 2007, 2008, 2012 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. @@ -20,6 +20,7 @@ import java.util.Collection; * Scans the classpath for top-level classes within particular packages. * * @see org.apache.tapestry5.ioc.services.ClasspathURLConverter + * @see ClasspathScanner */ public interface ClassNameLocator { http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c8fd6480/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClasspathMatcher.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClasspathMatcher.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClasspathMatcher.java new file mode 100644 index 0000000..27a23c5 --- /dev/null +++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClasspathMatcher.java @@ -0,0 +1,38 @@ +// Copyright 2012 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.services; + +/** + * Used to determine which files will be included in the set of matches paths within a particular + * package. + * + * @see ClasspathScanner + * @since 5.4 + */ +public interface ClasspathMatcher +{ + /** + * Invoked for each located file, to determine if it belongs. May be passed file names + * that are actually nested folders. Typically, an implementation determined what matches + * based on a file extension of naming pattern. + * + * @param packagePath + * package path containing the file, ending with '/' + * @param fileName + * name of file within the package + * @return true to include, false to exclude + */ + boolean matches(String packagePath, String fileName); +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c8fd6480/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClasspathScanner.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClasspathScanner.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClasspathScanner.java new file mode 100644 index 0000000..e308374 --- /dev/null +++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/ClasspathScanner.java @@ -0,0 +1,38 @@ +// Copyright 2012 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.services; + +import java.io.IOException; +import java.util.Set; + +/** + * Used to scan a portion of the classpath for files that match a particular pattern, defined by a {@link ClasspathMatcher}. + * + * @since 5.4 + */ +public interface ClasspathScanner +{ + /** + * Perform a scan of the indicated package path and any nested packages. + * + * @param packagePath + * defines the root of the search as a path, e.g., "org/apache/tapestry5/" not "org.apache.tapestry5" + * @param matcher + * passed each potential match to determine which are included in the final result + * @return matching paths based on the search and the matcher + * @throws IOException + */ + Set scan(String packagePath, ClasspathMatcher matcher) throws IOException; +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c8fd6480/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/TapestryIOCModule.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/TapestryIOCModule.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/TapestryIOCModule.java index d0e0c52..3bd7a6d 100644 --- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/TapestryIOCModule.java +++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/TapestryIOCModule.java @@ -65,6 +65,7 @@ public final class TapestryIOCModule binder.bind(Runnable.class, RegistryStartup.class).withSimpleId(); binder.bind(MasterObjectProvider.class, MasterObjectProviderImpl.class).preventReloading(); binder.bind(ClassNameLocator.class, ClassNameLocatorImpl.class); + binder.bind(ClasspathScanner.class, ClasspathScannerImpl.class); binder.bind(AspectDecorator.class, AspectDecoratorImpl.class); binder.bind(ClasspathURLConverter.class, ClasspathURLConverterImpl.class); binder.bind(ServiceOverride.class, ServiceOverrideImpl.class); http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c8fd6480/tapestry-ioc/src/test/groovy/ioc/specs/ClassNameLocatorImplSpec.groovy ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/ClassNameLocatorImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/ClassNameLocatorImplSpec.groovy index 106cb29..f90ff2c 100644 --- a/tapestry-ioc/src/test/groovy/ioc/specs/ClassNameLocatorImplSpec.groovy +++ b/tapestry-ioc/src/test/groovy/ioc/specs/ClassNameLocatorImplSpec.groovy @@ -1,76 +1,80 @@ package ioc.specs import org.apache.tapestry5.ioc.internal.services.ClassNameLocatorImpl +import org.apache.tapestry5.ioc.internal.services.ClasspathScannerImpl import org.apache.tapestry5.ioc.internal.services.ClasspathURLConverterImpl import org.apache.tapestry5.ioc.services.ClassNameLocator +import org.apache.tapestry5.ioc.services.ClasspathScanner import spock.lang.Specification class ClassNameLocatorImplSpec extends Specification { - ClassNameLocator locator = new ClassNameLocatorImpl(new ClasspathURLConverterImpl()); + ClasspathScanner scanner = new ClasspathScannerImpl(new ClasspathURLConverterImpl()) - def assertInList(classNames, packageName, String... expectedNames) { + ClassNameLocator locator = new ClassNameLocatorImpl(scanner); - expectedNames.each { name -> - String qualifiedName = "${packageName}.${name}" + def assertInList(classNames, packageName, String... expectedNames) { - assert classNames.contains(qualifiedName), "[$qualifiedName] not present in ${classNames.join(', ')}." + expectedNames.each { name -> + String qualifiedName = "${packageName}.${name}" + + assert classNames.contains(qualifiedName), "[$qualifiedName] not present in ${classNames.join(', ')}." + } } - } - def assertNotInList(classNames, packageName, String... expectedNames) { + def assertNotInList(classNames, packageName, String... expectedNames) { - expectedNames.each { name -> - String qualifiedName = "${packageName}.${name}" + expectedNames.each { name -> + String qualifiedName = "${packageName}.${name}" - assert !classNames.contains(qualifiedName), "[$qualifiedName] should not be present in ${classNames.join(', ')}." + assert !classNames.contains(qualifiedName), "[$qualifiedName] should not be present in ${classNames.join(', ')}." + } } - } - def "locate classes inside a JAR file on the classpath"() { + def "locate classes inside a JAR file on the classpath"() { - expect: + expect: - assertInList locator.locateClassNames("javax.inject"), - "javax.inject", - "Inject", "Named", "Singleton" - } + assertInList locator.locateClassNames("javax.inject"), + "javax.inject", + "Inject", "Named", "Singleton" + } - def "can locate classes inside a subpackage, inside a classpath JAR file"() { + def "can locate classes inside a subpackage, inside a classpath JAR file"() { - expect: + expect: - assertInList locator.locateClassNames("org.slf4j"), - "org.slf4j", - "spi.MDCAdapter" - } + assertInList locator.locateClassNames("org.slf4j"), + "org.slf4j", + "spi.MDCAdapter" + } - def "can locate classes in local folder, but exclude inner classes"() { + def "can locate classes in local folder, but exclude inner classes"() { - def packageName = "org.apache.tapestry5.ioc.services" + def packageName = "org.apache.tapestry5.ioc.services" - when: + when: - def names = locator.locateClassNames packageName + def names = locator.locateClassNames packageName - then: + then: - assertInList names, packageName, "SymbolSource", "TapestryIOCModule" + assertInList names, packageName, "SymbolSource", "TapestryIOCModule" - assertNotInList names, packageName, 'TapestryIOCMOdules$1' - } + assertNotInList names, packageName, 'TapestryIOCMOdules$1' + } - def "can locate classes in subpackage of local folders"() { - def packageName = "org.apache.tapestry5" + def "can locate classes in subpackage of local folders"() { + def packageName = "org.apache.tapestry5" - when: + when: - def names = locator.locateClassNames packageName + def names = locator.locateClassNames packageName - then: + then: - assertInList names, packageName, "ioc.Orderable", "ioc.services.ChainBuilder" - assertNotInList names, packageName, 'services.TapestryIOCModule$1' - } + assertInList names, packageName, "ioc.Orderable", "ioc.services.ChainBuilder" + assertNotInList names, packageName, 'services.TapestryIOCModule$1' + } }