1 package org.unicode.cldr.util;
2 
3 import static com.google.common.base.Preconditions.checkArgument;
4 import static com.google.common.base.Preconditions.checkNotNull;
5 import static com.google.common.collect.Comparators.lexicographical;
6 import static com.google.common.collect.ImmutableList.toImmutableList;
7 
8 import java.util.Collection;
9 import java.util.Comparator;
10 import java.util.Set;
11 import java.util.stream.Stream;
12 
13 import com.google.common.base.Joiner;
14 import com.google.common.base.Splitter;
15 import com.google.common.collect.ImmutableList;
16 
17 public final class PreferredAndAllowedHour implements Comparable<PreferredAndAllowedHour> {
18     // DO NOT change enum item names, they are mapped directly from data values via "valueOf".
19     public enum HourStyle {
20         H, Hb(H), HB(H), k, h, hb(h), hB(h), K;
21         public final HourStyle base;
22 
HourStyle()23         HourStyle() {
24             base = this;
25         }
26 
HourStyle(HourStyle base)27         HourStyle(HourStyle base) {
28             this.base = base;
29         }
30 
isHourCharacter(String c)31         public static boolean isHourCharacter(String c) {
32             try {
33                 HourStyle.valueOf(c);
34                 return true;
35             } catch (IllegalArgumentException e) {
36                 return false;
37             }
38         }
39     }
40 
41     // For splitting the style ID string.
42     private static final Splitter SPACE_SPLITTER = Splitter.on(' ').trimResults();
43 
44     // If this used "getter" method references it wouldn't need so much explicit generic typing.
45     private static final Comparator<PreferredAndAllowedHour> COMPARATOR =
46         Comparator.<PreferredAndAllowedHour, HourStyle>comparing(t -> t.preferred)
47             .thenComparing(t -> t.allowed, lexicographical(Comparator.<HourStyle>naturalOrder()));
48 
49     public final HourStyle preferred;
50     /** Unique allowed styles, in the order they were specified during construction. */
51     public final ImmutableList<HourStyle> allowed;
52 
53     /**
54      * Creates a PreferredAndAllowedHour instance with "allowed" styles derived from single
55      * character IDs in the given collection. Note that the iteration order of the allowed
56      * styles is retained.
57      *
58      * <p>This constructor is limiting, since some styles are identified by two character
59      * strings, which cannot be referenced via this constructor.
60      */
PreferredAndAllowedHour(char preferred, Set<Character> allowed)61     public PreferredAndAllowedHour(char preferred, Set<Character> allowed) {
62         this(String.valueOf(preferred), allowed.stream().map(String::valueOf));
63     }
64 
65     /**
66      * Creates a PreferredAndAllowedHour instance with "allowed" styles derived from a space
67      * separated list of unique IDs. Note that the iteration order of the allowed styles is
68      * retained, since in some situations it is necessary that the preferred style should
69      * also be the first allowed style. The list of allowed style IDs must not contain
70      * duplicates.
71      */
PreferredAndAllowedHour(String preferred, String allowedString)72     public PreferredAndAllowedHour(String preferred, String allowedString) {
73         this(preferred, SPACE_SPLITTER.splitToList(allowedString).stream());
74     }
75 
PreferredAndAllowedHour(String preferredStyle, Stream<String> allowedStyles)76     private PreferredAndAllowedHour(String preferredStyle, Stream<String> allowedStyles) {
77         this.preferred = checkNotNull(HourStyle.valueOf(preferredStyle));
78         this.allowed = allowedStyles.map(HourStyle::valueOf).collect(toImmutableList());
79         checkArgument(allowed.stream().distinct().count() == allowed.size(),
80                 "Allowed (%s) must not contain duplicates", allowed);
81         // Note: In *some* cases the preferred style is required to be the first style in
82         // the allowed set, but not always (thus we cannot do a better check here).
83         // TODO: Figure out if we can enforce preferred == first(allowed) here.
84         checkArgument(allowed.contains(preferred),
85                 "Allowed (%s) must contain preferred (%s)", allowed, preferred);
86     }
87 
88     @Override
compareTo(PreferredAndAllowedHour other)89     public int compareTo(PreferredAndAllowedHour other) {
90         return COMPARATOR.compare(this, other);
91     }
92 
93     @Override
toString()94     public String toString() {
95         return toString(ImmutableList.of("?"));
96     }
97 
toString(Collection<String> regions)98     public String toString(Collection<String> regions) {
99         Joiner withSpaces = Joiner.on(" ");
100         return "<hours preferred=\""
101             + preferred
102             + "\" allowed=\""
103             + withSpaces.join(allowed)
104             + "\" regions=\""
105             + withSpaces.join(regions)
106             + "\"/>";
107     }
108 
109     @Override
equals(Object obj)110     public boolean equals(Object obj) {
111         return obj instanceof PreferredAndAllowedHour && compareTo((PreferredAndAllowedHour) obj) == 0;
112     }
113 }