001// Copyright 2007, 2008, 2010, 2012 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.ioc.internal.services;
016
017import org.apache.tapestry5.func.F;
018import org.apache.tapestry5.func.Mapper;
019import org.apache.tapestry5.ioc.services.ClassNameLocator;
020import org.apache.tapestry5.ioc.services.ClasspathMatcher;
021import org.apache.tapestry5.ioc.services.ClasspathScanner;
022
023import java.io.IOException;
024import java.util.Collection;
025import java.util.regex.Pattern;
026
027public class ClassNameLocatorImpl implements ClassNameLocator
028{
029    private final ClasspathScanner scanner;
030
031    // This matches normal class files but not inner class files (which contain a '$'.
032
033    private final Pattern CLASS_NAME_PATTERN = Pattern.compile("^\\p{javaJavaIdentifierStart}[\\p{javaJavaIdentifierPart}&&[^\\$]]*\\.class$", Pattern.CASE_INSENSITIVE);
034
035    /**
036     * Matches paths that are classes, but not for inner classes, or the package-info.class psuedo-class (used for package-level annotations).
037     */
038    private final ClasspathMatcher CLASS_NAME_MATCHER = new ClasspathMatcher()
039    {
040        public boolean matches(String packagePath, String fileName)
041        {
042            if (!CLASS_NAME_PATTERN.matcher(fileName).matches())
043            {
044                return false;
045            }
046
047            // Filter out inner classes.
048
049            if (fileName.contains("$") || fileName.equals("package-info.class"))
050            {
051                return false;
052            }
053
054            return true;
055        }
056    };
057
058    /**
059     * Maps a path name ("foo/bar/Baz.class") to a class name ("foo.bar.Baz").
060     */
061    private final Mapper<String, String> CLASS_NAME_MAPPER = new Mapper<String, String>()
062    {
063        public String map(String element)
064        {
065            return element.substring(0, element.length() - 6).replace('/', '.');
066        }
067    };
068
069
070    public ClassNameLocatorImpl(ClasspathScanner scanner)
071    {
072        this.scanner = scanner;
073    }
074
075    /**
076     * Synchronization should not be necessary, but perhaps the underlying ClassLoader's are sensitive to threading.
077     */
078    public synchronized Collection<String> locateClassNames(String packageName)
079    {
080        String packagePath = packageName.replace('.', '/') + "/";
081
082        try
083        {
084            Collection<String> matches = scanner.scan(packagePath, CLASS_NAME_MATCHER);
085
086            return F.flow(matches).map(CLASS_NAME_MAPPER).toSet();
087
088        } catch (IOException ex)
089        {
090            throw new RuntimeException(ex);
091        }
092    }
093
094
095}