1 | /* |
2 | * Copyright 2001-2005 Stephen Colebourne |
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 | package org.joda.time.tz; |
17 | |
18 | import org.joda.time.DateTimeZone; |
19 | |
20 | /** |
21 | * Improves the performance of requesting time zone offsets and name keys by |
22 | * caching the results. Time zones that have simple rules or are fixed should |
23 | * not be cached, as it is unlikely to improve performance. |
24 | * <p> |
25 | * CachedDateTimeZone is thread-safe and immutable. |
26 | * |
27 | * @author Brian S O'Neill |
28 | * @since 1.0 |
29 | */ |
30 | public class CachedDateTimeZone extends DateTimeZone { |
31 | |
32 | private static final long serialVersionUID = 5472298452022250685L; |
33 | |
34 | private static final int cInfoCacheMask; |
35 | |
36 | static { |
37 | Integer i; |
38 | try { |
39 | i = Integer.getInteger("org.joda.time.tz.CachedDateTimeZone.size"); |
40 | } catch (SecurityException e) { |
41 | i = null; |
42 | } |
43 | |
44 | int cacheSize; |
45 | if (i == null) { |
46 | // With a cache size of 512, dates that lie within any 69.7 year |
47 | // period have no cache collisions. |
48 | cacheSize = 512; // (1 << 9) |
49 | } else { |
50 | cacheSize = i.intValue(); |
51 | // Ensure cache size is even power of 2. |
52 | cacheSize--; |
53 | int shift = 0; |
54 | while (cacheSize > 0) { |
55 | shift++; |
56 | cacheSize >>= 1; |
57 | } |
58 | cacheSize = 1 << shift; |
59 | } |
60 | |
61 | cInfoCacheMask = cacheSize - 1; |
62 | } |
63 | |
64 | /** |
65 | * Returns a new CachedDateTimeZone unless given zone is already cached. |
66 | */ |
67 | public static CachedDateTimeZone forZone(DateTimeZone zone) { |
68 | if (zone instanceof CachedDateTimeZone) { |
69 | return (CachedDateTimeZone)zone; |
70 | } |
71 | return new CachedDateTimeZone(zone); |
72 | } |
73 | |
74 | /* |
75 | * Caching is performed by breaking timeline down into periods of 2^32 |
76 | * milliseconds, or about 49.7 days. A year has about 7.3 periods, usually |
77 | * with only 2 time zone offset periods. Most of the 49.7 day periods will |
78 | * have no transition, about one quarter have one transition, and very rare |
79 | * cases have multiple transitions. |
80 | */ |
81 | |
82 | private final DateTimeZone iZone; |
83 | |
84 | private transient Info[] iInfoCache; |
85 | |
86 | private CachedDateTimeZone(DateTimeZone zone) { |
87 | super(zone.getID()); |
88 | iZone = zone; |
89 | iInfoCache = new Info[cInfoCacheMask + 1]; |
90 | } |
91 | |
92 | private void readObject(java.io.ObjectInputStream in) |
93 | throws java.io.IOException, ClassNotFoundException |
94 | { |
95 | in.defaultReadObject(); |
96 | iInfoCache = new Info[cInfoCacheMask + 1]; |
97 | } |
98 | |
99 | /** |
100 | * Returns the DateTimeZone being wrapped. |
101 | */ |
102 | public DateTimeZone getUncachedZone() { |
103 | return iZone; |
104 | } |
105 | |
106 | public String getNameKey(long instant) { |
107 | return getInfo(instant).getNameKey(instant); |
108 | } |
109 | |
110 | public int getOffset(long instant) { |
111 | return getInfo(instant).getOffset(instant); |
112 | } |
113 | |
114 | public int getStandardOffset(long instant) { |
115 | return getInfo(instant).getStandardOffset(instant); |
116 | } |
117 | |
118 | public boolean isFixed() { |
119 | return iZone.isFixed(); |
120 | } |
121 | |
122 | public long nextTransition(long instant) { |
123 | return iZone.nextTransition(instant); |
124 | } |
125 | |
126 | public long previousTransition(long instant) { |
127 | return iZone.previousTransition(instant); |
128 | } |
129 | |
130 | public int hashCode() { |
131 | return iZone.hashCode(); |
132 | } |
133 | |
134 | public boolean equals(Object obj) { |
135 | if (this == obj) { |
136 | return true; |
137 | } |
138 | if (obj instanceof CachedDateTimeZone) { |
139 | return iZone.equals(((CachedDateTimeZone)obj).iZone); |
140 | } |
141 | return false; |
142 | } |
143 | |
144 | // Although accessed by multiple threads, this method doesn't need to be |
145 | // synchronized. |
146 | |
147 | private Info getInfo(long millis) { |
148 | int period = (int)(millis >> 32); |
149 | Info[] cache = iInfoCache; |
150 | int index = period & cInfoCacheMask; |
151 | Info info = cache[index]; |
152 | if (info == null || (int)((info.iPeriodStart >> 32)) != period) { |
153 | info = createInfo(millis); |
154 | cache[index] = info; |
155 | } |
156 | return info; |
157 | } |
158 | |
159 | private Info createInfo(long millis) { |
160 | long periodStart = millis & (0xffffffffL << 32); |
161 | Info info = new Info(iZone, periodStart); |
162 | |
163 | long end = periodStart | 0xffffffffL; |
164 | Info chain = info; |
165 | while (true) { |
166 | long next = iZone.nextTransition(periodStart); |
167 | if (next == periodStart || next > end) { |
168 | break; |
169 | } |
170 | periodStart = next; |
171 | chain = (chain.iNextInfo = new Info(iZone, periodStart)); |
172 | } |
173 | |
174 | return info; |
175 | } |
176 | |
177 | private final static class Info { |
178 | // For first Info in chain, iPeriodStart's lower 32 bits are clear. |
179 | public final long iPeriodStart; |
180 | public final DateTimeZone iZoneRef; |
181 | |
182 | Info iNextInfo; |
183 | |
184 | private String iNameKey; |
185 | private int iOffset = Integer.MIN_VALUE; |
186 | private int iStandardOffset = Integer.MIN_VALUE; |
187 | |
188 | Info(DateTimeZone zone, long periodStart) { |
189 | iPeriodStart = periodStart; |
190 | iZoneRef = zone; |
191 | } |
192 | |
193 | public String getNameKey(long millis) { |
194 | if (iNextInfo == null || millis < iNextInfo.iPeriodStart) { |
195 | if (iNameKey == null) { |
196 | iNameKey = iZoneRef.getNameKey(iPeriodStart); |
197 | } |
198 | return iNameKey; |
199 | } |
200 | return iNextInfo.getNameKey(millis); |
201 | } |
202 | |
203 | public int getOffset(long millis) { |
204 | if (iNextInfo == null || millis < iNextInfo.iPeriodStart) { |
205 | if (iOffset == Integer.MIN_VALUE) { |
206 | iOffset = iZoneRef.getOffset(iPeriodStart); |
207 | } |
208 | return iOffset; |
209 | } |
210 | return iNextInfo.getOffset(millis); |
211 | } |
212 | |
213 | public int getStandardOffset(long millis) { |
214 | if (iNextInfo == null || millis < iNextInfo.iPeriodStart) { |
215 | if (iStandardOffset == Integer.MIN_VALUE) { |
216 | iStandardOffset = iZoneRef.getStandardOffset(iPeriodStart); |
217 | } |
218 | return iStandardOffset; |
219 | } |
220 | return iNextInfo.getStandardOffset(millis); |
221 | } |
222 | } |
223 | } |