Java tutorial
/* * Copyright (C) 2008 Google Inc. * * Licensed 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 com.google.gxp.base.dynamic; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.io.CharStreams; import com.google.common.io.ByteStreams; import com.google.gxp.base.GxpTemplate; import com.google.gxp.compiler.Compiler; import com.google.gxp.compiler.Configuration; import com.google.gxp.compiler.InvalidConfigException; import com.google.gxp.compiler.alerts.AlertPolicy; import com.google.gxp.compiler.alerts.AlertSet; import com.google.gxp.compiler.fs.FileRef; import com.google.gxp.compiler.fs.FileSystem; import com.google.gxp.compiler.fs.InMemoryFileSystem; import com.google.gxp.compiler.fs.JavaFileManagerImpl; import com.google.gxp.compiler.fs.JavaFileRef; import com.google.gxp.compiler.fs.SystemFileSystem; 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 java.io.ByteArrayInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.ProtectionDomain; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Base class for all GxpTemplates that compile their source .gxp at runtime. */ public class StubGxpTemplate extends GxpTemplate { // system filesystem // can be changed for testing protected static FileSystem systemFS = SystemFileSystem.INSTANCE; public static void setSystemFileSystem(FileSystem systemFS) { StubGxpTemplate.systemFS = systemFS; } public static void setJavaCompiler(JavaCompiler javaCompiler) { StubGxpTemplate.javaCompiler = javaCompiler; } protected static FileRef parseFilename(String filename) { return systemFS.parseFilename(filename); } protected static Set<FileRef> parseFilenames(String... filenames) { Set<FileRef> fileRefs = Sets.newHashSet(); for (String filename : filenames) { fileRefs.add(parseFilename(filename)); } return fileRefs; } // java compiler // can be changed for testing private static JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); /** * Reconstruct an {@code AlertPolicy} that has been serialized to a byte array. */ protected static AlertPolicy createAlertPolicy(byte[] bytes) { try { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais); return (AlertPolicy) ois.readObject(); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } protected static FileRef compileGxp(InMemoryFileSystem outFs, Set<FileRef> srcGxps, Set<FileRef> srcSchemas, Set<FileRef> srcPaths, String javaBase, long compilationVersion, AlertPolicy alertPolicy) throws GxpCompilationException { String javaFile = javaBase + compilationVersion + ".java"; Configuration configuration = new RuntimeConfiguration(systemFS, outFs, srcGxps, srcSchemas, srcPaths, javaFile, compilationVersion, alertPolicy); try { // Perform GXP Compilation AlertSet alertSet = new Compiler(configuration).call(); // check for gxp compilation errors if (alertSet.hasErrors(alertPolicy)) { throw new GxpCompilationException.Gxp(alertPolicy, alertSet); } } catch (InvalidConfigException e) { throw new GxpCompilationException.Throw(e); } return outFs.parseFilename(javaFile); } protected static Map<String, Method> compileJava(InMemoryFileSystem outFs, String classBase, final long compilationVersion) { // compile java DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>(); JavaFileManager javaFileManager = new JavaFileManagerImpl( javaCompiler.getStandardFileManager(diagnosticCollector, Locale.US, Charsets.US_ASCII), outFs); String className = classBase + compilationVersion; try { JavaFileObject compilationUnit = javaFileManager.getJavaFileForInput(StandardLocation.SOURCE_PATH, className, JavaFileObject.Kind.SOURCE); Iterable<JavaFileObject> compilationUnits = ImmutableList.of(compilationUnit); javaCompiler.getTask(null, javaFileManager, diagnosticCollector, null, 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 : outFs.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(ByteStreams.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 scheme, 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); // get methods return getMethodMap(c); } catch (GxpCompilationException e) { throw e; } catch (Throwable e) { throw new GxpCompilationException.Throw(e); } } protected static Map<String, Method> getMethodMap(Class<?> c) { Map<String, Method> map = Maps.newHashMap(); for (Method method : c.getMethods()) { map.put(method.getName(), method); } return map; } private static List<byte[]> defineClasses(List<byte[]> classFiles) throws Throwable { List<byte[]> failures = Lists.newArrayList(); for (byte[] classFile : classFiles) { try { defineClass(classFile); } catch (NoClassDefFoundError e) { failures.add(classFile); } } return failures; } // TODO(harryh): Consider strategies for detecting and generating an error // when, at runtime, two parameters of the same type but different // names are swapped. Name mangling of the method name with a // parameters name list might be a good way to do this. protected static Object exec(Map<String, Method> methods, String function, Object[] args) throws Throwable { try { return methods.get(function).invoke(null, args); } catch (InvocationTargetException e) { throw e.getCause(); } catch (IllegalArgumentException e) { throw new GxpCompilationException.GxpParamChange(e); } catch (Exception e) { throw new GxpCompilationException.Throw(e); } } protected static <T> T execNoExceptions(Map<String, Method> methods, String function, Object[] args) { try { @SuppressWarnings("unchecked") T result = (T) exec(methods, function, args); return result; } catch (Error e) { throw e; } catch (RuntimeException e) { throw e; } catch (Throwable t) { throw new GxpCompilationException.Throw(t); } } /** * @return a filtered list of {@code Diagnostic}s that only contains * errors. */ 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 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(); } } }); private static final ProtectionDomain PROTECTION_DOMAIN = StubGxpTemplate.class.getProtectionDomain(); /** * 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 { Object[] args = new Object[] { null, classFile, new Integer(0), new Integer(classFile.length), PROTECTION_DOMAIN }; try { return (Class<?>) DEFINE_CLASS.invoke(ClassLoader.getSystemClassLoader(), args); } catch (InvocationTargetException e) { throw e.getCause(); } } /** * The pattern for a line directive; 1->file 2->line 3->col. */ private final static Pattern LINE_DIRECTIVE = Pattern.compile("^.* // (.*): L(\\d*), C(\\d*)$"); /** * Examine each element of the stack trace that belongs to this throwable * looking for a filename that matches the source file for this template. * If we find a match, rewrite the filename and line number. The line number * is based on line # comments in the source file. */ protected static void rewriteStackTraceElements(Throwable throwable, FileRef sourceFile) { try { if (sourceFile != null) { String sourceFileName = sourceFile.getName().substring(1).replace('/', '.'); StackTraceElement[] stackTrace = throwable.getStackTrace(); for (int i = 0; i < stackTrace.length; i++) { if (sourceFileName.equals(stackTrace[i].getFileName())) { // get source name String[] parts = sourceFileName.split("[\\.\\$]"); String sourceName = parts[parts.length - 3] + ".gxp"; // get source line String line = CharStreams.readLines(sourceFile.openReader(Charsets.UTF_8)) .get(stackTrace[i].getLineNumber() - 1); Matcher m = LINE_DIRECTIVE.matcher(line); int sourceLine = m.find() ? Integer.valueOf(m.group(2)) : -1; // fix class name String className = stackTrace[i].getClassName().split("\\$")[0]; stackTrace[i] = new StackTraceElement(className, stackTrace[i].getMethodName(), sourceName, sourceLine); throwable.setStackTrace(stackTrace); return; } } } } catch (Exception e) { throw new AssertionError(e); } } }