/src/logging-log4cxx/src/main/cpp/cacheddateformat.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 0 | formatter(dateFormat), |
33 | 0 | millisecondStart(0), |
34 | 0 | slotBegin(std::numeric_limits<log4cxx_time_t>::min()), |
35 | 0 | cache(50, 0x20), |
36 | 0 | expiration(expiration1), |
37 | 0 | previousTime(std::numeric_limits<log4cxx_time_t>::min()) |
38 | 0 | {} |
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 | 0 | m_priv(std::make_unique<CachedDateFormatPriv>(dateFormat, expiration1)) |
128 | 0 | { |
129 | 0 | if (dateFormat == NULL) |
130 | 0 | { |
131 | 0 | throw IllegalArgumentException(LOG4CXX_STR("dateFormat cannot be null")); |
132 | 0 | } |
133 | | |
134 | 0 | if (expiration1 < 0) |
135 | 0 | { |
136 | 0 | throw IllegalArgumentException(LOG4CXX_STR("expiration must be non-negative")); |
137 | 0 | } |
138 | 0 | } |
139 | | |
140 | 0 | 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 | | Pool& pool) |
156 | 0 | { |
157 | |
|
158 | 0 | log4cxx_time_t slotBegin = (time / 1000000) * 1000000; |
159 | |
|
160 | 0 | if (slotBegin > time) |
161 | 0 | { |
162 | 0 | slotBegin -= 1000000; |
163 | 0 | } |
164 | |
|
165 | 0 | int millis = (int) (time - slotBegin) / 1000; |
166 | | |
167 | | // the magic numbers are in microseconds |
168 | 0 | int magic = magic1; |
169 | 0 | LogString magicString(magicString1); |
170 | |
|
171 | 0 | if (millis == magic1 / 1000) |
172 | 0 | { |
173 | 0 | magic = magic2; |
174 | 0 | magicString = magicString2; |
175 | 0 | } |
176 | |
|
177 | 0 | LogString plusMagic; |
178 | 0 | formatter->format(plusMagic, slotBegin + magic, pool); |
179 | | |
180 | | /** |
181 | | * If the string lengths differ then |
182 | | * we can't use the cache except for duplicate requests. |
183 | | */ |
184 | 0 | if (plusMagic.length() != formatted.length()) |
185 | 0 | { |
186 | 0 | return UNRECOGNIZED_MILLISECONDS; |
187 | 0 | } |
188 | 0 | else |
189 | 0 | { |
190 | | // find first difference between values |
191 | 0 | for (LogString::size_type i = 0; i < formatted.length(); i++) |
192 | 0 | { |
193 | 0 | if (formatted[i] != plusMagic[i]) |
194 | 0 | { |
195 | | // |
196 | | // determine the expected digits for the base time |
197 | 0 | const logchar abc[] = { 0x41, 0x42, 0x43, 0 }; |
198 | 0 | LogString formattedMillis(abc); |
199 | 0 | millisecondFormat(millis, formattedMillis, 0); |
200 | |
|
201 | 0 | LogString plusZero; |
202 | 0 | formatter->format(plusZero, slotBegin, pool); |
203 | | |
204 | | // Test if the next 1..3 characters match the magic string, main problem is that magic |
205 | | // available millis in formatted can overlap. Therefore the current i is not always the |
206 | | // index of the first millis char, but may be already within the millis. Besides that |
207 | | // the millis can occur everywhere in formatted. See LOGCXX-420 and following. |
208 | 0 | size_t magicLength = magicString.length(); |
209 | 0 | size_t overlapping = magicString.find(plusMagic[i]); |
210 | 0 | int possibleRetVal = int(i - overlapping); |
211 | |
|
212 | 0 | if (plusZero.length() == formatted.length() |
213 | 0 | && regionMatches(magicString, 0, plusMagic, possibleRetVal, magicLength) |
214 | 0 | && regionMatches(formattedMillis, 0, formatted, possibleRetVal, magicLength) |
215 | 0 | && regionMatches(zeroString, 0, plusZero, possibleRetVal, magicLength) |
216 | | // The following will and should fail for patterns with more than one SSS because |
217 | | // we only seem to be able to change one SSS in e.g. format and need to reformat the |
218 | | // whole string in other cases. |
219 | 0 | && (formatted.length() == possibleRetVal + magicLength |
220 | 0 | || plusZero.compare(possibleRetVal + magicLength, |
221 | 0 | LogString::npos, plusMagic, possibleRetVal + magicLength, LogString::npos) == 0)) |
222 | 0 | { |
223 | 0 | return possibleRetVal; |
224 | 0 | } |
225 | 0 | else |
226 | 0 | { |
227 | 0 | return UNRECOGNIZED_MILLISECONDS; |
228 | 0 | } |
229 | 0 | } |
230 | 0 | } |
231 | 0 | } |
232 | | |
233 | 0 | return NO_MILLISECONDS; |
234 | 0 | } |
235 | | |
236 | | |
237 | | /** |
238 | | * Formats a millisecond count into a date/time string. |
239 | | * |
240 | | * @param now Number of milliseconds after midnight 1 Jan 1970 GMT. |
241 | | * @param sbuf the string buffer to write to |
242 | | */ |
243 | | void CachedDateFormat::format(LogString& buf, log4cxx_time_t now, Pool& p) const |
244 | 0 | { |
245 | | |
246 | | // |
247 | | // If the current requested time is identical to the previously |
248 | | // requested time, then append the cache contents. |
249 | | // |
250 | 0 | if (now == m_priv->previousTime) |
251 | 0 | { |
252 | 0 | buf.append(m_priv->cache); |
253 | 0 | return; |
254 | 0 | } |
255 | | |
256 | | // |
257 | | // If millisecond pattern was not unrecognized |
258 | | // (that is if it was found or milliseconds did not appear) |
259 | | // |
260 | 0 | if (m_priv->millisecondStart != UNRECOGNIZED_MILLISECONDS) |
261 | 0 | { |
262 | | // Check if the cache is still valid. |
263 | | // If the requested time is within the same integral second |
264 | | // as the last request and a shorter expiration was not requested. |
265 | 0 | if (now < m_priv->slotBegin + m_priv->expiration |
266 | 0 | && now >= m_priv->slotBegin |
267 | 0 | && now < m_priv->slotBegin + 1000000L) |
268 | 0 | { |
269 | | // |
270 | | // if there was a millisecond field then update it |
271 | | // |
272 | 0 | if (m_priv->millisecondStart >= 0) |
273 | 0 | { |
274 | 0 | millisecondFormat((int) ((now - m_priv->slotBegin) / 1000), m_priv->cache, m_priv->millisecondStart); |
275 | 0 | } |
276 | | |
277 | | // |
278 | | // update the previously requested time |
279 | | // (the slot begin should be unchanged) |
280 | 0 | m_priv->previousTime = now; |
281 | 0 | buf.append(m_priv->cache); |
282 | |
|
283 | 0 | return; |
284 | 0 | } |
285 | 0 | } |
286 | | |
287 | | // |
288 | | // could not use previous value. |
289 | | // Call underlying formatter to format date. |
290 | 0 | m_priv->cache.erase(m_priv->cache.begin(), m_priv->cache.end()); |
291 | 0 | m_priv->formatter->format(m_priv->cache, now, p); |
292 | 0 | buf.append(m_priv->cache); |
293 | 0 | m_priv->previousTime = now; |
294 | 0 | m_priv->slotBegin = (m_priv->previousTime / 1000000) * 1000000; |
295 | |
|
296 | 0 | if (m_priv->slotBegin > m_priv->previousTime) |
297 | 0 | { |
298 | 0 | m_priv->slotBegin -= 1000000; |
299 | 0 | } |
300 | | |
301 | | // |
302 | | // if the milliseconds field was previous found |
303 | | // then reevaluate in case it moved. |
304 | | // |
305 | 0 | if (m_priv->millisecondStart >= 0) |
306 | 0 | { |
307 | 0 | m_priv->millisecondStart = findMillisecondStart(now, m_priv->cache, m_priv->formatter, p); |
308 | 0 | } |
309 | 0 | } |
310 | | |
311 | | /** |
312 | | * Formats a count of milliseconds (0-999) into a numeric representation. |
313 | | * @param millis Millisecond count between 0 and 999. |
314 | | * @buf String buffer, may not be null. |
315 | | * @offset Starting position in buffer, the length of the |
316 | | * buffer must be at least offset + 3. |
317 | | */ |
318 | | void CachedDateFormat::millisecondFormat(int millis, |
319 | | LogString& buf, |
320 | | int offset) |
321 | 0 | { |
322 | 0 | buf[offset] = digits[millis / 100]; |
323 | 0 | buf[offset + 1] = digits[(millis / 10) % 10]; |
324 | 0 | buf[offset + 2] = digits[millis % 10]; |
325 | 0 | } |
326 | | |
327 | | /** |
328 | | * Set timezone. |
329 | | * |
330 | | * @remarks Setting the timezone using getCalendar().setTimeZone() |
331 | | * will likely cause caching to misbehave. |
332 | | * @param timeZone TimeZone new timezone |
333 | | */ |
334 | | void CachedDateFormat::setTimeZone(const TimeZonePtr& timeZone) |
335 | 0 | { |
336 | 0 | m_priv->formatter->setTimeZone(timeZone); |
337 | 0 | m_priv->previousTime = std::numeric_limits<log4cxx_time_t>::min(); |
338 | 0 | m_priv->slotBegin = std::numeric_limits<log4cxx_time_t>::min(); |
339 | 0 | } |
340 | | |
341 | | |
342 | | |
343 | | void CachedDateFormat::numberFormat(LogString& s, int n, Pool& p) const |
344 | 0 | { |
345 | 0 | m_priv->formatter->numberFormat(s, n, p); |
346 | 0 | } |
347 | | |
348 | | |
349 | | /** |
350 | | * Gets maximum cache validity for the specified SimpleDateTime |
351 | | * conversion pattern. |
352 | | * @param pattern conversion pattern, may not be null. |
353 | | * @returns Duration in microseconds from an integral second |
354 | | * that the cache will return consistent results. |
355 | | */ |
356 | | int CachedDateFormat::getMaximumCacheValidity(const LogString& pattern) |
357 | 0 | { |
358 | | // |
359 | | // If there are more "S" in the pattern than just one "SSS" then |
360 | | // (for example, "HH:mm:ss,SSS SSS"), then set the expiration to |
361 | | // one millisecond which should only perform duplicate request caching. |
362 | | // |
363 | 0 | const logchar S = 0x53; |
364 | 0 | const logchar SSS[] = { 0x53, 0x53, 0x53, 0 }; |
365 | 0 | size_t firstS = pattern.find(S); |
366 | 0 | size_t len = pattern.length(); |
367 | | |
368 | | // |
369 | | // if there are no S's or |
370 | | // three that start with the first S and no fourth S in the string |
371 | | // |
372 | 0 | if (firstS == LogString::npos || |
373 | 0 | (len >= firstS + 3 && pattern.compare(firstS, 3, SSS) == 0 |
374 | 0 | && (len == firstS + 3 || |
375 | 0 | pattern.find(S, firstS + 3) == LogString::npos))) |
376 | 0 | { |
377 | 0 | return 1000000; |
378 | 0 | } |
379 | | |
380 | 0 | return 1000; |
381 | 0 | } |
382 | | |
383 | | |
384 | | /** |
385 | | * Tests if two string regions are equal. |
386 | | * @param target target string. |
387 | | * @param toffset character position in target to start comparison. |
388 | | * @param other other string. |
389 | | * @param ooffset character position in other to start comparison. |
390 | | * @param len length of region. |
391 | | * @return true if regions are equal. |
392 | | */ |
393 | | bool CachedDateFormat::regionMatches( |
394 | | const LogString& target, |
395 | | size_t toffset, |
396 | | const LogString& other, |
397 | | size_t ooffset, |
398 | | size_t len) |
399 | 0 | { |
400 | 0 | return target.compare(toffset, len, other, ooffset, len) == 0; |
401 | 0 | } |
402 | | |