Java tutorial
/* * 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.ADVISING_APPENDABLE_TYPE; import static com.google.template.soy.jbcsrc.BytecodeUtils.NULLARY_INIT; import static com.google.template.soy.jbcsrc.BytecodeUtils.OBJECT; import static com.google.template.soy.jbcsrc.BytecodeUtils.RENDER_CONTEXT_TYPE; import static com.google.template.soy.jbcsrc.BytecodeUtils.SOY_RECORD_TYPE; import static com.google.template.soy.jbcsrc.FieldRef.createField; import static com.google.template.soy.jbcsrc.FieldRef.createFinalField; 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.IJ_FIELD; import static com.google.template.soy.jbcsrc.StandardNames.PARAMS_FIELD; import static com.google.template.soy.jbcsrc.StandardNames.STATE_FIELD; import static com.google.template.soy.soytree.SoytreeUtils.getAllNodesOfType; import com.google.auto.value.AutoAnnotation; import com.google.common.collect.ImmutableMap; import com.google.template.soy.data.SanitizedContent.ContentKind; import com.google.template.soy.data.SoyRecord; import com.google.template.soy.data.SoyValueProvider; import com.google.template.soy.exprtree.VarRefNode; import com.google.template.soy.jbcsrc.SoyNodeCompiler.CompiledMethodBody; import com.google.template.soy.jbcsrc.shared.CompiledTemplate; import com.google.template.soy.jbcsrc.shared.TemplateMetadata; import com.google.template.soy.soytree.CallBasicNode; import com.google.template.soy.soytree.CallDelegateNode; import com.google.template.soy.soytree.CallParamContentNode; import com.google.template.soy.soytree.CallParamValueNode; import com.google.template.soy.soytree.LetContentNode; import com.google.template.soy.soytree.LetValueNode; import com.google.template.soy.soytree.SoyNode; import com.google.template.soy.soytree.TemplateDelegateNode; import com.google.template.soy.soytree.TemplateNode; import com.google.template.soy.soytree.defn.LocalVar; import com.google.template.soy.soytree.defn.TemplateParam; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; /** * Compiles the top level {@link CompiledTemplate} class for a single template and all related * classes. */ final class TemplateCompiler { private static final AnnotationRef<TemplateMetadata> TEMPLATE_METADATA_REF = AnnotationRef .forType(TemplateMetadata.class); private static final TypeInfo TEMPLATE_TYPE = TypeInfo.create(CompiledTemplate.class); private final CompiledTemplateRegistry registry; private final FieldRef paramsField; private final FieldRef ijField; private final FieldRef stateField; private final UniqueNameGenerator fieldNames = UniqueNameGenerator.forFieldNames(); private final ImmutableMap<String, FieldRef> paramFields; private final CompiledTemplateMetadata template; private final InnerClasses innerClasses; private SoyClassWriter writer; TemplateCompiler(CompiledTemplateRegistry registry, CompiledTemplateMetadata template) { this.registry = registry; this.template = template; TypeInfo ownerType = template.typeInfo(); this.paramsField = createFinalField(ownerType, PARAMS_FIELD, SoyRecord.class).asNonNull(); this.ijField = createFinalField(ownerType, IJ_FIELD, SoyRecord.class).asNonNull(); this.stateField = createField(ownerType, STATE_FIELD, Type.INT_TYPE); this.innerClasses = new InnerClasses(ownerType); fieldNames.claimName(PARAMS_FIELD); fieldNames.claimName(IJ_FIELD); fieldNames.claimName(STATE_FIELD); ImmutableMap.Builder<String, FieldRef> builder = ImmutableMap.builder(); for (TemplateParam param : template.node().getAllParams()) { String name = param.name(); fieldNames.claimName(name); builder.put(name, createFinalField(ownerType, name, SoyValueProvider.class).asNonNull()); } this.paramFields = builder.build(); } /** * Returns the list of classes needed to implement this template. * * <p>For each template, we generate: * <ul> * <li>A {@link com.google.template.soy.jbcsrc.shared.CompiledTemplate.Factory} * <li>A {@link CompiledTemplate} * <li>A SoyAbstractCachingProvider subclass for each {@link LetValueNode} and * {@link CallParamValueNode} * <li>A RenderableThunk subclass for each {@link LetContentNode} and * {@link CallParamContentNode} * </li> * * <p>Note: This will <em>not</em> generate classes for other templates, only the template * configured in the constructor. But it will generate classes that <em>reference</em> the * classes that are generated for other templates. It is the callers responsibility to ensure * that all referenced templates are generated and available in the classloader that ultimately * loads the returned classes. */ Iterable<ClassData> compile() { List<ClassData> classes = new ArrayList<>(); // first generate the factory // TODO(lukes): don't generate factory if the template is private? The factories are only // useful to instantiate templates for calls from java. Soy->Soy calls should invoke // constructors directly. new TemplateFactoryCompiler(template, innerClasses).compile(); writer = SoyClassWriter.builder(template.typeInfo()) .setAccess(Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER + Opcodes.ACC_FINAL).implementing(TEMPLATE_TYPE) .sourceFileName(template.node().getSourceLocation().getFileName()).build(); generateTemplateMetadata(); stateField.defineField(writer); paramsField.defineField(writer); ijField.defineField(writer); for (FieldRef field : paramFields.values()) { field.defineField(writer); } Statement fieldInitializers = generateRenderMethod(); generateConstructor(fieldInitializers); innerClasses.registerAllInnerClasses(writer); writer.visitEnd(); classes.add(writer.toClassData()); classes.addAll(innerClasses.getInnerClassData()); writer = null; return classes; } /** Writes a {@link TemplateMetadata} to the generated class. */ private void generateTemplateMetadata() { ContentKind contentKind = template.node().getContentKind(); String kind = contentKind == null ? "" : contentKind.name(); // using linked hash sets below for determinism Set<String> uniqueIjs = new LinkedHashSet<>(); for (VarRefNode var : getAllNodesOfType(template.node(), VarRefNode.class)) { if (var.isInjected()) { uniqueIjs.add(var.getName()); } } Set<String> callees = new LinkedHashSet<>(); for (CallBasicNode call : getAllNodesOfType(template.node(), CallBasicNode.class)) { callees.add(call.getCalleeName()); } Set<String> delCallees = new LinkedHashSet<>(); for (CallDelegateNode call : getAllNodesOfType(template.node(), CallDelegateNode.class)) { delCallees.add(call.getDelCalleeName()); } TemplateMetadata.DelTemplateMetadata deltemplateMetadata; if (template.node().getKind() == SoyNode.Kind.TEMPLATE_DELEGATE_NODE) { TemplateDelegateNode delegateNode = (TemplateDelegateNode) template.node(); deltemplateMetadata = createDelTemplateMetadata( delegateNode.getDelPackageName() == null ? "" : delegateNode.getDelPackageName(), delegateNode.getDelTemplateName(), delegateNode.getDelTemplateVariant()); } else { deltemplateMetadata = createDefaultDelTemplateMetadata(); } TemplateMetadata metadata = createTemplateMetadata(kind, uniqueIjs, callees, delCallees, deltemplateMetadata); TEMPLATE_METADATA_REF.write(metadata, writer); } @AutoAnnotation static TemplateMetadata createTemplateMetadata(String contentKind, Set<String> injectedParams, Set<String> callees, Set<String> delCallees, TemplateMetadata.DelTemplateMetadata deltemplateMetadata) { return new AutoAnnotation_TemplateCompiler_createTemplateMetadata(contentKind, injectedParams, callees, delCallees, deltemplateMetadata); } @AutoAnnotation static TemplateMetadata.DelTemplateMetadata createDefaultDelTemplateMetadata() { return new AutoAnnotation_TemplateCompiler_createDefaultDelTemplateMetadata(); } @AutoAnnotation static TemplateMetadata.DelTemplateMetadata createDelTemplateMetadata(String delPackage, String name, String variant) { return new AutoAnnotation_TemplateCompiler_createDelTemplateMetadata(delPackage, name, variant); } private Statement generateRenderMethod() { final Label start = new Label(); final Label end = new Label(); final LocalVariable thisVar = createThisVar(template.typeInfo(), start, end); final LocalVariable appendableVar = createLocal("appendable", 1, ADVISING_APPENDABLE_TYPE, start, end) .asNonNullable(); final LocalVariable contextVar = createLocal("context", 2, RENDER_CONTEXT_TYPE, start, end).asNonNullable(); final VariableSet variableSet = new VariableSet(fieldNames, template.typeInfo(), thisVar, template.renderMethod().method()); TemplateNode node = template.node(); TemplateVariables variables = new TemplateVariables(variableSet, thisVar, contextVar); final CompiledMethodBody methodBody = SoyNodeCompiler.create(registry, innerClasses, stateField, thisVar, AppendableExpression.forLocal(appendableVar), variableSet, variables).compile(node); final Statement returnDone = Statement.returnExpression(MethodRef.RENDER_RESULT_DONE.invoke()); new Statement() { @Override void doGen(CodeBuilder adapter) { adapter.mark(start); methodBody.body().gen(adapter); adapter.mark(end); returnDone.gen(adapter); thisVar.tableEntry(adapter); appendableVar.tableEntry(adapter); contextVar.tableEntry(adapter); variableSet.generateTableEntries(adapter); } }.writeIOExceptionMethod(Opcodes.ACC_PUBLIC, template.renderMethod().method(), writer); writer.setNumDetachStates(methodBody.numberOfDetachStates()); variableSet.defineStaticFields(writer); return variableSet.defineFields(writer); } /** * Generate a public constructor that assigns our final field and checks for missing required * params. * * <p>This constructor is called by the generate factory classes. * * @param fieldInitializers additional statements to initialize fields (other than params) */ private void generateConstructor(Statement fieldInitializers) { final Label start = new Label(); final Label end = new Label(); final LocalVariable thisVar = createThisVar(template.typeInfo(), 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 List<Statement> assignments = new ArrayList<>(); assignments.add(fieldInitializers); // for other fields needed by the compiler. assignments.add(paramsField.putInstanceField(thisVar, paramsVar)); assignments.add(ijField.putInstanceField(thisVar, ijVar)); for (final TemplateParam param : template.node().getAllParams()) { Expression paramProvider = getParam(paramsVar, ijVar, param); assignments.add(paramFields.get(param.name()).putInstanceField(thisVar, paramProvider)); } Statement constructorBody = new Statement() { @Override void doGen(CodeBuilder ga) { ga.mark(start); // call super() thisVar.gen(ga); ga.invokeConstructor(OBJECT.type(), NULLARY_INIT); for (Statement assignment : assignments) { assignment.gen(ga); } ga.visitInsn(Opcodes.RETURN); ga.visitLabel(end); thisVar.tableEntry(ga); paramsVar.tableEntry(ga); ijVar.tableEntry(ga); } }; constructorBody.writeMethod(Opcodes.ACC_PUBLIC, template.constructor().method(), writer); } /** * Returns an expression that fetches the given param from the params record or the ij record and * enforces the {@link TemplateParam#isRequired()} flag, throwing SoyDataException if a required * parameter is missing. */ private static Expression getParam(LocalVariable paramsVar, LocalVariable ijVar, TemplateParam param) { Expression fieldName = BytecodeUtils.constant(param.name()); Expression record = param.isInjected() ? ijVar : paramsVar; // NOTE: for compatibility with Tofu and jssrc we do not check for missing required parameters // here instead they will just turn into null. Existing templates depend on this. return MethodRef.RUNTIME_GET_FIELD_PROVIDER.invoke(record, fieldName); } private final class TemplateVariables implements VariableLookup { private final VariableSet variableSet; private final Expression thisRef; private final Expression renderContext; TemplateVariables(VariableSet variableSet, Expression thisRef, Expression renderContext) { this.variableSet = variableSet; this.thisRef = thisRef; this.renderContext = renderContext; } @Override public Expression getParam(TemplateParam param) { return paramFields.get(param.name()).accessor(thisRef); } @Override public Expression getLocal(LocalVar local) { return variableSet.getVariable(local.name()).local(); } @Override public Expression getLocal(SyntheticVarName varName) { return variableSet.getVariable(varName).local(); } @Override public Expression getRenderContext() { return renderContext; } @Override public Expression getParamsRecord() { return paramsField.accessor(thisRef); } @Override public Expression getIjRecord() { return ijField.accessor(thisRef); } } }