Package com.google.template.soy.javasrc.dyncompile

Source Code of com.google.template.soy.javasrc.dyncompile.SoyToJavaDynamicCompiler

/*
* Copyright 2010 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.template.soy.javasrc.dyncompile;

import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.template.soy.base.BaseUtils;
import com.google.template.soy.data.SoyData;
import com.google.template.soy.data.SoyMapData;
import com.google.template.soy.javasrc.SoyTemplateRuntime;
import com.google.template.soy.javasrc.SoyTemplateRuntimes;
import com.google.template.soy.shared.SoyCssRenamingMap;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import java.util.Locale;
import java.util.logging.Logger;

import javax.annotation.Nullable;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;


/**
* Dynamically compiles Soy templates to Java classes that are exposed as
* {@link SoyTemplateRuntimes}.
*
* @author Mike Samuel
*/
public final class SoyToJavaDynamicCompiler {

  /** Name of a Java package for compiled Soy templates. */
  static final String PACKAGE_FOR_COMPILED_SOY = "com.google.template.soy.javasrc.dyncompiled";

  /** A logger that receives messages about compilation problems. */
  private static final Logger LOGGER = Logger.getLogger(SoyToJavaDynamicCompiler.class.getName());


  /**
   * @param bundleName The class name used for the bundle of Soy templates.  This will show up in
   *     Java stack traces.
   * @param javaClassBody Java source code as from
   *     {@link com.google.template.soy.SoyFileSet#compileToJavaSrc}.
   */
  public static ImmutableMap<String, SoyTemplateRuntime> compile(
      String bundleName, String javaClassBody) {
    if (!BaseUtils.isDottedIdentifier(bundleName)) {
      throw new IllegalArgumentException(
          "Bundle name should be a dotted identifier, not " + bundleName);
    }

    // The javaClassBody contains one static method per template and the name is the full template
    // name but with dots ('.') replaces with dollar signs ('$').
    // This class relies on that convention, and the parameter convention.
    String javaSourceCode = Joiner.on('\n').join(
        "package " + PACKAGE_FOR_COMPILED_SOY + ";",

        "public final class " + bundleName + " {",
        "  private final " + SoyMapData.class.getName() + " $$ijData;",
        "  private final " + SoyCssRenamingMap.class.getName() + " $$cssRenamingMap;",

        // Receive the injected data and CSS renaming scheme from the SoyTemplateRuntime instance.
        "  public " + bundleName + "(",
        "      " + SoyMapData.class.getName() + " ijData,",
        "      " + SoyCssRenamingMap.class.getName() + " cssRenamingMap) {",
        "    this.$$ijData = ijData;",
        "    this.$$cssRenamingMap = cssRenamingMap;",
        "  }",

        // GenJavaExprsVisitor generates code that uses this method to handle CSS nodes,
        "  private String $$renameCss(String selectorText) {",
        "    return $$cssRenamingMap.get(selectorText);",
        "  }",

        // TranslateToJavaExprVisitor generates code that uses this method to fetch injected data,
        "  private " + SoyData.class.getName() + " $$getIjData(String key) {",
        "    return $$ijData.get(key);",
        "  }",

          javaClassBody,

        "}");


    // Pipe compiler problems to this class's logger.
    DiagnosticListener<JavaFileObject> diagnosticListener =
        new DiagnosticListener<JavaFileObject>() {

      @Override
      public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
        StringBuilder message = new StringBuilder();
        @Nullable JavaFileObject src = diagnostic.getSource();
        if (src != null) {
          // TODO: Map the line and character back to the source inputs.
          message.append(src.getName()).append(':').append(diagnostic.getLineNumber()).append('+')
              .append(diagnostic.getColumnNumber()).append(": ");
        }
        message.append(diagnostic.getMessage(Locale.getDefault()));
        switch (diagnostic.getKind()) {
          case ERROR:
          case OTHER:
            LOGGER.severe(message.toString());
            break;
          case MANDATORY_WARNING:
          case WARNING:
            LOGGER.warning(message.toString());
            break;
          default// OTHER and NOTE are not noteworthy.
            break;
        }
      }

    };


    // Set up a virtual file system for javac that reads the inputs from memory.
    // Temporary files, are slow, error-prone, and can be a vector for injecting malicious code
    // into running systems when an application uses a temporary directory that is accessible by
    // less privileged users than its process owner.
    JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
    DynamicCompilerJavaFileManager fileManager = new DynamicCompilerJavaFileManager(
        javaCompiler.getStandardFileManager(
            diagnosticListener, Locale.getDefault(), Charsets.UTF_8));

