blob: 15185a3301edd46b423cee0905431dd7c86fccaa [file] [log] [blame]
\htmlhr
\chapterAndLabel{Internationalization Format String Checker (I18n Format String Checker)}{i18n-formatter-checker}
The Internationalization Format String Checker, or I18n Format String Checker,
prevents use of incorrect i18n format strings.
If the I18n Format String Checker issues no warnings or errors, then
\sunjavadoc{java.base/java/text/MessageFormat.html\#format(java.lang.String,java.lang.Object...)}{MessageFormat.format}
will raise no error at run time.
``I18n'' is short for
``internationalization'' because there are 18 characters between the ``i'' and
the ``n''.
Here are the examples of errors that the
I18n Format Checker
detects at compile time.
\begin{Verbatim}
// Warning: the second argument is missing.
MessageFormat.format("{0} {1}", 3.1415);
// String argument cannot be formatted as Time type.
MessageFormat.format("{0, time}", "my string");
// Invalid format string: unknown format type: thyme.
MessageFormat.format("{0, thyme}", new Date());
// Invalid format string: missing the right brace.
MessageFormat.format("{0", new Date());
// Invalid format string: the argument index is not an integer.
MessageFormat.format("{0.2, time}", new Date());
// Invalid format string: "#.#.#" subformat is invalid.
MessageFormat.format("{0, number, #.#.#}", 3.1415);
\end{Verbatim}
For instructions on how to run the Internationalization Format String
Checker, see Section~\ref{i18n-format-running}.
The Internationalization Checker or I18n Checker (\chapterpageref{i18n-checker})
has a different purpose. It verifies that your code is properly
internationalized: any user-visible text should be obtained from a
localization resource and all keys exist in that resource.
\sectionAndLabel{Internationalization Format String Checker annotations}{i18n-format-annotation}
\begin{figure}
\includeimage{i18n-format-type-hierarchy}{3cm}
\caption{The
Internationalization
Format String Checker type qualifier hierarchy.
The type qualifiers are applicable to \<CharSequence> and its subtypes.
The figure does not show the subtyping rules among different
\refqualclass{checker/i18nformatter/qual}{I18nFormat}\code{(...)}
qualifiers; see
Section~\ref{i18n-format-conversion-categories}.
All \refqualclass{checker/i18nformatter/qual}{I18nFormatFor} annotations
are unrelated by subtyping, unless they are identical.
The qualifiers in gray are used internally by
the checker and should never be written by a programmer.
}
\label{i18n-format-type-hierarchy}
\end{figure}
The \sunjavadoc{java.base/java/text/MessageFormat.html}{MessageFormat} documentation
specifies the syntax of the i18n format string.
These are the qualifiers that make up the I18n Format String type system.
Figure~\ref{i18n-format-type-hierarchy} shows their subtyping relationships.
\begin{description}
\item[\refqualclass{checker/i18nformatter/qual}{I18nFormat}]
represents a valid i18n format string. For example,
\code{@I18nFormat(\{GENERAL, NUMBER, UNUSED, DATE\})} is a legal type for
\code{"\{0\}\{1, number\} \{3, date\}"}, indicating that when the format
string is used,
the first argument should be of \code{GENERAL} conversion category,
the second argument should be of \code{NUMBER} conversion category, and so on.
Conversion categories such as \code{GENERAL} are described in
Section~\ref{i18n-format-conversion-categories}.
\item[\refqualclass{checker/i18nformatter/qual}{I18nFormatFor}]
indicates that the qualified type is a valid i18n format string for use
with some array of values. For example,
\code{@I18nFormatFor("\#2")} indicates that the string can be used to
format the contents of the second parameter array.
The argument is a Java expression whose syntax
is explained in Section~\ref{java-expressions-as-arguments}.
An example of its use is:
\begin{Verbatim}
static void method(@I18nFormatFor("#2") String format, Object... args) {
// the body may use the parameters like this:
MessageFormat.format(format, args);
}
method("{0, number} {1}", 3.1415, "A string"); // OK
// error: The string "hello" cannot be formatted as a Number.
method("{0, number} {1}", "hello", "goodbye");
\end{Verbatim}
\item[\refqualclass{checker/i18nformatter/qual}{I18nInvalidFormat}]
represents an invalid i18n format string. Programmers are not allowed to
write this annotation. It is only used internally by the type checker.
\item[\refqualclass{checker/i18nformatter/qual}{I18nUnknownFormat}]
represents any string. The string might or might not be a valid i18n
format string. Programmers are not allowed to write this annotation.
\item[\refqualclass{checker/i18nformatter/qual}{I18nFormatBottom}]
indicates that the value is definitely \<null>. Programmers are not allowed
to write this annotation.
\end{description}
\sectionAndLabel{Conversion categories}{i18n-format-conversion-categories}
In a message string, the optional second element within the curly braces is
called a \emph{format type} and must be one of \<number>, \<date>,
\<time>, and \<choice>. These four format types correspond to different
conversion categories. \<date> and \<time> correspond to \emph{DATE} in the
conversion categories figure. \<choice> corresponds to \emph{NUMBER}.
The format type restricts what arguments are legal.
For example, a date argument is not compatible with
the \<number> format type, i.e., \code{MessageFormat.format("\{0, number\}",
new Date())} will throw an exception.
The I18n Checker represents the possible arguments via \emph{conversion
categories}. A conversion category defines a set of restrictions or a
subtyping rule.
Figure~\ref{i18n-format-category} summarizes the subset
relationship among all conversion categories.
\begin{figure}
\includeimage{i18n-format-category}{5cm}
\caption{The subset relationship among
i18n
conversion categories.}
\label{i18n-format-category}
\end{figure}
\sectionAndLabel{Subtyping rules for \<@I18nFormat>}{i18n-formatter-format-subtyping}
Here are the subtyping rules among different
\code{@I18nFormat}
qualifiers.
It is legal to:
\begin{itemize}
\item use a format string with a weaker (less restrictive) conversion category than required.
\item use a format string with fewer format specifiers than required.
Although this is legal a warning is issued because most occurrences of
this are due to programmer error.
\end{itemize}
The following example shows the subtyping rules in action:
\begin{Verbatim}
@I18nFormat({NUMBER, DATE}) String f;
f = "{0, number, #.#} {1, date}"; // OK
f = "{0, number} {1}"; // OK, GENERAL is weaker (less restrictive) than DATE
f = "{0} {1, date}"; // OK, GENERAL is weaker (less restrictive) than NUMBER
f = "{0, number}"; // warning: last argument is ignored
f = "{0}"; // warning: last argument is ignored
f = "{0, number} {1, number}"; // error: NUMBER is stronger (more restrictive) than DATE
f = "{0} {1} {2}"; // error: too many arguments
\end{Verbatim}
The conversion categories are:
\begin{description}
\item[\refenum{checker/i18nformatter/qual}{I18nConversionCategory}{UNUSED}{}]
indicates an unused argument. For example, in
\code{MessageFormat.format("\{0, number\} \{2, number\}", 3.14, "Hello", 2.718)}
, the second argument \code{Hello} is unused. Thus, the conversion
categories for the format, \code{{0, number} {2, number}}, is
\code{(NUMBER, UNUSED, NUMBER)}.
\item[\refenum{checker/i18nformatter/qual}{I18nConversionCategory}{GENERAL}{}]
means that any value can be supplied as an argument.
\item[\refenum{checker/i18nformatter/qual}{I18nConversionCategory}{DATE}{}]
is applicable for date, time, and number types. An argument needs to be
of \sunjavadoc{java.sql/java/sql/Date.html}{Date},
\sunjavadoc{java.sql/java/sql/Time.html}{Time}, or
\sunjavadoc{java.base/java/lang/Number.html}{Number} type or a subclass of them,
including \sunjavadoc{java.sql/java/sql/Timestamp.html}{Timestamp} and the classes
listed immediately below.
\item[\refenum{checker/i18nformatter/qual}{I18nConversionCategory}{NUMBER}{}]
means that the argument needs to be of \code{Number}
type or a subclass:
\sunjavadoc{java.base/java/lang/Number.html}{Number},
\sunjavadoc{java.base/java/util/concurrent/atomic/AtomicInteger.html}{AtomicInteger},
\sunjavadoc{java.base/java/util/concurrent/atomic/AtomicLong.html}{AtomicLong},
\sunjavadoc{java.base/java/math/BigDecimal.html}{BigDecimal},
\sunjavadoc{java.base/java/math/BigInteger.html}{BigInteger},
\sunjavadoc{java.base/java/lang/Byte.html}{Byte},
\sunjavadoc{java.base/java/lang/Double.html}{Double},
\sunjavadoc{java.base/java/lang/Float.html}{Float},
\sunjavadoc{java.base/java/lang/Integer.html}{Integer},
\sunjavadoc{java.base/java/lang/Long.html}{Long},
\sunjavadoc{java.base/java/lang/Short.html}{Short}.
\end{description}
\sectionAndLabel{What the Internationalization Format String Checker checks}{i18n-format-checks}
The Internationalization Format String Checker checks calls to the i18n
formatting method \sunjavadoc{java.base/java/text/MessageFormat.html\#format(java.lang.String,java.lang.Object...)}{MessageFormat.format}
and guarantees the following:
\begin{enumerate}
\item{The checker issues a warning for the following cases:}
\begin{enumerate}
\item There are missing arguments from what is required by the format string.
\code{MessageFormat.format("\{0, number\} \{1, number\}", 3.14); // Output: 3.14 \{1\}}
\item More arguments are passed than what is required by the format string.
\code{MessageFormat.format("\{0, number\}", 1, new Date());}
\code{MessageFormat.format("\{0, number\} \{0, number\}", 3.14, 3.14);}
This does not cause an error at run time, but it often indicates a
programmer mistake. If it is intentional, then you should suppress
the warning (see Chapter~\ref{suppressing-warnings}).
\item Some argument is an array of objects.
\code{MessageFormat.format("\{0, number\} \{1\}", array);}
The checker cannot verify whether the format string is valid, so
the checker conservatively issues a warning. This is a limitation of
the Internationalization Format String Checker.
\end{enumerate}
\item The checker issues an error for the following cases:
\begin{enumerate}
\item The format string is invalid.
\begin{itemize}
\item Unmatched braces.
\code{MessageFormat.format("\{0, time", new Date());}
\item The argument index is not an integer or is negative.
\code{MessageFormat.format("\{0.2, time\}", new Date());}
\code{MessageFormat.format("\{-1, time\}", new Date());}
\item Unknown format type.
\code{MessageFormat.format("\{0, foo\}", 3.14);}
\item Missing a format style required for \<choice> format.
\code{MessageFormat.format("\{0, choice\}", 3.14);}
\item Wrong format style.
\code{MessageFormat.format("\{0, time, number\}", 3.14);}
\item Invalid subformats.
\code{MessageFormat.format("\{0, number, \#.\#.\#\}", 3.14)}
\end{itemize}
\item Some argument's type doesn't satisfy its conversion category.
\code{MessageFormat.format("\{0, number\}", new Date());}
\end{enumerate}
\end{enumerate}
The Checker also detects illegal assignments: assigning a non-format-string
or an incompatible format string to a variable declared as containing a
specific type of format string. For example,
\begin{Verbatim}
@I18nFormat({GENERAL, NUMBER}) String format;
// OK.
format = "{0} {1, number}";
// OK, GENERAL is weaker (less restrictive) than NUMBER.
format = "{0} {1}";
// OK, it is legal to have fewer arguments than required (less restrictive).
// But the warning will be issued instead.
format = "{0}";
// Error, the format string is stronger (more restrictive) than the specifiers.
format = "{0} {1} {2}";
// Error, the format string is more restrictive. NUMBER is a subtype of GENERAL.
format = "{0, number} {1, number}";
\end{Verbatim}
\sectionAndLabel{Resource files}{i18n-format-resource-files}
A programmer rarely writes an i18n format string literally. (The examples
in this chapter show that for simplicity.) Rather, the i18n format strings are
read from a resource file. The program chooses a resource file at run time
depending on the locale (for example, different resource files for English
and Spanish users).
\noindent For example, suppose that the \<resource1.properties> file contains
\begin{Verbatim}
key1 = The number is {0, number}.
\end{Verbatim}
\noindent Then code such as the following:
\begin{Verbatim}
String formatPattern = ResourceBundle.getBundle("resource1").getString("key1");
System.out.println(MessageFormat.format(formatPattern, 2.2361));
\end{Verbatim}
\noindent will output ``The number is 2.2361.'' A different resource file would contain
\code{key1 = El n\'{u}mero es \{0, number\}.}
When you run the I18n Format String Checker, you need to indicate which resource file it
should check. If you change the resource file or use a different resource
file, you should re-run the checker
to ensure that you did not make an error. The I18n Format String Checker supports two types of
resource files: ResourceBundles and property files. The example above shows use of
resource bundles.
For more about checking property files, see \chapterpageref{propkey-checker}.
\sectionAndLabel{Running the Internationalization Format Checker}{i18n-format-running}
The checker can be invoked by running one of the following commands (with
the whole command on one line).
\begin{itemize}
\item Using ResourceBundles:
\begin{smaller}
\code{javac -processor
org.checkerframework.checker.i18nformatter.I18nFormatterChecker
-Abundlenames=MyResource MyFile.java}
\end{smaller}
\item Using property files:
\begin{smaller}
\code{javac -processor
org.checkerframework.checker.i18nformatter.I18nFormatterChecker
-Apropfiles=MyResource.properties MyFile.java}
\end{smaller}
\item Not using a property file. Use this if the programmer hard-coded the
format patterns without loading them from a property file.
\begin{smaller}
\code{javac -processor
org.checkerframework.checker.i18nformatter.I18nFormatterChecker MyFile.java}
\end{smaller}
\end{itemize}
\sectionAndLabel{Testing whether a string has an i18n format type}{i18n-format-testing}
In the case that the checker cannot infer the i18n format type of a string,
you can use the \refmethod{checker/i18nformatter/util}{I18nFormatUtil}{hasFormat}{-java.lang.String-org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory...-}
method to define the type of the string in the scope of a conditional statement.
\begin{description}
\item[\refmethod{checker/i18nformatter/util}{I18nFormatUtil}{hasFormat}{-java.lang.String-org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory...-}]
returns \<true> if the given string has the given i18n format type.
\end{description}
\noindent For an example, see Section~\ref{i18n-format-examples}.
To use the \refclass{checker/i18nformatter/util}{I18nFormatUtil} class, the \<checker-util.jar> file
must be on the classpath at run time.
\sectionAndLabel{Examples of using the Internationalization Format Checker}{i18n-format-examples}
\begin{itemize}
\item Using \sunjavadoc{java.base/java/text/MessageFormat.html\#format(java.lang.String,java.lang.Object...)}{MessageFormat.format}.
\begin{Verbatim}
// suppose the bundle "MyResource" contains: key1={0, number} {1, date}
String value = ResourceBundle.getBundle("MyResource").getString("key1");
MessageFormat.format(value, 3.14, new Date()); // OK
// error: incompatible types in argument; found String, expected number
MessageFormat.format(value, "Text", new Date());
\end{Verbatim}
\item Using the
\refmethod{checker/i18nformatter/util}{I18nFormatUtil}{hasFormat}{-java.lang.String-org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory...-}
method to check whether a format
string has particular conversion categories.
\begin{Verbatim}
void test1(String format) {
if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.GENERAL,
I18nConversionCategory.NUMBER)) {
MessageFormat.format(format, "Hello", 3.14); // OK
// error: incompatible types in argument; found String, expected number
MessageFormat.format(format, "Hello", "Bye");
// error: missing arguments; expected 2 but 1 given
MessageFormat.format(format, "Bye");
// error: too many arguments; expected 2 but 3 given
MessageFormat.format(format, "A String", 3.14, 3.14);
}
}
\end{Verbatim}
\item Using \refqualclass{checker/i18nformatter/qual}{I18nFormatFor}
to ensure that an argument is a particular type of format string.
\begin{Verbatim}
static void method(@I18nFormatFor("#2") String f, Object... args) {...}
// OK, MessageFormat.format(...) would return "3.14 Hello greater than one"
method("{0, number} {1} {2, choice,0#zero|1#one|1<greater than one}",
3.14, "Hello", 100);
// error: incompatible types in argument; found String, expected number
method("{0, number} {1}", "Bye", "Bye");
\end{Verbatim}
\item Annotating a string with
\refqualclass{checker/i18nformatter/qual}{I18nFormat}.
\begin{Verbatim}
@I18nFormat({I18nConversionCategory.DATE}) String;
s1 = "{0}";
s1 = "{0, number}"; // error: incompatible types in assignment
\end{Verbatim}
\end{itemize}
%% LocalWords: I18n i18n java MessageFormat I18nFormat I18nFormatFor
%% LocalWords: arg I18nInvalid I18nUnknownFormat I18nFormatBottom
%% LocalWords: Timestamp AtomicInteger AtomicLong BigDecimal BigInteger
%% LocalWords: number' foo subformats resource1 key1 mero Abundlenames
%% LocalWords: ResourceBundles MyResource MyFile Apropfiles hasFormat
%% LocalWords: I18nFormatUtil formatter CharSequence I18nInvalidFormat