All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.icu.text.MessagePatternUtil Maven / Gradle / Ivy

There is a newer version: 2.12.15
Show newest version
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
/*
*******************************************************************************
*   Copyright (C) 2011-2014, International Business Machines
*   Corporation and others.  All Rights Reserved.
*******************************************************************************
*   created on: 2011jul14
*   created by: Markus W. Scherer
*/

package com.ibm.icu.text;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Utilities for working with a MessagePattern.
 * Intended for use in tools when convenience is more important than
 * minimizing runtime and object creations.
 *
 * 

This class only has static methods. * Each of the nested classes is immutable and thread-safe. * *

This class and its nested classes are not intended for public subclassing. * @stable ICU 49 * @author Markus Scherer */ public final class MessagePatternUtil { // Private constructor preventing object instantiation private MessagePatternUtil() { } /** * Factory method, builds and returns a MessageNode from a MessageFormat pattern string. * @param patternString a MessageFormat pattern string * @return a MessageNode or a ComplexArgStyleNode * @throws IllegalArgumentException if the MessagePattern is empty * or does not represent a MessageFormat pattern * @stable ICU 49 */ public static MessageNode buildMessageNode(String patternString) { return buildMessageNode(new MessagePattern(patternString)); } /** * Factory method, builds and returns a MessageNode from a MessagePattern. * @param pattern a parsed MessageFormat pattern string * @return a MessageNode or a ComplexArgStyleNode * @throws IllegalArgumentException if the MessagePattern is empty * or does not represent a MessageFormat pattern * @stable ICU 49 */ public static MessageNode buildMessageNode(MessagePattern pattern) { int limit = pattern.countParts() - 1; if (limit < 0) { throw new IllegalArgumentException("The MessagePattern is empty"); } else if (pattern.getPartType(0) != MessagePattern.Part.Type.MSG_START) { throw new IllegalArgumentException( "The MessagePattern does not represent a MessageFormat pattern"); } return buildMessageNode(pattern, 0, limit); } /** * Common base class for all elements in a tree of nodes * returned by {@link MessagePatternUtil#buildMessageNode(MessagePattern)}. * This class and all subclasses are immutable and thread-safe. * @stable ICU 49 */ public static class Node { private Node() {} } /** * A Node representing a parsed MessageFormat pattern string. * @stable ICU 49 */ public static class MessageNode extends Node { /** * @return the list of MessageContentsNode nodes that this message contains * @stable ICU 49 */ public List getContents() { return list; } /** * {@inheritDoc} * @stable ICU 49 */ @Override public String toString() { return list.toString(); } private MessageNode() { super(); } private void addContentsNode(MessageContentsNode node) { if (node instanceof TextNode && !list.isEmpty()) { // Coalesce adjacent text nodes. MessageContentsNode lastNode = list.get(list.size() - 1); if (lastNode instanceof TextNode) { TextNode textNode = (TextNode)lastNode; textNode.text = textNode.text + ((TextNode)node).text; return; } } list.add(node); } private MessageNode freeze() { list = Collections.unmodifiableList(list); return this; } private volatile List list = new ArrayList(); } /** * A piece of MessageNode contents. * Use getType() to determine the type and the actual Node subclass. * @stable ICU 49 */ public static class MessageContentsNode extends Node { /** * The type of a piece of MessageNode contents. * @stable ICU 49 */ public enum Type { /** * This is a TextNode containing literal text (downcast and call getText()). * @stable ICU 49 */ TEXT, /** * This is an ArgNode representing a message argument * (downcast and use specific methods). * @stable ICU 49 */ ARG, /** * This Node represents a place in a plural argument's variant where * the formatted (plural-offset) value is to be put. * @stable ICU 49 */ REPLACE_NUMBER } /** * Returns the type of this piece of MessageNode contents. * @stable ICU 49 */ public Type getType() { return type; } /** * {@inheritDoc} * @stable ICU 49 */ @Override public String toString() { // Note: There is no specific subclass for REPLACE_NUMBER // because it would not provide any additional API. // Therefore we have a little bit of REPLACE_NUMBER-specific code // here in the contents-node base class. return "{REPLACE_NUMBER}"; } private MessageContentsNode(Type type) { super(); this.type = type; } private static MessageContentsNode createReplaceNumberNode() { return new MessageContentsNode(Type.REPLACE_NUMBER); } private Type type; } /** * Literal text, a piece of MessageNode contents. * @stable ICU 49 */ public static class TextNode extends MessageContentsNode { /** * @return the literal text at this point in the message * @stable ICU 49 */ public String getText() { return text; } /** * {@inheritDoc} * @stable ICU 49 */ @Override public String toString() { return "«" + text + "»"; } private TextNode(String text) { super(Type.TEXT); this.text = text; } private String text; } /** * A piece of MessageNode contents representing a message argument and its details. * @stable ICU 49 */ public static class ArgNode extends MessageContentsNode { /** * @return the argument type * @stable ICU 49 */ public MessagePattern.ArgType getArgType() { return argType; } /** * @return the argument name string (the decimal-digit string if the argument has a number) * @stable ICU 49 */ public String getName() { return name; } /** * @return the argument number, or -1 if none (for a named argument) * @stable ICU 49 */ public int getNumber() { return number; } /** * @return the argument type string, or null if none was specified * @stable ICU 49 */ public String getTypeName() { return typeName; } /** * @return the simple-argument style string, * or null if no style is specified and for other argument types * @stable ICU 49 */ public String getSimpleStyle() { return style; } /** * @return the complex-argument-style object, * or null if the argument type is NONE_ARG or SIMPLE_ARG * @stable ICU 49 */ public ComplexArgStyleNode getComplexStyle() { return complexStyle; } /** * {@inheritDoc} * @stable ICU 49 */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append('{').append(name); if (argType != MessagePattern.ArgType.NONE) { sb.append(',').append(typeName); if (argType == MessagePattern.ArgType.SIMPLE) { if (style != null) { sb.append(',').append(style); } } else { sb.append(',').append(complexStyle.toString()); } } return sb.append('}').toString(); } private ArgNode() { super(Type.ARG); } private static ArgNode createArgNode() { return new ArgNode(); } private MessagePattern.ArgType argType; private String name; private int number = -1; private String typeName; private String style; private ComplexArgStyleNode complexStyle; } /** * A Node representing details of the argument style of a complex argument. * (Which is a choice/plural/select argument which selects among nested messages.) * @stable ICU 49 */ public static class ComplexArgStyleNode extends Node { /** * @return the argument type (same as getArgType() on the parent ArgNode) * @stable ICU 49 */ public MessagePattern.ArgType getArgType() { return argType; } /** * @return true if this is a plural style with an explicit offset * @stable ICU 49 */ public boolean hasExplicitOffset() { return explicitOffset; } /** * @return the plural offset, or 0 if this is not a plural style or * the offset is explicitly or implicitly 0 * @stable ICU 49 */ public double getOffset() { return offset; } /** * @return the list of variants: the nested messages with their selection criteria * @stable ICU 49 */ public List getVariants() { return list; } /** * Separates the variants by type. * Intended for use with plural and select argument styles, * not useful for choice argument styles. * *

Both parameters are used only for output, and are first cleared. * @param numericVariants Variants with numeric-value selectors (if any) are added here. * Can be null for a select argument style. * @param keywordVariants Variants with keyword selectors, except "other", are added here. * For a plural argument, if this list is empty after the call, then * all variants except "other" have explicit values * and PluralRules need not be called. * @return the "other" variant (the first one if there are several), * null if none (choice style) * @stable ICU 49 */ public VariantNode getVariantsByType(List numericVariants, List keywordVariants) { if (numericVariants != null) { numericVariants.clear(); } keywordVariants.clear(); VariantNode other = null; for (VariantNode variant : list) { if (variant.isSelectorNumeric()) { numericVariants.add(variant); } else if ("other".equals(variant.getSelector())) { if (other == null) { // Return the first "other" variant. (MessagePattern allows duplicates.) other = variant; } } else { keywordVariants.add(variant); } } return other; } /** * {@inheritDoc} * @stable ICU 49 */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append('(').append(argType.toString()).append(" style) "); if (hasExplicitOffset()) { sb.append("offset:").append(offset).append(' '); } return sb.append(list.toString()).toString(); } private ComplexArgStyleNode(MessagePattern.ArgType argType) { super(); this.argType = argType; } private void addVariant(VariantNode variant) { list.add(variant); } private ComplexArgStyleNode freeze() { list = Collections.unmodifiableList(list); return this; } private MessagePattern.ArgType argType; private double offset; private boolean explicitOffset; private volatile List list = new ArrayList(); } /** * A Node representing a nested message (nested inside an argument) * with its selection criterium. * @stable ICU 49 */ public static class VariantNode extends Node { /** * Returns the selector string. * For example: A plural/select keyword ("few"), a plural explicit value ("=1"), * a choice comparison operator ("#"). * @return the selector string * @stable ICU 49 */ public String getSelector() { return selector; } /** * @return true for choice variants and for plural explicit values * @stable ICU 49 */ public boolean isSelectorNumeric() { return numericValue != MessagePattern.NO_NUMERIC_VALUE; } /** * @return the selector's numeric value, or NO_NUMERIC_VALUE if !isSelectorNumeric() * @stable ICU 49 */ public double getSelectorValue() { return numericValue; } /** * @return the nested message * @stable ICU 49 */ public MessageNode getMessage() { return msgNode; } /** * {@inheritDoc} * @stable ICU 49 */ @Override public String toString() { StringBuilder sb = new StringBuilder(); if (isSelectorNumeric()) { sb.append(numericValue).append(" (").append(selector).append(") {"); } else { sb.append(selector).append(" {"); } return sb.append(msgNode.toString()).append('}').toString(); } private VariantNode() { super(); } private String selector; private double numericValue = MessagePattern.NO_NUMERIC_VALUE; private MessageNode msgNode; } private static MessageNode buildMessageNode(MessagePattern pattern, int start, int limit) { int prevPatternIndex = pattern.getPart(start).getLimit(); MessageNode node = new MessageNode(); for (int i = start + 1;; ++i) { MessagePattern.Part part = pattern.getPart(i); int patternIndex = part.getIndex(); if (prevPatternIndex < patternIndex) { node.addContentsNode( new TextNode(pattern.getPatternString().substring(prevPatternIndex, patternIndex))); } if (i == limit) { break; } MessagePattern.Part.Type partType = part.getType(); if (partType == MessagePattern.Part.Type.ARG_START) { int argLimit = pattern.getLimitPartIndex(i); node.addContentsNode(buildArgNode(pattern, i, argLimit)); i = argLimit; part = pattern.getPart(i); } else if (partType == MessagePattern.Part.Type.REPLACE_NUMBER) { node.addContentsNode(MessageContentsNode.createReplaceNumberNode()); // else: ignore SKIP_SYNTAX and INSERT_CHAR parts. } prevPatternIndex = part.getLimit(); } return node.freeze(); } private static ArgNode buildArgNode(MessagePattern pattern, int start, int limit) { ArgNode node = ArgNode.createArgNode(); MessagePattern.Part part = pattern.getPart(start); MessagePattern.ArgType argType = node.argType = part.getArgType(); part = pattern.getPart(++start); // ARG_NAME or ARG_NUMBER node.name = pattern.getSubstring(part); if (part.getType() == MessagePattern.Part.Type.ARG_NUMBER) { node.number = part.getValue(); } ++start; switch(argType) { case SIMPLE: // ARG_TYPE node.typeName = pattern.getSubstring(pattern.getPart(start++)); if (start < limit) { // ARG_STYLE node.style = pattern.getSubstring(pattern.getPart(start)); } break; case CHOICE: node.typeName = "choice"; node.complexStyle = buildChoiceStyleNode(pattern, start, limit); break; case PLURAL: node.typeName = "plural"; node.complexStyle = buildPluralStyleNode(pattern, start, limit, argType); break; case SELECT: node.typeName = "select"; node.complexStyle = buildSelectStyleNode(pattern, start, limit); break; case SELECTORDINAL: node.typeName = "selectordinal"; node.complexStyle = buildPluralStyleNode(pattern, start, limit, argType); break; default: // NONE type, nothing else to do break; } return node; } private static ComplexArgStyleNode buildChoiceStyleNode(MessagePattern pattern, int start, int limit) { ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.CHOICE); while (start < limit) { int valueIndex = start; MessagePattern.Part part = pattern.getPart(start); double value = pattern.getNumericValue(part); start += 2; int msgLimit = pattern.getLimitPartIndex(start); VariantNode variant = new VariantNode(); variant.selector = pattern.getSubstring(pattern.getPart(valueIndex + 1)); variant.numericValue = value; variant.msgNode = buildMessageNode(pattern, start, msgLimit); node.addVariant(variant); start = msgLimit + 1; } return node.freeze(); } private static ComplexArgStyleNode buildPluralStyleNode(MessagePattern pattern, int start, int limit, MessagePattern.ArgType argType) { ComplexArgStyleNode node = new ComplexArgStyleNode(argType); MessagePattern.Part offset = pattern.getPart(start); if (offset.getType().hasNumericValue()) { node.explicitOffset = true; node.offset = pattern.getNumericValue(offset); ++start; } while (start < limit) { MessagePattern.Part selector = pattern.getPart(start++); double value = MessagePattern.NO_NUMERIC_VALUE; MessagePattern.Part part = pattern.getPart(start); if (part.getType().hasNumericValue()) { value = pattern.getNumericValue(part); ++start; } int msgLimit = pattern.getLimitPartIndex(start); VariantNode variant = new VariantNode(); variant.selector = pattern.getSubstring(selector); variant.numericValue = value; variant.msgNode = buildMessageNode(pattern, start, msgLimit); node.addVariant(variant); start = msgLimit + 1; } return node.freeze(); } private static ComplexArgStyleNode buildSelectStyleNode(MessagePattern pattern, int start, int limit) { ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.SELECT); while (start < limit) { MessagePattern.Part selector = pattern.getPart(start++); int msgLimit = pattern.getLimitPartIndex(start); VariantNode variant = new VariantNode(); variant.selector = pattern.getSubstring(selector); variant.msgNode = buildMessageNode(pattern, start, msgLimit); node.addVariant(variant); start = msgLimit + 1; } return node.freeze(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy