package org.xmlsh.sh.module;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.xmlsh.core.CoreException;
import org.xmlsh.core.ScriptSource;
import org.xmlsh.core.XClassLoader;
import org.xmlsh.core.XValue;
import org.xmlsh.sh.shell.Shell;
import org.xmlsh.sh.shell.ShellConstants;
import org.xmlsh.sh.shell.StaticContext;
import org.xmlsh.util.Util;
public abstract class Module implements IModule {
static Logger mLogger = LogManager.getLogger();
private ModuleConfig mConfig;
// Predeclared function classes indexed by *simple name* (no package component)
private HashMap<String, Class<?>> mFunctionClassCache = new HashMap<String, Class<?>>();
// Maps complete class name to class for caching
private HashMap<String, Class<?>> mClassCache = new HashMap<String, Class<?>>();
private HashMap<String, Boolean> mClassCacheMisses = new HashMap<String, Boolean>();
private XClassLoader mClassLoader;
protected StaticContext mStaticContext = null;
protected Module( ModuleConfig config , XClassLoader parent ) {
mConfig = config ;
List<URL> classPath = config.getClassPath() ;
if( parent == null )
parent = XClassLoader.newInstance(Shell.getContextClassLoader());
if( classPath == null || classPath.isEmpty() )
mClassLoader = parent ;
else
mClassLoader = XClassLoader.newInstance( classPath , parent );
}
@Override
public void addClassPaths(Shell shell, List<URL> urls) throws CoreException {
// Augment the class path and hence possibly augment the class loader
if( mConfig.addClassPaths( urls ) ){ // changed
chainClassLoader( shell , urls );
}
}
// Map complete class name to class for caching
protected void cacheClass(String className, Class<?> cls) {
// Store class in cache even if null
synchronized( mClassCache ){
mClassCache.put(className, cls);
}
}
/*
* Most derived classes should override parent classes
*
*/
protected void cacheFunctionClass(List<String> names, Class<?> cls) {
mLogger.entry(names, cls);
// Names start with primary name and may have aliases
for( String name : names ){
synchronized( mFunctionClassCache ){
Class<?> exists = mFunctionClassCache.get(name);
if( exists == null )
mFunctionClassCache.put( name , cls );
else {
// Override with most derived type
if( exists.isAssignableFrom(cls)){ // Exists is equal to or a super class
mLogger.trace("Overriding base class {} with derived class {} ",exists, cls );
// Override it with sub class
mFunctionClassCache.put( name , cls );
}
}
}
}
cacheClass( cls.getName() , cls );
}
protected void cacheCommandClass(List<String> names, Class<?> cls) {
cacheFunctionClass( names , cls );
}
//
// Reset the modules ClassLoader to be a new one child of the current one
private synchronized void chainClassLoader(Shell shell, List<URL> urls) throws CoreException {
mLogger.entry(shell, urls );
synchronized( mClassLoader ){
mClassLoader.add( urls );
}
mLogger.exit( mClassLoader );
}
@Override
protected void finalize() {
// Clear refs
if (mClassCache != null)
mClassCache.clear();
mClassCache = null;
if( mFunctionClassCache != null )
mFunctionClassCache.clear();
mFunctionClassCache = null;
if(mClassCacheMisses != null)
mClassCacheMisses.clear();
mClassCacheMisses = null;
}
protected Class<?> findClass(String className) {
mLogger.entry(className);
// Find cached class name even if null
// This caches failures as well as successes
// Consider changing to a WeakHashMap<> if this uses up too much memory
// caching failed lookups
synchronized( mClassCache ){
if (mClassCache.containsKey(className))
return mClassCache.get(className);
}
Class<?> cls = null;
try {
cls = Class.forName(className, true, getClassLoader());
} catch (ClassNotFoundException e) {
}
cacheClass(className, cls);
return cls;
}
protected Class<?> findClass(String name, List<String> packages) {
for (String pkg : packages) {
Class<?> cls = findClass(Util.isEmpty(pkg) ? name : (pkg + "." + name));
if (cls != null)
return cls;
}
return null;
}
protected Class<?> findFunctionClass(String className) {
synchronized( mFunctionClassCache ){
return mFunctionClassCache.get(className);
}
}
protected Class<?> findCommandClass(String className) {
synchronized( mFunctionClassCache ){
return mFunctionClassCache.get(className);
}
}
protected URL findResourceInPackages(String name, List<String> packages) {
/*
* Undocumented: When using a classloader to get a resource, then the
* name should NOT begin with a "/"
*/
/*
* Get cached indication of if there is a resource by this name
*/
if (hasClassLookupFailed(name))
return null;
for (String pkg : packages) {
URL is = getClassLoader().getResource(toResourceName(name, pkg));
if (is != null) {
setCacheHit(name);
return is;
}
}
setCacheMissed(name);
return null;
}
@Override
public synchronized XClassLoader getClassLoader() {
return mClassLoader;
}
public List<URL> getClassPath() {
return mConfig.getClassPath();
}
@Override
public ModuleConfig getConfig() {
return mConfig;
}
@Override
public URL getHelpURL() {
return null ;
}
protected Logger getLogger() {
return LogManager.getLogger(getClass());
}
@Override
public ModuleConfig getModuleConfig(Shell shell , String name , List<URL> at ) throws Exception {
mLogger.error("NOT IMPLEMENTED");
return null;
}
@Override
public String getName() {
return mConfig.getName();
}
@Override
public URL getResource(String res) {
/*
* Undocumented: When using a classloader to get a resource, then the
* name should NOT begin with a "/"
*/
if (res.startsWith("/"))
res = res.substring(1);
return getClassLoader().getResource(res);
}
@Override
public StaticContext getStaticContext() {
getLogger().entry();
return mStaticContext;
}
public String getTextEncoding() {
return mConfig.getInputTextEncoding();
}
protected boolean hasClassLookupFailed(String name) {
synchronized( mClassCacheMisses ) {
Boolean hasResource = mClassCacheMisses.get(name);
if (hasResource != null && !hasResource.booleanValue())
return true;
return false;
}
}
@Override
public void onInit(Shell shell, List<XValue> args) throws Exception {
getLogger().trace("module {} onInit()", getName());
}
@Override
public void onLoad(Shell shell) {
getLogger().trace("module {} onLoad()", getName());
}
protected void setCacheHit(String name) {
synchronized( mClassCacheMisses ) {
mClassCacheMisses.put(name, true);
}
}
protected void setCacheMissed(String name) {
synchronized( mClassCacheMisses ) {
mClassCacheMisses.put(name, false);
}
}
protected String toResourceName(String name, String pkg) {
if( Util.isBlank(pkg))
return name ;
String resource = pkg.replace(ShellConstants.kDOT_CHAR, '/') + "/" + name;
return resource;
}
@Override
public String toString() {
return describe();
}
}