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}