001 /*
002 * Copyright 2001-2005 Stephen Colebourne
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.joda.time.tz;
017
018 import org.joda.time.DateTimeZone;
019
020 /**
021 * Improves the performance of requesting time zone offsets and name keys by
022 * caching the results. Time zones that have simple rules or are fixed should
023 * not be cached, as it is unlikely to improve performance.
024 * <p>
025 * CachedDateTimeZone is thread-safe and immutable.
026 *
027 * @author Brian S O'Neill
028 * @since 1.0
029 */
030 public class CachedDateTimeZone extends DateTimeZone {
031
032 private static final long serialVersionUID = 5472298452022250685L;
033
034 private static final int cInfoCacheMask;
035
036 static {
037 Integer i;
038 try {
039 i = Integer.getInteger("org.joda.time.tz.CachedDateTimeZone.size");
040 } catch (SecurityException e) {
041 i = null;
042 }
043
044 int cacheSize;
045 if (i == null) {
046 // With a cache size of 512, dates that lie within any 69.7 year
047 // period have no cache collisions.
048 cacheSize = 512; // (1 << 9)
049 } else {
050 cacheSize = i.intValue();
051 // Ensure cache size is even power of 2.
052 cacheSize--;
053 int shift = 0;
054 while (cacheSize > 0) {
055 shift++;
056 cacheSize >>= 1;
057 }
058 cacheSize = 1 << shift;
059 }
060
061 cInfoCacheMask = cacheSize - 1;
062 }
063
064 /**
065 * Returns a new CachedDateTimeZone unless given zone is already cached.
066 */
067 public static CachedDateTimeZone forZone(DateTimeZone zone) {
068 if (zone instanceof CachedDateTimeZone) {
069 return (CachedDateTimeZone)zone;
070 }
071 return new CachedDateTimeZone(zone);
072 }
073
074 /*
075 * Caching is performed by breaking timeline down into periods of 2^32
076 * milliseconds, or about 49.7 days. A year has about 7.3 periods, usually
077 * with only 2 time zone offset periods. Most of the 49.7 day periods will
078 * have no transition, about one quarter have one transition, and very rare
079 * cases have multiple transitions.
080 */
081
082 private final DateTimeZone iZone;
083
084 private transient Info[] iInfoCache;
085
086 private CachedDateTimeZone(DateTimeZone zone) {
087 super(zone.getID());
088 iZone = zone;
089 iInfoCache = new Info[cInfoCacheMask + 1];
090 }
091
092 private void readObject(java.io.ObjectInputStream in)
093 throws java.io.IOException, ClassNotFoundException
094 {
095 in.defaultReadObject();
096 iInfoCache = new Info[cInfoCacheMask + 1];
097 }
098
099 /**
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 }