com.google.template.soy.jssrc.dsl.Conditional.java Source code

Java tutorial

Introduction

Here is the source code for com.google.template.soy.jssrc.dsl.Conditional.java

Source

/*
 * Copyright 2016 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.jssrc.dsl;

import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.Immutable;
import javax.annotation.Nullable;

/** Represents an {@code if}-{@code else if}-{@code else} statement. */
@AutoValue
@Immutable
abstract class Conditional extends CodeChunk {
    abstract ImmutableList<IfThenPair> conditions();

    @Nullable
    abstract CodeChunk trailingElse();

    static Conditional create(ImmutableList<IfThenPair> conditions, @Nullable CodeChunk trailingElse) {
        Preconditions.checkArgument(!conditions.isEmpty());
        return new AutoValue_Conditional(conditions, trailingElse);
    }

    @Override
    void doFormatInitialStatements(FormattingContext ctx) {
        formatIfClause(ctx);
        int numRightBracesToClose = 0;
        CodeChunk.WithValue firstPredicate = conditions().get(0).predicate;
        for (IfThenPair condition : conditions().subList(1, conditions().size())) {
            if (firstPredicate.initialStatements().containsAll(condition.predicate.initialStatements())) {
                formatElseIfClauseWithNoDependencies(condition, ctx);
            } else {
                formatElseIfClauseWithDependencies(condition, ctx);
                ++numRightBracesToClose;
            }
        }
        formatElseClause(ctx);
        // Explicitly close the extra blocks opened by formatElseIfClauseWithDependencies.
        for (int i = 0; i < numRightBracesToClose; ++i) {
            ctx.close();
        }
        ctx.endLine();
    }

    @Override
    public void collectRequires(RequiresCollector collector) {
        for (IfThenPair child : conditions()) {
            child.predicate.collectRequires(collector);
            child.consequent.collectRequires(collector);
        }
        if (trailingElse() != null) {
            trailingElse().collectRequires(collector);
        }
    }

    private void formatIfClause(FormattingContext ctx) {
        IfThenPair first = conditions().get(0);
        ctx.appendInitialStatements(first.predicate).append("if (").appendOutputExpression(first.predicate)
                .append(") ");
        try (FormattingContext ignored = ctx.enterBlock()) {
            ctx.appendAll(first.consequent);
        }
    }

    /**
     * When the predicate of an {@code else if} clause is representable as a single expression,
     * format it directly as an {@code else if} clause.
     */
    private static void formatElseIfClauseWithNoDependencies(IfThenPair condition, FormattingContext ctx) {
        ctx.append(" else if (").appendOutputExpression(condition.predicate).append(") ");
        try (FormattingContext ignored = ctx.enterBlock()) {
            ctx.appendAll(condition.consequent);
        }
    }

    /**
     * When the predicate of an {@code else if} clause is not representable as a single expression,
     * we need to conditionally evaluate its initial statements. We do this by opening
     * an {@code else} clause, dumping its initial statements there, then opening a nested
     * {@code if} statement where the predicate expression and consequent chunk go.
     * Since this opens two blocks while only closing one, the caller
     * ({@link CodeChunk#doFormatInitialStatements}) has to remember to close the extra block
     * at the end of formatting.
     */
    private static void formatElseIfClauseWithDependencies(IfThenPair condition, FormattingContext ctx) {
        ctx.append(" else ");
        try (FormattingContext ignored = ctx.enterBlock()) {
            ctx.appendInitialStatements(condition.predicate).append("if (")
                    .appendOutputExpression(condition.predicate).append(") ");

            // Most enterBlock callers use try-with-resources to automatically close the block,
            // but here, we need to leave the block open so that subsequent `else if` clauses
            // are chained appropriately. The block will be closed by
            // Conditional#doFormatInitialStatements.
            ctx.enterBlock();
            ctx.appendAll(condition.consequent);
        }
    }

    private void formatElseClause(FormattingContext ctx) {
        if (trailingElse() == null) {
            return;
        }
        ctx.append(" else ");
        try (FormattingContext ignored = ctx.enterBlock()) {
            ctx.appendAll(trailingElse());
        }
        ctx.endLine();
    }

    private boolean everyBranchHasAValue() {
        for (IfThenPair condition : conditions()) {
            if (!(condition.consequent instanceof CodeChunk.WithValue)) {
                return false;
            }
        }
        return trailingElse() instanceof CodeChunk.WithValue;
    }

    /**
     * If every branch in an {@code if}-{@code else if}-{@code else} statement represents a value, the
     * whole statement represents a value, namely that of the taken branch. Make this explicit by
     * declaring a variable before the statement and assigning into it in every branch.
     */
    CodeChunk.WithValue asConditionalExpression(CodeChunk.Generator codeGenerator) {
        Preconditions.checkState(everyBranchHasAValue());
        Declaration decl = codeGenerator.declare(WithValue.LITERAL_NULL);
        CodeChunk.WithValue var = decl.ref();
        ConditionalBuilder builder = null;
        for (IfThenPair oldCondition : conditions()) {
            CodeChunk.WithValue newConsequent = var.assign((CodeChunk.WithValue) oldCondition.consequent);
            if (builder == null) {
                builder = CodeChunk.ifStatement(oldCondition.predicate, newConsequent);
            } else {
                builder.elseif_(oldCondition.predicate, newConsequent);
            }
        }
        if (trailingElse() != null) {
            builder.else_(var.assign((CodeChunk.WithValue) trailingElse()));
        }
        return var.withInitialStatements(ImmutableList.of(decl, builder.build()));
    }
}