1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18
19 package org.apache.juli;
20
21
22 import java.text.SimpleDateFormat;
23 import java.util.Date;
24 import java.util.Locale;
25 import java.util.TimeZone;
26
27 /**
28  * <p>Cache structure for SimpleDateFormat formatted timestamps based on
29  * seconds.</p>
30  *
31  * <p>Millisecond formatting using S is not supported. You should add the
32  * millisecond information after getting back the second formatting.</p>
33  *
34  * <p>The cache consists of entries for a consecutive range of
35  * seconds. The length of the range is configurable. It is
36  * implemented based on a cyclic buffer. New entries shift the range.</p>
37  *
38  * <p>The cache is not threadsafe. It can be used without synchronization
39  * via thread local instances, or with synchronization as a global cache.</p>
40  *
41  * <p>The cache can be created with a parent cache to build a cache hierarchy.
42  * Access to the parent cache is threadsafe.</p>
43  */

44 public class DateFormatCache {
45
46     public static final char MSEC_PATTERN = '#';
47
48     /* Timestamp format */
49     private final String format;
50
51     /* Number of cached entries */
52     private final int cacheSize;
53
54     private final Cache cache;
55
56     /**
57      * Replace the millisecond formatting character 'S' by
58      * some dummy characters in order to make the resulting
59      * formatted time stamps cacheable. Our consumer might
60      * choose to replace the dummy chars with the actual
61      * milliseconds because that's relatively cheap.
62      */

63     private String tidyFormat(String format) {
64         boolean escape = false;
65         StringBuilder result = new StringBuilder();
66         int len = format.length();
67         char x;
68         for (int i = 0; i < len; i++) {
69             x = format.charAt(i);
70             if (escape || x != 'S') {
71                 result.append(x);
72             } else {
73                 result.append(MSEC_PATTERN);
74             }
75             if (x == '\'') {
76                 escape = !escape;
77             }
78         }
79         return result.toString();
80     }
81
82     public DateFormatCache(int size, String format, DateFormatCache parent) {
83         cacheSize = size;
84         this.format = tidyFormat(format);
85         Cache parentCache = null;
86         if (parent != null) {
87             synchronized(parent) {
88                 parentCache = parent.cache;
89             }
90         }
91         cache = new Cache(parentCache);
92     }
93
94     public String getFormat(long time) {
95         return cache.getFormat(time);
96     }
97
98     public String getTimeFormat() {
99         return format;
100     }
101
102     private class Cache {
103
104         /* Second formatted in most recent invocation */
105         private long previousSeconds = Long.MIN_VALUE;
106         /* Formatted timestamp generated in most recent invocation */
107         private String previousFormat = "";
108
109         /* First second contained in cache */
110         private long first = Long.MIN_VALUE;
111         /* Last second contained in cache */
112         private long last = Long.MIN_VALUE;
113         /* Index of "first" in the cyclic cache */
114         private int offset = 0;
115         /* Helper object to be able to call SimpleDateFormat.format(). */
116         private final Date currentDate = new Date();
117
118         private String cache[];
119         private SimpleDateFormat formatter;
120
121         private Cache parent = null;
122
123         private Cache(Cache parent) {
124             cache = new String[cacheSize];
125             formatter = new SimpleDateFormat(format, Locale.US);
126             formatter.setTimeZone(TimeZone.getDefault());
127             this.parent = parent;
128         }
129
130         private String getFormat(long time) {
131
132             long seconds = time / 1000;
133
134             /* First step: if we have seen this timestamp
135                during the previous call, return the previous value. */

136             if (seconds == previousSeconds) {
137                 return previousFormat;
138             }
139
140             /* Second step: Try to locate in cache */
141             previousSeconds = seconds;
142             int index = (offset + (int)(seconds - first)) % cacheSize;
143             if (index < 0) {
144                 index += cacheSize;
145             }
146             if (seconds >= first && seconds <= last) {
147                 if (cache[index] != null) {
148                     /* Found, so remember for next call and return.*/
149                     previousFormat = cache[index];
150                     return previousFormat;
151                 }
152
153             /* Third step: not found in cache, adjust cache and add item */
154             } else if (seconds >= last + cacheSize || seconds <= first - cacheSize) {
155                 first = seconds;
156                 last = first + cacheSize - 1;
157                 index = 0;
158                 offset = 0;
159                 for (int i = 1; i < cacheSize; i++) {
160                     cache[i] = null;
161                 }
162             } else if (seconds > last) {
163                 for (int i = 1; i < seconds - last; i++) {
164                     cache[(index + cacheSize - i) % cacheSize] = null;
165                 }
166                 first = seconds - (cacheSize - 1);
167                 last = seconds;
168                 offset = (index + 1) % cacheSize;
169             } else if (seconds < first) {
170                 for (int i = 1; i < first - seconds; i++) {
171                     cache[(index + i) % cacheSize] = null;
172                 }
173                 first = seconds;
174                 last = seconds + (cacheSize - 1);
175                 offset = index;
176             }
177
178             /* Last step: format new timestamp either using
179              * parent cache or locally. */

180             if (parent != null) {
181                 synchronized(parent) {
182                     previousFormat = parent.getFormat(time);
183                 }
184             } else {
185                 currentDate.setTime(time);
186                 previousFormat = formatter.format(currentDate);
187             }
188             cache[index] = previousFormat;
189             return previousFormat;
190         }
191     }
192 }
193