com.google.template.soy.parsepasses.RewriteGenderMsgsVisitor.java Source code

Java tutorial

Introduction

Here is the source code for com.google.template.soy.parsepasses.RewriteGenderMsgsVisitor.java

Source

/*
 * Copyright 2012 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.parsepasses;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.template.soy.base.internal.IdGenerator;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.error.ErrorReporter.Checkpoint;
import com.google.template.soy.error.SoyError;
import com.google.template.soy.exprtree.ExprRootNode;
import com.google.template.soy.soytree.AbstractSoyNodeVisitor;
import com.google.template.soy.soytree.MsgNode;
import com.google.template.soy.soytree.MsgPluralNode;
import com.google.template.soy.soytree.MsgSelectCaseNode;
import com.google.template.soy.soytree.MsgSelectDefaultNode;
import com.google.template.soy.soytree.MsgSelectNode;
import com.google.template.soy.soytree.MsgSubstUnitBaseVarNameUtils;
import com.google.template.soy.soytree.SoyNode;
import com.google.template.soy.soytree.SoyNode.ParentSoyNode;
import com.google.template.soy.soytree.SoyNode.StandaloneNode;
import com.google.template.soy.soytree.SoytreeUtils;

import java.util.List;

import javax.annotation.Nullable;

/**
 * Visitor for rewriting 'msg' nodes with 'genders' attribute into 'msg' nodes with one or more
 * levels of 'select'.
 *
 */
public final class RewriteGenderMsgsVisitor extends AbstractSoyNodeVisitor<Void> {

    private static final SoyError GENDER_AND_SELECT_NOT_ALLOWED = SoyError
            .of("Cannot mix ''genders'' attribute with ''select'' command in the same message.");
    private static final SoyError MORE_THAN_TWO_GENDER_EXPRS = SoyError
            .of("In a msg with ''plural'', the ''genders'' attribute can contain at most 2 expressions "
                    + "(otherwise, combinatorial explosion would cause a gigantic generated message).");

    /** Fallback base select var name. */
    public static final String FALLBACK_BASE_SELECT_VAR_NAME = "GENDER";

    /** Node id generator for the Soy tree being visited. */
    private IdGenerator nodeIdGen;

    /**
     * Constructs a rewriter using the same node ID generator as the tree.
     *
     * @param nodeIdGen The same node ID generator used to generate the existing tree nodes.
     */
    public RewriteGenderMsgsVisitor(IdGenerator nodeIdGen, ErrorReporter errorReporter) {
        super(errorReporter);
        this.nodeIdGen = Preconditions.checkNotNull(nodeIdGen);
    }

    // -----------------------------------------------------------------------------------------------
    // Implementations for specific nodes.

    @Override
    protected void visitMsgNode(MsgNode msg) {

        List<ExprRootNode> genderExprs = msg.getAndRemoveGenderExprs();
        if (genderExprs == null) {
            return; // not a msg that this pass should rewrite
        }

        // Check that 'genders' attribute and 'select' command are not used together.
        if (msg.getChild(0) instanceof MsgSelectNode) {
            errorReporter.report(msg.getChild(0).getSourceLocation(), GENDER_AND_SELECT_NOT_ALLOWED);
        }

        // If plural msg, check that there are max 2 genders.
        if (msg.getChild(0) instanceof MsgPluralNode && genderExprs.size() > 2) {
            errorReporter.report(msg.getSourceLocation(), MORE_THAN_TWO_GENDER_EXPRS);
        }

        // ------ Do the rewrite. ------

        // Note: We process the genders in reverse order so that the first listed gender will end up
        // being the outermost 'select' level.
        genderExprs = Lists.reverse(genderExprs);

        Checkpoint checkpoint = errorReporter.checkpoint();
        List<String> baseSelectVarNames = MsgSubstUnitBaseVarNameUtils.genNoncollidingBaseNamesForExprs(
                ExprRootNode.unwrap(genderExprs), FALLBACK_BASE_SELECT_VAR_NAME, errorReporter);
        if (errorReporter.errorsSince(checkpoint)) {
            return; // To prevent an IndexOutOfBoundsException below.
        }

        for (int i = 0; i < genderExprs.size(); i++) {
            ExprRootNode genderExpr = genderExprs.get(i);
            String baseSelectVarName = baseSelectVarNames.get(i);

            // Check whether the generated base name would be the same (both for the old naive algorithm
            // and the new algorithm). If so, then there's no need to specify the baseSelectVarName.
            if (MsgSubstUnitBaseVarNameUtils
                    .genNaiveBaseNameForExpr(genderExpr.getRoot(), FALLBACK_BASE_SELECT_VAR_NAME)
                    .equals(baseSelectVarName)
                    && MsgSubstUnitBaseVarNameUtils
                            .genShortestBaseNameForExpr(genderExpr.getRoot(), FALLBACK_BASE_SELECT_VAR_NAME)
                            .equals(baseSelectVarName)) {
                baseSelectVarName = null;
            }

            splitMsgForGender(msg, genderExpr, baseSelectVarName);
        }
    }

    /**
     * Helper to split a msg for gender, by adding a 'select' node and cloning the msg's contents
     * into all 3 cases of the 'select' node ('female'/'male'/default).
     * @param msg The message to split.
     * @param genderExpr The expression for the gender value.
     * @param baseSelectVarName The base select var name to use, or null if it should be generated
     *     from the gender expression.
     */
    private void splitMsgForGender(MsgNode msg, ExprRootNode genderExpr, @Nullable String baseSelectVarName) {

        List<StandaloneNode> origChildren = ImmutableList.copyOf(msg.getChildren());
        msg.clearChildren();

        MsgSelectCaseNode femaleCase = new MsgSelectCaseNode.Builder(nodeIdGen.genId(), "'female'",
                msg.getSourceLocation()).build(errorReporter);
        femaleCase.addChildren(SoytreeUtils.cloneListWithNewIds(origChildren, nodeIdGen));
        MsgSelectCaseNode maleCase = new MsgSelectCaseNode.Builder(nodeIdGen.genId(), "'male'",
                msg.getSourceLocation()).build(errorReporter);
        maleCase.addChildren(SoytreeUtils.cloneListWithNewIds(origChildren, nodeIdGen));
        MsgSelectDefaultNode defaultCase = new MsgSelectDefaultNode(nodeIdGen.genId(), msg.getSourceLocation());
        defaultCase.addChildren(SoytreeUtils.cloneListWithNewIds(origChildren, nodeIdGen));

        MsgSelectNode selectNode = new MsgSelectNode(nodeIdGen.genId(), msg.getSourceLocation(), genderExpr,
                baseSelectVarName);
        selectNode.addChild(femaleCase);
        selectNode.addChild(maleCase);
        selectNode.addChild(defaultCase);

        msg.addChild(selectNode);
    }

    // -----------------------------------------------------------------------------------------------
    // Fallback implementation.

    @Override
    protected void visitSoyNode(SoyNode node) {
        if (node instanceof ParentSoyNode<?>) {
            visitChildrenAllowingConcurrentModification((ParentSoyNode<?>) node);
        }
    }

}