Home | History | Annotate | Download | only in text
      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