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}