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
17package com.android.deskclock.data;
18
19import java.text.Collator;
20import java.util.Comparator;
21import 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 */
27public 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
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
64    public String getId() { return mId; }
65    public int getIndex() { return mIndex; }
66    public String getName() { return mName; }
67    public TimeZone getTimeZone() { return mTimeZone; }
68    public String getTimeZoneId() { return mTimeZoneId; }
69    public String getIndexString() { return mIndexString; }
70    public String getPhoneticName() { return mPhoneticName; }
71
72    public String getNameUpperCase() {
73        if (mNameUpperCase == null) {
74            mNameUpperCase = mName.toUpperCase();
75        }
76        return mNameUpperCase;
77    }
78
79    @Override
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
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
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
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
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}