summaryrefslogtreecommitdiff
path: root/icu4j/main/core/src/main/java/com/ibm/icu/message2/MessageFormatter.java
blob: 7185f3cf69b246b9ca8fd12646b186c18cafb624 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
// © 2022 and later: Unicode, Inc. and others.
// License & terms of use: https://www.unicode.org/copyright.html

package com.ibm.icu.message2;

import java.util.Locale;
import java.util.Map;

/**
 * <h3>Overview of {@code MessageFormatter}</h3>
 *
 * <p>In ICU4J, the {@code MessageFormatter} class is the next iteration of {@link com.ibm.icu.text.MessageFormat}.
 * This new version will build on the lessons learned from using MessageFormat for 25 years
 * in various environments, when used directly or as a base for other public APIs.</p>
 *
 *
 * <p>The effort to design a succesor to {@code MessageFormat} will result in a specification
 * referred to as MessageFormat 2.0.
 * The reasoning for this effort is shared in the
 * <a target="github" href="https://github.com/unicode-org/message-format-wg/blob/main/docs/why_mf_next.md">“Why
 * MessageFormat needs a successor”</a> document.</p>
 *
 * <p>MessageFormat 2.0 will be more modular and easier to port and backport.
 * It will also provide extension points via interfaces to allow users to supply new formatters and selectors without having to modify the specification.
 * ICU will eventually include support for new formatters, such as intervals, relative time, lists, measurement units, personal names, and more,
 * as well as the ability for users to supply their own custom implementations.
 * These will potentially support use cases like grammatical gender, inflection, markup regimes (such as those require for text-to-speech),
 * and other complex message management needs.</p>
 *
 * <p>The MessageFormat Working Group, which develops the new data model, semantics, and syntax,
 * is hosted on <a target="github" href="https://github.com/unicode-org/message-format-wg">GitHub</a>.
 * The current specification for the syntax and data model can be found
 * <a target="github" href="https://github.com/unicode-org/message-format-wg/blob/main/spec/syntax.md">here</a>.</p>
 *
 * <p>This technical preview implements enough functions for {@code MessageFormatter} to be useful in many situations,
 * but the final set of functions and the parameters accepted by those functions is not yet finalized.</p>
 *
 * <h3>Examples</h3>
 *
 * <h4>Basic usage</h4>
 *
 * <blockquote><pre>
 * import static org.junit.Assert.assertEquals;
 * import java.util.Date;
 * import java.util.HashMap;
 * import java.util.Locale;
 * import java.util.Map;
 *
 * import com.ibm.icu.message2.MessageFormatter;
 *
 * &#064;Test
 * public void test() {
 *     final Locale enGb = Locale.forLanguageTag("en-GB");
 *     Map<String, Object> arguments = new HashMap<>();
 *     arguments.put("name", "John");
 *     arguments.put("exp", new Date(2023 - 1900, 2, 27, 19, 42, 51));  // March 27, 2023, 7:42:51 PM
 *
 *     MessageFormatter mf2 = MessageFormatter.builder()
 *             .setPattern("Hello {$name}, your card expires on {$exp :datetime year=numeric month=short day=numeric weekday=short}!")
 *             .setLocale(enGb)
 *             .build();
 *
 *     assertEquals(
 *             "Hello John, your card expires on Mon, 27 Mar 2023!",
 *             mf2.formatToString(arguments));
 * }
 * </pre></blockquote>
 *
 * <h4>Placeholder examples</h4>
 *
 * <table border="1">
 *   <tr>
 *     <th>Code to set runtime value for placeholder</th>
 *     <th>Examples of placeholder in message pattern</th>
 *   </tr>
 *   <tr>
 *     <td><code>arguments.put("name", "John")</code></td>
 *     <td><code>{$name}</code></td>
 *   </tr>
 *   <tr>
 *     <td><code>arguments.put("exp", new Date(…))</code></td>
 *     <td><code>{$exp :datetime skeleton=year=numeric month=short day=numeric weekday=short}</code> <br>
 *         <code>{$exp :datetime dateStyle=full}</code></td>
 *   </tr>
 *   <tr>
 *     <td><code>arguments.put("val", 3.141592653)</code></td>
 *     <td><code>{$val}</code> <br> <code>{$val :number minimumFractionDigits=5}</code></td>
 *   </tr>
 *   <tr>
 *     <td>No argument for fixed values known at build time</td>
 *     <td><code>{|123456789.531| :number}</code></td>
 *   </tr>
 * </table>
 *
 * <h4>Plural selection message</h4>
 *
 * <blockquote><pre>
 * &#064;Test
 * public void testSelection() {
 *    final String message = ".match {$count :number}\n"
 *            + " 1 {{You have one notification.}}\n"
 *            + " * {{You have {$count} notifications.}}\n";
 *    final Locale enGb = Locale.forLanguageTag("en-GB");
 *    Map<String, Object> arguments = new HashMap<>();
 *
 *    MessageFormatter mf2 = MessageFormatter.builder()
 *        .setPattern(message)
 *        .setLocale(enGb)
 *        .build();
 *
 *    arguments.put("count", 1);
 *    assertEquals(
 *        "You have one notification.",
 *        mf2.formatToString(arguments));
 *
 *    arguments.put("count", 42);
 *    assertEquals(
 *        "You have 42 notifications.",
 *        mf2.formatToString(arguments));
 * }
 * </pre></blockquote>
 *
 * <h4>Built-in formatter functions</h4>
 *
 * <p>The tech preview implementation comes with formatters for numbers ({@code :number}),
 * date / time ({@code :datetime}, {@code :date}, {@code :time}),
 * plural selectors ({@code :number} with options for {@code plural} and {@code ordinal} selection),
 * and general selector ({@code :string}),
 * very similar to what {@code MessageFormat} offers.</p>
 *
 * <p>The <a target="github" href="https://github.com/unicode-org/icu/tree/main/icu4j/main/core/src/test/java/com/ibm/icu/dev/test/message2">ICU test code</a>
 * covers most features, and has examples of how to make custom placeholder formatters;
 * you can look for classes that implement {@code com.ibm.icu.message2.FormatterFactory}
 * (they are named {@code Custom*Test.java}).</p>
 *
 * <p>The complete list of valid options for each function, and how they infulence the results, can be found at
 * <a target="github" href="https://github.com/unicode-org/message-format-wg/blob/main/spec/registry.md">here</a>.<p>
 *
 * @internal ICU 72 technology preview
 * @deprecated This API is for technology preview only.
 */
