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.passes; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import com.google.auto.value.AutoValue; import com.google.common.collect.Iterables; import com.google.template.soy.soytree.TagName; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * An abstract representation of an {@code IfNode} or a {@code SwitchNode} in a Soy template. * * <p>This class is used for {@code StrictHtmlValidationPass} so that we can validate if a template * with control flow is a valid HTML. */ final class ConditionalBranches { /** * Inner class that represents a conditional branch. Note that it is possible to have nested * control flows, and each {@HtmlTagEntry} might contain another {@code CondtionalBranch}. */ @AutoValue abstract static class ConditionalBranch { static ConditionalBranch create(Condition condition, ArrayDeque<HtmlTagEntry> deque) { return new AutoValue_ConditionalBranches_ConditionalBranch(condition, deque); } abstract Condition condition(); abstract ArrayDeque<HtmlTagEntry> deque(); } private final List<ConditionalBranch> branches = new ArrayList<>(); ConditionalBranches() { } ConditionalBranches(ConditionalBranches branches) { this(); addAll(branches); } void clear() { branches.clear(); } List<ConditionalBranch> getBranches() { removeEmptyDeque(); return branches; } @Override public String toString() { return branches.toString(); } private void removeEmptyDeque() { // Remove the empty deque if necessary. for (Iterator<ConditionalBranch> it = branches.iterator(); it.hasNext();) { ConditionalBranch branch = it.next(); // Recursively remove empty branches within each deque. for (Iterator<HtmlTagEntry> it2 = branch.deque().iterator(); it2.hasNext();) { HtmlTagEntry entry = it2.next(); if (entry.getBranches() != null) { if (entry.getBranches().isEmpty()) { it2.remove(); } } } if (branch.deque().isEmpty()) { it.remove(); } } } /** * Checks if the list of branches contains a "default" conditional branch (i.e., a {@code * IfElseNode} or a {@code SwitchDefaultNode} at the end of the list). * * <p>If this is true, we will try to match {@code TagName} for all branches. */ private boolean hasDefaultCond() { if (branches.isEmpty()) { return false; } return Iterables.getLast(branches).condition().isDefaultCond(); } /** Checks if all branches contain the given {@TagName} at the head of their open stacks. */ boolean hasCommonPrefix(TagName tag) { if (!hasDefaultCond()) { return false; } for (ConditionalBranch branch : branches) { if (branch.deque().isEmpty()) { return false; } HtmlTagEntry entry = branch.deque().peek(); // Remove optional tags that do not match the desired tag. while (entry != null && entry.hasTagName() && !entry.getTagName().equals(tag) && entry.isDefinitelyOptional()) { branch.deque().poll(); entry = branch.deque().peek(); } if (entry.hasTagName()) { if (!entry.getTagName().equals(tag)) { return false; } } else { // Recursively search if (!entry.getBranches().hasCommonPrefix(tag)) { return false; } } } return true; } /** * Remove a common {@code TagName} from all branches. Note that we assume that each branch * contains the {@code TagName}. You will need to explicitly call {@code hasCommonPrefix} before * calling this method. */ void popAllBranches() { for (ConditionalBranch branch : branches) { HtmlTagEntry entry = branch.deque().peek(); if (entry.hasTagName()) { branch.deque().pop(); } else { entry.getBranches().popAllBranches(); } } removeEmptyDeque(); } /** Removes optional tags from all branches. */ void popOptionalTags() { for (ConditionalBranch branch : branches) { HtmlTagEntry entry = branch.deque().peekFirst(); while (entry != null && entry.isDefinitelyOptional()) { entry = branch.deque().pollFirst(); } } removeEmptyDeque(); } boolean isEmpty() { removeEmptyDeque(); return branches.isEmpty(); } void add(Condition condition, ArrayDeque<HtmlTagEntry> deque) { checkArgument(!condition.equals(Condition.getEmptyCondition()), "Cannot add an empty condition into a branch. This should never happen."); checkState(!hasDefaultCond(), "Cannot add a new branch since the current ConditionalBranches already contains " + "a default condition."); ArrayDeque<HtmlTagEntry> newDeque = new ArrayDeque<>(); newDeque.addAll(deque); branches.add(ConditionalBranch.create(condition.copy(), newDeque)); } void addAll(ConditionalBranches branches) { checkState(!hasDefaultCond(), "Cannot add a new branch since the current ConditionalBranches already contain " + "a default condition."); // Always make deep copy so that the branches will not be accidentally changed. for (ConditionalBranch branch : branches.branches) { ArrayDeque<HtmlTagEntry> deque = branch.deque(); ArrayDeque<HtmlTagEntry> newDeque = new ArrayDeque<>(); newDeque.addAll(deque); this.branches.add(ConditionalBranch.create(branch.condition().copy(), newDeque)); } } }