/*
* 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.javacompiler;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import org.apache.log4j.Logger;
import com.aionemu.commons.scripting.CompilationResult;
import com.aionemu.commons.scripting.ScriptClassLoader;
import com.aionemu.commons.scripting.ScriptCompiler;
/**
* Wrapper for JavaCompiler api
*
* @author SoulKeeper
*/
public class ScriptCompilerImpl implements ScriptCompiler
{
/**
* Logger for this class
*/
private static final Logger log = Logger.getLogger(ScriptCompilerImpl.class);
/**
* Instance of JavaCompiler that will be used to compile classes
*/
protected final JavaCompiler javaCompiler;
/**
* List of jar files
*/
protected Iterable<File> libraries;
/**
* Parent classloader that has to be used for this compiler
*/
protected ScriptClassLoader parentClassLoader;
/**
* Creates new instance of JavaCompilerImpl. If system compiler is not available - throws RuntimeExcetion
*
* @throws RuntimeException
* if compiler is not available
*/
public ScriptCompilerImpl()
{
this.javaCompiler = ToolProvider.getSystemJavaCompiler();
if(javaCompiler == null)
{
if(ToolProvider.getSystemJavaCompiler() != null)
{
throw new RuntimeException(new InstantiationException("JavaCompiler is not aviable."));
}
}
}
/**
* Sets parent classLoader for this JavaCompilerImpl
*
* @param classLoader
* parent classloader
*/
@Override
public void setParentClassLoader(ScriptClassLoader classLoader)
{
this.parentClassLoader = classLoader;
}
/**
* Sets jar files that should be used for this compiler as libraries
*
* @param files
* list of jar files
*/
@Override
public void setLibraires(Iterable<File> files)
{
libraries = files;
}
/**
* Compiles given class.
*
* @param className
* Name of the class
* @param sourceCode
* source code
* @return CompilationResult with the class
* @throws RuntimeException
* if compilation failed with errros
*/
@Override
public CompilationResult compile(String className, String sourceCode)
{
return compile(new String[] { className }, new String[] { sourceCode });
}
/**
* Compiles list of classes. Amount of classNames must be equal to amount of sourceCodes
*
* @param classNames
* classNames
* @param sourceCode
* list of source codes
* @return CompilationResult with needed files
* @throws IllegalArgumentException
* if size of classNames not equals to size of sourceCodes
* @throws RuntimeException
* if compilation failed with errros
*/
@Override
public CompilationResult compile(String[] classNames, String[] sourceCode) throws IllegalArgumentException
{
if(classNames.length != sourceCode.length)
{
throw new IllegalArgumentException("Amount of classes is not equal to amount of sources");
}
List<JavaFileObject> compilationUnits = new ArrayList<JavaFileObject>();
for(int i = 0; i < classNames.length; i++)
{
JavaFileObject compilationUnit = new JavaSourceFromString(classNames[i], sourceCode[i]);
compilationUnits.add(compilationUnit);
}
return doCompilation(compilationUnits);
}
/**
* Compiles given files. Files must be java sources.
*
* @param compilationUnits
* files to compile
* @return CompilationResult with classes
* @throws RuntimeException
* if compilation failed with errros
*/
@Override
public CompilationResult compile(Iterable<File> compilationUnits)
{
List<JavaFileObject> list = new ArrayList<JavaFileObject>();
for(File f : compilationUnits)
{
list.add(new JavaSourceFromFile(f, JavaFileObject.Kind.SOURCE));
}
return doCompilation(list);
}
/**
* Actually performs compilation. Compiler expects sources in UTF-8 encoding. Also compiler generates full debugging
* info for classes.
*
* @param compilationUnits
* Units that will be compiled
* @return CompilationResult with compiledClasses
* @throws RuntimeException
* if compilation failed with errros
*/
@SuppressWarnings("unchecked")
protected CompilationResult doCompilation(Iterable<JavaFileObject> compilationUnits)
{
List<String> options = Arrays.asList("-encoding", "UTF-8", "-g");
DiagnosticListener<JavaFileObject> listener = new ErrorListener();
ClassFileManager manager = new ClassFileManager(javaCompiler, listener);
manager.setParentClassLoader(parentClassLoader);
if(libraries != null)
{
try
{
manager.addLibraries(libraries);
}
catch(IOException e)
{
log.error("Can't set libraries for compiler.", e);
}
}
JavaCompiler.CompilationTask task = javaCompiler.getTask(null, manager, listener, options, null,
compilationUnits);
if(!task.call())
{
throw new RuntimeException("Error while compiling classes");
}
ScriptClassLoader cl = manager.getClassLoader(null);
Class[] compiledClasses = classNamesToClasses(manager.getCompiledClasses().keySet(), cl);
return new CompilationResult(compiledClasses, cl);
}
/**
* Reolves list of classes by their names
*
* @param classNames
* names of the classes
* @param cl
* classLoader to use to resove classes
* @return resolved classes
* @throws RuntimeException
* if can't find class
*/
@SuppressWarnings("unchecked")
protected Class[] classNamesToClasses(Collection<String> classNames, ScriptClassLoader cl)
{
Class<?>[] classes = new Class<?>[classNames.size()];
int i = 0;
for(String className : classNames)
{
try
{
Class<?> clazz = cl.loadClass(className);
classes[i] = clazz;
}
catch(ClassNotFoundException e)
{
throw new RuntimeException(e);
}
i++;
}
return classes;
}
/**
* Only java files are supported by java compiler
*
* @return "java";
*/
@Override
public String[] getSupportedFileTypes()
{
return new String[] { "java" };
}
}