1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2012-2016, Google, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.text; 10 11 import java.io.IOException; 12 import java.util.ArrayList; 13 import java.util.Arrays; 14 import java.util.Collection; 15 import java.util.Iterator; 16 import java.util.Locale; 17 18 import com.ibm.icu.impl.ICUCache; 19 import com.ibm.icu.impl.ICUData; 20 import com.ibm.icu.impl.ICUResourceBundle; 21 import com.ibm.icu.impl.SimpleCache; 22 import com.ibm.icu.impl.SimpleFormatterImpl; 23 import com.ibm.icu.util.ICUUncheckedIOException; 24 import com.ibm.icu.util.ULocale; 25 import com.ibm.icu.util.UResourceBundle; 26 27 /** 28 * Immutable class for formatting a list, using data from CLDR (or supplied 29 * separately). The class is not subclassable. 30 * 31 * @author Mark Davis 32 * @stable ICU 50 33 */ 34 final public class ListFormatter { 35 // Compiled SimpleFormatter patterns. 36 private final String two; 37 private final String start; 38 private final String middle; 39 private final String end; 40 private final ULocale locale; 41 42 /** 43 * Indicates the style of Listformatter 44 * @internal 45 * @deprecated This API is ICU internal only. 46 */ 47 @Deprecated 48 public enum Style { 49 /** 50 * Standard style. 51 * @internal 52 * @deprecated This API is ICU internal only. 53 */ 54 @Deprecated 55 STANDARD("standard"), 56 /** 57 * Style for full durations 58 * @internal 59 * @deprecated This API is ICU internal only. 60 */ 61 @Deprecated 62 DURATION("unit"), 63 /** 64 * Style for durations in abbrevated form 65 * @internal 66 * @deprecated This API is ICU internal only. 67 */ 68 @Deprecated 69 DURATION_SHORT("unit-short"), 70 /** 71 * Style for durations in narrow form 72 * @internal 73 * @deprecated This API is ICU internal only. 74 */ 75 @Deprecated 76 DURATION_NARROW("unit-narrow"); 77 78 private final String name; 79 Style(String name)80 Style(String name) { 81 this.name = name; 82 } 83 /** 84 * @internal 85 * @deprecated This API is ICU internal only. 86 */ 87 @Deprecated getName()88 public String getName() { 89 return name; 90 } 91 92 } 93 94 /** 95 * <b>Internal:</b> Create a ListFormatter from component strings, 96 * with definitions as in LDML. 97 * 98 * @param two 99 * string for two items, containing {0} for the first, and {1} 100 * for the second. 101 * @param start 102 * string for the start of a list items, containing {0} for the 103 * first, and {1} for the rest. 104 * @param middle 105 * string for the start of a list items, containing {0} for the 106 * first part of the list, and {1} for the rest of the list. 107 * @param end 108 * string for the end of a list items, containing {0} for the 109 * first part of the list, and {1} for the last item. 110 * @internal 111 * @deprecated This API is ICU internal only. 112 */ 113 @Deprecated ListFormatter(String two, String start, String middle, String end)114 public ListFormatter(String two, String start, String middle, String end) { 115 this( 116 compilePattern(two, new StringBuilder()), 117 compilePattern(start, new StringBuilder()), 118 compilePattern(middle, new StringBuilder()), 119 compilePattern(end, new StringBuilder()), 120 null); 121 } 122 ListFormatter(String two, String start, String middle, String end, ULocale locale)123 private ListFormatter(String two, String start, String middle, String end, ULocale locale) { 124 this.two = two; 125 this.start = start; 126 this.middle = middle; 127 this.end = end; 128 this.locale = locale; 129 } 130 compilePattern(String pattern, StringBuilder sb)131 private static String compilePattern(String pattern, StringBuilder sb) { 132 return SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2); 133 } 134 135 /** 136 * Create a list formatter that is appropriate for a locale. 137 * 138 * @param locale 139 * the locale in question. 140 * @return ListFormatter 141 * @stable ICU 50 142 */ getInstance(ULocale locale)143 public static ListFormatter getInstance(ULocale locale) { 144 return getInstance(locale, Style.STANDARD); 145 } 146 147 /** 148 * Create a list formatter that is appropriate for a locale. 149 * 150 * @param locale 151 * the locale in question. 152 * @return ListFormatter 153 * @stable ICU 50 154 */ getInstance(Locale locale)155 public static ListFormatter getInstance(Locale locale) { 156 return getInstance(ULocale.forLocale(locale), Style.STANDARD); 157 } 158 159 /** 160 * Create a list formatter that is appropriate for a locale and style. 161 * 162 * @param locale the locale in question. 163 * @param style the style 164 * @return ListFormatter 165 * @internal 166 * @deprecated This API is ICU internal only. 167 */ 168 @Deprecated getInstance(ULocale locale, Style style)169 public static ListFormatter getInstance(ULocale locale, Style style) { 170 return cache.get(locale, style.getName()); 171 } 172 173 /** 174 * Create a list formatter that is appropriate for the default FORMAT locale. 175 * 176 * @return ListFormatter 177 * @stable ICU 50 178 */ getInstance()179 public static ListFormatter getInstance() { 180 return getInstance(ULocale.getDefault(ULocale.Category.FORMAT)); 181 } 182 183 /** 184 * Format a list of objects. 185 * 186 * @param items 187 * items to format. The toString() method is called on each. 188 * @return items formatted into a string 189 * @stable ICU 50 190 */ format(Object... items)191 public String format(Object... items) { 192 return format(Arrays.asList(items)); 193 } 194 195 /** 196 * Format a collection of objects. The toString() method is called on each. 197 * 198 * @param items 199 * items to format. The toString() method is called on each. 200 * @return items formatted into a string 201 * @stable ICU 50 202 */ format(Collection<?> items)203 public String format(Collection<?> items) { 204 return format(items, -1).toString(); 205 } 206 207 // Formats a collection of objects and returns the formatted string plus the offset 208 // in the string where the index th element appears. index is zero based. If index is 209 // negative or greater than or equal to the size of items then this function returns -1 for 210 // the offset. format(Collection<?> items, int index)211 FormattedListBuilder format(Collection<?> items, int index) { 212 Iterator<?> it = items.iterator(); 213 int count = items.size(); 214 switch (count) { 215 case 0: 216 return new FormattedListBuilder("", false); 217 case 1: 218 return new FormattedListBuilder(it.next(), index == 0); 219 case 2: 220 return new FormattedListBuilder(it.next(), index == 0).append(two, it.next(), index == 1); 221 } 222 FormattedListBuilder builder = new FormattedListBuilder(it.next(), index == 0); 223 builder.append(start, it.next(), index == 1); 224 for (int idx = 2; idx < count - 1; ++idx) { 225 builder.append(middle, it.next(), index == idx); 226 } 227 return builder.append(end, it.next(), index == count - 1); 228 } 229 230 /** 231 * Returns the pattern to use for a particular item count. 232 * @param count the item count. 233 * @return the pattern with {0}, {1}, {2}, etc. For English, 234 * getPatternForNumItems(3) == "{0}, {1}, and {2}" 235 * @throws IllegalArgumentException when count is 0 or negative. 236 * @stable ICU 52 237 */ getPatternForNumItems(int count)238 public String getPatternForNumItems(int count) { 239 if (count <= 0) { 240 throw new IllegalArgumentException("count must be > 0"); 241 } 242 ArrayList<String> list = new ArrayList<String>(); 243 for (int i = 0; i < count; i++) { 244 list.add(String.format("{%d}", i)); 245 } 246 return format(list); 247 } 248 249 /** 250 * Returns the locale of this object. 251 * @internal 252 * @deprecated This API is ICU internal only. 253 */ 254 @Deprecated getLocale()255 public ULocale getLocale() { 256 return locale; 257 } 258 259 // Builds a formatted list 260 static class FormattedListBuilder { 261 private StringBuilder current; 262 private int offset; 263 264 // Start is the first object in the list; If recordOffset is true, records the offset of 265 // this first object. FormattedListBuilder(Object start, boolean recordOffset)266 public FormattedListBuilder(Object start, boolean recordOffset) { 267 this.current = new StringBuilder(start.toString()); 268 this.offset = recordOffset ? 0 : -1; 269 } 270 271 // Appends additional object. pattern is a template indicating where the new object gets 272 // added in relation to the rest of the list. {0} represents the rest of the list; {1} 273 // represents the new object in pattern. next is the object to be added. If recordOffset 274 // is true, records the offset of next in the formatted string. append(String pattern, Object next, boolean recordOffset)275 public FormattedListBuilder append(String pattern, Object next, boolean recordOffset) { 276 int[] offsets = (recordOffset || offsetRecorded()) ? new int[2] : null; 277 SimpleFormatterImpl.formatAndReplace( 278 pattern, current, offsets, current, next.toString()); 279 if (offsets != null) { 280 if (offsets[0] == -1 || offsets[1] == -1) { 281 throw new IllegalArgumentException( 282 "{0} or {1} missing from pattern " + pattern); 283 } 284 if (recordOffset) { 285 offset = offsets[1]; 286 } else { 287 offset += offsets[0]; 288 } 289 } 290 return this; 291 } 292 appendTo(Appendable appendable)293 public void appendTo(Appendable appendable) { 294 try { 295 appendable.append(current); 296 } catch(IOException e) { 297 throw new ICUUncheckedIOException(e); 298 } 299 } 300 301 @Override toString()302 public String toString() { 303 return current.toString(); 304 } 305 306 // Gets the last recorded offset or -1 if no offset recorded. getOffset()307 public int getOffset() { 308 return offset; 309 } 310 offsetRecorded()311 private boolean offsetRecorded() { 312 return offset >= 0; 313 } 314 } 315 316 private static class Cache { 317 private final ICUCache<String, ListFormatter> cache = 318 new SimpleCache<String, ListFormatter>(); 319 get(ULocale locale, String style)320 public ListFormatter get(ULocale locale, String style) { 321 String key = String.format("%s:%s", locale.toString(), style); 322 ListFormatter result = cache.get(key); 323 if (result == null) { 324 result = load(locale, style); 325 cache.put(key, result); 326 } 327 return result; 328 } 329 load(ULocale ulocale, String style)330 private static ListFormatter load(ULocale ulocale, String style) { 331 ICUResourceBundle r = (ICUResourceBundle)UResourceBundle. 332 getBundleInstance(ICUData.ICU_BASE_NAME, ulocale); 333 StringBuilder sb = new StringBuilder(); 334 return new ListFormatter( 335 compilePattern(r.getWithFallback("listPattern/" + style + "/2").getString(), sb), 336 compilePattern(r.getWithFallback("listPattern/" + style + "/start").getString(), sb), 337 compilePattern(r.getWithFallback("listPattern/" + style + "/middle").getString(), sb), 338 compilePattern(r.getWithFallback("listPattern/" + style + "/end").getString(), sb), 339 ulocale); 340 } 341 } 342 343 static Cache cache = new Cache(); 344 } 345