/*
* This file is part of aion-emu <aion-emu.com>.
*
* aion-emu is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* aion-emu is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with aion-emu. If not, see <http://www.gnu.org/licenses/>.
*/
package com.aionemu.commons.scripting.impl;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import com.aionemu.commons.scripting.CompilationResult;
import com.aionemu.commons.scripting.ScriptCompiler;
import com.aionemu.commons.scripting.ScriptContext;
import com.aionemu.commons.scripting.classlistener.ClassListener;
import com.aionemu.commons.scripting.classlistener.DefaultClassListener;
/**
* This class is actual implementation of {@link com.aionemu.commons.scripting.ScriptContext}
*
* @author SoulKeeper
*/
public class ScriptContextImpl implements ScriptContext
{
/**
* logger for this class
*/
private static final Logger log = Logger.getLogger(ScriptContextImpl.class);
/**
* Script context that is parent for this script context
*/
private final ScriptContext parentScriptContext;
/**
* Libraries (list of jar files) that have to be loaded class loader
*/
private Iterable<File> libraries;
/**
* Root directory of this script context. It and it's subdirectories will be scanned for .java files.
*/
private final File root;
/**
* Result of compilation of script context
*/
private CompilationResult compilationResult;
/**
* List of child script contexts
*/
private Set<ScriptContext> childScriptContexts;
/**
* Classlistener for this script context
*/
private ClassListener classListener;
/**
* Class name of the compiler that will be used to compile sources
*/
private String compilerClassName;
/**
* Creates new scriptcontext with given root file
*
* @param root
* file that represents root directory of this script context
* @throws NullPointerException
* if root is null
* @throws IllegalArgumentException
* if root directory doesn't exists or is not a directory
*/
public ScriptContextImpl(File root)
{
this(root, null);
}
/**
* Creates new ScriptContext with given file as root and another ScriptContext as parent
*
* @param root
* file that represents root directory of this script context
* @param parent
* parent ScriptContex. It's classes and libraries will be accessible for this script context
* @throws NullPointerException
* if root is null
* @throws IllegalArgumentException
* if root directory doesn't exists or is not a directory
*/
public ScriptContextImpl(File root, ScriptContext parent)
{
if(root == null)
{
throw new NullPointerException("Root file must be specified");
}
if(!root.exists() || !root.isDirectory())
{
throw new IllegalArgumentException("Root directory not exists or is not a directory");
}
this.root = root;
this.parentScriptContext = parent;
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void init()
{
if(compilationResult != null)
{
log.error(new Exception("Init request on initialized ScriptContext"));
return;
}
ScriptCompiler scriptCompiler = instantiateCompiler();
@SuppressWarnings("unchecked")
Collection<File> files = FileUtils.listFiles(root, scriptCompiler.getSupportedFileTypes(), true);
if(parentScriptContext != null)
{
scriptCompiler.setParentClassLoader(parentScriptContext.getCompilationResult().getClassLoader());
}
scriptCompiler.setLibraires(libraries);
compilationResult = scriptCompiler.compile(files);
getClassListener().postLoad(compilationResult.getCompiledClasses());
if(childScriptContexts != null)
{
for(ScriptContext context : childScriptContexts)
{
context.init();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void shutdown()
{
if(compilationResult == null)
{
log.error("Shutdown of not initialized stript context", new Exception());
return;
}
if(childScriptContexts != null)
{
for(ScriptContext child : childScriptContexts)
{
child.shutdown();
}
}
getClassListener().preUnload(compilationResult.getCompiledClasses());
compilationResult = null;
}
/**
* {@inheritDoc}
*/
@Override
public void reload()
{
shutdown();
init();
}
/**
* {@inheritDoc}
*/
@Override
public File getRoot()
{
return root;
}
/**
* {@inheritDoc}
*/
@Override
public CompilationResult getCompilationResult()
{
return compilationResult;
}
/**
* {@inheritDoc}
*/
@Override
public synchronized boolean isInitialized()
{
return compilationResult != null;
}
/**
* {@inheritDoc}
*/
@Override
public void setLibraries(Iterable<File> files)
{
this.libraries = files;
}
/**
* {@inheritDoc}
*/
@Override
public Iterable<File> getLibraries()
{
return libraries;
}
/**
* {@inheritDoc}
*/
@Override
public ScriptContext getParentScriptContext()
{
return parentScriptContext;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<ScriptContext> getChildScriptContexts()
{
return childScriptContexts;
}
/**
* {@inheritDoc}
*/
@Override
public void addChildScriptContext(ScriptContext context)
{
synchronized(this)
{
if(childScriptContexts == null)
{
childScriptContexts = new HashSet<ScriptContext>();
}
if(childScriptContexts.contains(context))
{
log.error("Double child definition, root: " + root.getAbsolutePath() + ", child: "
+ context.getRoot().getAbsolutePath());
return;
}
if(isInitialized())
{
context.init();
}
}
childScriptContexts.add(context);
}
/**
* {@inheritDoc}
*/
@Override
public void setClassListener(ClassListener cl)
{
classListener = cl;
}
/**
* {@inheritDoc}
*/
@Override
public ClassListener getClassListener()
{
if(classListener == null)
{
if(getParentScriptContext() == null)
{
setClassListener(new DefaultClassListener());
return classListener;
}
else
{
return getParentScriptContext().getClassListener();
}
}
else
{
return classListener;
}
}
/**
* {@inheritDoc}
*/
@Override
public void setCompilerClassName(String className)
{
this.compilerClassName = className;
}
/**
* {@inheritDoc}
*/
@Override
public String getCompilerClassName()
{
return this.compilerClassName;
}
/**
* Creates new instance of ScriptCompiler that should be used with this ScriptContext
*
* @return instance of ScriptCompiler
* @throws RuntimeException
* if failed to create instance
*/
protected ScriptCompiler instantiateCompiler() throws RuntimeException
{
ClassLoader cl = getClass().getClassLoader();
if(getParentScriptContext() != null)
{
cl = getParentScriptContext().getCompilationResult().getClassLoader();
}
ScriptCompiler sc;
try
{
sc = (ScriptCompiler) Class.forName(getCompilerClassName(), true, cl).newInstance();
}
catch(Exception e)
{
RuntimeException e1 = new RuntimeException("Can't create instance of compiler", e);
log.error(e1);
throw e1;
}
return sc;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj)
{
if(!(obj instanceof ScriptContextImpl))
{
return false;
}
ScriptContextImpl another = (ScriptContextImpl) obj;
if(parentScriptContext == null)
{
return another.getRoot().equals(root);
}
else
{
return another.getRoot().equals(root) && parentScriptContext.equals(another.parentScriptContext);
}
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
int result = parentScriptContext != null ? parentScriptContext.hashCode() : 0;
result = 31 * result + root.hashCode();
return result;
}
/**
* {@inheritDoc}
*/
@Override
public void finalize() throws Throwable
{
if(compilationResult != null)
{
log.error("Finalization of initialized ScriptContext. Forcing context shutdown.");
shutdown();
}
super.finalize();
}
}