Class MessagePattern

  • All Implemented Interfaces:
    Freezable<MessagePattern>, java.lang.Cloneable

    public final class MessagePattern
    extends java.lang.Object
    implements java.lang.Cloneable, Freezable<MessagePattern>
    Parses and represents ICU MessageFormat patterns. Also handles patterns for ChoiceFormat, PluralFormat and SelectFormat. Used in the implementations of those classes as well as in tools for message validation, translation and format conversion.

    The parser handles all syntax relevant for identifying message arguments. This includes "complex" arguments whose style strings contain nested MessageFormat pattern substrings. For "simple" arguments (with no nested MessageFormat pattern substrings), the argument style is not parsed any further.

    The parser handles named and numbered message arguments and allows both in one message.

    Once a pattern has been parsed successfully, iterate through the parsed data with countParts(), getPart() and related methods.

    The data logically represents a parse tree, but is stored and accessed as a list of "parts" for fast and simple parsing and to minimize object allocations. Arguments and nested messages are best handled via recursion. For every _START "part", getLimitPartIndex(int) efficiently returns the index of the corresponding _LIMIT "part".

    List of "parts":

     message = MSG_START (SKIP_SYNTAX | INSERT_CHAR | REPLACE_NUMBER | argument)* MSG_LIMIT
     argument = noneArg | simpleArg | complexArg
     complexArg = choiceArg | pluralArg | selectArg
    
     noneArg = ARG_START.NONE (ARG_NAME | ARG_NUMBER) ARG_LIMIT.NONE
     simpleArg = ARG_START.SIMPLE (ARG_NAME | ARG_NUMBER) ARG_TYPE [ARG_STYLE] ARG_LIMIT.SIMPLE
     choiceArg = ARG_START.CHOICE (ARG_NAME | ARG_NUMBER) choiceStyle ARG_LIMIT.CHOICE
     pluralArg = ARG_START.PLURAL (ARG_NAME | ARG_NUMBER) pluralStyle ARG_LIMIT.PLURAL
     selectArg = ARG_START.SELECT (ARG_NAME | ARG_NUMBER) selectStyle ARG_LIMIT.SELECT
    
     choiceStyle = ((ARG_INT | ARG_DOUBLE) ARG_SELECTOR message)+
     pluralStyle = [ARG_INT | ARG_DOUBLE] (ARG_SELECTOR [ARG_INT | ARG_DOUBLE] message)+
     selectStyle = (ARG_SELECTOR message)+
     
    • Literal output text is not represented directly by "parts" but accessed between parts of a message, from one part's getLimit() to the next part's getIndex().
    • ARG_START.CHOICE stands for an ARG_START Part with ArgType CHOICE.
    • In the choiceStyle, the ARG_SELECTOR has the '<', the '#' or the less-than-or-equal-to sign (U+2264).
    • In the pluralStyle, the first, optional numeric Part has the "offset:" value. The optional numeric Part between each (ARG_SELECTOR, message) pair is the value of an explicit-number selector like "=2", otherwise the selector is a non-numeric identifier.
    • The REPLACE_NUMBER Part can occur only in an immediate sub-message of the pluralStyle.

    This class is not intended for public subclassing.

    • Constructor Detail

      • MessagePattern

        public MessagePattern()
        Constructs an empty MessagePattern with default ApostropheMode.
      • MessagePattern

        public MessagePattern​(MessagePattern.ApostropheMode mode)
        Constructs an empty MessagePattern.
        Parameters:
        mode - Explicit ApostropheMode.
      • MessagePattern

        public MessagePattern​(java.lang.String pattern)
        Constructs a MessagePattern with default ApostropheMode and parses the MessageFormat pattern string.
        Parameters:
        pattern - a MessageFormat pattern string
        Throws:
        java.lang.IllegalArgumentException - for syntax errors in the pattern string
        java.lang.IndexOutOfBoundsException - if certain limits are exceeded (e.g., argument number too high, argument name too long, etc.)
        java.lang.NumberFormatException - if a number could not be parsed
    • Method Detail

      • parse

        public MessagePattern parse​(java.lang.String pattern)
        Parses a MessageFormat pattern string.
        Parameters:
        pattern - a MessageFormat pattern string
        Returns:
        this
        Throws:
        java.lang.IllegalArgumentException - for syntax errors in the pattern string
        java.lang.IndexOutOfBoundsException - if certain limits are exceeded (e.g., argument number too high, argument name too long, etc.)
        java.lang.NumberFormatException - if a number could not be parsed
      • parseChoiceStyle

        public MessagePattern parseChoiceStyle​(java.lang.String pattern)
        Parses a ChoiceFormat pattern string.
        Parameters:
        pattern - a ChoiceFormat pattern string
        Returns:
        this
        Throws:
        java.lang.IllegalArgumentException - for syntax errors in the pattern string
        java.lang.IndexOutOfBoundsException - if certain limits are exceeded (e.g., argument number too high, argument name too long, etc.)
        java.lang.NumberFormatException - if a number could not be parsed
      • parsePluralStyle

        public MessagePattern parsePluralStyle​(java.lang.String pattern)
        Parses a PluralFormat pattern string.
        Parameters:
        pattern - a PluralFormat pattern string
        Returns:
        this
        Throws:
        java.lang.IllegalArgumentException - for syntax errors in the pattern string
        java.lang.IndexOutOfBoundsException - if certain limits are exceeded (e.g., argument number too high, argument name too long, etc.)
        java.lang.NumberFormatException - if a number could not be parsed
      • parseSelectStyle

        public MessagePattern parseSelectStyle​(java.lang.String pattern)
        Parses a SelectFormat pattern string.
        Parameters:
        pattern - a SelectFormat pattern string
        Returns:
        this
        Throws:
        java.lang.IllegalArgumentException - for syntax errors in the pattern string
        java.lang.IndexOutOfBoundsException - if certain limits are exceeded (e.g., argument number too high, argument name too long, etc.)
        java.lang.NumberFormatException - if a number could not be parsed
      • clear

        public void clear()
        Clears this MessagePattern. countParts() will return 0.
      • clearPatternAndSetApostropheMode

        public void clearPatternAndSetApostropheMode​(MessagePattern.ApostropheMode mode)
        Clears this MessagePattern and sets the ApostropheMode. countParts() will return 0.
        Parameters:
        mode - The new ApostropheMode.
      • equals

        public boolean equals​(java.lang.Object other)
        Overrides:
        equals in class java.lang.Object
        Parameters:
        other - another object to compare with.
        Returns:
        true if this object is equivalent to the other one.
      • hashCode

        public int hashCode()
        Overrides:
        hashCode in class java.lang.Object
      • jdkAposMode

        boolean jdkAposMode()
        Returns:
        true if getApostropheMode() == ApostropheMode.DOUBLE_REQUIRED
      • getPatternString

        public java.lang.String getPatternString()
        Returns:
        the parsed pattern string (null if none was parsed).
      • hasNamedArguments

        public boolean hasNamedArguments()
        Does the parsed pattern have named arguments like {first_name}?
        Returns:
        true if the parsed pattern has at least one named argument.
      • hasNumberedArguments

        public boolean hasNumberedArguments()
        Does the parsed pattern have numbered arguments like {2}?
        Returns:
        true if the parsed pattern has at least one numbered argument.
      • toString

        public java.lang.String toString()
        Overrides:
        toString in class java.lang.Object
      • validateArgumentName

        public static int validateArgumentName​(java.lang.String name)
        Validates and parses an argument name or argument number string. An argument name must be a "pattern identifier", that is, it must contain no Unicode Pattern_Syntax or Pattern_White_Space characters. If it only contains ASCII digits, then it must be a small integer with no leading zero.
        Parameters:
        name - Input string.
        Returns:
        >=0 if the name is a valid number, ARG_NAME_NOT_NUMBER (-1) if it is a "pattern identifier" but not all ASCII digits, ARG_NAME_NOT_VALID (-2) if it is neither.
      • autoQuoteApostropheDeep

        public java.lang.String autoQuoteApostropheDeep()
        Returns a version of the parsed pattern string where each ASCII apostrophe is doubled (escaped) if it is not already, and if it is not interpreted as quoting syntax.

        For example, this turns "I don't '{know}' {gender,select,female{h''er}other{h'im}}." into "I don''t '{know}' {gender,select,female{h''er}other{h''im}}."

        Returns:
        the deep-auto-quoted version of the parsed pattern string.
        See Also:
        MessageFormat.autoQuoteApostrophe(String)
      • countParts

        public int countParts()
        Returns the number of "parts" created by parsing the pattern string. Returns 0 if no pattern has been parsed or clear() was called.
        Returns:
        the number of pattern parts.
      • getPart

        public MessagePattern.Part getPart​(int i)
        Gets the i-th pattern "part".
        Parameters:
        i - The index of the Part data. (0..countParts()-1)
        Returns:
        the i-th pattern "part".
        Throws:
        java.lang.IndexOutOfBoundsException - if i is outside the (0..countParts()-1) range
      • getPartType

        public MessagePattern.Part.Type getPartType​(int i)
        Returns the Part.Type of the i-th pattern "part". Convenience method for getPart(i).getType().
        Parameters:
        i - The index of the Part data. (0..countParts()-1)
        Returns:
        The Part.Type of the i-th Part.
        Throws:
        java.lang.IndexOutOfBoundsException - if i is outside the (0..countParts()-1) range
      • getPatternIndex

        public int getPatternIndex​(int partIndex)
        Returns the pattern index of the specified pattern "part". Convenience method for getPart(partIndex).getIndex().
        Parameters:
        partIndex - The index of the Part data. (0..countParts()-1)
        Returns:
        The pattern index of this Part.
        Throws:
        java.lang.IndexOutOfBoundsException - if partIndex is outside the (0..countParts()-1) range
      • getSubstring

        public java.lang.String getSubstring​(MessagePattern.Part part)
        Returns the substring of the pattern string indicated by the Part. Convenience method for getPatternString().substring(part.getIndex(), part.getLimit()).
        Parameters:
        part - a part of this MessagePattern.
        Returns:
        the substring associated with part.
      • partSubstringMatches

        public boolean partSubstringMatches​(MessagePattern.Part part,
                                            java.lang.String s)
        Compares the part's substring with the input string s.
        Parameters:
        part - a part of this MessagePattern.
        s - a string.
        Returns:
        true if getSubstring(part).equals(s).
      • getNumericValue

        public double getNumericValue​(MessagePattern.Part part)
        Returns the numeric value associated with an ARG_INT or ARG_DOUBLE.
        Parameters:
        part - a part of this MessagePattern.
        Returns:
        the part's numeric value, or NO_NUMERIC_VALUE if this is not a numeric part.
      • getPluralOffset

        public double getPluralOffset​(int pluralStart)
        Returns the "offset:" value of a PluralFormat argument, or 0 if none is specified.
        Parameters:
        pluralStart - the index of the first PluralFormat argument style part. (0..countParts()-1)
        Returns:
        the "offset:" value.
        Throws:
        java.lang.IndexOutOfBoundsException - if pluralStart is outside the (0..countParts()-1) range
      • getLimitPartIndex

        public int getLimitPartIndex​(int start)
        Returns the index of the ARG|MSG_LIMIT part corresponding to the ARG|MSG_START at start.
        Parameters:
        start - The index of some Part data (0..countParts()-1); this Part should be of Type ARG_START or MSG_START.
        Returns:
        The first i>start where getPart(i).getType()==ARG|MSG_LIMIT at the same nesting level, or start itself if getPartType(msgStart)!=ARG|MSG_START.
        Throws:
        java.lang.IndexOutOfBoundsException - if start is outside the (0..countParts()-1) range
      • clone

        public java.lang.Object clone()
        Creates and returns a copy of this object.
        Overrides:
        clone in class java.lang.Object
        Returns:
        a copy of this object (or itself if frozen).
      • isFrozen

        public boolean isFrozen()
        Determines whether this object is frozen (immutable) or not.
        Specified by:
        isFrozen in interface Freezable<MessagePattern>
        Returns:
        true if this object is frozen.
      • preParse

        private void preParse​(java.lang.String pattern)
      • postParse

        private void postParse()
      • parseMessage

        private int parseMessage​(int index,
                                 int msgStartLength,
                                 int nestingLevel,
                                 MessagePattern.ArgType parentType)
      • parseArg

        private int parseArg​(int index,
                             int argStartLength,
                             int nestingLevel)
      • parseSimpleStyle

        private int parseSimpleStyle​(int index)
      • parseChoiceStyle

        private int parseChoiceStyle​(int index,
                                     int nestingLevel)
      • parsePluralOrSelectStyle

        private int parsePluralOrSelectStyle​(MessagePattern.ArgType argType,
                                             int index,
                                             int nestingLevel)
      • parseArgNumber

        private static int parseArgNumber​(java.lang.CharSequence s,
                                          int start,
                                          int limit)
        Validates and parses an argument name or argument number string. This internal method assumes that the input substring is a "pattern identifier".
        Returns:
        >=0 if the name is a valid number, ARG_NAME_NOT_NUMBER (-1) if it is a "pattern identifier" but not all ASCII digits, ARG_NAME_NOT_VALID (-2) if it is neither.
        See Also:
        validateArgumentName(String)
      • parseArgNumber

        private int parseArgNumber​(int start,
                                   int limit)
      • parseDouble

        private void parseDouble​(int start,
                                 int limit,
                                 boolean allowInfinity)
        Parses a number from the specified message substring.
        Parameters:
        start - start index into the message string
        limit - limit index into the message string, must be start
        allowInfinity - true if U+221E is allowed (for ChoiceFormat)
      • appendReducedApostrophes

        static void appendReducedApostrophes​(java.lang.String s,
                                             int start,
                                             int limit,
                                             java.lang.StringBuilder sb)
        Appends the s[start, limit[ substring to sb, but with only half of the apostrophes according to JDK pattern behavior.
      • skipWhiteSpace

        private int skipWhiteSpace​(int index)
      • skipIdentifier

        private int skipIdentifier​(int index)
      • skipDouble

        private int skipDouble​(int index)
        Skips a sequence of characters that could occur in a double value. Does not fully parse or validate the value.
      • isArgTypeChar

        private static boolean isArgTypeChar​(int c)
      • isChoice

        private boolean isChoice​(int index)
      • isPlural

        private boolean isPlural​(int index)
      • isSelect

        private boolean isSelect​(int index)
      • isOrdinal

        private boolean isOrdinal​(int index)
      • inMessageFormatPattern

        private boolean inMessageFormatPattern​(int nestingLevel)
        Returns:
        true if we are inside a MessageFormat (sub-)pattern, as opposed to inside a top-level choice/plural/select pattern.
      • inTopLevelChoiceMessage

        private boolean inTopLevelChoiceMessage​(int nestingLevel,
                                                MessagePattern.ArgType parentType)
        Returns:
        true if we are in a MessageFormat sub-pattern of a top-level ChoiceFormat pattern.
      • addLimitPart

        private void addLimitPart​(int start,
                                  MessagePattern.Part.Type type,
                                  int index,
                                  int length,
                                  int value)
      • addArgDoublePart

        private void addArgDoublePart​(double numericValue,
                                      int start,
                                      int length)
      • prefix

        private static java.lang.String prefix​(java.lang.String s,
                                               int start)
        Returns a prefix of s.substring(start). Used for Exception messages.
        Parameters:
        s -
        start - start index in s
        Returns:
        s.substring(start) or a prefix of that
      • prefix

        private static java.lang.String prefix​(java.lang.String s)
      • prefix

        private java.lang.String prefix​(int start)
      • prefix

        private java.lang.String prefix()