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    }