Coverage Report

Created: 2025-07-01 06:08

/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