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