Java tutorial
/* * 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())); } }