/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.module;
import gw.config.CommonServices;
import gw.fs.IDirectory;
import gw.fs.IResource;
import gw.internal.gosu.dynamic.DynamicTypeLoader;
import gw.internal.gosu.parser.DefaultTypeLoader;
import gw.internal.gosu.parser.FileSystemGosuClassRepository;
import gw.internal.gosu.parser.IModuleClassLoader;
import gw.internal.gosu.parser.ModuleClassLoader;
import gw.internal.gosu.parser.ModuleTypeLoader;
import gw.internal.gosu.properties.PropertiesTypeLoader;
import gw.lang.parser.ILanguageLevel;
import gw.lang.reflect.ITypeLoader;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.TypeSystemShutdownListener;
import gw.lang.reflect.gs.GosuClassTypeLoader;
import gw.lang.reflect.gs.IFileSystemGosuClassRepository;
import gw.lang.reflect.gs.IGosuClassRepository;
import gw.lang.reflect.module.Dependency;
import gw.lang.reflect.module.IExecutionEnvironment;
import gw.lang.reflect.module.IModule;
import gw.lang.reflect.module.INativeModule;
import gw.util.Extensions;
import gw.util.GosuExceptionUtil;
import gw.util.concurrent.LocklessLazyVar;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Module implements IModule
{
private final IExecutionEnvironment _execEnv;
private String _strName;
private List<Dependency> _dependencies = new ArrayList<Dependency>();
private LocklessLazyVar<IModule[]> _traversalList = new LocklessLazyVar<IModule[]>() {
@Override
protected IModule[] init() {
return buildTraversalList();
}
};
private ModuleTypeLoader _modTypeLoader;
// Paths
private List<IDirectory> _roots = new ArrayList<IDirectory>();
protected List<IDirectory> _classpath = new ArrayList<IDirectory>();
private INativeModule _nativeModule;
private ClassLoader _moduleClassLoader;
private ClassLoader _extensionsClassLoader;
private final IFileSystemGosuClassRepository _fileRepository = new FileSystemGosuClassRepository(this);
public Module(IExecutionEnvironment execEnv, String strName)
{
_execEnv = execEnv;
_strName = strName;
}
public final IExecutionEnvironment getExecutionEnvironment()
{
return _execEnv;
}
@Override
public IFileSystemGosuClassRepository getFileRepository() {
return _fileRepository;
}
@Override
public void setDependencies(List<Dependency> newDeps) {
_dependencies = new ArrayList<Dependency>(newDeps);
_traversalList.clear();
}
@Override
public List<IDirectory> getRoots() {
return _roots;
}
@Override
public void setRoots(List<IDirectory> roots) {
_roots = new ArrayList<IDirectory>(roots);
}
@Override
public List<Dependency> getDependencies()
{
return _dependencies;
}
@Override
public void addDependency( Dependency d )
{
_dependencies.add(d);
_traversalList.clear();
}
public void removeDependency( Dependency d )
{
_dependencies.remove(d);
_traversalList.clear();
}
@Override
public List<IDirectory> getSourcePath()
{
return Arrays.asList(_fileRepository.getSourcePath());
}
@Override
public void setSourcePath( List<IDirectory> sourcePaths )
{
List<IDirectory> sources = new ArrayList<IDirectory>(sourcePaths);
sources.addAll(getAdditionalSourceRoots());
_fileRepository.setSourcePath(sources.toArray(new IDirectory[sourcePaths.size()]));
}
@Override
public ClassLoader getModuleClassLoader() {
if (_moduleClassLoader == null) {
_moduleClassLoader = ModuleClassLoader.create(this);
}
return _moduleClassLoader;
}
@Override
public void disposeLoader() {
if (_moduleClassLoader instanceof IModuleClassLoader) {
((IModuleClassLoader) _moduleClassLoader).dispose();
}
_moduleClassLoader = null;
}
private static void scanPaths(List<IDirectory> paths, Set<String> extensions, List<IDirectory> roots) {
extensions.add(".java");
extensions.add(".xsd");
extensions.addAll(Arrays.asList(GosuClassTypeLoader.ALL_EXTS));
for (IDirectory root : paths) {
// roots without manifests are considered source roots
if (!Extensions.containsManifest(root) || !Extensions.getExtensions(root, Extensions.CONTAINS_SOURCES).isEmpty()) {
roots.add(root);
}
}
}
@Override
public IDirectory getOutputPath()
{
return _nativeModule.getOutputPath();
}
public ModuleTypeLoader getModuleTypeLoader()
{
return _modTypeLoader;
}
public void setModuleTypeLoader( ModuleTypeLoader modTypeLoader )
{
_modTypeLoader = modTypeLoader;
}
@Override
public void configurePaths(List<IDirectory> classpath, List<IDirectory> sourcePaths)
{
// Scan....
List<IDirectory> sourceRoots = new ArrayList<IDirectory>(sourcePaths);
Set<String> extensions = new HashSet<String>();
scanPaths(classpath, extensions, sourceRoots);
// FIXME: extensions...
setSourcePath(sourceRoots);
setJavaClassPath(classpath);
}
@Override
public List<IDirectory> getJavaClassPath()
{
return _classpath;
}
@Override
public void setJavaClassPath( List<IDirectory> classpath ) {
_classpath = classpath;
}
@Override
public String toString()
{
return _strName;
}
@Override
public Object getNativeModule()
{
return _nativeModule != null ? _nativeModule.getNativeModule() : null;
}
@Override
public void setNativeModule( INativeModule nativeModule )
{
_nativeModule = nativeModule;
}
public void initializeTypeLoaders() {
maybeCreateModuleTypeLoader();
createStandardTypeLoaders();
if( CommonServices.getEntityAccess().getLanguageLevel().isStandard() ) {
createExtensionTypeLoaders();
}
// initialize all loaders
List<ITypeLoader> loaders = getModuleTypeLoader().getTypeLoaders();
for (int i = loaders.size() - 1; i >= 0; i--) {
loaders.get(i).init();
}
}
protected void createExtensionTypeLoaders() {
createExtenxioTypeloadersImpl();
}
protected void createExtenxioTypeloadersImpl() {
Set<String> typeLoaders = getExtensionTypeloaderNames();
for( String additionalTypeLoader : typeLoaders) {
try {
createAndPushTypeLoader(_fileRepository, additionalTypeLoader);
} catch (Throwable e) {
System.err.println("==> WARNING: Cannot create extension typeloader " + additionalTypeLoader + ". " + e.getMessage());
// e.printStackTrace(System.err);
System.err.println("==> END WARNING.");
}
}
}
private Set<String> getExtensionTypeloaderNames() {
Set<String> set = new HashSet<String>();
for (IModule m : getModuleTraversalList()) {
for (IDirectory dir : m.getJavaClassPath()) {
Extensions.getExtensions(set, dir, "Gosu-Typeloaders");
}
}
return set;
}
protected void createStandardTypeLoaders()
{
CommonServices.getTypeSystem().pushTypeLoader( this, new GosuClassTypeLoader( this, _fileRepository ) );
CommonServices.getTypeSystem().pushTypeLoader( this, new PropertiesTypeLoader( this ) );
if( ILanguageLevel.Util.DYNAMICE_TYPE() ) {
CommonServices.getTypeSystem().pushTypeLoader( this, new DynamicTypeLoader( this ) );
}
}
protected void maybeCreateModuleTypeLoader() {
if (getModuleTypeLoader() == null) {
ModuleTypeLoader tla = new ModuleTypeLoader( this, new DefaultTypeLoader(this) );
setModuleTypeLoader(tla);
}
}
public final IModule[] getModuleTraversalList() {
return _traversalList.get();
}
private IModule[] buildTraversalList() {
// create default traversal list
List<IModule> traversalList = new ArrayList<IModule>();
traverse(this, traversalList);
// make sure that the jre module is last
IModule jreModule = getExecutionEnvironment().getJreModule();
if (traversalList.remove(jreModule)) {
traversalList.add(jreModule);
}
IModule globalModule = getExecutionEnvironment().getGlobalModule();
if (this != globalModule) {
traversalList.add(0, globalModule);
}
return traversalList.toArray(new IModule[traversalList.size()]);
}
protected void traverse(final IModule theModule, List<IModule> traversalList) {
traversalList.add(theModule);
for (Dependency dependency : theModule.getDependencies()) {
IModule dependencyModule = dependency.getModule();
// traverse all direct dependency and indirect exported dependencies
if (!traversalList.contains(dependencyModule) &&
(dependency.isExported() || theModule == this)) {
traverse(dependencyModule, traversalList);
}
}
}
@Override
public <T extends ITypeLoader> List<? extends T> getTypeLoaders(Class<T> typeLoaderClass) {
List<T> results = new ArrayList<T>();
if (_modTypeLoader == null) {
return results;
}
for (ITypeLoader loader : getModuleTypeLoader().getTypeLoaderStack()) {
if (typeLoaderClass.isInstance(loader)) {
results.add(typeLoaderClass.cast(loader));
}
}
return results;
}
private ITypeLoader createAndPushTypeLoader(IFileSystemGosuClassRepository classRepository, String className)
{
ITypeLoader typeLoader = null;
try
{
Class loaderClass = getExtensionClassLoader().loadClass( className );
CommonServices.getGosuInitializationHooks().beforeTypeLoaderCreation( loaderClass );
Constructor constructor = getConstructor( loaderClass, IModule.class );
if( constructor != null )
{
typeLoader = (ITypeLoader) constructor.newInstance( this );
}
else
{
constructor = getConstructor( loaderClass, IModule.class );
if( constructor != null )
{
typeLoader = (ITypeLoader) constructor.newInstance( this );
}
else
{
if( constructor != null )
{
typeLoader = (ITypeLoader) constructor.newInstance( this );
}
else
{
constructor = getConstructor( loaderClass, IGosuClassRepository.class );
if( constructor != null )
{
typeLoader = (ITypeLoader) constructor.newInstance( classRepository );
}
else
{
constructor = getConstructor( loaderClass );
if( constructor != null )
{
typeLoader = (ITypeLoader) constructor.newInstance();
}
}
}
}
}
}
catch( Exception e )
{
throw GosuExceptionUtil.forceThrow( e );
}
if( typeLoader != null )
{
CommonServices.getTypeSystem().pushTypeLoader( this, typeLoader );
CommonServices.getGosuInitializationHooks().afterTypeLoaderCreation();
}
else
{
throw new IllegalStateException(
"TypeLoader class " + className + " must have one of the following constructor signatures:\n" +
" <init>()\n" +
" <init>(gw.lang.reflect.module.IModule)\n" +
" <init>(gw.lang.reflect.gs.IGosuClassRepository)\n" );
}
return typeLoader;
}
private ClassLoader getExtensionClassLoader() {
if (_extensionsClassLoader == null) {
_extensionsClassLoader = ExtensionClassLoader.create(getExtensionURLs());
}
return _extensionsClassLoader;
}
private URL[] getExtensionURLs() {
List<URL> urls = new ArrayList<URL>();
for (IModule m : getModuleTraversalList()) {
for (IDirectory path : m.getJavaClassPath()) {
try {
urls.add(path.toURI().toURL());
} catch (MalformedURLException e) {
//ignore
}
}
}
return urls.toArray(new URL[urls.size()]);
}
private Constructor getConstructor( Class loaderClass, Class... argTypes )
{
try
{
return loaderClass.getConstructor( argTypes );
}
catch( NoSuchMethodException e )
{
return null;
}
}
public boolean equals(Object o) {
if (!(o instanceof IModule)) {
return false;
}
IModule m = (IModule) o;
return this.getName().equals(m.getName());
}
public int hashCode() {
return _strName.hashCode();
}
@Override
public String getName()
{
return _strName;
}
@Override
public void setName(String name) {
_strName = name;
}
protected List<IDirectory> getAdditionalSourceRoots() {
return Collections.emptyList();
}
/**
* Singleton extension classloader. Used for loading typeloaders.
*/
private static class ExtensionClassLoader extends URLClassLoader {
private static final LocklessLazyVar<ExtensionClassLoader> INSTANCE
= new LocklessLazyVar<ExtensionClassLoader>() {
@Override
protected ExtensionClassLoader init() {
return new ExtensionClassLoader(ExtensionClassLoader.class.getClassLoader());
}
};
static {
TypeSystem.addShutdownListener(new TypeSystemShutdownListener() {
@Override
public void shutdown() {
INSTANCE.clear();
}
});
}
public static ClassLoader create(URL[] urls) {
ExtensionClassLoader loader = INSTANCE.get();
for (URL url : urls) {
loader.addURL(url);
}
return loader;
}
private ExtensionClassLoader(ClassLoader parent) {
super(new URL[0], parent);
}
}
@Override
public String pathRelativeToRoot(IResource resource) {
for (IDirectory root : getSourcePath()) {
if (resource.isDescendantOf(root)) {
return root.relativePath(resource);
}
}
for (IDirectory root : getRoots()) {
if (resource.isDescendantOf(root)) {
return root.relativePath(resource);
}
}
return null;
}
}