Home | History | Annotate | Download | only in text
      1 /* GENERATED SOURCE. DO NOT MODIFY. */
      2 //  2016 and later: Unicode, Inc. and others.
      3 // License & terms of use: http://www.unicode.org/copyright.html#License
      4 /*
      5 *******************************************************************************
      6 *   Copyright (C) 2011-2014, International Business Machines
      7 *   Corporation and others.  All Rights Reserved.
      8 *******************************************************************************
      9 *   created on: 2011jul14
     10 *   created by: Markus W. Scherer
     11 */
     12 
     13 package android.icu.text;
     14 
     15 import java.util.ArrayList;
     16 import java.util.Collections;
     17 import java.util.List;
     18 
     19 /**
     20  * Utilities for working with a MessagePattern.
     21  * Intended for use in tools when convenience is more important than
     22  * minimizing runtime and object creations.
     23  *
     24  * <p>This class only has static methods.
     25  * Each of the nested classes is immutable and thread-safe.
     26  *
     27  * <p>This class and its nested classes are not intended for public subclassing.
     28  * @author Markus Scherer
     29  * @hide Only a subset of ICU is exposed in Android
     30  */
     31 public final class MessagePatternUtil {
     32 
     33     // Private constructor preventing object instantiation
     34     private MessagePatternUtil() {
     35     }
     36 
     37     /**
     38      * Factory method, builds and returns a MessageNode from a MessageFormat pattern string.
     39      * @param patternString a MessageFormat pattern string
     40      * @return a MessageNode or a ComplexArgStyleNode
     41      * @throws IllegalArgumentException if the MessagePattern is empty
     42      *         or does not represent a MessageFormat pattern
     43      */
     44     public static MessageNode buildMessageNode(String patternString) {
     45         return buildMessageNode(new MessagePattern(patternString));
     46     }
     47 
     48     /**
     49      * Factory method, builds and returns a MessageNode from a MessagePattern.
     50      * @param pattern a parsed MessageFormat pattern string
     51      * @return a MessageNode or a ComplexArgStyleNode
     52      * @throws IllegalArgumentException if the MessagePattern is empty
     53      *         or does not represent a MessageFormat pattern
     54      */
     55     public static MessageNode buildMessageNode(MessagePattern pattern) {
     56         int limit = pattern.countParts() - 1;
     57         if (limit < 0) {
     58             throw new IllegalArgumentException("The MessagePattern is empty");
     59         } else if (pattern.getPartType(0) != MessagePattern.Part.Type.MSG_START) {
     60             throw new IllegalArgumentException(
     61             "The MessagePattern does not represent a MessageFormat pattern");
     62         }
     63         return buildMessageNode(pattern, 0, limit);
     64     }
     65 
     66     /**
     67      * Common base class for all elements in a tree of nodes
     68      * returned by {@link MessagePatternUtil#buildMessageNode(MessagePattern)}.
     69      * This class and all subclasses are immutable and thread-safe.
     70      */
     71     public static class Node {
     72         private Node() {}
     73     }
     74 
     75     /**
     76      * A Node representing a parsed MessageFormat pattern string.
     77      */
     78     public static class MessageNode extends Node {
     79         /**
     80          * @return the list of MessageContentsNode nodes that this message contains
     81          */
     82         public List<MessageContentsNode> getContents() {
     83             return list;
     84         }
     85         /**
     86          * {@inheritDoc}
     87          */
     88         @Override
     89         public String toString() {
     90             return list.toString();
     91         }
     92 
     93         private MessageNode() {
     94             super();
     95         }
     96         private void addContentsNode(MessageContentsNode node) {
     97             if (node instanceof TextNode && !list.isEmpty()) {
     98                 // Coalesce adjacent text nodes.
     99                 MessageContentsNode lastNode = list.get(list.size() - 1);
    100                 if (lastNode instanceof TextNode) {
    101                     TextNode textNode = (TextNode)lastNode;
    102                     textNode.text = textNode.text + ((TextNode)node).text;
    103                     return;
    104                 }
    105             }
    106             list.add(node);
    107         }
    108         private MessageNode freeze() {
    109             list = Collections.unmodifiableList(list);
    110             return this;
    111         }
    112 
    113         private volatile List<MessageContentsNode> list = new ArrayList<MessageContentsNode>();
    114     }
    115 
    116     /**
    117      * A piece of MessageNode contents.
    118      * Use getType() to determine the type and the actual Node subclass.
    119      */
    120     public static class MessageContentsNode extends Node {
    121         /**
    122          * The type of a piece of MessageNode contents.
    123          */
    124         public enum Type {
    125             /**
    126              * This is a TextNode containing literal text (downcast and call getText()).
    127              */
    128             TEXT,
    129             /**
    130              * This is an ArgNode representing a message argument
    131              * (downcast and use specific methods).
    132              */
    133             ARG,
    134             /**
    135              * This Node represents a place in a plural argument's variant where
    136              * the formatted (plural-offset) value is to be put.
    137              */
    138             REPLACE_NUMBER
    139         }
    140         /**
    141          * Returns the type of this piece of MessageNode contents.
    142          */
    143         public Type getType() {
    144             return type;
    145         }
    146         /**
    147          * {@inheritDoc}
    148          */
    149         @Override
    150         public String toString() {
    151             // Note: There is no specific subclass for REPLACE_NUMBER
    152             // because it would not provide any additional API.
    153             // Therefore we have a little bit of REPLACE_NUMBER-specific code
    154             // here in the contents-node base class.
    155             return "{REPLACE_NUMBER}";
    156         }
    157 
    158         private MessageContentsNode(Type type) {
    159             super();
    160             this.type = type;
    161         }
    162         private static MessageContentsNode createReplaceNumberNode() {
    163             return new MessageContentsNode(Type.REPLACE_NUMBER);
    164         }
    165 
    166         private Type type;
    167     }
    168 
    169     /**
    170      * Literal text, a piece of MessageNode contents.
    171      */
    172     public static class TextNode extends MessageContentsNode {
    173         /**
    174          * @return the literal text at this point in the message
    175          */
    176         public String getText() {
    177             return text;
    178         }
    179         /**
    180          * {@inheritDoc}
    181          */
    182         @Override
    183         public String toString() {
    184             return "" + text + "";
    185         }
    186 
    187         private TextNode(String text) {
    188             super(Type.TEXT);
    189             this.text = text;
    190         }
    191 
    192         private String text;
    193     }
    194 
    195     /**
    196      * A piece of MessageNode contents representing a message argument and its details.
    197      */
    198     public static class ArgNode extends MessageContentsNode {
    199         /**
    200          * @return the argument type
    201          */
    202         public MessagePattern.ArgType getArgType() {
    203             return argType;
    204         }
    205         /**
    206          * @return the argument name string (the decimal-digit string if the argument has a number)
    207          */
    208         public String getName() {
    209             return name;
    210         }
    211         /**
    212          * @return the argument number, or -1 if none (for a named argument)
    213          */
    214         public int getNumber() {
    215             return number;
    216         }
    217         /**
    218          * @return the argument type string, or null if none was specified
    219          */
    220         public String getTypeName() {
    221             return typeName;
    222         }
    223         /**
    224          * @return the simple-argument style string,
    225          *         or null if no style is specified and for other argument types
    226          */
    227         public String getSimpleStyle() {
    228             return style;
    229         }
    230         /**
    231          * @return the complex-argument-style object,
    232          *         or null if the argument type is NONE_ARG or SIMPLE_ARG
    233          */
    234         public ComplexArgStyleNode getComplexStyle() {
    235             return complexStyle;
    236         }
    237         /**
    238          * {@inheritDoc}
    239          */
    240         @Override
    241         public String toString() {
    242             StringBuilder sb = new StringBuilder();
    243             sb.append('{').append(name);
    244             if (argType != MessagePattern.ArgType.NONE) {
    245                 sb.append(',').append(typeName);
    246                 if (argType == MessagePattern.ArgType.SIMPLE) {
    247                     if (style != null) {
    248                         sb.append(',').append(style);
    249                     }
    250                 } else {
    251                     sb.append(',').append(complexStyle.toString());
    252                 }
    253             }
    254             return sb.append('}').toString();
    255         }
    256 
    257         private ArgNode() {
    258             super(Type.ARG);
    259         }
    260         private static ArgNode createArgNode() {
    261             return new ArgNode();
    262         }
    263 
    264         private MessagePattern.ArgType argType;
    265         private String name;
    266         private int number = -1;
    267         private String typeName;
    268         private String style;
    269         private ComplexArgStyleNode complexStyle;
    270     }
    271 
    272     /**
    273      * A Node representing details of the argument style of a complex argument.
    274      * (Which is a choice/plural/select argument which selects among nested messages.)
    275      */
    276     public static class ComplexArgStyleNode extends Node {
    277         /**
    278          * @return the argument type (same as getArgType() on the parent ArgNode)
    279          */
    280         public MessagePattern.ArgType getArgType() {
    281             return argType;
    282         }
    283         /**
    284          * @return true if this is a plural style with an explicit offset
    285          */
    286         public boolean hasExplicitOffset() {
    287             return explicitOffset;
    288         }
    289         /**
    290          * @return the plural offset, or 0 if this is not a plural style or
    291          *         the offset is explicitly or implicitly 0
    292          */
    293         public double getOffset() {
    294             return offset;
    295         }
    296         /**
    297          * @return the list of variants: the nested messages with their selection criteria
    298          */
    299         public List<VariantNode> getVariants() {
    300             return list;
    301         }
    302         /**
    303          * Separates the variants by type.
    304          * Intended for use with plural and select argument styles,
    305          * not useful for choice argument styles.
    306          *
    307          * <p>Both parameters are used only for output, and are first cleared.
    308          * @param numericVariants Variants with numeric-value selectors (if any) are added here.
    309          *        Can be null for a select argument style.
    310          * @param keywordVariants Variants with keyword selectors, except "other", are added here.
    311          *        For a plural argument, if this list is empty after the call, then
    312          *        all variants except "other" have explicit values
    313          *        and PluralRules need not be called.
    314          * @return the "other" variant (the first one if there are several),
    315          *         null if none (choice style)
    316          */
    317         public VariantNode getVariantsByType(List<VariantNode> numericVariants,
    318                                              List<VariantNode> keywordVariants) {
    319             if (numericVariants != null) {
    320                 numericVariants.clear();
    321             }
    322             keywordVariants.clear();
    323             VariantNode other = null;
    324             for (VariantNode variant : list) {
    325                 if (variant.isSelectorNumeric()) {
    326                     numericVariants.add(variant);
    327                 } else if ("other".equals(variant.getSelector())) {
    328                     if (other == null) {
    329                         // Return the first "other" variant. (MessagePattern allows duplicates.)
    330                         other = variant;
    331                     }
    332                 } else {
    333                     keywordVariants.add(variant);
    334                 }
    335             }
    336             return other;
    337         }
    338         /**
    339          * {@inheritDoc}
    340          */
    341         @Override
    342         public String toString() {
    343             StringBuilder sb = new StringBuilder();
    344             sb.append('(').append(argType.toString()).append(" style) ");
    345             if (hasExplicitOffset()) {
    346                 sb.append("offset:").append(offset).append(' ');
    347             }
    348             return sb.append(list.toString()).toString();
    349         }
    350 
    351         private ComplexArgStyleNode(MessagePattern.ArgType argType) {
    352             super();
    353             this.argType = argType;
    354         }
    355         private void addVariant(VariantNode variant) {
    356             list.add(variant);
    357         }
    358         private ComplexArgStyleNode freeze() {
    359             list = Collections.unmodifiableList(list);
    360             return this;
    361         }
    362 
    363         private MessagePattern.ArgType argType;
    364         private double offset;
    365         private boolean explicitOffset;
    366         private volatile List<VariantNode> list = new ArrayList<VariantNode>();
    367     }
    368 
    369     /**
    370      * A Node representing a nested message (nested inside an argument)
    371      * with its selection criterium.
    372      */
    373     public static class VariantNode extends Node {
    374         /**
    375          * Returns the selector string.
    376          * For example: A plural/select keyword ("few"), a plural explicit value ("=1"),
    377          * a choice comparison operator ("#").
    378          * @return the selector string
    379          */
    380         public String getSelector() {
    381             return selector;
    382         }
    383         /**
    384          * @return true for choice variants and for plural explicit values
    385          */
    386         public boolean isSelectorNumeric() {
    387             return numericValue != MessagePattern.NO_NUMERIC_VALUE;
    388         }
    389         /**
    390          * @return the selector's numeric value, or NO_NUMERIC_VALUE if !isSelectorNumeric()
    391          */
    392         public double getSelectorValue() {
    393             return numericValue;
    394         }
    395         /**
    396          * @return the nested message
    397          */
    398         public MessageNode getMessage() {
    399             return msgNode;
    400         }
    401         /**
    402          * {@inheritDoc}
    403          */
    404         @Override
    405         public String toString() {
    406             StringBuilder sb = new StringBuilder();
    407             if (isSelectorNumeric()) {
    408                 sb.append(numericValue).append(" (").append(selector).append(") {");
    409             } else {
    410                 sb.append(selector).append(" {");
    411             }
    412             return sb.append(msgNode.toString()).append('}').toString();
    413         }
    414 
    415         private VariantNode() {
    416             super();
    417         }
    418 
    419         private String selector;
    420         private double numericValue = MessagePattern.NO_NUMERIC_VALUE;
    421         private MessageNode msgNode;
    422     }
    423 
    424     private static MessageNode buildMessageNode(MessagePattern pattern, int start, int limit) {
    425         int prevPatternIndex = pattern.getPart(start).getLimit();
    426         MessageNode node = new MessageNode();
    427         for (int i = start + 1;; ++i) {
    428             MessagePattern.Part part = pattern.getPart(i);
    429             int patternIndex = part.getIndex();
    430             if (prevPatternIndex < patternIndex) {
    431                 node.addContentsNode(
    432                         new TextNode(pattern.getPatternString().substring(prevPatternIndex,
    433                                      patternIndex)));
    434             }
    435             if (i == limit) {
    436                 break;
    437             }
    438             MessagePattern.Part.Type partType = part.getType();
    439             if (partType == MessagePattern.Part.Type.ARG_START) {
    440                 int argLimit = pattern.getLimitPartIndex(i);
    441                 node.addContentsNode(buildArgNode(pattern, i, argLimit));
    442                 i = argLimit;
    443                 part = pattern.getPart(i);
    444             } else if (partType == MessagePattern.Part.Type.REPLACE_NUMBER) {
    445                 node.addContentsNode(MessageContentsNode.createReplaceNumberNode());
    446                 // else: ignore SKIP_SYNTAX and INSERT_CHAR parts.
    447             }
    448             prevPatternIndex = part.getLimit();
    449         }
    450         return node.freeze();
    451     }
    452 
    453     private static ArgNode buildArgNode(MessagePattern pattern, int start, int limit) {
    454         ArgNode node = ArgNode.createArgNode();
    455         MessagePattern.Part part = pattern.getPart(start);
    456         MessagePattern.ArgType argType = node.argType = part.getArgType();
    457         part = pattern.getPart(++start);  // ARG_NAME or ARG_NUMBER
    458         node.name = pattern.getSubstring(part);
    459         if (part.getType() == MessagePattern.Part.Type.ARG_NUMBER) {
    460             node.number = part.getValue();
    461         }
    462         ++start;
    463         switch(argType) {
    464         case SIMPLE:
    465             // ARG_TYPE
    466             node.typeName = pattern.getSubstring(pattern.getPart(start++));
    467             if (start < limit) {
    468                 // ARG_STYLE
    469                 node.style = pattern.getSubstring(pattern.getPart(start));
    470             }
    471             break;
    472         case CHOICE:
    473             node.typeName = "choice";
    474             node.complexStyle = buildChoiceStyleNode(pattern, start, limit);
    475             break;
    476         case PLURAL:
    477             node.typeName = "plural";
    478             node.complexStyle = buildPluralStyleNode(pattern, start, limit, argType);
    479             break;
    480         case SELECT:
    481             node.typeName = "select";
    482             node.complexStyle = buildSelectStyleNode(pattern, start, limit);
    483             break;
    484         case SELECTORDINAL:
    485             node.typeName = "selectordinal";
    486             node.complexStyle = buildPluralStyleNode(pattern, start, limit, argType);
    487             break;
    488         default:
    489             // NONE type, nothing else to do
    490             break;
    491         }
    492         return node;
    493     }
    494 
    495     private static ComplexArgStyleNode buildChoiceStyleNode(MessagePattern pattern,
    496                                                             int start, int limit) {
    497         ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.CHOICE);
    498         while (start < limit) {
    499             int valueIndex = start;
    500             MessagePattern.Part part = pattern.getPart(start);
    501             double value = pattern.getNumericValue(part);
    502             start += 2;
    503             int msgLimit = pattern.getLimitPartIndex(start);
    504             VariantNode variant = new VariantNode();
    505             variant.selector = pattern.getSubstring(pattern.getPart(valueIndex + 1));
    506             variant.numericValue = value;
    507             variant.msgNode = buildMessageNode(pattern, start, msgLimit);
    508             node.addVariant(variant);
    509             start = msgLimit + 1;
    510         }
    511         return node.freeze();
    512     }
    513 
    514     private static ComplexArgStyleNode buildPluralStyleNode(MessagePattern pattern,
    515                                                             int start, int limit,
    516                                                             MessagePattern.ArgType argType) {
    517         ComplexArgStyleNode node = new ComplexArgStyleNode(argType);
    518         MessagePattern.Part offset = pattern.getPart(start);
    519         if (offset.getType().hasNumericValue()) {
    520             node.explicitOffset = true;
    521             node.offset = pattern.getNumericValue(offset);
    522             ++start;
    523         }
    524         while (start < limit) {
    525             MessagePattern.Part selector = pattern.getPart(start++);
    526             double value = MessagePattern.NO_NUMERIC_VALUE;
    527             MessagePattern.Part part = pattern.getPart(start);
    528             if (part.getType().hasNumericValue()) {
    529                 value = pattern.getNumericValue(part);
    530                 ++start;
    531             }
    532             int msgLimit = pattern.getLimitPartIndex(start);
    533             VariantNode variant = new VariantNode();
    534             variant.selector = pattern.getSubstring(selector);
    535             variant.numericValue = value;
    536             variant.msgNode = buildMessageNode(pattern, start, msgLimit);
    537             node.addVariant(variant);
    538             start = msgLimit + 1;
    539         }
    540         return node.freeze();
    541     }
    542 
    543     private static ComplexArgStyleNode buildSelectStyleNode(MessagePattern pattern,
    544                                                             int start, int limit) {
    545         ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.SELECT);
    546         while (start < limit) {
    547             MessagePattern.Part selector = pattern.getPart(start++);
    548             int msgLimit = pattern.getLimitPartIndex(start);
    549             VariantNode variant = new VariantNode();
    550             variant.selector = pattern.getSubstring(selector);
    551             variant.msgNode = buildMessageNode(pattern, start, msgLimit);
    552             node.addVariant(variant);
    553             start = msgLimit + 1;
    554         }
    555         return node.freeze();
    556     }
    557 }
    558