/*
* Copyright 2009 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.msgs.internal;
import com.google.common.collect.ImmutableList;
import com.google.template.soy.msgs.restricted.SoyMsgPart;
import com.google.template.soy.msgs.restricted.SoyMsgPlaceholderPart;
import com.google.template.soy.msgs.restricted.SoyMsgPluralCaseSpec;
import com.google.template.soy.msgs.restricted.SoyMsgPluralCaseSpec.Type;
import com.google.template.soy.msgs.restricted.SoyMsgPluralPart;
import com.google.template.soy.msgs.restricted.SoyMsgRawTextPart;
import com.google.template.soy.msgs.restricted.SoyMsgSelectPart;
import com.google.template.soy.soytree.CaseOrDefaultNode;
import com.google.template.soy.soytree.MsgNode;
import com.google.template.soy.soytree.MsgPlaceholderNode;
import com.google.template.soy.soytree.MsgPluralCaseNode;
import com.google.template.soy.soytree.MsgPluralDefaultNode;
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.RawTextNode;
import com.google.template.soy.soytree.SoyNode.BlockNode;
import com.google.template.soy.soytree.SoyNode.StandaloneNode;
/**
* Soy-specific utilities for working with messages.
*
*/
public class MsgUtils {
private MsgUtils() {}
// -----------------------------------------------------------------------------------------------
// Utilities independent of msg id format.
/**
* Builds the list of SoyMsgParts for the given MsgNode.
*
* @param msgNode The message parsed from the Soy source.
* @return The list of SoyMsgParts.
*/
public static ImmutableList<SoyMsgPart> buildMsgParts(MsgNode msgNode) {
return buildMsgPartsForChildren(msgNode, msgNode);
}
// -----------------------------------------------------------------------------------------------
// Utilities assuming a specific dual format: use unbraced placeholders for regular messages and
// use braced placeholders for plural/select messages.
/**
* Builds the list of SoyMsgParts and computes the unique message id for the given MsgNode,
* assuming a specific dual format.
*
* <p>Note: The field {@code idUsingBracedPhs} in the return value is simply set to -1L.
*
* @param msgNode The message parsed from the Soy source.
* @return A {@code MsgPartsAndIds} object, assuming a specific dual format, with field {@code
* idUsingBracedPhs} set to -1L.
*/
public static MsgPartsAndIds buildMsgPartsAndComputeMsgIdForDualFormat(MsgNode msgNode) {
if (msgNode.isPlrselMsg()) {
MsgPartsAndIds mpai = buildMsgPartsAndComputeMsgIds(msgNode, true);
return new MsgPartsAndIds(mpai.parts, mpai.idUsingBracedPhs, -1L);
} else {
return buildMsgPartsAndComputeMsgIds(msgNode, false);
}
}
/**
* Computes the unique message id for the given MsgNode, assuming a specific dual format.
*
* @param msgNode The message parsed from the Soy source.
* @return The message id, assuming a specific dual format.
*/
public static long computeMsgIdForDualFormat(MsgNode msgNode) {
return msgNode.isPlrselMsg() ? computeMsgIdUsingBracedPhs(msgNode) : computeMsgId(msgNode);
}
// -----------------------------------------------------------------------------------------------
// Flexible utilities. Currently private to prevent accidental usage, but can be public if needed.
/** Value class for the return value of {@code buildMsgPartsAndComputeMsgId()}. */
public static class MsgPartsAndIds {
/** The parts that make up the message content. */
public final ImmutableList<SoyMsgPart> parts;
/** A unique id for this message (same across all translations). */
public final long id;
/** An alternate unique id for this message. Only use this if you use braced placeholders. */
public final long idUsingBracedPhs;
private MsgPartsAndIds(ImmutableList<SoyMsgPart> parts, long id, long idUsingBracedPhs) {
this.parts = parts;
this.id = id;
this.idUsingBracedPhs = idUsingBracedPhs;
}
}
/**
* Builds the list of SoyMsgParts and computes the unique message id(s) for the given MsgNode.
*
* @param msgNode The message parsed from the Soy source.
* @param doComputeMsgIdUsingBracedPhs Whether to compute the alternate message id using braced
* placeholders. If set to false, then the field {@code idUsingBracedPhs} in the return value
* is simply set to -1L.
* @return A {@code MsgPartsAndIds} object.
*/
private static MsgPartsAndIds buildMsgPartsAndComputeMsgIds(
MsgNode msgNode, boolean doComputeMsgIdUsingBracedPhs) {
ImmutableList<SoyMsgPart> msgParts = buildMsgParts(msgNode);
long msgId =
SoyMsgIdComputer.computeMsgId(msgParts, msgNode.getMeaning(), msgNode.getContentType());
long msgIdUsingBracedPhs =
doComputeMsgIdUsingBracedPhs
? SoyMsgIdComputer.computeMsgIdUsingBracedPhs(
msgParts, msgNode.getMeaning(), msgNode.getContentType())
: -1L;
return new MsgPartsAndIds(msgParts, msgId, msgIdUsingBracedPhs);
}
/**
* Computes the unique message id for the given MsgNode.
*
* @param msgNode The message parsed from the Soy source.
* @return The message id.
*/
private static long computeMsgId(MsgNode msgNode) {
return SoyMsgIdComputer.computeMsgId(
buildMsgParts(msgNode), msgNode.getMeaning(), msgNode.getContentType());
}
/**
* Computes the alternate unique id for this message. Only use this if you use braced
* placeholders.
*
* @param msgNode The message parsed from the Soy source.
* @return The alternate message id using braced placeholders.
*/
private static long computeMsgIdUsingBracedPhs(MsgNode msgNode) {
return SoyMsgIdComputer.computeMsgIdUsingBracedPhs(
buildMsgParts(msgNode), msgNode.getMeaning(), msgNode.getContentType());
}
// -----------------------------------------------------------------------------------------------
// Private helpers for building the list of message parts.
/**
* Builds the list of SoyMsgParts for all the children of a given parent node.
*
* @param parent Can be MsgNode, MsgPluralCaseNode, MsgPluralDefaultNode, MsgSelectCaseNode, or
* MsgSelectDefaultNode.
* @param msgNode The MsgNode containing 'parent'.
*/
private static ImmutableList<SoyMsgPart> buildMsgPartsForChildren(
BlockNode parent, MsgNode msgNode) {
ImmutableList.Builder<SoyMsgPart> msgParts = ImmutableList.builder();
for (StandaloneNode child : parent.getChildren()) {
if (child instanceof RawTextNode) {
String rawText = ((RawTextNode) child).getRawText();
msgParts.add(SoyMsgRawTextPart.of(rawText));
} else if (child instanceof MsgPlaceholderNode) {
String placeholderName = msgNode.getPlaceholderName((MsgPlaceholderNode) child);
msgParts.add(new SoyMsgPlaceholderPart(placeholderName));
} else if (child instanceof MsgPluralNode) {
msgParts.add(buildMsgPartForPlural((MsgPluralNode) child, msgNode));
} else if (child instanceof MsgSelectNode) {
msgParts.add(buildMsgPartForSelect((MsgSelectNode) child, msgNode));
}
}
return msgParts.build();
}
/**
* Builds the list of SoyMsgParts for the given MsgPluralNode.
*
* @param msgPluralNode The plural node parsed from the Soy source.
* @param msgNode The MsgNode containing 'msgPluralNode'.
* @return A SoyMsgPluralPart.
*/
private static SoyMsgPluralPart buildMsgPartForPlural(
MsgPluralNode msgPluralNode, MsgNode msgNode) {
// This is the list of the cases.
ImmutableList.Builder<SoyMsgPart.Case<SoyMsgPluralCaseSpec>> pluralCases =
ImmutableList.builder();
for (CaseOrDefaultNode child : msgPluralNode.getChildren()) {
ImmutableList<SoyMsgPart> caseMsgParts = buildMsgPartsForChildren(child, msgNode);
SoyMsgPluralCaseSpec caseSpec;
if (child instanceof MsgPluralCaseNode) {
caseSpec = new SoyMsgPluralCaseSpec(((MsgPluralCaseNode) child).getCaseNumber());
} else if (child instanceof MsgPluralDefaultNode) {
caseSpec = new SoyMsgPluralCaseSpec(Type.OTHER);
} else {
throw new AssertionError("Unidentified node under a plural node.");
}
pluralCases.add(SoyMsgPart.Case.create(caseSpec, caseMsgParts));
}
return new SoyMsgPluralPart(
msgNode.getPluralVarName(msgPluralNode), msgPluralNode.getOffset(), pluralCases.build());
}
/**
* Builds the list of SoyMsgParts for the given MsgSelectNode.
*
* @param msgSelectNode The select node parsed from the Soy source.
* @param msgNode The MsgNode containing 'msgSelectNode'.
* @return A SoyMsgSelectPart.
*/
private static SoyMsgSelectPart buildMsgPartForSelect(
MsgSelectNode msgSelectNode, MsgNode msgNode) {
// This is the list of the cases.
ImmutableList.Builder<SoyMsgPart.Case<String>> selectCases = ImmutableList.builder();
for (CaseOrDefaultNode child : msgSelectNode.getChildren()) {
ImmutableList<SoyMsgPart> caseMsgParts = buildMsgPartsForChildren(child, msgNode);
String caseValue;
if (child instanceof MsgSelectCaseNode) {
caseValue = ((MsgSelectCaseNode) child).getCaseValue();
} else if (child instanceof MsgSelectDefaultNode) {
caseValue = null;
} else {
throw new AssertionError("Unidentified node under a select node.");
}
selectCases.add(SoyMsgPart.Case.create(caseValue, caseMsgParts));
}
return new SoyMsgSelectPart(msgNode.getSelectVarName(msgSelectNode), selectCases.build());
}
}