    String className = PACKAGE_FOR_COMPILED_SOY + "." + bundleName;
    ReadableInMemoryJavaFileObject inputFile = new ReadableInMemoryJavaFileObject(
        "/src/" + className.replace('.', '/') + ".java", javaSourceCode);

    fileManager.addInput(inputFile);


    // Invoke the compiler.
    boolean wasSuccessfullyCompiled = javaCompiler.getTask(
        null, fileManager, diagnosticListener,
        ImmutableList.<String>of()// Compiler options
        ImmutableList.<String>of()// Classes,
        ImmutableList.of(inputFile))
        .call();

    if (wasSuccessfullyCompiled) {  // True indicates compilation succeeded.
      // Load the compiled classes (in binary bytecode form) into this JVM.
      SoyTemplateClassLoader templateClassLoader = new SoyTemplateClassLoader();

      for (WritableInMemoryJavaFileObject outputClass : fileManager.getOutputFiles()) {
        templateClassLoader.defineClassCompiledFromSoy(outputClass.getByteContent());
      }

      // Now that we've defined the class, we can generate a list of templates, and create our
      // template runtime objects.
      ImmutableMap.Builder<String, SoyTemplateRuntime> runtimes = ImmutableMap.builder();
      try {
        Class<?> compiledClass = templateClassLoader.loadClass(className);

        // Find a constructor that takes the injected data map and CSS renaming map.
        final Constructor<?> ctor;
        try {
          ctor = compiledClass.getDeclaredConstructor(SoyMapData.class, SoyCssRenamingMap.class);
        } catch (NoSuchMethodException ex) {
          throw new IllegalStateException(
              "Could not find ctor for generated java class " + compiledClass);
        }
        if (!Modifier.isPublic(ctor.getModifiers())) {
          throw new AssertionError(ctor.toString());
        }

        // Look for public methods that take a Map and a buffer.
        // For each of these, create a SoyTemplateRuntime.
        for (final Method method : compiledClass.getDeclaredMethods()) {
          int modifiers = method.getModifiers();
          // The Soy methods in the generated Java have the form
          //   public static void soy$namespace$with$dollars$instead$of$dots$TemplateName(
          //       SoyMapData, StringBuilder)
          // Look for these methods and reverse-engineer the Soy template name.
          if (Modifier.isPublic(modifiers)) {
            Class<?>[] paramTypes = method.getParameterTypes();
            if (paramTypes.length == 2 && paramTypes[0].isAssignableFrom(SoyMapData.class) &&
                paramTypes[1].isAssignableFrom(StringBuilder.class)) {
              for (Class<?> thrownType : method.getExceptionTypes()) {
                if (!(Error.class.isAssignableFrom(thrownType) ||
                      RuntimeException.class.isAssignableFrom(thrownType))) {
                  // SoyTemplateRuntime needs to be updated to throw checked exceptions if the
                  // generated Java starts raising checked exceptions.
                  continue;
                }
              }

              String methodName = method.getName();
              final String templateName = methodName.replace('$', '.');

              runtimes.put(templateName, new AbstractSoyTemplateRuntime() {


                @Override
                protected void renderMain(
                    SoyMapData data, SoyMapData ijData, SoyCssRenamingMap cssRenamingMap,
                    StringBuilder out) {
                  try {
                    method.invoke(ctor.newInstance(ijData, cssRenamingMap), data, out);
                  } catch (InvocationTargetException ex) {
                    // Checked above that there are no checked exceptions.
                    Throwables.propagate(ex.getTargetException());
                  } catch (InstantiationException ex) {
                    // Checked above that there are no checked exceptions.
                    Throwables.propagate(ex);
                  } catch (IllegalAccessException ex) {
                    // Checked isPublic above, and the generated class is a public top-level class.
                    Throwables.propagate(ex);
                  }
                }


                @Override
                public String toString() {
                  return "[SoyTemplateRuntime " + templateName + "]";
                }

              });

            }
          }
        }

      } catch (ClassNotFoundException ex) {
        Throwables.propagate(ex)// The class we successfully compiled should be present.
      }

      return runtimes.build();
    } else {
      // TODO: Choose an appropriate way to signal failure, and collect the javac output.
      System.err.println("Java code\n" + javaSourceCode + "\n");
      throw new RuntimeException();
    }
  }


  private SoyToJavaDynamicCompiler() {
    // Uninstantiable.  Consists only of static methods.
  }

}
TOP

Related Classes of com.google.template.soy.javasrc.dyncompile.SoyToJavaDynamicCompiler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.