/*
* Copyright 2015 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.jbcsrc;
import static com.google.template.soy.jbcsrc.BytecodeUtils.SOY_RECORD_TYPE;
import static com.google.template.soy.jbcsrc.BytecodeUtils.defineDefaultConstructor;
import static com.google.template.soy.jbcsrc.LocalVariable.createLocal;
import static com.google.template.soy.jbcsrc.LocalVariable.createThisVar;
import static com.google.template.soy.jbcsrc.StandardNames.FACTORY_CLASS;
import com.google.template.soy.data.SoyRecord;
import com.google.template.soy.jbcsrc.shared.CompiledTemplate;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.Method;
/**
* Generates {@link com.google.template.soy.jbcsrc.shared.CompiledTemplate.Factory} implementations.
*
* <p>Each factory is incredibly simple, essentially we are generating this class:
*
* <pre>{@code
* public final class FooFactory implements CompiledTemplate.Factory {
* public CompiledTemplate create(SoyRecord params) {
* return new Foo(params);
* }
* }
* }</pre>
*
* <p>Where the only thing that differs is the name of the template being constructed.
*/
final class TemplateFactoryCompiler {
private static final TypeInfo FACTORY_TYPE = TypeInfo.create(CompiledTemplate.Factory.class);
private static final int FACTORY_ACCESS = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL;
private static final Method CREATE_METHOD;
static {
try {
CREATE_METHOD =
Method.getMethod(
CompiledTemplate.Factory.class.getDeclaredMethod(
"create", SoyRecord.class, SoyRecord.class));
} catch (NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
}
private final CompiledTemplateMetadata template;
private final InnerClasses innerClasses;
TemplateFactoryCompiler(CompiledTemplateMetadata currentClass, InnerClasses innerClasses) {
this.template = currentClass;
this.innerClasses = innerClasses;
}
/** Compiles the factory. */
void compile() {
TypeInfo factoryType = innerClasses.registerInnerClass(FACTORY_CLASS, FACTORY_ACCESS);
SoyClassWriter cw =
SoyClassWriter.builder(factoryType)
.implementing(FACTORY_TYPE)
.setAccess(FACTORY_ACCESS)
.sourceFileName(template.node().getSourceLocation().getFileName())
.build();
innerClasses.registerAsInnerClass(cw, factoryType);
generateStaticInitializer(cw);
defineDefaultConstructor(cw, factoryType);
generateCreateMethod(cw, factoryType);
cw.visitEnd();
innerClasses.add(cw.toClassData());
}
/**
* Generates a static initializer that references the CompiledTemplate class to force eager
* classloading (and thus verification errors). For example,
*
* <pre>{@code
* static {
* Class<?> clz = GeneratedTemplateClass.class;
* }
* }</pre>
*/
private void generateStaticInitializer(ClassVisitor cv) {
if (Flags.DEBUG) {
new Statement() {
@Override
void doGen(CodeBuilder adapter) {
adapter.pushType(template.typeInfo().type());
adapter.visitVarInsn(Opcodes.ASTORE, 0);
adapter.returnValue();
}
}.writeMethod(Opcodes.ACC_STATIC, BytecodeUtils.CLASS_INIT, cv);
}
}
/**
* Writes the {@link CompiledTemplate.Factory#create} method, which directly delegates to the
* constructor of the {@link #template}.
*/
private void generateCreateMethod(ClassVisitor cv, TypeInfo factoryType) {
final Label start = new Label();
final Label end = new Label();
final LocalVariable thisVar = createThisVar(factoryType, start, end);
final LocalVariable paramsVar = createLocal("params", 1, SOY_RECORD_TYPE, start, end);
final LocalVariable ijVar = createLocal("ij", 2, SOY_RECORD_TYPE, start, end);
final Statement returnTemplate =
Statement.returnExpression(template.constructor().construct(paramsVar, ijVar));
new Statement() {
@Override
void doGen(CodeBuilder ga) {
ga.mark(start);
returnTemplate.gen(ga);
ga.mark(end);
thisVar.tableEntry(ga);
paramsVar.tableEntry(ga);
ijVar.tableEntry(ga);
}
}.writeMethod(Opcodes.ACC_PUBLIC, CREATE_METHOD, cv);
}
}