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
040: .getInteger("org.joda.time.tz.CachedDateTimeZone.size");
041: } catch (SecurityException e) {
042: i = null;
043: }
044:
045: int cacheSize;
046: if (i == null) {
047: // With a cache size of 512, dates that lie within any 69.7 year
048: // period have no cache collisions.
049: cacheSize = 512; // (1 << 9)
050: } else {
051: cacheSize = i.intValue();
052: // Ensure cache size is even power of 2.
053: cacheSize--;
054: int shift = 0;
055: while (cacheSize > 0) {
056: shift++;
057: cacheSize >>= 1;
058: }
059: cacheSize = 1 << shift;
060: }
061:
062: cInfoCacheMask = cacheSize - 1;
063: }
064:
065: /**
066: * Returns a new CachedDateTimeZone unless given zone is already cached.
067: */
068: public static CachedDateTimeZone forZone(DateTimeZone zone) {
069: if (zone instanceof CachedDateTimeZone) {
070: return (CachedDateTimeZone) zone;
071: }
072: return new CachedDateTimeZone(zone);
073: }
074:
075: /*
076: * Caching is performed by breaking timeline down into periods of 2^32
077: * milliseconds, or about 49.7 days. A year has about 7.3 periods, usually
078: * with only 2 time zone offset periods. Most of the 49.7 day periods will
079: * have no transition, about one quarter have one transition, and very rare
080: * cases have multiple transitions.
081: */
082:
083: private final DateTimeZone iZone;
084:
085: private transient Info[] iInfoCache;
086:
087: private CachedDateTimeZone(DateTimeZone zone) {
088: super (zone.getID());
089: iZone = zone;
090: iInfoCache = new Info[cInfoCacheMask + 1];
091: }
092:
093: private void readObject(java.io.ObjectInputStream in)
094: throws java.io.IOException, ClassNotFoundException {
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
217: .getStandardOffset(iPeriodStart);
218: }
219: return iStandardOffset;
220: }
221: return iNextInfo.getStandardOffset(millis);
222: }
223: }
224: }
|