1 /*
2  *  Copyright 2001-2012 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 final transient Info[] iInfoCache = new Info[cInfoCacheMask + 1];
85
86     private CachedDateTimeZone(DateTimeZone zone) {
87         super(zone.getID());
88         iZone = zone;
89     }
90
91     /**
92      * Returns the DateTimeZone being wrapped.
93      */

94     public DateTimeZone getUncachedZone() {
95         return iZone;
96     }
97
98     public String getNameKey(long instant) {
99         return getInfo(instant).getNameKey(instant);
100     }
101
102     public int getOffset(long instant) {
103         return getInfo(instant).getOffset(instant);
104     }
105
106     public int getStandardOffset(long instant) {
107         return getInfo(instant).getStandardOffset(instant);
108     }
109
110     public boolean isFixed() {
111         return iZone.isFixed();
112     }
113
114     public long nextTransition(long instant) {
115         return iZone.nextTransition(instant);
116     }
117
118     public long previousTransition(long instant) {
119         return iZone.previousTransition(instant);
120     }
121
122     public int hashCode() {
123         return iZone.hashCode();
124     }
125
126     public boolean equals(Object obj) {
127         if (this == obj) {
128             return true;
129         }
130         if (obj instanceof CachedDateTimeZone) {
131             return iZone.equals(((CachedDateTimeZone)obj).iZone);
132         }
133         return false;
134     }
135
136     // Although accessed by multiple threads, this method doesn't need to be
137     // synchronized.
138
139     private Info getInfo(long millis) {
140         int period = (int)(millis >> 32);
141         Info[] cache = iInfoCache;
142         int index = period & cInfoCacheMask;
143         Info info = cache[index];
144         if (info == null || (int)((info.iPeriodStart >> 32)) != period) {
145             info = createInfo(millis);
146             cache[index] = info;
147         }
148         return info;
149     }
150
151     private Info createInfo(long millis) {
152         long periodStart = millis & (0xffffffffL << 32);
153         Info info = new Info(iZone, periodStart);
154         
155         long end = periodStart | 0xffffffffL;
156         Info chain = info;
157         while (true) {
158             long next = iZone.nextTransition(periodStart);
159             if (next == periodStart || next > end) {
160                 break;
161             }
162             periodStart = next;
163             chain = (chain.iNextInfo = new Info(iZone, periodStart));
164         }
165
166         return info;
167     }
168
169     private final static class Info {
170         // For first Info in chain, iPeriodStart's lower 32 bits are clear.
171         public final long iPeriodStart;
172         public final DateTimeZone iZoneRef;
173
174         Info iNextInfo;
175
176         private String iNameKey;
177         private int iOffset = Integer.MIN_VALUE;
178         private int iStandardOffset = Integer.MIN_VALUE;
179
180         Info(DateTimeZone zone, long periodStart) {
181             iPeriodStart = periodStart;
182             iZoneRef = zone;
183         }
184
185         public String getNameKey(long millis) {
186             if (iNextInfo == null || millis < iNextInfo.iPeriodStart) {
187                 if (iNameKey == null) {
188                     iNameKey = iZoneRef.getNameKey(iPeriodStart);
189                 }
190                 return iNameKey;
191             }
192             return iNextInfo.getNameKey(millis);
193         }
194
195         public int getOffset(long millis) {
196             if (iNextInfo == null || millis < iNextInfo.iPeriodStart) {
197                 if (iOffset == Integer.MIN_VALUE) {
198                     iOffset = iZoneRef.getOffset(iPeriodStart);
199                 }
200                 return iOffset;
201             }
202             return iNextInfo.getOffset(millis);
203         }
204
205         public int getStandardOffset(long millis) {
206             if (iNextInfo == null || millis < iNextInfo.iPeriodStart) {
207                 if (iStandardOffset == Integer.MIN_VALUE) {
208                     iStandardOffset = iZoneRef.getStandardOffset(iPeriodStart);
209                 }
210                 return iStandardOffset;
211             }
212             return iNextInfo.getStandardOffset(millis);
213         }
214     }
215 }
216