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