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