1 // © 2022 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 4 package com.ibm.icu.message2; 5 6 import java.util.Locale; 7 import java.util.Map; 8 9 /** 10 * {@code MessageFormatter} is the next iteration of {@link com.ibm.icu.text.MessageFormat}. 11 * 12 * <p>This new version builds on what we learned from using {@code MessageFormat} for 20 years 13 * in various environments, either exposed "as is" or as a base for other public APIs.</p> 14 * 15 * <p>It is more modular, easier to backport, and provides extension points to add new 16 * formatters and selectors without having to modify the specification.</p> 17 * 18 * <p>We will be able to add formatters for intervals, relative times, lists, measurement units, 19 * people names, and more, and support custom formatters implemented by developers 20 * outside of ICU itself, for company or even product specific needs.</p> 21 * 22 * <p>MessageFormat 2 will support more complex grammatical features, such as gender, inflections, 23 * and tagging parts of the message for style changes or speech.</p> 24 * 25 * <p>The reasoning for this effort is shared in the 26 * <a target="github" href="https://github.com/unicode-org/message-format-wg/blob/main/docs/why_mf_next.md">“Why 27 * MessageFormat needs a successor”</a> document.</p> 28 * 29 * <p>The “MessageFormat 2” project, which develops the new data model, semantics, and syntax, 30 * is hosted on <a target="github" href="https://github.com/unicode-org/message-format-wg">GitHub</a>.</p> 31 * 32 * <p>The current specification for the syntax and data model can be found 33 * <a target="github" href="https://github.com/unicode-org/message-format-wg/blob/main/spec/syntax.md">here</a>.</p> 34 * 35 * <p>This tech preview implements enough of the {@code MessageFormat} functions to be useful, 36 * but the final set of functions and the parameters accepted by those functions is not yet finalized.</p> 37 * 38 * <p>These are the functions interpreted right now:</p> 39 * 40 * <table border="1"> 41 * <tr> 42 * <td rowspan="4">{@code datetime}</td> 43 * <td>Similar to the ICU {@code "date"} and {@code "time"}.</td> 44 * </tr> 45 * 46 * <tr><td>{@code datestyle} and {@code timestyle}<br> 47 * Similar to {@code argStyle : short | medium | long | full}.<br> 48 * Same values are accepted, but we can use both in one placeholder, 49 * for example <code>{$due :datetime datestyle=full timestyle=long}</code>. 50 * </td></tr> 51 * 52 * <tr><td>{@code pattern}<br> 53 * Similar to {@code argStyle = argStyleText}.<br> 54 * This is bad i18n practice, and will probably be dropped.<br> 55 * This is included just to support migration to MessageFormat 2. 56 * </td></tr> 57 * 58 * <tr><td>{@code skeleton}<br> 59 * Same as {@code argStyle = argSkeletonText}.<br> 60 * These are the date/time skeletons as supported by {@link com.ibm.icu.text.SimpleDateFormat}. 61 * </td></tr> 62 * 63 * <tr> 64 * <td rowspan="4">{@code number}</td> 65 * <td>Similar to the ICU "number".</td> 66 * </tr> 67 * 68 * <tr><td>{@code skeleton}<br> 69 * These are the number skeletons as supported by {@link com.ibm.icu.number.NumberFormatter}.</td></tr> 70 * 71 * <tr><td>{@code minimumFractionDigits}<br> 72 * Only implemented to be able to pass the unit tests from the ECMA tech preview implementation, 73 * which prefers options bags to skeletons.<br> 74 * TBD if the final {@number} function will support skeletons, option backs, or both.</td></tr> 75 * 76 * <tr><td>{@code offset}<br> 77 * Used to support plural with an offset.</td></tr> 78 * 79 * <tr><td >{@code identity}</td><td>Returns the direct string value of the argument (calling {@code toString()}).</td></tr> 80 * 81 * <tr> 82 * <td rowspan="3">{@code plural}</td> 83 * <td>Similar to the ICU {@code "plural"}.</td> 84 * </tr> 85 * 86 * <tr><td>{@code skeleton}<br> 87 * These are the number skeletons as supported by {@link com.ibm.icu.number.NumberFormatter}.<br> 88 * Can also be indirect, from a local variable of type {@code number} (recommended).</td></tr> 89 * 90 * <tr><td>{@code offset}<br> 91 * Used to support plural with an offset.<br> 92 * Can also be indirect, from a local variable of type {@code number} (recommended).</td></tr> 93 * 94 * <tr> 95 * <td>{@code selectordinal}</td> 96 * <td>Similar to the ICU {@code "selectordinal"}.<br> 97 * For now it accepts the same parameters as "plural", although there is no use case for them.<br> 98 * TBD if this will be merged into "plural" (with some {@code kind} option) or not.</td></tr> 99 * 100 * <tr><td>{@code select}</td><td>Literal match, same as the ICU4 {@code "select"}.</td></tr> 101 * </table> 102 * 103 * @internal ICU 72 technology preview 104 * @deprecated This API is for technology preview only. 105 */ 106 @Deprecated 107 public class MessageFormatter { 108 private final Locale locale; 109 private final String pattern; 110 private final Mf2FunctionRegistry functionRegistry; 111 private final Mf2DataModel dataModel; 112 private final Mf2DataModelFormatter modelFormatter; 113 MessageFormatter(Builder builder)114 private MessageFormatter(Builder builder) { 115 this.locale = builder.locale; 116 this.functionRegistry = builder.functionRegistry; 117 if ((builder.pattern == null && builder.dataModel == null) 118 || (builder.pattern != null && builder.dataModel != null)) { 119 throw new IllegalArgumentException("You need to set either a pattern, or a dataModel, but not both."); 120 } 121 122 if (builder.dataModel != null) { 123 this.dataModel = builder.dataModel; 124 this.pattern = Mf2Serializer.dataModelToString(this.dataModel); 125 } else { 126 this.pattern = builder.pattern; 127 Mf2Serializer tree = new Mf2Serializer(); 128 Mf2Parser parser = new Mf2Parser(pattern, tree); 129 try { 130 parser.parse_Message(); 131 dataModel = tree.build(); 132 } catch (Mf2Parser.ParseException pe) { 133 throw new IllegalArgumentException( 134 "Parse error:\n" 135 + "Message: <<" + pattern + ">>\n" 136 + "Error:" + parser.getErrorMessage(pe) + "\n"); 137 } 138 } 139 modelFormatter = new Mf2DataModelFormatter(dataModel, locale, functionRegistry); 140 } 141 142 /** 143 * Creates a builder. 144 * 145 * @return the Builder. 146 * 147 * @internal ICU 72 technology preview 148 * @deprecated This API is for technology preview only. 149 */ 150 @Deprecated builder()151 public static Builder builder() { 152 return new Builder(); 153 } 154 155 /** 156 * Get the locale to use for all the formatting and selections in 157 * the current {@code MessageFormatter}. 158 * 159 * @return the locale. 160 * 161 * @internal ICU 72 technology preview 162 * @deprecated This API is for technology preview only. 163 */ 164 @Deprecated getLocale()165 public Locale getLocale() { 166 return locale; 167 } 168 169 /** 170 * Get the pattern (the serialized message in MessageFormat 2 syntax) of 171 * the current {@code MessageFormatter}. 172 * 173 * <p>If the {@code MessageFormatter} was created from an {@link Mf2DataModel} 174 * the this string is generated from that model.</p> 175 * 176 * @return the pattern. 177 * 178 * @internal ICU 72 technology preview 179 * @deprecated This API is for technology preview only. 180 */ 181 @Deprecated getPattern()182 public String getPattern() { 183 return pattern; 184 } 185 186 /** 187 * Give public access to the message data model. 188 * 189 * <p>This data model is similar to the functionality we have today 190 * in {@link com.ibm.icu.text.MessagePatternUtil} maybe even a bit more higher level.</p> 191 * 192 * <p>We can also imagine a model where one parses the string syntax, takes the data model, 193 * modifies it, and then uses that modified model to create a {@code MessageFormatter}.</p> 194 * 195 * @return the data model. 196 * 197 * @internal ICU 72 technology preview 198 * @deprecated This API is for technology preview only. 199 */ 200 @Deprecated getDataModel()201 public Mf2DataModel getDataModel() { 202 return dataModel; 203 } 204 205 /** 206 * Formats a map of objects by iterating over the MessageFormat's pattern, 207 * with the plain text “as is” and the arguments replaced by the formatted objects. 208 * 209 * @param arguments a map of objects to be formatted and substituted. 210 * @return the string representing the message with parameters replaced. 211 * 212 * @throws IllegalArgumentException when something goes wrong 213 * (for example wrong argument type, or null arguments, etc.) 214 * 215 * @internal ICU 72 technology preview 216 * @deprecated This API is for technology preview only. 217 */ 218 @Deprecated formatToString(Map<String, Object> arguments)219 public String formatToString(Map<String, Object> arguments) { 220 return modelFormatter.format(arguments); 221 } 222 223 /** 224 * Not yet implemented: formats a map of objects by iterating over the MessageFormat's 225 * pattern, with the plain text “as is” and the arguments replaced by the formatted objects. 226 * 227 * @param arguments a map of objects to be formatted and substituted. 228 * @return the {@link FormattedMessage} class representing the message with parameters replaced. 229 * 230 * @internal ICU 72 technology preview 231 * @deprecated This API is for technology preview only. 232 */ 233 @Deprecated format(Map<String, Object> arguments)234 public FormattedMessage format(Map<String, Object> arguments) { 235 throw new RuntimeException("Not yet implemented."); 236 } 237 238 /** 239 * A {@code Builder} used to build instances of {@link MessageFormatter}. 240 * 241 * @internal ICU 72 technology preview 242 * @deprecated This API is for technology preview only. 243 */ 244 @Deprecated 245 public static class Builder { 246 private Locale locale = Locale.getDefault(Locale.Category.FORMAT); 247 private String pattern = null; 248 private Mf2FunctionRegistry functionRegistry = Mf2FunctionRegistry.builder().build(); 249 private Mf2DataModel dataModel = null; 250 251 // Prevent direct creation Builder()252 private Builder() { 253 } 254 255 /** 256 * Sets the locale to use for all formatting and selection operations. 257 * 258 * @param locale the locale to set. 259 * @return the builder, for fluent use. 260 * 261 * @internal ICU 72 technology preview 262 * @deprecated This API is for technology preview only. 263 */ 264 @Deprecated setLocale(Locale locale)265 public Builder setLocale(Locale locale) { 266 this.locale = locale; 267 return this; 268 } 269 270 /** 271 * Sets the pattern (in MessageFormat 2 syntax) used to create the message.<br> 272 * It conflicts with the data model, so it will reset it (the last call on setter wins). 273 * 274 * @param pattern the pattern to set. 275 * @return the builder, for fluent use. 276 * 277 * @internal ICU 72 technology preview 278 * @deprecated This API is for technology preview only. 279 */ 280 @Deprecated setPattern(String pattern)281 public Builder setPattern(String pattern) { 282 this.pattern = pattern; 283 this.dataModel = null; 284 return this; 285 } 286 287 /** 288 * Sets an instance of {@link Mf2FunctionRegistry} that should register any 289 * custom functions used by the message. 290 * 291 * <p>There is no need to do this in order to use standard functions 292 * (for example date / time / number formatting, plural / ordinal / literal selection).<br> 293 * The exact set of standard functions, with the types they format and the options 294 * they accept is still TBD.</p> 295 * 296 * @param functionRegistry the function registry to set. 297 * @return the builder, for fluent use. 298 * 299 * @internal ICU 72 technology preview 300 * @deprecated This API is for technology preview only. 301 */ 302 @Deprecated setFunctionRegistry(Mf2FunctionRegistry functionRegistry)303 public Builder setFunctionRegistry(Mf2FunctionRegistry functionRegistry) { 304 this.functionRegistry = functionRegistry; 305 return this; 306 } 307 308 /** 309 * Sets the data model used to create the message.<br> 310 * It conflicts with the pattern, so it will reset it (the last call on setter wins). 311 * 312 * @param dataModel the pattern to set. 313 * @return the builder, for fluent use. 314 * 315 * @internal ICU 72 technology preview 316 * @deprecated This API is for technology preview only. 317 */ 318 @Deprecated setDataModel(Mf2DataModel dataModel)319 public Builder setDataModel(Mf2DataModel dataModel) { 320 this.dataModel = dataModel; 321 this.pattern = null; 322 return this; 323 } 324 325 /** 326 * Builds an instance of {@link MessageFormatter}. 327 * 328 * @return the {@link MessageFormatter} created. 329 * 330 * @internal ICU 72 technology preview 331 * @deprecated This API is for technology preview only. 332 */ 333 @Deprecated build()334 public MessageFormatter build() { 335 return new MessageFormatter(this); 336 } 337 } 338 } 339