All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gxp.base.dynamic..svn.text-base.StubGxpTemplate.svn-base Maven / Gradle / Ivy

Go to download

Google XML Pages (GXP) is a templating system used to generate XML/SGML markup (most often HTML).

The newest version!
/*
 * 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 parseFilenames(String... filenames) {
    Set 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 srcGxps,
                                      Set srcSchemas,
                                      Set 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 compileJava(InMemoryFileSystem outFs,
                                                   String classBase,
                                                   final long compilationVersion) {
    // compile java
    DiagnosticCollector diagnosticCollector
        = new DiagnosticCollector();

    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 compilationUnits = ImmutableList.of(compilationUnit);

      javaCompiler.getTask(null, javaFileManager, diagnosticCollector,
                           null, null, compilationUnits).call();

      List> diagnostics =
          filterErrors(diagnosticCollector.getDiagnostics());

      if (!diagnostics.isEmpty()) {
        throw new GxpCompilationException.Java(diagnostics);
      }

      List 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 getMethodMap(Class c) {
    Map map = Maps.newHashMap();
    for (Method method : c.getMethods()) {
      map.put(method.getName(), method);
    }
    return map;
  }

  private static List defineClasses(List classFiles)
      throws Throwable {
    List 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 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 execNoExceptions(Map 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  List> filterErrors(
      List> diagnostics) {
    List> newList = Lists.newArrayList();
    for (Diagnostic 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() {
        public Method run() {
          try {
            Class 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);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy