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.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import org.objectweb.asm.Label; import org.objectweb.asm.Type; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; /** * An expression has a {@link #resultType()} and can {@link #gen generate} code to evaluate the * expression. * * <p>Expressions should be side effect free and also should not <em>consume</em> stack items. */ abstract class Expression extends BytecodeProducer { /** * Expression features track additional metadata for expressions. * * <p>Features should be defined such that not setting a feature on an expression is a safe * default. That way if they get accidentally dropped in a transformation we simply generate * less efficient code, not incorrect code. */ enum Feature { /** The expression is guaranteed to not return null. */ NON_NULLABLE, /** * The expression is 'cheap'. As a rule of thumb, if it involves allocation, it is not cheap. * * <p>Cheapness is useful when deciding if it would be reasonable to evaluate an expression more * than once if the alternative is generating additional fields and save/restore code. */ CHEAP; // TODO(lukes): an idempotent feature would be useful some expressions are not safe to gen more // than once. } /** An immutable wrapper of an EnumSet of {@link Feature}. */ static final class Features { private static final Features EMPTY = new Features(EnumSet.noneOf(Feature.class)); static Features of() { return EMPTY; } static Features of(Feature first, Feature... rest) { EnumSet<Feature> set = EnumSet.of(first); Collections.addAll(set, rest); return new Features(set); } private static Features forType(Type expressionType, Features features) { switch (expressionType.getSort()) { case Type.OBJECT: case Type.ARRAY: return features; case Type.BOOLEAN: case Type.BYTE: case Type.CHAR: case Type.DOUBLE: case Type.INT: case Type.SHORT: case Type.LONG: case Type.FLOAT: // primitives are never null return features.plus(Feature.NON_NULLABLE); case Type.VOID: case Type.METHOD: throw new IllegalArgumentException("Invalid type: " + expressionType); default: throw new AssertionError("unexpected type " + expressionType); } } private final EnumSet<Feature> set; private Features(EnumSet<Feature> set) { this.set = checkNotNull(set); } boolean has(Feature feature) { return set.contains(feature); } Features plus(Feature feature) { if (set.contains(feature)) { return this; } EnumSet<Feature> newSet = copyFeatures(); newSet.add(feature); return new Features(newSet); } Features minus(Feature feature) { if (!set.contains(feature)) { return this; } EnumSet<Feature> newSet = copyFeatures(); newSet.remove(feature); return new Features(newSet); } private EnumSet<Feature> copyFeatures() { // Can't use EnumSet.copyOf() because it throws on empty collections! EnumSet<Feature> newSet = EnumSet.noneOf(Feature.class); newSet.addAll(set); return newSet; } } /** Returns true if all referenced expressions are {@linkplain #isCheap() cheap}. */ static boolean areAllCheap(Iterable<? extends Expression> args) { for (Expression arg : args) { if (!arg.isCheap()) { return false; } } return true; } /** Returns true if all referenced expressions are {@linkplain #isCheap() cheap}. */ static boolean areAllCheap(Expression first, Expression... rest) { return areAllCheap(ImmutableList.<Expression>builder().add(first).add(rest).build()); } /** * Checks that the given expressions are compatible with the given types. */ static void checkTypes(ImmutableList<Type> types, Expression... exprs) { checkTypes(types, Arrays.asList(exprs)); } /** * Checks that the given expressions are compatible with the given types. */ static void checkTypes(ImmutableList<Type> types, Iterable<? extends Expression> exprs) { int size = Iterables.size(exprs); checkArgument(size == types.size(), "Supplied the wrong number of parameters. Expected %s, got %s", types.size(), size); int i = 0; for (Expression expr : exprs) { expr.checkAssignableTo(types.get(i), "Parameter %s", i); i++; } } private final Features features; private final Type resultType; Expression(Type resultType) { this(resultType, Features.of()); } Expression(Type resultType, Feature first, Feature... rest) { this(resultType, Features.of(first, rest)); } Expression(Type resultType, Features features) { this.resultType = checkNotNull(resultType); this.features = Features.forType(resultType, features); } /** * Generate code to evaluate the expression. * * <p>The generated code satisfies the invariant that the top of the runtime stack will contain a * value with this {@link #resultType()} immediately after evaluation of the code. */ @Override abstract void doGen(CodeBuilder adapter); /** The type of the expression. */ final Type resultType() { return resultType; } /** Whether or not this expression is {@link Feature#CHEAP cheap}. */ boolean isCheap() { return features.has(Feature.CHEAP); } /** Whether or not this expression is {@link Feature#NON_NULLABLE non nullable}. */ boolean isNonNullable() { return features.has(Feature.NON_NULLABLE); } /** * Returns all the feature bits. * Typically, users will want to invoke one of the convenience accessors {@link #isCheap()} or * {@link #isNonNullable()}. */ Features features() { return features; } /** * Check that this expression is assignable to {@code expected}. */ final void checkAssignableTo(Type expected) { checkAssignableTo(expected, ""); } /** * Check that this expression is assignable to {@code expected}. */ final void checkAssignableTo(Type expected, String fmt, Object... args) { if (resultType().equals(expected)) { return; } if (expected.getSort() == resultType().getSort() && expected.getSort() == Type.OBJECT) { // for class types we really need to know type hierarchy information to test for // whether actualType is assignable to expectedType. // This test is mostly optimistic so we just assume that they match. The verifier will tell // us ultimately if we screw up. // TODO(lukes): see if we can do something better here, special case the SoyValue // hierarchy? We could use BytecodeUtils.classFromAsmType, but that might be overly expensive return; } String message = String.format("Type mismatch. Expected %s, got %s.", expected, resultType()); if (!fmt.isEmpty()) { message = String.format(fmt, args) + ". " + message; } throw new IllegalArgumentException(message); } /** * Convert this expression to a statement, by executing it and throwing away the result. * * <p>This is useful for invoking non-void methods when we don't care about the result. */ Statement toStatement() { return new Statement() { @Override void doGen(CodeBuilder adapter) { Expression.this.gen(adapter); switch (resultType().getSize()) { case 0: throw new AssertionError("void expressions are not allowed"); case 1: adapter.pop(); break; case 2: adapter.pop2(); break; } } }; } /** Returns an equivalent expression where {@link #isCheap()} returns {@code true}. */ Expression asCheap() { if (isCheap()) { return this; } return new Expression(resultType, features.plus(Feature.CHEAP)) { @Override void doGen(CodeBuilder adapter) { Expression.this.gen(adapter); } }; } /** Returns an equivalent expression where {@link #isNonNullable()} returns {@code true}. */ Expression asNonNullable() { if (isNonNullable()) { return this; } return new Expression(resultType, features.plus(Feature.NON_NULLABLE)) { @Override void doGen(CodeBuilder adapter) { Expression.this.gen(adapter); } }; } /** * Returns an expression that performs a checked cast from the current type to the target type. * * @throws IllegalArgumentException if either type is not a reference type. */ Expression cast(final Type target) { checkArgument(target.getSort() == Type.OBJECT, "cast targets must be reference types."); checkArgument(resultType().getSort() == Type.OBJECT, "you may only cast from reference types."); if (target.equals(resultType())) { return this; } return new Expression(target, features()) { @Override void doGen(CodeBuilder adapter) { Expression.this.gen(adapter); adapter.checkCast(resultType()); } }; } /** * Returns an expression that performs a checked cast from the current type to the target type. * * @throws IllegalArgumentException if either type is not a reference type. */ Expression cast(Class<?> target) { return cast(Type.getType(target)); } /** * A simple helper that calls through to {@link MethodRef#invoke(Expression...)}, but allows a * more natural fluent call style. */ Expression invoke(MethodRef method, Expression... args) { return method.invoke(ImmutableList.<Expression>builder().add(this).add(args).build()); } /** * A simple helper that calls through to {@link MethodRef#invokeVoid(Expression...)}, but allows a * more natural fluent call style. */ Statement invokeVoid(MethodRef method, Expression... args) { return method.invokeVoid(ImmutableList.<Expression>builder().add(this).add(args).build()); } /** * Returns a new expression identical to this one but with the given label applied at the start * of the expression. */ Expression labelStart(final Label label) { return new Expression(resultType(), features) { @Override void doGen(CodeBuilder adapter) { adapter.mark(label); Expression.this.gen(adapter); } }; } /** * Returns a new expression identical to this one but with the given label applied at the end * of the expression. */ Expression labelEnd(final Label label) { return new Expression(resultType(), features) { @Override void doGen(CodeBuilder adapter) { Expression.this.gen(adapter); adapter.mark(label); } }; } @Override public String toString() { String name = getClass().getSimpleName(); if (name.isEmpty()) { // provide a default for anonymous subclasses name = "Expression"; } name = name + "(" + resultType + "){"; boolean needsLeadingSpace = false; if (features.has(Feature.CHEAP)) { name += "cheap"; needsLeadingSpace = true; } if (features.has(Feature.NON_NULLABLE) && !BytecodeUtils.isPrimitive(resultType)) { name += (needsLeadingSpace ? " " : "") + "non-null"; } return name + "}<" + resultType() + ">:\n" + trace(); } }