blob: f4f093e405b43152ea74e9bafea2c8ed12d77732 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.jci2.compilers;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.jci2.problems.CompilationProblem;
import org.apache.commons.jci2.readers.ResourceReader;
import org.apache.commons.jci2.stores.ResourceStore;
import org.apache.commons.jci2.utils.ConversionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.GeneratedClassLoader;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.optimizer.ClassCompiler;
/**
* @author tcurdt
*/
public final class RhinoJavaCompiler extends AbstractJavaCompiler {
private final Log log = LogFactory.getLog(RhinoJavaCompiler.class);
private final JavaCompilerSettings defaultSettings;
public RhinoJavaCompiler() {
defaultSettings = new RhinoJavaCompilerSettings();
}
/**
* based on code from dev.helma.org
* http://dev.helma.org/source/file/helma/branches/rhinoloader/src/org/helma/javascript/RhinoLoader.java/?revision=95
*/
private final class RhinoCompilingClassLoader extends ClassLoader {
private final ScriptableObject scope;
private final ResourceReader reader;
private final ResourceStore store;
private final Collection<CompilationProblem> problems = new ArrayList<CompilationProblem>();
private final class ProblemCollector implements ErrorReporter {
public void error(String pMessage, String pFileName, int pLine, String pScript, int pColumn) {
final CompilationProblem problem = new RhinoCompilationProblem(pMessage, pFileName, pLine, pScript, pColumn, true);
if (problemHandler != null) {
problemHandler.handle(problem);
}
problems.add(problem);
}
public void warning(String pMessage, String pFileName, int pLine, String pScript, int pColumn) {
final CompilationProblem problem = new RhinoCompilationProblem(pMessage, pFileName, pLine, pScript, pColumn, false);
if (problemHandler != null) {
problemHandler.handle(problem);
}
problems.add(problem);
}
public EvaluatorException runtimeError(String pMessage, String pFileName, int pLine, String pScript, int pColumn) {
return new EvaluatorException(pMessage, pFileName, pLine, pScript, pColumn);
}
}
public RhinoCompilingClassLoader( final ResourceReader pReader, final ResourceStore pStore, final ClassLoader pClassLoader) {
super(pClassLoader);
reader = pReader;
store = pStore;
final Context context = Context.enter();
scope = new ImporterTopLevel(context);
Context.exit();
}
public Collection<CompilationProblem> getProblems() {
return problems;
}
@Override
protected Class<?> findClass( final String pName ) throws ClassNotFoundException {
final Context context = Context.enter();
context.setErrorReporter(new ProblemCollector());
try {
return compileClass(context, pName);
} catch( EvaluatorException e ) {
throw new ClassNotFoundException(e.getMessage(), e);
} catch (IOException e) {
throw new ClassNotFoundException(e.getMessage(), e);
} finally {
Context.exit();
}
}
private Class<?> compileClass( final Context pContext, final String pClassName) throws IOException, ClassNotFoundException {
Class<?> superclass = null;
final String pSourceName = pClassName.replace('.', '/') + ".js";
final Scriptable target = evaluate(pContext, pSourceName);
final Object baseClassName = ScriptableObject.getProperty(target, "__extends__");
if (baseClassName instanceof String) {
superclass = Class.forName((String) baseClassName);
}
final List<Class<?>> interfaceClasses = new ArrayList<Class<?>>();
final Object interfaceNames = ScriptableObject.getProperty(target, "__implements__");
if (interfaceNames instanceof NativeArray) {
final NativeArray interfaceNameArray = (NativeArray) interfaceNames;
for (int i=0; i<interfaceNameArray.getLength(); i++) {
final Object obj = interfaceNameArray.get(i, interfaceNameArray);
if (obj instanceof String) {
interfaceClasses.add(Class.forName((String) obj));
}
}
} else if (interfaceNames instanceof String) {
interfaceClasses.add(Class.forName((String) interfaceNames));
}
final Class<?>[] interfaces;
if (!interfaceClasses.isEmpty()) {
interfaces = new Class[interfaceClasses.size()];
interfaceClasses.toArray(interfaces);
} else {
// FIXME: hm ...really no empty array good enough?
interfaces = null;
}
return compileClass(pContext, pSourceName, pClassName, superclass, interfaces);
}
private Class<?> compileClass( final Context pContext, final String pSourceName, final String pClassName, final Class<?> pSuperClass, final Class<?>[] pInterfaces) {
final CompilerEnvirons environments = new CompilerEnvirons();
environments.initFromContext(pContext);
final ClassCompiler compiler = new ClassCompiler(environments);
if (pSuperClass != null) {
compiler.setTargetExtends(pSuperClass);
}
if (pInterfaces != null) {
compiler.setTargetImplements(pInterfaces);
}
final byte[] sourceBytes = reader.getBytes(pSourceName);
final Object[] classes = compiler.compileToClassFiles(new String(sourceBytes), getName(pSourceName), 1, pClassName);
final GeneratedClassLoader loader = pContext.createClassLoader(pContext.getApplicationClassLoader());
Class<?> clazz = null;
for (int i = 0; i < classes.length; i += 2) {
final String clazzName = (String) classes[i];
final byte[] clazzBytes = (byte[]) classes[i+1];
store.write(clazzName.replace('.', '/') + ".class", clazzBytes);
Class<?> c = loader.defineClass(clazzName, clazzBytes);
loader.linkClass(c);
if (i == 0) {
clazz = c;
}
}
return clazz;
}
private String getName(String s) {
final int i = s.lastIndexOf('/');
if (i < 0) {
return s;
}
return s.substring(i + 1);
}
private Scriptable evaluate( final Context pContext, final String pSourceName) throws JavaScriptException, IOException {
if (!reader.isAvailable(pSourceName)) {
throw new FileNotFoundException("File " + pSourceName + " not found");
}
final Scriptable target = pContext.newObject(scope);
final byte[] sourceBytes = reader.getBytes(pSourceName);
final Reader reader = new InputStreamReader(new ByteArrayInputStream(sourceBytes));
pContext.evaluateReader(target, reader, getName(pSourceName), 1, null);
return target;
}
}
public CompilationResult compile( final String[] pResourcePaths, final ResourceReader pReader, final ResourceStore pStore, final ClassLoader pClassLoader, final JavaCompilerSettings pSettings ) {
final RhinoCompilingClassLoader cl = new RhinoCompilingClassLoader(pReader, pStore, pClassLoader);
for (int i = 0; i < pResourcePaths.length; i++) {
log.debug("compiling " + pResourcePaths[i]);
final String clazzName = ConversionUtils.convertResourceToClassName(pResourcePaths[i]);
try {
cl.loadClass(clazzName);
} catch (ClassNotFoundException e) {
}
}
final Collection<CompilationProblem> problems = cl.getProblems();
final CompilationProblem[] result = new CompilationProblem[problems.size()];
problems.toArray(result);
return new CompilationResult(result);
}
public JavaCompilerSettings createDefaultSettings() {
return defaultSettings;
}
}