1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.deskclock.data;
18 
19 import java.text.Collator;
20 import java.util.Comparator;
21 import java.util.TimeZone;
22 
23 /**
24  * A read-only domain object representing a city of the world and associated time information. It
25  * also contains static comparators that can be instantiated to order cities in common sort orders.
26  */
27 public final class City {
28 
29     /** A unique identifier for the city. */
30     private final String mId;
31 
32     /** An optional numeric index used to order cities for display; -1 if no such index exists. */
33     private final int mIndex;
34 
35     /** An index string used to order cities for display. */
36     private final String mIndexString;
37 
38     /** The display name of the city. */
39     private final String mName;
40 
41     /** The phonetic name of the city used to order cities for display. */
42     private final String mPhoneticName;
43 
44     /** The {@link TimeZone#getID() id} of the timezone in which the city is located. */
45     private final String mTimeZoneId;
46 
47     /** The TimeZone corresponding to the {@link #mTimeZoneId}. */
48     private final TimeZone mTimeZone;
49 
50     /** A cached upper case form of the {@link #mName} used in case-insensitive name comparisons. */
51     private String mNameUpperCase;
52 
City(String id, int index, String indexString, String name, String phoneticName, String timeZoneId)53     City(String id, int index, String indexString, String name, String phoneticName,
54             String timeZoneId) {
55         mId = id;
56         mIndex = index;
57         mIndexString = indexString;
58         mName = name;
59         mPhoneticName = phoneticName;
60         mTimeZoneId = timeZoneId;
61         mTimeZone = TimeZone.getTimeZone(mTimeZoneId);
62     }
63 
getId()64     public String getId() { return mId; }
getIndex()65     public int getIndex() { return mIndex; }
getName()66     public String getName() { return mName; }
getTimeZone()67     public TimeZone getTimeZone() { return mTimeZone; }
getTimeZoneId()68     public String getTimeZoneId() { return mTimeZoneId; }
getIndexString()69     public String getIndexString() { return mIndexString; }
getPhoneticName()70     public String getPhoneticName() { return mPhoneticName; }
71 
getNameUpperCase()72     public String getNameUpperCase() {
73         if (mNameUpperCase == null) {
74             mNameUpperCase = mName.toUpperCase();
75         }
76         return mNameUpperCase;
77     }
78 
79     @Override
toString()80     public String toString() {
81         return String.format("City {id=%s, index=%d, indexString=%s, name=%s, phonetic=%s, tz=%s}",
82                 mId, mIndex, mIndexString, mName, mPhoneticName, mTimeZoneId);
83     }
84 
85     /**
86      * Orders by:
87      *
88      * <ol>
89      *     <li>UTC offset of {@link #getTimeZone() timezone}</li>
90      *     <li>{@link #getIndex() numeric index}</li>
91      *     <li>{@link #getIndexString()} alphabetic index}</li>
92      *     <li>{@link #getPhoneticName() phonetic name}</li>
93      * </ol>
94      */
95     public static final class UtcOffsetComparator implements Comparator<City> {
96 
97         private final Comparator<City> mDelegate1 = new UtcOffsetIndexComparator();;
98 
99         private final Comparator<City> mDelegate2 = new NameComparator();
100 
compare(City c1, City c2)101         public int compare(City c1, City c2) {
102             int result = mDelegate1.compare(c1, c2);
103 
104             if (result == 0) {
105                 result = mDelegate2.compare(c1, c2);
106             }
107 
108             return result;
109         }
110     }
111 
112     /**
113      * Orders by:
114      *
115      * <ol>
116      *     <li>UTC offset of {@link #getTimeZone() timezone}</li>
117      * </ol>
118      */
119     public static final class UtcOffsetIndexComparator implements Comparator<City> {
120 
121         // Snapshot the current time when the Comparator is created to obtain consistent offsets.
122         private final long now = System.currentTimeMillis();
123 
compare(City c1, City c2)124         public int compare(City c1, City c2) {
125             final int utcOffset1 = c1.getTimeZone().getOffset(now);
126             final int utcOffset2 = c2.getTimeZone().getOffset(now);
127             return Integer.compare(utcOffset1, utcOffset2);
128         }
129     }
130 
131     /**
132      * This comparator sorts using the city fields that influence natural name sort order:
133      *
134      * <ol>
135      *     <li>{@link #getIndex() numeric index}</li>
136      *     <li>{@link #getIndexString()} alphabetic index}</li>
137      *     <li>{@link #getPhoneticName() phonetic name}</li>
138      * </ol>
139      */
140     public static final class NameComparator implements Comparator<City> {
141 
142         private final Comparator<City> mDelegate = new NameIndexComparator();
143 
144         // Locale-sensitive comparator for phonetic names.
145         private final Collator mNameCollator = Collator.getInstance();
146 
147         @Override
compare(City c1, City c2)148         public int compare(City c1, City c2) {
149             int result = mDelegate.compare(c1, c2);
150 
151             if (result == 0) {
152                 result = mNameCollator.compare(c1.getPhoneticName(), c2.getPhoneticName());
153             }
154 
155             return result;
156         }
157     }
158 
159     /**
160      * Orders by:
161      *
162      * <ol>
163      *     <li>{@link #getIndex() numeric index}</li>
164      *     <li>{@link #getIndexString()} alphabetic index}</li>
165      * </ol>
166      */
167     public static final class NameIndexComparator implements Comparator<City> {
168 
169         // Locale-sensitive comparator for index strings.
170         private final Collator mNameCollator = Collator.getInstance();
171 
172         @Override
compare(City c1, City c2)173         public int compare(City c1, City c2) {
174             int result = Integer.compare(c1.getIndex(), c2.getIndex());
175 
176             if (result == 0) {
177                 result = mNameCollator.compare(c1.getIndexString(), c2.getIndexString());
178             }
179 
180             return result;
181         }
182     }
183 }