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    }