/*
* 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.common.base.Preconditions.checkState;
import static com.google.template.soy.jbcsrc.Expression.areAllCheap;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import com.google.protobuf.Message;
import com.google.template.soy.data.SanitizedContent.ContentKind;
import com.google.template.soy.data.SoyList;
import com.google.template.soy.data.SoyMap;
import com.google.template.soy.data.SoyProtoValue;
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.UnsafeSanitizedContentOrdainer;
import com.google.template.soy.data.internal.DictImpl;
import com.google.template.soy.data.internal.ListImpl;
import com.google.template.soy.data.internal.ParamStore;
import com.google.template.soy.data.restricted.BooleanData;
import com.google.template.soy.data.restricted.FloatData;
import com.google.template.soy.data.restricted.IntegerData;
import com.google.template.soy.data.restricted.StringData;
import com.google.template.soy.jbcsrc.Expression.Feature;
import com.google.template.soy.jbcsrc.Expression.Features;
import com.google.template.soy.jbcsrc.api.AdvisingAppendable;
import com.google.template.soy.jbcsrc.api.AdvisingStringBuilder;
import com.google.template.soy.jbcsrc.api.RenderResult;
import com.google.template.soy.jbcsrc.runtime.Runtime;
import com.google.template.soy.jbcsrc.shared.CompiledTemplate;
import com.google.template.soy.jbcsrc.shared.RenderContext;
import com.google.template.soy.msgs.restricted.SoyMsg;
import com.google.template.soy.msgs.restricted.SoyMsgRawTextPart;
import com.google.template.soy.shared.internal.SharedRuntime;
import com.google.template.soy.shared.restricted.SoyJavaFunction;
import com.google.template.soy.shared.restricted.SoyJavaPrintDirective;
import com.ibm.icu.util.ULocale;
import java.io.PrintStream;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;
/** A reference to a method that can be called at runtime. */
@AutoValue
abstract class MethodRef {
static final MethodRef ADVISING_STRING_BUILDER_GET_AND_CLEAR =
create(AdvisingStringBuilder.class, "getAndClearBuffer").asNonNullable();
static final MethodRef ARRAY_LIST_ADD = create(ArrayList.class, "add", Object.class);
static final MethodRef BOOLEAN_DATA_FOR_VALUE =
create(BooleanData.class, "forValue", boolean.class).asNonNullable();
static final MethodRef BOOLEAN_VALUE = create(Boolean.class, "booleanValue").asCheap();
static final MethodRef BOOLEAN_TO_STRING =
create(Boolean.class, "toString", boolean.class).asCheap().asNonNullable();
static final MethodRef COMPILED_TEMPLATE_RENDER =
create(CompiledTemplate.class, "render", AdvisingAppendable.class, RenderContext.class)
.asNonNullable();
static final MethodRef DICT_IMPL_FOR_PROVIDER_MAP =
create(DictImpl.class, "forProviderMap", Map.class).asNonNullable();
static final MethodRef DOUBLE_TO_STRING =
create(Double.class, "toString", double.class).asNonNullable();
static final MethodRef EQUALS = create(Object.class, "equals", Object.class);
static final MethodRef FLOAT_DATA_FOR_VALUE =
create(FloatData.class, "forValue", double.class).asNonNullable();
// cheap() because it's zero-arg
static final MethodRef IMMUTABLE_LIST_OF =
create(ImmutableList.class, "of").asCheap().asNonNullable();
static final MethodRef IMMUTABLE_LIST_COPY_OF_COLLECTION =
create(ImmutableList.class, "copyOf", Collection.class).asCheap().asNonNullable();
static final MethodRef INTEGER_DATA_FOR_VALUE =
create(IntegerData.class, "forValue", long.class).asNonNullable();
static final MethodRef INTS_CHECKED_CAST =
create(Ints.class, "checkedCast", long.class).asCheap();
static final MethodRef LINKED_HASH_MAP_CLEAR = create(LinkedHashMap.class, "clear");
static final MethodRef LINKED_HASH_MAP_PUT =
create(LinkedHashMap.class, "put", Object.class, Object.class);
static final MethodRef LIST_GET = create(List.class, "get", int.class).asCheap();
static final MethodRef LIST_SIZE = create(List.class, "size").asCheap();
static final MethodRef LIST_IMPL_FOR_PROVIDER_LIST =
create(ListImpl.class, "forProviderList", List.class);
static final MethodRef LONG_PARSE_LONG =
create(Long.class, "parseLong", String.class).asCheap().asNonNullable();
static final MethodRef LONG_TO_STRING = create(Long.class, "toString", long.class);
static final MethodRef NUMBER_DOUBLE_VALUE = create(Number.class, "doubleValue").asCheap();
static final MethodRef NUMBER_LONG_VALUE = create(Number.class, "longValue").asCheap();
static final MethodRef OBJECT_TO_STRING = create(Object.class, "toString");
static final MethodRef ORDAIN_AS_SAFE =
create(UnsafeSanitizedContentOrdainer.class, "ordainAsSafe", String.class, ContentKind.class);
static final MethodRef PARAM_STORE_SET_FIELD =
create(ParamStore.class, "setField", String.class, SoyValueProvider.class);
static final MethodRef PRINT_STREAM_PRINTLN = create(PrintStream.class, "println");
static final MethodRef RENDER_CONTEXT_BOX =
create(RenderContext.class, "box", Message.class).asNonNullable();
static final MethodRef RENDER_CONTEXT_GET_DELTEMPLATE =
create(
RenderContext.class,
"getDelTemplate",
String.class,
String.class,
boolean.class,
SoyRecord.class,
SoyRecord.class);
static final MethodRef RENDER_CONTEXT_GET_FUNCTION =
create(RenderContext.class, "getFunction", String.class);
static final MethodRef RENDER_CONTEXT_GET_LOCALE = create(RenderContext.class, "getLocale");
static final MethodRef RENDER_CONTEXT_GET_PRINT_DIRECTIVE =
create(RenderContext.class, "getPrintDirective", String.class);
static final MethodRef RENDER_CONTEXT_GET_SOY_MSG_PARTS =
create(RenderContext.class, "getSoyMsgParts", long.class, ImmutableList.class);
static final MethodRef RENDER_CONTEXT_RENAME_CSS_SELECTOR =
create(RenderContext.class, "renameCssSelector", String.class).asNonNullable();
static final MethodRef RENDER_CONTEXT_RENAME_XID =
create(RenderContext.class, "renameXid", String.class).asNonNullable();
static final MethodRef RENDER_CONTEXT_USE_PRIMARY_MSG =
create(RenderContext.class, "usePrimaryMsg", long.class, long.class);
static final MethodRef RENDER_RESULT_DONE =
create(RenderResult.class, "done").asCheap().asNonNullable();
static final MethodRef RENDER_RESULT_IS_DONE = create(RenderResult.class, "isDone").asCheap();
static final MethodRef RENDER_RESULT_LIMITED =
create(RenderResult.class, "limited").asCheap().asNonNullable();
static final MethodRef RUNTIME_APPLY_ESCAPERS_DYNAMIC =
create(Runtime.class, "applyEscapersDynamic", CompiledTemplate.class, List.class);
static final MethodRef RUNTIME_APPLY_ESCAPERS =
create(Runtime.class, "applyEscapers", CompiledTemplate.class, ContentKind.class, List.class);
static final MethodRef RUNTIME_APPLY_PRINT_DIRECTIVE =
create(
Runtime.class,
"applyPrintDirective",
SoyJavaPrintDirective.class,
SoyValue.class,
List.class);
static final MethodRef RUNTIME_CALL_SOY_FUNCTION =
create(Runtime.class, "callSoyFunction", SoyJavaFunction.class, List.class);
static final MethodRef RUNTIME_COERCE_DOUBLE_TO_BOOLEAN =
create(Runtime.class, "coerceToBoolean", double.class);
static final MethodRef RUNTIME_COERCE_TO_STRING =
create(Runtime.class, "coerceToString", SoyValue.class).asNonNullable();
static final MethodRef RUNTIME_EQUAL =
create(SharedRuntime.class, "equal", SoyValue.class, SoyValue.class);
static final MethodRef RUNTIME_COMPARE_STRING =
create(SharedRuntime.class, "compareString", String.class, SoyValue.class);
static final MethodRef RUNTIME_GET_FIELD_PROVIDER =
create(Runtime.class, "getFieldProvider", SoyRecord.class, String.class).asNonNullable();
static final MethodRef RUNTIME_GET_LIST_ITEM =
create(Runtime.class, "getSoyListItem", List.class, long.class);
static final MethodRef RUNTIME_GET_LIST_STATUS =
create(Runtime.class, "getListStatus", List.class);
static final MethodRef RUNTIME_GET_MAP_ITEM =
create(Runtime.class, "getSoyMapItem", SoyMap.class, SoyValue.class);
static final MethodRef RUNTIME_LESS_THAN =
create(SharedRuntime.class, "lessThan", SoyValue.class, SoyValue.class).asNonNullable();
static final MethodRef RUNTIME_LESS_THAN_OR_EQUAL =
create(SharedRuntime.class, "lessThanOrEqual", SoyValue.class, SoyValue.class)
.asNonNullable();
static final MethodRef RUNTIME_LOGGER = create(Runtime.class, "logger").asCheap().asNonNullable();
static final MethodRef RUNTIME_MINUS =
create(SharedRuntime.class, "minus", SoyValue.class, SoyValue.class).asNonNullable();
static final MethodRef RUNTIME_NEGATIVE =
create(SharedRuntime.class, "negative", SoyValue.class).asNonNullable();
static final MethodRef RUNTIME_PLUS =
create(SharedRuntime.class, "plus", SoyValue.class, SoyValue.class).asNonNullable();
static final MethodRef RUNTIME_RENDER_SOY_MSG_PARTS_WITH_PLACEHOLDERS =
create(
Runtime.class,
"renderSoyMsgPartsWithPlaceholders",
ImmutableList.class,
ULocale.class,
Map.class,
Appendable.class);
static final MethodRef RUNTIME_STRING_EQUALS_AS_NUMBER =
create(Runtime.class, "stringEqualsAsNumber", String.class, double.class).asNonNullable();
static final MethodRef RUNTIME_TIMES =
create(SharedRuntime.class, "times", SoyValue.class, SoyValue.class).asNonNullable();
static final MethodRef RUNTIME_UNEXPECTED_STATE_ERROR =
create(Runtime.class, "unexpectedStateError", int.class).asNonNullable();
static final MethodRef SOY_LIST_AS_JAVA_LIST =
create(SoyList.class, "asJavaList").asNonNullable();
static final MethodRef SOY_MSG_GET_PARTS =
create(SoyMsg.class, "getParts").asCheap().asNonNullable();
static final MethodRef SOY_MSG_RAW_TEXT_PART_GET_RAW_TEXT =
create(SoyMsgRawTextPart.class, "getRawText").asCheap().asNonNullable();
static final MethodRef SOY_PROTO_VALUE_GET_FIELD =
create(SoyProtoValue.class, "getField", String.class).asCheap().asNonNullable();
static final MethodRef SOY_PROTO_VALUE_GET_PROTO =
create(SoyProtoValue.class, "getProto").asCheap().asNonNullable();
static final MethodRef SOY_VALUE_COERCE_TO_BOOLEAN =
create(SoyValue.class, "coerceToBoolean").asCheap();
static final MethodRef SOY_VALUE_BOOLEAN_VALUE = create(SoyValue.class, "booleanValue").asCheap();
static final MethodRef SOY_VALUE_FLOAT_VALUE = create(SoyValue.class, "floatValue").asCheap();
static final MethodRef SOY_VALUE_LONG_VALUE = create(SoyValue.class, "longValue").asCheap();
static final MethodRef SOY_VALUE_NUMBER_VALUE =
create(SoyValue.class, "numberValue").asNonNullable();
static final MethodRef SOY_VALUE_STRING_VALUE =
create(SoyValue.class, "stringValue").asCheap().asNonNullable();
static final MethodRef SOY_VALUE_PROVIDER_RENDER_AND_RESOLVE =
create(SoyValueProvider.class, "renderAndResolve", AdvisingAppendable.class, boolean.class)
.asNonNullable();
static final MethodRef SOY_VALUE_PROVIDER_RESOLVE =
create(Runtime.class, "resolveSoyValueProvider", SoyValueProvider.class);
static final MethodRef SOY_VALUE_PROVIDER_STATUS =
create(SoyValueProvider.class, "status").asNonNullable();
static final MethodRef STRING_CONCAT =
create(String.class, "concat", String.class).asNonNullable();
static final MethodRef STRING_IS_EMPTY = create(String.class, "isEmpty");
static final MethodRef STRING_VALUE_OF =
create(String.class, "valueOf", Object.class).asNonNullable();
static final MethodRef STRING_DATA_FOR_VALUE =
create(StringData.class, "forValue", String.class).asCheap().asNonNullable();
static MethodRef create(Class<?> clazz, String methodName, Class<?>... params) {
java.lang.reflect.Method m;
try {
// Ensure that the method exists and is public.
m = clazz.getMethod(methodName, params);
} catch (Exception e) {
throw new RuntimeException(e);
}
return create(m);
}
static MethodRef create(java.lang.reflect.Method method) {
Class<?> clazz = method.getDeclaringClass();
TypeInfo ownerType = TypeInfo.create(method.getDeclaringClass());
boolean isStatic = Modifier.isStatic(method.getModifiers());
ImmutableList<Type> argTypes;
if (isStatic) {
argTypes = ImmutableList.copyOf(Type.getArgumentTypes(method));
} else {
// for instance methods the first 'argument' is always an instance of the class.
argTypes =
ImmutableList.<Type>builder()
.add(ownerType.type())
.add(Type.getArgumentTypes(method))
.build();
}
return new AutoValue_MethodRef(
clazz.isInterface()
? Opcodes.INVOKEINTERFACE
: isStatic ? Opcodes.INVOKESTATIC : Opcodes.INVOKEVIRTUAL,
ownerType,
Method.getMethod(method),
Type.getType(method.getReturnType()),
argTypes,
Features.of());
}
static MethodRef createInstanceMethod(TypeInfo owner, Method method) {
return new AutoValue_MethodRef(
Opcodes.INVOKEVIRTUAL,
owner,
method,
method.getReturnType(),
ImmutableList.<Type>builder().add(owner.type()).add(method.getArgumentTypes()).build(),
Features.of());
}
static MethodRef createStaticMethod(TypeInfo owner, Method method) {
return new AutoValue_MethodRef(
Opcodes.INVOKESTATIC,
owner,
method,
method.getReturnType(),
ImmutableList.<Type>builder().add(method.getArgumentTypes()).build(),
Features.of());
}
/**
* The opcode to use to invoke the method. Will be one of {@link Opcodes#INVOKEINTERFACE}, {@link
* Opcodes#INVOKESTATIC} or {@link Opcodes#INVOKEVIRTUAL}.
*/
abstract int opcode();
/** The 'internal name' of the type that owns the method. */
abstract TypeInfo owner();
abstract Method method();
abstract Type returnType();
abstract ImmutableList<Type> argTypes();
abstract Features features();
// TODO(lukes): consider different names. 'invocation'? invoke() makes it sounds like we are
// actually calling the method rather than generating an expression that will output code that
// will invoke the method.
Statement invokeVoid(final Expression... args) {
return invokeVoid(Arrays.asList(args));
}
Statement invokeVoid(final Iterable<? extends Expression> args) {
checkState(Type.VOID_TYPE.equals(returnType()), "Method return type is not void.");
Expression.checkTypes(argTypes(), args);
return new Statement() {
@Override
void doGen(CodeBuilder adapter) {
doInvoke(adapter, args);
}
};
}
Expression invoke(final Expression... args) {
return invoke(Arrays.asList(args));
}
Expression invoke(final Iterable<? extends Expression> args) {
// void methods violate the expression contract of pushing a result onto the runtime stack.
checkState(
!Type.VOID_TYPE.equals(returnType()), "Cannot produce an expression from a void method.");
Expression.checkTypes(argTypes(), args);
Features features = features();
if (!areAllCheap(args)) {
features = features.minus(Feature.CHEAP);
}
return new Expression(returnType(), features) {
@Override
void doGen(CodeBuilder mv) {
doInvoke(mv, args);
}
};
}
MethodRef asCheap() {
return withFeature(Feature.CHEAP);
}
MethodRef asNonNullable() {
return withFeature(Feature.NON_NULLABLE);
}
private MethodRef withFeature(Feature feature) {
if (features().has(feature)) {
return this;
}
return new AutoValue_MethodRef(
opcode(), owner(), method(), returnType(), argTypes(), features().plus(feature));
}
/**
* Writes an invoke instruction for this method to the given adapter. Useful when the expression
* is not useful for representing operations. For example, explicit dup operations are awkward in
* the Expression api.
*/
void invokeUnchecked(CodeBuilder cb) {
cb.visitMethodInsn(
opcode(),
owner().internalName(),
method().getName(),
method().getDescriptor(),
// This is for whether the methods owner is an interface. This is mostly to handle java8
// default methods on interfaces. We don't care about those currently, but ASM requires
// this.
opcode() == Opcodes.INVOKEINTERFACE);
}
private void doInvoke(CodeBuilder mv, Iterable<? extends Expression> args) {
for (Expression arg : args) {
arg.gen(mv);
}
invokeUnchecked(mv);
}
}