    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        @Override
    041        public boolean matches(String packagePath, String fileName)
    042        {
    043            if (!CLASS_NAME_PATTERN.matcher(fileName).matches())
    044            {
    045                return false;
    046            }
    047
    048            // Filter out inner classes.
    049
    050            if (fileName.contains("$") || fileName.equals("package-info.class"))
    051            {
    052                return false;
    053            }
    054
    055            return true;
    056        }
    057    };
    058
    059    /**
    060     * Maps a path name ("foo/bar/Baz.class") to a class name ("foo.bar.Baz").
    061     */
    062    private final Mapper<String, String> CLASS_NAME_MAPPER = new Mapper<String, String>()
    063    {
    064        @Override
    065        public String map(String element)
    066        {
    067            return element.substring(0, element.length() - 6).replace('/', '.');
    068        }
    069    };
    070
    071
    072    public ClassNameLocatorImpl(ClasspathScanner scanner)
    073    {
    074        this.scanner = scanner;
    075    }
    076
    077    /**
    078     * Synchronization should not be necessary, but perhaps the underlying ClassLoader's are sensitive to threading.
    079     */
    080    @Override
    081    public synchronized Collection<String> locateClassNames(String packageName)
    082    {
    083        String packagePath = packageName.replace('.', '/') + "/";
    084
    085        try
    086        {
    087            Collection<String> matches = scanner.scan(packagePath, CLASS_NAME_MATCHER);
    088
    089            return F.flow(matches).map(CLASS_NAME_MAPPER).toSet();
    090
    091        } catch (IOException ex)
    092        {
    093            throw new RuntimeException(ex);
    094        }
    095    }
    096
    097
    098}