@Deprecated
public class MessageFormatter {
    private final Locale locale;
    private final String pattern;
    private final MFFunctionRegistry functionRegistry;
    private final MFDataModel.Message dataModel;
    private final MFDataModelFormatter modelFormatter;

    private MessageFormatter(Builder builder) {
        this.locale = builder.locale;
        this.functionRegistry = builder.functionRegistry;
        if ((builder.pattern == null && builder.dataModel == null)
                || (builder.pattern != null && builder.dataModel != null)) {
            throw new IllegalArgumentException(
                    "You need to set either a pattern, or a dataModel, but not both.");
        }

        if (builder.dataModel != null) {
            this.dataModel = builder.dataModel;
            // this.pattern = MFSerializer.dataModelToString(this.dataModel);
            this.pattern = MFSerializer.dataModelToString(dataModel);
        } else {
            this.pattern = builder.pattern;
            try {
                this.dataModel = MFParser.parse(pattern);
            } catch (MFParseException pe) {
                throw new IllegalArgumentException(""
                        + "Parse error:\n"
                        + "Message: <<" + pattern + ">>\n"
                        + "Error: " + pe.getMessage() + "\n");
            }
        }
        modelFormatter = new MFDataModelFormatter(dataModel, locale, functionRegistry);
    }

    /**
     * Creates a builder.
     *
     * @return the Builder.
     *
     * @internal ICU 72 technology preview
     * @deprecated This API is for technology preview only.
     */
    @Deprecated
    public static Builder builder() {
        return new Builder();
    }

    /**
     * Get the locale to use for all the formatting and selections in
     * the current {@code MessageFormatter}.
     *
     * @return the locale.
     *
     * @internal ICU 72 technology preview
     * @deprecated This API is for technology preview only.
     */
    @Deprecated
    public Locale getLocale() {
        return locale;
    }

    /**
     * Get the pattern (the serialized message in MessageFormat 2 syntax) of
     * the current {@code MessageFormatter}.
     *
     * <p>If the {@code MessageFormatter} was created from an {@link MFDataModel}
     * the this string is generated from that model.</p>
     *
     * @return the pattern.
     *
     * @internal ICU 72 technology preview
     * @deprecated This API is for technology preview only.
     */
    @Deprecated
    public String getPattern() {
        return pattern;
    }

