/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.parser;
import gw.config.CommonServices;
import gw.fs.IDirectory;
import gw.fs.IFile;
import gw.internal.gosu.module.fs.FileSystemImpl;
import gw.lang.reflect.IDefaultTypeLoader;
import gw.lang.reflect.gs.TypeName;
import gw.lang.reflect.module.IClassPath;
import gw.lang.reflect.module.IFileSystem;
import gw.lang.reflect.module.IModule;
import gw.util.cache.FqnCache;
import gw.util.cache.FqnCacheNode;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ClassPath implements IClassPath
{
private static final String CLASS_FILE_EXT = ".class";
private IModule _module;
private ClassPathFilter _filter;
private FqnCache<Object> _cache = new FqnCache<Object>();
private IFileSystem _fs;
private boolean _bStableFiles;
public ClassPath(IModule module, ClassPathFilter filter)
{
_module = module;
_filter = filter;
// Files are assumed stable outside an IDE
_fs = CommonServices.getFileSystem();
_bStableFiles = _fs instanceof FileSystemImpl;
loadClasspathInfo();
}
public ArrayList<IDirectory> getPaths()
{
return new ArrayList<IDirectory>( _module.getJavaClassPath());
}
public boolean contains(String fqn) {
return _cache.contains(fqn);
}
public IFile get( String fqn ) {
FqnCacheNode<Object> node = _cache.getNode( fqn );
if( node == null ) {
return null;
}
Object value = node.getUserData();
if( value instanceof IFile ) {
if( _bStableFiles ) {
// Files are assumed stable outside an IDE
return (IFile)value;
}
try {
// Lazily compute and cache the URL, it can be an expensive native file system call
value = ((IFile)value).toURI().toURL();
node.setUserData( value );
}
catch( MalformedURLException e ) {
throw new RuntimeException( e );
}
}
//## todo: it'd be better if ClassPath could listen to FS changes and not query the FS for every
// Get the file *fresh* from the abstract file system
return _fs.getIFile( (URL)value );
}
public Set<String> getFilteredClassNames() {
return _cache.getFqns();
}
public boolean isEmpty() {
return _cache.getRoot().isLeaf();
}
// ====================== PRIVATE ====================================
private void loadClasspathInfo()
{
List<IDirectory> javaClassPath = _module.getJavaClassPath();
IDirectory[] paths = javaClassPath.toArray(new IDirectory[javaClassPath.size()]);
for (int i = 0; i < paths.length; i++) {
addClassNames(paths[i], paths[i], _filter );
}
}
private void addClassNames(final IDirectory root, IDirectory dir, final ClassPathFilter filter)
{
for (IFile file : dir.listFiles()) {
if( isClassFileName( file.getName() ) )
{
String strClassName = getClassNameFromFile( root, file );
if( isValidClassName( strClassName ) )
{
putClassName( file, strClassName, filter );
}
}
}
for (IDirectory subDir : dir.listDirs()) {
addClassNames(root, subDir, filter);
}
}
private void putClassName( final IFile file, String strClassName, ClassPathFilter filter )
{
boolean bFiltered = filter != null && !filter.acceptClass( strClassName );
if( bFiltered )
{
// We need to store packages so we can resolve them in the gosu parser
strClassName = getPlaceholderClassNameForFilteredPackage( strClassName );
}
if( strClassName != null )
{
// Store the abstract file, not the URL; we compute and recache that lazily, see #get()
_cache.add( strClassName, file );
}
}
static private String getPlaceholderClassNameForFilteredPackage( String strClassName )
{
int iIndex = strClassName.lastIndexOf( '.' );
if( iIndex > 0 )
{
return strClassName.substring( 0, iIndex+1 ) + PLACEHOLDER_FOR_PACKAGE;
}
return null;
}
private String getClassNameFromFile( IDirectory root, IFile file )
{
String strQualifiedClassName = root.relativePath(file);
if( !isClassFileName( strQualifiedClassName ) )
{
throw new IllegalArgumentException(
file.getPath() + " is not a legal Java class name. " +
"It does not end with " + CLASS_FILE_EXT );
}
strQualifiedClassName =
strQualifiedClassName.substring( 0, strQualifiedClassName.length() -
CLASS_FILE_EXT.length() );
return strQualifiedClassName.replace('/', '.');
}
private boolean isClassFileName( String strFileName )
{
return strFileName.toLowerCase().endsWith( ".class" );
}
private boolean isValidClassName( String strClassName )
{
if (strClassName.endsWith("package-info")) {
return false;
}
// look for private or anonymous inner classes
int index = strClassName.lastIndexOf('$');
return !(_filter.isIgnoreAnonymous() &&
index >= 0 && index < strClassName.length() - 1 &&
Character.isDigit( strClassName.charAt( index + 1 ) ));
}
public boolean hasNamespace(String namespace) {
FqnCacheNode infoNode = _cache.getNode(namespace);
return infoNode != null && !infoNode.isLeaf();
}
@Override
public Set<TypeName> getTypeNames(String namespace) {
FqnCacheNode<?> node = _cache.getNode(namespace);
IDefaultTypeLoader defaultTypeLoader = _module.getModuleTypeLoader().getDefaultTypeLoader();
if (node != null) {
Set<TypeName> names = new HashSet<TypeName>();
for (FqnCacheNode<?> child : node.getChildren()) {
if (child.isLeaf()) {
names.add(new TypeName(namespace + "." + child.getName(), defaultTypeLoader, TypeName.Kind.TYPE, TypeName.Visibility.PUBLIC));
} else {
names.add(new TypeName(child.getName(), defaultTypeLoader, TypeName.Kind.NAMESPACE, TypeName.Visibility.PUBLIC));
}
}
return names;
} else {
return Collections.emptySet();
}
}
@Override
public String toString() {
return _module.getName();
}
}