Coverage Report

Created: 2026-06-15 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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