    /**
     * Give public access to the message data model.
     *
     * <p>This data model is similar to the functionality we have today
     * in {@link com.ibm.icu.text.MessagePatternUtil} maybe even a bit more higher level.</p>
     *
     * <p>We can also imagine a model where one parses the string syntax, takes the data model,
     * modifies it, and then uses that modified model to create a {@code MessageFormatter}.</p>
     *
     * @return the data model.
     *
     * @internal ICU 72 technology preview
     * @deprecated This API is for technology preview only.
     */
    @Deprecated
    public MFDataModel.Message getDataModel() {
        return dataModel;
    }

    /**
     * Formats a map of objects by iterating over the MessageFormat's pattern,
     * with the plain text “as is” and the arguments replaced by the formatted objects.
     *
     * @param arguments a map of objects to be formatted and substituted.
     * @return the string representing the message with parameters replaced.
     *
     * @throws IllegalArgumentException when something goes wrong
     *         (for example wrong argument type, or null arguments, etc.)
     *
     * @internal ICU 72 technology preview
     * @deprecated This API is for technology preview only.
     */
    @Deprecated
    public String formatToString(Map<String, Object> arguments) {
        return modelFormatter.format(arguments);
    }

    /**
     * Not yet implemented: formats a map of objects by iterating over the MessageFormat's
     * pattern, with the plain text “as is” and the arguments replaced by the formatted objects.
     *
     * @param arguments a map of objects to be formatted and substituted.
     * @return the {@link FormattedMessage} class representing the message with parameters replaced.
     *
     * @internal ICU 72 technology preview
     * @deprecated This API is for technology preview only.
     */
    @Deprecated
    @SuppressWarnings("static-method")
    public FormattedMessage format(Map<String, Object> arguments) {
        throw new RuntimeException("Not yet implemented.");
    }

    /**
     * A {@code Builder} used to build instances of {@link MessageFormatter}.
     *
     * @internal ICU 72 technology preview
     * @deprecated This API is for technology preview only.
     */
    @Deprecated
    public static class Builder {
        private Locale locale = Locale.getDefault(Locale.Category.FORMAT);
        private String pattern = null;
        private MFFunctionRegistry functionRegistry = MFFunctionRegistry.builder().build();
        private MFDataModel.Message dataModel = null;

        // Prevent direct creation
        private Builder() {}

        /**
         * Sets the locale to use for all formatting and selection operations.
         *
         * @param locale the locale to set.
         * @return the builder, for fluent use.
         *
         * @internal ICU 72 technology preview
         * @deprecated This API is for technology preview only.
         */
        @Deprecated
        public Builder setLocale(Locale locale) {
            this.locale = locale;
            return this;
        }

        /**
         * Sets the pattern (in MessageFormat 2 syntax) used to create the message.<br>
         * It conflicts with the data model, so it will reset it (the last call on setter wins).
         *
         * @param pattern the pattern to set.
         * @return the builder, for fluent use.
         *
         * @internal ICU 72 technology preview
         * @deprecated This API is for technology preview only.
         */
        @Deprecated
        public Builder setPattern(String pattern) {
            this.pattern = pattern;
            this.dataModel = null;
            return this;
        }

        /**
         * Sets an instance of {@link MFFunctionRegistry} that should register any
         * custom functions used by the message.
         *
         * <p>There is no need to do this in order to use standard functions
         * (for example date / time / number formatting, plural / ordinal / literal selection).<br>
         * The exact set of standard functions, with the types they format and the options
         * they accept is still TBD.</p>
         *
         * @param functionRegistry the function registry to set.
         * @return the builder, for fluent use.
         *
         * @internal ICU 72 technology preview
         * @deprecated This API is for technology preview only.
         */
        @Deprecated
        public Builder setFunctionRegistry(MFFunctionRegistry functionRegistry) {
            this.functionRegistry = functionRegistry;
            return this;
        }

        /**
         * Sets the data model used to create the message.<br>
         * It conflicts with the pattern, so it will reset it (the last call on setter wins).
         *
         * @param dataModel the pattern to set.
         * @return the builder, for fluent use.
         *
         * @internal ICU 72 technology preview
         * @deprecated This API is for technology preview only.
         */
        @Deprecated
        public Builder setDataModel(MFDataModel.Message dataModel) {
            this.dataModel = dataModel;
            this.pattern = null;
            return this;
        }

        /**
         * Builds an instance of {@link MessageFormatter}.
         *
         * @return the {@link MessageFormatter} created.
         *
         * @internal ICU 72 technology preview
         * @deprecated This API is for technology preview only.
         */
        @Deprecated
        public MessageFormatter build() {
            return new MessageFormatter(this);
        }
    }
}