/src/logging-log4cxx/src/main/cpp/cacheddateformat.cpp
Line | Count | Source |
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 | | #define __STDC_CONSTANT_MACROS |
18 | | #define NOMINMAX /* tell wnidows not to define min/max macros */ |
19 | | #include <log4cxx/logstring.h> |
20 | | #include <log4cxx/helpers/cacheddateformat.h> |
21 | | #include <log4cxx/helpers/pool.h> |
22 | | #include <limits> |
23 | | #include <log4cxx/helpers/exception.h> |
24 | | |
25 | | using namespace LOG4CXX_NS; |
26 | | using namespace LOG4CXX_NS::helpers; |
27 | | using namespace LOG4CXX_NS::pattern; |
28 | | |
29 | | struct CachedDateFormat::CachedDateFormatPriv |
30 | | { |
31 | | CachedDateFormatPriv(DateFormatPtr dateFormat, int expiration1) : |
32 | 1.51k | formatter(dateFormat), |
33 | 1.51k | millisecondStart(0), |
34 | 1.51k | slotBegin(std::numeric_limits<log4cxx_time_t>::min()), |
35 | 1.51k | cache(50, 0x20), |
36 | 1.51k | expiration(expiration1), |
37 | 1.51k | previousTime(std::numeric_limits<log4cxx_time_t>::min()) |
38 | 1.51k | {} |
39 | | |
40 | | /** |
41 | | * Wrapped formatter. |
42 | | */ |
43 | | LOG4CXX_NS::helpers::DateFormatPtr formatter; |
44 | | |
45 | | /** |
46 | | * Index of initial digit of millisecond pattern or |
47 | | * UNRECOGNIZED_MILLISECONDS or NO_MILLISECONDS. |
48 | | */ |
49 | | mutable int millisecondStart; |
50 | | |
51 | | /** |
52 | | * Integral second preceding the previous convered Date. |
53 | | */ |
54 | | mutable log4cxx_time_t slotBegin; |
55 | | |
56 | | |
57 | | /** |
58 | | * Cache of previous conversion. |
59 | | */ |
60 | | mutable LogString cache; |
61 | | |
62 | | |
63 | | /** |
64 | | * Maximum validity period for the cache. |
65 | | * Typically 1, use cache for duplicate requests only, or |
66 | | * 1000000, use cache for requests within the same integral second. |
67 | | */ |
68 | | const int expiration; |
69 | | |
70 | | /** |
71 | | * Date requested in previous conversion. |
72 | | */ |
73 | | mutable log4cxx_time_t previousTime; |
74 | | }; |
75 | | |
76 | | |
77 | | /** |
78 | | * Supported digit set. If the wrapped DateFormat uses |
79 | | * a different unit set, the millisecond pattern |
80 | | * will not be recognized and duplicate requests |
81 | | * will use the cache. |
82 | | */ |
83 | | const logchar CachedDateFormat::digits[] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0 }; |
84 | | |
85 | | |
86 | | /** |
87 | | * First magic number (in microseconds) used to detect |
88 | | * the millisecond position. |
89 | | */ |
90 | | const int CachedDateFormat::magic1 = 654000; |
91 | | |
92 | | |
93 | | /** |
94 | | * Expected representation of first magic number in milliseconds. |
95 | | */ |
96 | | const logchar CachedDateFormat::magicString1[] = { 0x36, 0x35, 0x34, 0 }; |
97 | | |
98 | | |
99 | | /** |
100 | | * Second magic number (in microseconds) used to detect |
101 | | * the millisecond position. |
102 | | */ |
103 | | const int CachedDateFormat::magic2 = 987000; |
104 | | |
105 | | |
106 | | /** |
107 | | * Expected representation of second magic number in milliseconds. |
108 | | */ |
109 | | const logchar CachedDateFormat::magicString2[] = { 0x39, 0x38, 0x37, 0}; |
110 | | |
111 | | |
112 | | /** |
113 | | * Expected representation of 0 milliseconds. |
114 | | */ |
115 | | const logchar CachedDateFormat::zeroString[] = { 0x30, 0x30, 0x30, 0 }; |
116 | | |
117 | | /** |
118 | | * Creates a new CachedDateFormat object. |
119 | | * @param dateFormat Date format, may not be null. |
120 | | * @param expiration maximum cached range in milliseconds. |
121 | | * If the dateFormat is known to be incompatible with the |
122 | | * caching algorithm, use a value of 0 to totally disable |
123 | | * caching or 1 to only use cache for duplicate requests. |
124 | | */ |
125 | | CachedDateFormat::CachedDateFormat(const DateFormatPtr& dateFormat, |
126 | | int expiration1) : |
127 | 1.51k | m_priv(std::make_unique<CachedDateFormatPriv>(dateFormat, expiration1)) |
128 | 1.51k | { |
129 | 1.51k | if (dateFormat == NULL) |
130 | 0 | { |
131 | 0 | throw NullPointerException(LOG4CXX_STR("dateFormat")); |
132 | 0 | } |
133 | | |
134 | 1.51k | if (expiration1 < 0) |
135 | 0 | { |
136 | 0 | throw IllegalArgumentException(LOG4CXX_STR("expiration must be non-negative")); |
137 | 0 | } |
138 | 1.51k | } |
139 | | |
140 | 1.51k | CachedDateFormat::~CachedDateFormat() {} |
141 | | |
142 | | |
143 | | /** |
144 | | * Finds start of millisecond field in formatted time. |
145 | | * @param time long time, must be integral number of seconds |
146 | | * @param formatted String corresponding formatted string |
147 | | * @param formatter DateFormat date format |
148 | | * @return int position in string of first digit of milliseconds, |
149 | | * -1 indicates no millisecond field, -2 indicates unrecognized |
150 | | * field (likely RelativeTimeDateFormat) |
151 | | */ |
152 | | int CachedDateFormat::findMillisecondStart( |
153 | | log4cxx_time_t time, const LogString& formatted, |
154 | | const DateFormatPtr& formatter) |
155 | 1.51k | { |
156 | | |
157 | 1.51k | log4cxx_time_t slotBegin = (time / 1000000) * 1000000; |
158 | | |
159 | 1.51k | if (slotBegin > time) |
160 | 0 | { |
161 | 0 | slotBegin -= 1000000; |
162 | 0 | } |
163 | | |
164 | 1.51k | int millis = (int) (time - slotBegin) / 1000; |
165 | | |
166 | | // the magic numbers are in microseconds |
167 | 1.51k | int magic = magic1; |
168 | 1.51k | LogString magicString(magicString1); |
169 | | |
170 | 1.51k | if (millis == magic1 / 1000) |
171 | 0 | { |
172 | 0 | magic = magic2; |
173 | 0 | magicString = magicString2; |
174 | 0 | } |
175 | | |
176 | 1.51k | LogString plusMagic; |
177 | 1.51k | formatter->format(plusMagic, slotBegin + magic); |
178 | | |
179 | | /** |
180 | | * If the string lengths differ then |
181 | | * we can't use the cache except for duplicate requests. |
182 | | */ |
183 | 1.51k | if (plusMagic.length() != formatted.length()) |
184 | 405 | { |
185 | 405 | return UNRECOGNIZED_MILLISECONDS; |
186 | 405 | } |
187 | 1.11k | else |
188 | 1.11k | { |
189 | | // find first difference between values |
190 | 118k | for (LogString::size_type i = 0; i < formatted.length(); i++) |
191 | 118k | { |
192 | 118k | if (formatted[i] != plusMagic[i]) |
193 | 693 | { |
194 | | // |
195 | | // determine the expected digits for the base time |
196 | 693 | const logchar abc[] = { 0x41, 0x42, 0x43, 0 }; |
197 | 693 | LogString formattedMillis(abc); |
198 | 693 | millisecondFormat(millis, formattedMillis, 0); |
199 | | |
200 | 693 | LogString plusZero; |
201 | 693 | formatter->format(plusZero, slotBegin); |
202 | | |
203 | | // Test if the next 1..3 characters match the magic string, main problem is that magic |
204 | | // available millis in formatted can overlap. Therefore the current i is not always the |
205 | | // index of the first millis char, but may be already within the millis. Besides that |
206 | | // the millis can occur everywhere in formatted. See LOGCXX-420 and following. |
207 | 693 | size_t magicLength = magicString.length(); |
208 | 693 | size_t overlapping = magicString.find(plusMagic[i]); |
209 | 693 | int possibleRetVal = int(i - overlapping); |
210 | | |
211 | 693 | if (plusZero.length() == formatted.length() |
212 | 660 | && regionMatches(magicString, 0, plusMagic, possibleRetVal, magicLength) |
213 | 660 | && regionMatches(formattedMillis, 0, formatted, possibleRetVal, magicLength) |
214 | 660 | && regionMatches(zeroString, 0, plusZero, possibleRetVal, magicLength) |
215 | | // The following will and should fail for patterns with more than one SSS because |
216 | | // we only seem to be able to change one SSS in e.g. format and need to reformat the |
217 | | // whole string in other cases. |
218 | 660 | && (formatted.length() == possibleRetVal + magicLength |
219 | 98 | || plusZero.compare(possibleRetVal + magicLength, |
220 | 98 | LogString::npos, plusMagic, possibleRetVal + magicLength, LogString::npos) == 0)) |
221 | 622 | { |
222 | 622 | return possibleRetVal; |
223 | 622 | } |
224 | 71 | else |
225 | 71 | { |
226 | 71 | return UNRECOGNIZED_MILLISECONDS; |
227 | 71 | } |
228 | 693 | } |
229 | 118k | } |
230 | 1.11k | } |
231 | | |
232 | 417 | return NO_MILLISECONDS; |
233 | 1.51k | } |
234 | | #if LOG4CXX_ABI_VERSION <= 15 |
235 | | int CachedDateFormat::findMillisecondStart( |
236 | | log4cxx_time_t time, const LogString& formatted, |
237 | | const DateFormatPtr& formatter, |
238 | | Pool& pool) |
239 | 0 | { |
240 | 0 | return findMillisecondStart(time, formatted, formatter); |
241 | 0 | } |
242 | | #endif |
243 | | |
244 | | /** |
245 | | * Formats a millisecond count into a date/time string. |
246 | | * |
247 | | * @param now Number of milliseconds after midnight 1 Jan 1970 GMT. |
248 | | * @param sbuf the string buffer to write to |
249 | | */ |
250 | | void CachedDateFormat::format( LOG4CXX_FORMAT_TIME_FORMAL_PARAMETERS ) const |
251 | 1.51k | { |
252 | | |
253 | | // |
254 | | // If the current requested time is identical to the previously |
255 | | // requested time, then append the cache contents. |
256 | | // |
257 | 1.51k | if (tm == m_priv->previousTime) |
258 | 0 | { |
259 | 0 | toAppendTo.append(m_priv->cache); |
260 | 0 | return; |
261 | 0 | } |
262 | | |
263 | | // |
264 | | // If millisecond pattern was not unrecognized |
265 | | // (that is if it was found or milliseconds did not appear) |
266 | | // |
267 | 1.51k | if (m_priv->millisecondStart != UNRECOGNIZED_MILLISECONDS) |
268 | 1.51k | { |
269 | | // Check if the cache is still valid. |
270 | | // If the requested time is within the same integral second |
271 | | // as the last request and a shorter expiration was not requested. |
272 | 1.51k | if (tm < m_priv->slotBegin + m_priv->expiration |
273 | 0 | && tm >= m_priv->slotBegin |
274 | 0 | && tm < m_priv->slotBegin + 1000000L) |
275 | 0 | { |
276 | | // |
277 | | // if there was a millisecond field then update it |
278 | | // |
279 | 0 | if (m_priv->millisecondStart >= 0) |
280 | 0 | { |
281 | 0 | millisecondFormat((int) ((tm - m_priv->slotBegin) / 1000), m_priv->cache, m_priv->millisecondStart); |
282 | 0 | } |
283 | | |
284 | | // |
285 | | // update the previously requested time |
286 | | // (the slot begin should be unchanged) |
287 | 0 | m_priv->previousTime = tm; |
288 | 0 | toAppendTo.append(m_priv->cache); |
289 | |
|
290 | 0 | return; |
291 | 0 | } |
292 | 1.51k | } |
293 | | |
294 | | // |
295 | | // could not use previous value. |
296 | | // Call underlying formatter to format date. |
297 | 1.51k | m_priv->cache.erase(m_priv->cache.begin(), m_priv->cache.end()); |
298 | 1.51k | m_priv->formatter->format(m_priv->cache, tm); |
299 | 1.51k | toAppendTo.append(m_priv->cache); |
300 | 1.51k | m_priv->previousTime = tm; |
301 | 1.51k | m_priv->slotBegin = (m_priv->previousTime / 1000000) * 1000000; |
302 | | |
303 | 1.51k | if (m_priv->slotBegin > m_priv->previousTime) |
304 | 0 | { |
305 | 0 | m_priv->slotBegin -= 1000000; |
306 | 0 | } |
307 | | |
308 | | // |
309 | | // if the milliseconds field was previous found |
310 | | // then reevaluate in case it moved. |
311 | | // |
312 | 1.51k | if (m_priv->millisecondStart >= 0) |
313 | 1.51k | { |
314 | 1.51k | m_priv->millisecondStart = findMillisecondStart(tm, m_priv->cache, m_priv->formatter); |
315 | 1.51k | } |
316 | 1.51k | } |
317 | | |
318 | | /** |
319 | | * Formats a count of milliseconds (0-999) into a numeric representation. |
320 | | * @param millis Millisecond count between 0 and 999. |
321 | | * @buf String buffer, may not be null. |
322 | | * @offset Starting position in buffer, the length of the |
323 | | * buffer must be at least offset + 3. |
324 | | */ |
325 | | void CachedDateFormat::millisecondFormat(int millis, |
326 | | LogString& buf, |
327 | | int offset) |
328 | 693 | { |
329 | 693 | buf[offset] = digits[millis / 100]; |
330 | 693 | buf[offset + 1] = digits[(millis / 10) % 10]; |
331 | 693 | buf[offset + 2] = digits[millis % 10]; |
332 | 693 | } |
333 | | |
334 | | /** |
335 | | * Set timezone. |
336 | | * |
337 | | * @remarks Setting the timezone using getCalendar().setTimeZone() |
338 | | * will likely cause caching to misbehave. |
339 | | * @param timeZone TimeZone new timezone |
340 | | */ |
341 | | void CachedDateFormat::setTimeZone(const TimeZonePtr& timeZone) |
342 | 0 | { |
343 | 0 | m_priv->formatter->setTimeZone(timeZone); |
344 | 0 | m_priv->previousTime = std::numeric_limits<log4cxx_time_t>::min(); |
345 | 0 | m_priv->slotBegin = std::numeric_limits<log4cxx_time_t>::min(); |
346 | 0 | } |
347 | | |
348 | | |
349 | | |
350 | | void CachedDateFormat::numberFormat( LOG4CXX_FORMAT_NUMBER_FORMAL_PARAMETERS ) const |
351 | 0 | { |
352 | 0 | m_priv->formatter->numberFormat(toAppendTo, n); |
353 | 0 | } |
354 | | |
355 | | |
356 | | /** |
357 | | * Gets maximum cache validity for the specified SimpleDateTime |
358 | | * conversion pattern. |
359 | | * @param pattern conversion pattern, may not be null. |
360 | | * @returns Duration in microseconds from an integral second |
361 | | * that the cache will return consistent results. |
362 | | */ |
363 | | int CachedDateFormat::getMaximumCacheValidity(const LogString& pattern) |
364 | 927 | { |
365 | | // |
366 | | // If there are more "S" in the pattern than just one "SSS" then |
367 | | // (for example, "HH:mm:ss,SSS SSS"), then set the expiration to |
368 | | // one millisecond which should only perform duplicate request caching. |
369 | | // |
370 | 927 | const logchar S = 0x53; |
371 | 927 | const logchar SSS[] = { 0x53, 0x53, 0x53, 0 }; |
372 | 927 | size_t firstS = pattern.find(S); |
373 | 927 | size_t len = pattern.length(); |
374 | | |
375 | | // |
376 | | // if there are no S's or |
377 | | // three that start with the first S and no fourth S in the string |
378 | | // |
379 | 927 | if (firstS == LogString::npos || |
380 | 561 | (len >= firstS + 3 && pattern.compare(firstS, 3, SSS) == 0 |
381 | 132 | && (len == firstS + 3 || |
382 | 124 | pattern.find(S, firstS + 3) == LogString::npos))) |
383 | 425 | { |
384 | 425 | return 1000000; |
385 | 425 | } |
386 | | |
387 | 502 | return 1000; |
388 | 927 | } |
389 | | |
390 | | |
391 | | /** |
392 | | * Tests if two string regions are equal. |
393 | | * @param target target string. |
394 | | * @param toffset character position in target to start comparison. |
395 | | * @param other other string. |
396 | | * @param ooffset character position in other to start comparison. |
397 | | * @param len length of region. |
398 | | * @return true if regions are equal. |
399 | | */ |
400 | | bool CachedDateFormat::regionMatches( |
401 | | const LogString& target, |
402 | | size_t toffset, |
403 | | const LogString& other, |
404 | | size_t ooffset, |
405 | | size_t len) |
406 | 1.98k | { |
407 | 1.98k | return target.compare(toffset, len, other, ooffset, len) == 0; |
408 | 1.98k | } |
409 | | |