/*
* Copyright 2001-2012 Stephen Colebourne
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.joda.time.tz;
import org.joda.time.DateTimeZone;
/**
* Improves the performance of requesting time zone offsets and name keys by
* caching the results. Time zones that have simple rules or are fixed should
* not be cached, as it is unlikely to improve performance.
* <p>
* CachedDateTimeZone is thread-safe and immutable.
*
* @author Brian S O'Neill
* @since 1.0
*/
public class CachedDateTimeZone extends DateTimeZone {
private static final long serialVersionUID = 5472298452022250685L;
private static final int cInfoCacheMask;
static {
Integer i;
try {
i = Integer.getInteger("org.joda.time.tz.CachedDateTimeZone.size");
} catch (SecurityException e) {
i = null;
}
int cacheSize;
if (i == null) {
// With a cache size of 512, dates that lie within any 69.7 year
// period have no cache collisions.
cacheSize = 512; // (1 << 9)
} else {
cacheSize = i.intValue();
// Ensure cache size is even power of 2.
cacheSize--;
int shift = 0;
while (cacheSize > 0) {
shift++;
cacheSize >>= 1;
}
cacheSize = 1 << shift;
}
cInfoCacheMask = cacheSize - 1;
}
/**
* Returns a new CachedDateTimeZone unless given zone is already cached.
*/
public static CachedDateTimeZone forZone(DateTimeZone zone) {
if (zone instanceof CachedDateTimeZone) {
return (CachedDateTimeZone)zone;
}
return new CachedDateTimeZone(zone);
}
/*
* Caching is performed by breaking timeline down into periods of 2^32
* milliseconds, or about 49.7 days. A year has about 7.3 periods, usually
* with only 2 time zone offset periods. Most of the 49.7 day periods will
* have no transition, about one quarter have one transition, and very rare
* cases have multiple transitions.
*/
private final DateTimeZone iZone;
private final transient Info[] iInfoCache = new Info[cInfoCacheMask + 1];
private CachedDateTimeZone(DateTimeZone zone) {
super(zone.getID());
iZone = zone;
}
/**
* Returns the DateTimeZone being wrapped.
*/
public DateTimeZone getUncachedZone() {
return iZone;
}
public String getNameKey(long instant) {
return getInfo(instant).getNameKey(instant);
}
public int getOffset(long instant) {
return getInfo(instant).getOffset(instant);
}
public int getStandardOffset(long instant) {
return getInfo(instant).getStandardOffset(instant);
}
public boolean isFixed() {
return iZone.isFixed();
}
public long nextTransition(long instant) {
return iZone.nextTransition(instant);
}
public long previousTransition(long instant) {
return iZone.previousTransition(instant);
}
public int hashCode() {
return iZone.hashCode();
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof CachedDateTimeZone) {
return iZone.equals(((CachedDateTimeZone)obj).iZone);
}
return false;
}
// Although accessed by multiple threads, this method doesn't need to be
// synchronized.
private Info getInfo(long millis) {
int period = (int)(millis >> 32);
Info[] cache = iInfoCache;
int index = period & cInfoCacheMask;
Info info = cache[index];
if (info == null || (int)((info.iPeriodStart >> 32)) != period) {
info = createInfo(millis);
cache[index] = info;
}
return info;
}
private Info createInfo(long millis) {
long periodStart = millis & (0xffffffffL << 32);
Info info = new Info(iZone, periodStart);
long end = periodStart | 0xffffffffL;
Info chain = info;
while (true) {
long next = iZone.nextTransition(periodStart);
if (next == periodStart || next > end) {
break;
}
periodStart = next;
chain = (chain.iNextInfo = new Info(iZone, periodStart));
}
return info;
}
private final static class Info {
// For first Info in chain, iPeriodStart's lower 32 bits are clear.
public final long iPeriodStart;
public final DateTimeZone iZoneRef;
Info iNextInfo;
private String iNameKey;
private int iOffset = Integer.MIN_VALUE;
private int iStandardOffset = Integer.MIN_VALUE;
Info(DateTimeZone zone, long periodStart) {
iPeriodStart = periodStart;
iZoneRef = zone;
}
public String getNameKey(long millis) {
if (iNextInfo == null || millis < iNextInfo.iPeriodStart) {
if (iNameKey == null) {
iNameKey = iZoneRef.getNameKey(iPeriodStart);
}
return iNameKey;
}
return iNextInfo.getNameKey(millis);
}
public int getOffset(long millis) {
if (iNextInfo == null || millis < iNextInfo.iPeriodStart) {
if (iOffset == Integer.MIN_VALUE) {
iOffset = iZoneRef.getOffset(iPeriodStart);
}
return iOffset;
}
return iNextInfo.getOffset(millis);
}
public int getStandardOffset(long millis) {
if (iNextInfo == null || millis < iNextInfo.iPeriodStart) {
if (iStandardOffset == Integer.MIN_VALUE) {
iStandardOffset = iZoneRef.getStandardOffset(iPeriodStart);
}
return iStandardOffset;
}
return iNextInfo.getStandardOffset(millis);
}
}
}