com.google.template.soy.passes.ConditionalBranches.java Source code

Java tutorial

Introduction

Here is the source code for com.google.template.soy.passes.ConditionalBranches.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.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));
        }
    }
}