/*
* Copyright 2010 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.soytree;
import com.google.common.collect.ImmutableList;
import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.basetree.CopyState;
import com.google.template.soy.error.ErrorReporter.Checkpoint;
import com.google.template.soy.error.SoyErrorKind;
import com.google.template.soy.exprparse.ExpressionParser;
import com.google.template.soy.exprparse.SoyParsingContext;
import com.google.template.soy.exprtree.ExprNode;
import com.google.template.soy.exprtree.ExprRootNode;
import com.google.template.soy.soytree.CommandTextAttributesParser.Attribute;
import com.google.template.soy.soytree.SoyNode.ExprHolderNode;
import com.google.template.soy.soytree.SoyNode.MsgSubstUnitNode;
import com.google.template.soy.soytree.SoyNode.SplitLevelTopNode;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Node representing a 'plural' block.
*
* <p>Important: Do not use outside of Soy code (treat as superpackage-private).
*
*/
public final class MsgPluralNode extends AbstractParentCommandNode<CaseOrDefaultNode>
implements MsgSubstUnitNode, SplitLevelTopNode<CaseOrDefaultNode>, ExprHolderNode {
private static final SoyErrorKind INVALID_PLURAL_COMMAND_TEXT =
SoyErrorKind.of("Invalid ''plural'' command text \"{0}\".");
private static final SoyErrorKind PLURAL_OFFSET_OUT_OF_BOUNDS =
SoyErrorKind.of("The ''offset'' for plural must be a nonnegative integer.");
private static final SoyErrorKind MALFORMED_PLURAL_OFFSET =
SoyErrorKind.of("Invalid offset in ''plural'' command text \"{0}\".");
/** An expression, and optional "offset" attribute. */
private static final Pattern COMMAND_TEXT_PATTERN =
Pattern.compile("(.+?) ( \\s+ offset= .+ )?", Pattern.COMMENTS);
/** Parser for the attributes in the command text. */
private static final CommandTextAttributesParser ATTRIBUTES_PARSER =
new CommandTextAttributesParser(
"plural", new Attribute("offset", Attribute.ALLOW_ALL_VALUES, null));
/** Fallback base plural var name. */
public static final String FALLBACK_BASE_PLURAL_VAR_NAME = "NUM";
/** The offset. */
private final int offset;
/** The parsed expression. */
private final ExprRootNode pluralExpr;
/** The base plural var name (what the translator sees). */
private final String basePluralVarName;
private MsgPluralNode(
int id,
SourceLocation sourceLocation,
String commandText,
int offset,
ExprRootNode pluralExpr,
String basePluralVarName) {
super(id, sourceLocation, "plural", commandText);
this.offset = offset;
this.pluralExpr = pluralExpr;
this.basePluralVarName = basePluralVarName;
}
/**
* Copy constructor.
*
* @param orig The node to copy.
*/
private MsgPluralNode(MsgPluralNode orig, CopyState copyState) {
super(orig, copyState);
this.offset = orig.offset;
this.pluralExpr = orig.pluralExpr.copy(copyState);
this.basePluralVarName = orig.basePluralVarName;
}
@Override
public Kind getKind() {
return Kind.MSG_PLURAL_NODE;
}
/** Returns the offset. */
public int getOffset() {
return offset;
}
/** Returns the expression text. */
public String getExprText() {
return pluralExpr.toSourceString();
}
/** Returns the parsed expression. */
public ExprRootNode getExpr() {
return pluralExpr;
}
/** Returns the base plural var name (what the translator sees). */
@Override
public String getBaseVarName() {
return basePluralVarName;
}
@Override
public boolean shouldUseSameVarNameAs(MsgSubstUnitNode other) {
return (other instanceof MsgPluralNode)
&& this.getCommandText().equals(((MsgPluralNode) other).getCommandText());
}
@Override
public ImmutableList<ExprRootNode> getExprList() {
return ImmutableList.of(pluralExpr);
}
@Override
public MsgBlockNode getParent() {
return (MsgBlockNode) super.getParent();
}
@Override
public MsgPluralNode copy(CopyState copyState) {
return new MsgPluralNode(this, copyState);
}
/** Builder for {@link MsgPluralNode}. */
public static final class Builder {
private static MsgPluralNode error() {
return new Builder(-1, "plural", SourceLocation.UNKNOWN)
.build(SoyParsingContext.exploding()); // guaranteed to be valid
}
private final int id;
private final String commandText;
private final SourceLocation sourceLocation;
/**
* @param id The node's id.
* @param commandText The node's command text.
* @param sourceLocation The node's source location.
*/
public Builder(int id, String commandText, SourceLocation sourceLocation) {
this.id = id;
this.commandText = commandText;
this.sourceLocation = sourceLocation;
}
/**
* Returns a new {@link MsgPluralNode} built from the builder's state. If the builder's state is
* invalid, errors are reported to {@code errorReporter} and {@link Builder#error} is returned.
*/
public MsgPluralNode build(SoyParsingContext context) {
Checkpoint checkpoint = context.errorReporter().checkpoint();
Matcher matcher = COMMAND_TEXT_PATTERN.matcher(commandText);
if (!matcher.matches()) {
context.report(sourceLocation, INVALID_PLURAL_COMMAND_TEXT, commandText);
}
ExprNode pluralExpr =
new ExpressionParser(matcher.group(1), sourceLocation, context).parseExpression();
int offset = 0;
// If attributes were given, parse them.
if (matcher.group(2) != null) {
try {
Map<String, String> attributes =
ATTRIBUTES_PARSER.parse(matcher.group(2).trim(), context, sourceLocation);
String offsetAttribute = attributes.get("offset");
offset = Integer.parseInt(offsetAttribute);
if (offset < 0) {
context.report(sourceLocation, PLURAL_OFFSET_OUT_OF_BOUNDS);
}
} catch (NumberFormatException nfe) {
context.report(sourceLocation, MALFORMED_PLURAL_OFFSET, commandText);
}
}
String basePluralVarName =
MsgSubstUnitBaseVarNameUtils.genNaiveBaseNameForExpr(
pluralExpr, FALLBACK_BASE_PLURAL_VAR_NAME);
if (context.errorReporter().errorsSince(checkpoint)) {
return error();
}
return new MsgPluralNode(
id, sourceLocation, commandText, offset, new ExprRootNode(pluralExpr), basePluralVarName);
}
}
}