blob: a2abd9137c7237fba8a73cfcf1bc3d9b492d5545 [file] [log] [blame]
package org.apache.struts2.gxp.template;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import com.google.gxp.base.GxpContext;
import com.google.gxp.base.dynamic.GxpCompilationException;
import com.google.gxp.base.dynamic.StubGxpTemplate;
import com.google.gxp.com.google.common.base.Charsets;
import com.google.gxp.com.google.common.collect.ImmutableList;
import com.google.gxp.com.google.common.collect.Lists;
import com.google.gxp.com.google.common.io.Bytes;
import com.google.gxp.compiler.Compiler;
import com.google.gxp.compiler.InvalidConfigException;
import com.google.gxp.compiler.alerts.AlertSink;
import com.google.gxp.compiler.alerts.ConfigurableAlertPolicy;
import com.google.gxp.compiler.alerts.PrintingAlertSink;
import com.google.gxp.compiler.cli.Gxpc;
import com.google.gxp.compiler.fs.FileRef;
import com.google.gxp.compiler.fs.JavaFileManagerImpl;
import com.google.gxp.compiler.fs.JavaFileRef;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
public abstract class GxpcUtil {
private static final Logger LOG = LoggerFactory.getLogger(GxpcUtil.class);
private static final Method DEFINE_CLASS = AccessController.doPrivileged(new PrivilegedAction<Method>() {
public Method run() {
try {
Class<ClassLoader> loader = ClassLoader.class;
Method m = loader.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class,
Integer.TYPE, Integer.TYPE, ProtectionDomain.class });
m.setAccessible(true);
return m;
} catch (NoSuchMethodException e) {
throw new RuntimeException();
}
}
});
public static void buildAndExec(String srcPath, String file, boolean dynamic, Appendable appendable,
GxpContext context) throws ClassNotFoundException, IllegalArgumentException, IllegalAccessException,
InvocationTargetException, InstantiationException, InvalidConfigException {
// create instance of template
String className = toJavaFileName(file);
Class clazz = null;
try {
clazz = Class.forName(className);
} catch (Exception e) {
// ok ok, lets compile it
}
if (clazz == null)
clazz = build(srcPath, file, dynamic);
Method method = getWriteMethod(clazz);
method.invoke(clazz.newInstance(), appendable, context);
}
private static Method getWriteMethod(Class clazz) {
try {
return clazz.getMethod("write", Appendable.class, GxpContext.class);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static Class build(String srcPath, String file, boolean dynamic) throws InvalidConfigException {
ConfigurableAlertPolicy alertPolicy = new ConfigurableAlertPolicy();
alertPolicy.setTreatWarningsAsErrors(false);
AlertSink alertSink = new PrintingAlertSink(alertPolicy, true, System.err);
GxpcConfiguration.Builder builder = new GxpcConfiguration.Builder();
builder.setSrcpaths(srcPath);
builder.setDynamic(dynamic);
builder.setAlertPolicy(alertPolicy);
List<String> fullPathFiles = new ArrayList<String>();
fullPathFiles.add(srcPath + "/" + file);
GxpcConfiguration configuration = builder.build(fullPathFiles);
Compiler compiler = new Compiler(configuration);
compiler.call(alertSink);
// compile java files
return compileJava(file.substring(0, file.indexOf(".")), configuration);
}
private static String toJavaFileName(String file) {
return file.substring(0, file.indexOf(".")).replace("/", ".");
}
private static Class compileJava(String javaFile, GxpcConfiguration configuration) {
// compile java
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>();
JavaFileManager javaFileManager = new JavaFileManagerImpl(javaCompiler.getStandardFileManager(
diagnosticCollector, Locale.US, Charsets.US_ASCII), configuration.getMemoryFileSystem());
try {
String className = javaFile.replace("/", ".");
JavaFileObject compilationUnit = javaFileManager.getJavaFileForInput(StandardLocation.SOURCE_PATH,
javaFile, JavaFileObject.Kind.SOURCE);
Iterable<JavaFileObject> compilationUnits = ImmutableList.of(compilationUnit);
// find the GXP jar file and add it to the classpath
ProtectionDomain protectionDomain = Gxpc.class.getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
File gxpJar = new File(codeSource.getLocation().getFile());
List<String> optionList = new ArrayList<String>();
optionList.addAll(Arrays.asList("-classpath", gxpJar.getAbsolutePath()));
javaCompiler.getTask(null, javaFileManager, diagnosticCollector, optionList, null, compilationUnits)
.call();
List<Diagnostic<? extends JavaFileObject>> diagnostics = filterErrors(diagnosticCollector
.getDiagnostics());
if (!diagnostics.isEmpty()) {
throw new GxpCompilationException.Java(diagnostics);
}
List<byte[]> classFiles = Lists.newArrayList();
for (FileRef fileRef : configuration.getMemoryFileSystem().getManifest()) {
if (fileRef.getKind().equals(JavaFileObject.Kind.CLASS)) {
String outputClassName = javaFileManager.inferBinaryName(StandardLocation.CLASS_OUTPUT,
new JavaFileRef(fileRef));
if (outputClassName.equals(className) || outputClassName.startsWith(className + "$")) {
classFiles.add(Bytes.toByteArray(fileRef.openInputStream()));
}
}
}
// A single java compile can generate many .class files due to inner
// classes, and it
// is difficult to know what order to load them in to avoid
// NoClassDefFoundErrors,
// so what we do is go through the whole list attempting to load
// them all, keeping
// track of which ones file with NoClassDefFoundError. Then we loop
// and try again.
// This should eventually work no matter what order the files come
// in.
//
// We have an additional check to make sure that at least one file
// is loaded each
// time through the loop to prevent infinite looping.
//
// I'm not entirely happy with this schema, but it's the best I can
// come up with
// for now.
int oldCount, newCount;
do {
oldCount = classFiles.size();
classFiles = defineClasses(classFiles);
newCount = classFiles.size();
} while (newCount != 0 && newCount != oldCount);
// get the main class generated durring this compile
Class c = Class.forName(className);
return c;
} catch (GxpCompilationException e) {
throw e;
} catch (Throwable e) {
throw new GxpCompilationException.Throw(e);
}
}
private static <T> List<Diagnostic<? extends T>> filterErrors(List<Diagnostic<? extends T>> diagnostics) {
List<Diagnostic<? extends T>> newList = Lists.newArrayList();
for (Diagnostic<? extends T> diagnostic : diagnostics) {
if (diagnostic.getKind().equals(Diagnostic.Kind.ERROR)) {
newList.add(diagnostic);
}
}
return Collections.unmodifiableList(newList);
}
private static List<byte[]> defineClasses(List<byte[]> classFiles) throws Throwable {
List<byte[]> failures = Lists.newArrayList();
for (byte[] classFile : classFiles) {
try {
Class clazz = defineClass(classFile);
LOG.debug("Template class [#0] loaded", clazz.getName());
} catch (NoClassDefFoundError e) {
failures.add(classFile);
}
}
return failures;
}
/**
* Define a class using the SystemClassLoader so that the class has access
* to package private items in its java package.
*/
private static Class defineClass(byte[] classFile) throws Throwable {
ProtectionDomain PROTECTION_DOMAIN = StubGxpTemplate.class.getProtectionDomain();
Object[] args = new Object[] { null, classFile, new Integer(0), new Integer(classFile.length),
PROTECTION_DOMAIN };
try {
return (Class) DEFINE_CLASS.invoke(Thread.currentThread().getContextClassLoader(), args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
}