/*
* Copyright 2014 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.sharedpasses.render;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.template.soy.data.SoyRecord;
import com.google.template.soy.data.SoyValue;
import com.google.template.soy.data.SoyValueProvider;
import com.google.template.soy.data.restricted.BooleanData;
import com.google.template.soy.data.restricted.IntegerData;
import com.google.template.soy.data.restricted.NullData;
import com.google.template.soy.data.restricted.UndefinedData;
import com.google.template.soy.exprtree.VarDefn;
import com.google.template.soy.exprtree.VarDefn.Kind;
import com.google.template.soy.soytree.TemplateNode;
import com.google.template.soy.soytree.defn.LoopVar;
import com.google.template.soy.soytree.defn.TemplateParam;
/**
* The local variable table.
*
* <p>All declared {@code @param}s and {@code {let ...}} statements define variables that are stored
* in a table. The mapping between local variable and
*
* <p>New empty environments can be created with the {@link #create} factory method and seeded with
* the {@link #bind} method.
*
* <p>For the most part this class is only used by this package, but it is publicly exposed to aid
* in testing usecases.
*/
public abstract class Environment {
Environment() {} // package private constructor to limit subclasses to this package.
/**
* The main way to create an environment.
*
* <p>Allocates the local variable table for the template and prepopulates it with data from the
* given SoyRecords.
*/
static Environment create(TemplateNode template, SoyRecord data, SoyRecord ijData) {
return new Impl(template, data, ijData);
}
/**
* For Prerendering we create an {@link Environment} for the given template where all entries are
* initialized to UndefinedData.
*/
public static Environment prerenderingEnvironment() {
return new EmptyImpl();
}
/** Associates a value with the given variable. */
abstract void bind(VarDefn var, SoyValueProvider value);
/** Sets the 'isLast' boolean for the given LoopVar. */
abstract void bindIsLast(LoopVar loopVar, boolean isLast);
/** Sets the currentIndex for the given LoopVar. */
abstract void bindCurrentIndex(LoopVar loopVar, int lastIndex);
/** Returns the resolved SoyValue for the given VarDefn. Guaranteed to not return null. */
abstract SoyValue getVar(VarDefn var);
/** Returns the resolved SoyValue for the given VarDefn. Guaranteed to not return null. */
abstract SoyValueProvider getVarProvider(VarDefn var);
/** Returns {@code true} if we are the last iteration for the given loop variable. */
abstract boolean isLast(LoopVar loopVar);
/** Returns the current iterator inject for the given loop variable. */
abstract int getIndex(LoopVar loopVar);
private static final class Impl extends Environment {
final SoyValueProvider[] localVariableTable;
final SoyRecord data;
Impl(TemplateNode template, SoyRecord data, SoyRecord ijData) {
// seed the lvt with the params
this.localVariableTable = new SoyValueProvider[template.getMaxLocalVariableTableSize()];
this.data = data;
for (TemplateParam param : template.getAllParams()) {
SoyValueProvider provider =
(param.isInjected() ? ijData : data).getFieldProvider(param.name());
if (provider == null) {
provider = param.isRequired() ? UndefinedData.INSTANCE : NullData.INSTANCE;
}
localVariableTable[param.localVariableIndex()] = provider;
}
}
@Override
void bind(VarDefn var, SoyValueProvider value) {
localVariableTable[var.localVariableIndex()] = value;
}
@Override
void bindIsLast(LoopVar loopVar, boolean isLast) {
localVariableTable[loopVar.isLastIteratorIndex()] = BooleanData.forValue(isLast);
}
@Override
void bindCurrentIndex(LoopVar loopVar, int lastIndex) {
localVariableTable[loopVar.currentLoopIndexIndex()] = IntegerData.forValue(lastIndex);
}
@Override
SoyValueProvider getVarProvider(VarDefn var) {
if (var.kind() == Kind.UNDECLARED) {
// Special case for legacy templates with undeclared params. Undeclared params aren't
// assigned indices in the local variable table.
SoyValueProvider provider = data.getFieldProvider(var.name());
return provider != null ? provider : checkNotNull(UndefinedData.INSTANCE);
}
return localVariableTable[var.localVariableIndex()];
}
@Override
SoyValue getVar(VarDefn var) {
if (var.kind() == Kind.UNDECLARED) {
// Special case for legacy templates with undeclared params. Undeclared params aren't
// assigned indices in the local variable table.
SoyValue value = data.getField(var.name());
return value != null ? value : checkNotNull(UndefinedData.INSTANCE);
}
return localVariableTable[var.localVariableIndex()].resolve();
}
@Override
boolean isLast(LoopVar loopVar) {
return localVariableTable[loopVar.isLastIteratorIndex()].resolve().booleanValue();
}
@Override
int getIndex(LoopVar loopVar) {
return localVariableTable[loopVar.currentLoopIndexIndex()].resolve().integerValue();
}
}
/** An environment that is empty and returns {@link UndefinedData} for everything. */
private static final class EmptyImpl extends Environment {
@Override
void bind(VarDefn var, SoyValueProvider value) {
throw new UnsupportedOperationException();
}
@Override
void bindIsLast(LoopVar loopVar, boolean isLast) {
throw new UnsupportedOperationException();
}
@Override
void bindCurrentIndex(LoopVar loopVar, int lastIndex) {
throw new UnsupportedOperationException();
}
@Override
SoyValueProvider getVarProvider(VarDefn var) {
return UndefinedData.INSTANCE;
}
@Override
SoyValue getVar(VarDefn var) {
return UndefinedData.INSTANCE;
}
@Override
boolean isLast(LoopVar loopVar) {
return UndefinedData.INSTANCE.booleanValue();
}
@Override
int getIndex(LoopVar loopVar) {
return UndefinedData.INSTANCE.integerValue();
}
}
}