001 /*
002 * Copyright 2001-2012 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 final Info[] iInfoCache = new Info[cInfoCacheMask + 1];
085
086 private CachedDateTimeZone(DateTimeZone zone) {
087 super(zone.getID());
088 iZone = zone;
089 }
090
091 /**
092 * Returns the DateTimeZone being wrapped.
093 */
094 public DateTimeZone getUncachedZone() {
095 return iZone;
096 }
097
098 public String getNameKey(long instant) {
099 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 }