blob: 6413baf3a88b87da810fbdf1ebacd3ef9999d394 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define __STDC_CONSTANT_MACROS
#include <log4cxx/logstring.h>
#include <log4cxx/helpers/cacheddateformat.h>
#include <apr_time.h>
#include <log4cxx/helpers/pool.h>
#include <limits>
#include <log4cxx/helpers/exception.h>
using namespace log4cxx;
using namespace log4cxx::helpers;
using namespace log4cxx::pattern;
/**
* Supported digit set. If the wrapped DateFormat uses
* a different unit set, the millisecond pattern
* will not be recognized and duplicate requests
* will use the cache.
*/
const logchar CachedDateFormat::digits[] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0 };
/**
* First magic number (in microseconds) used to detect
* the millisecond position.
*/
const int CachedDateFormat::magic1 = 654000;
/**
* Expected representation of first magic number in milliseconds.
*/
const logchar CachedDateFormat::magicString1[] = { 0x36, 0x35, 0x34, 0 };
/**
* Second magic number (in microseconds) used to detect
* the millisecond position.
*/
const int CachedDateFormat::magic2 = 987000;
/**
* Expected representation of second magic number in milliseconds.
*/
const logchar CachedDateFormat::magicString2[] = { 0x39, 0x38, 0x37, 0};
/**
* Expected representation of 0 milliseconds.
*/
const logchar CachedDateFormat::zeroString[] = { 0x30, 0x30, 0x30, 0 };
#undef min
/**
* Creates a new CachedDateFormat object.
* @param dateFormat Date format, may not be null.
* @param expiration maximum cached range in milliseconds.
* If the dateFormat is known to be incompatible with the
* caching algorithm, use a value of 0 to totally disable
* caching or 1 to only use cache for duplicate requests.
*/
CachedDateFormat::CachedDateFormat(const DateFormatPtr& dateFormat,
int expiration1) :
formatter(dateFormat),
millisecondStart(0),
slotBegin(std::numeric_limits<log4cxx_time_t>::min()),
cache(50, 0x20),
expiration(expiration1),
previousTime(std::numeric_limits<log4cxx_time_t>::min())
{
if (dateFormat == NULL)
{
throw IllegalArgumentException(LOG4CXX_STR("dateFormat cannot be null"));
}
if (expiration1 < 0)
{
throw IllegalArgumentException(LOG4CXX_STR("expiration must be non-negative"));
}
}
/**
* Finds start of millisecond field in formatted time.
* @param time long time, must be integral number of seconds
* @param formatted String corresponding formatted string
* @param formatter DateFormat date format
* @return int position in string of first digit of milliseconds,
* -1 indicates no millisecond field, -2 indicates unrecognized
* field (likely RelativeTimeDateFormat)
*/
int CachedDateFormat::findMillisecondStart(
log4cxx_time_t time, const LogString& formatted,
const DateFormatPtr& formatter,
Pool& pool)
{
apr_time_t slotBegin = (time / 1000000) * 1000000;
if (slotBegin > time)
{
slotBegin -= 1000000;
}
int millis = (int) (time - slotBegin) / 1000;
// the magic numbers are in microseconds
int magic = magic1;
LogString magicString(magicString1);
if (millis == magic1 / 1000)
{
magic = magic2;
magicString = magicString2;
}
LogString plusMagic;
formatter->format(plusMagic, slotBegin + magic, pool);
/**
* If the string lengths differ then
* we can't use the cache except for duplicate requests.
*/
if (plusMagic.length() != formatted.length())
{
return UNRECOGNIZED_MILLISECONDS;
}
else
{
// find first difference between values
for (LogString::size_type i = 0; i < formatted.length(); i++)
{
if (formatted[i] != plusMagic[i])
{
//
// determine the expected digits for the base time
const logchar abc[] = { 0x41, 0x42, 0x43, 0 };
LogString formattedMillis(abc);
millisecondFormat(millis, formattedMillis, 0);
LogString plusZero;
formatter->format(plusZero, slotBegin, pool);
// Test if the next 1..3 characters match the magic string, main problem is that magic
// available millis in formatted can overlap. Therefore the current i is not always the
// index of the first millis char, but may be already within the millis. Besides that
// the millis can occur everywhere in formatted. See LOGCXX-420 and following.
size_t magicLength = magicString.length();
size_t overlapping = magicString.find(plusMagic[i]);
int possibleRetVal = i - overlapping;
if (plusZero.length() == formatted.length()
&& regionMatches(magicString, 0, plusMagic, possibleRetVal, magicLength)
&& regionMatches(formattedMillis, 0, formatted, possibleRetVal, magicLength)
&& regionMatches(zeroString, 0, plusZero, possibleRetVal, magicLength)
// The following will and should fail for patterns with more than one SSS because
// we only seem to be able to change one SSS in e.g. format and need to reformat the
// whole string in other cases.
&& (formatted.length() == possibleRetVal + magicLength
|| plusZero.compare(possibleRetVal + magicLength,
LogString::npos, plusMagic, possibleRetVal + magicLength, LogString::npos) == 0))
{
return possibleRetVal;
}
else
{
return UNRECOGNIZED_MILLISECONDS;
}
}
}
}
return NO_MILLISECONDS;
}
/**
* Formats a millisecond count into a date/time string.
*
* @param now Number of milliseconds after midnight 1 Jan 1970 GMT.
* @param sbuf the string buffer to write to
*/
void CachedDateFormat::format(LogString& buf, log4cxx_time_t now, Pool& p) const
{
//
// If the current requested time is identical to the previously
// requested time, then append the cache contents.
//
if (now == previousTime)
{
buf.append(cache);
return;
}
//
// If millisecond pattern was not unrecognized
// (that is if it was found or milliseconds did not appear)
//
if (millisecondStart != UNRECOGNIZED_MILLISECONDS)
{
// Check if the cache is still valid.
// If the requested time is within the same integral second
// as the last request and a shorter expiration was not requested.
if (now < slotBegin + expiration
&& now >= slotBegin
&& now < slotBegin + 1000000L)
{
//
// if there was a millisecond field then update it
//
if (millisecondStart >= 0)
{
millisecondFormat((int) ((now - slotBegin) / 1000), cache, millisecondStart);
}
//
// update the previously requested time
// (the slot begin should be unchanged)
previousTime = now;
buf.append(cache);
return;
}
}
//
// could not use previous value.
// Call underlying formatter to format date.
cache.erase(cache.begin(), cache.end());
formatter->format(cache, now, p);
buf.append(cache);
previousTime = now;
slotBegin = (previousTime / 1000000) * 1000000;
if (slotBegin > previousTime)
{
slotBegin -= 1000000;
}
//
// if the milliseconds field was previous found
// then reevaluate in case it moved.
//
if (millisecondStart >= 0)
{
millisecondStart = findMillisecondStart(now, cache, formatter, p);
}
}
/**
* Formats a count of milliseconds (0-999) into a numeric representation.
* @param millis Millisecond count between 0 and 999.
* @buf String buffer, may not be null.
* @offset Starting position in buffer, the length of the
* buffer must be at least offset + 3.
*/
void CachedDateFormat::millisecondFormat(int millis,
LogString& buf,
int offset)
{
buf[offset] = digits[millis / 100];
buf[offset + 1] = digits[(millis / 10) % 10];
buf[offset + 2] = digits[millis % 10];
}
/**
* Set timezone.
*
* @remarks Setting the timezone using getCalendar().setTimeZone()
* will likely cause caching to misbehave.
* @param timeZone TimeZone new timezone
*/
void CachedDateFormat::setTimeZone(const TimeZonePtr& timeZone)
{
formatter->setTimeZone(timeZone);
previousTime = std::numeric_limits<log4cxx_time_t>::min();
slotBegin = std::numeric_limits<log4cxx_time_t>::min();
}
void CachedDateFormat::numberFormat(LogString& s, int n, Pool& p) const
{
formatter->numberFormat(s, n, p);
}
/**
* Gets maximum cache validity for the specified SimpleDateTime
* conversion pattern.
* @param pattern conversion pattern, may not be null.
* @returns Duration in microseconds from an integral second
* that the cache will return consistent results.
*/
int CachedDateFormat::getMaximumCacheValidity(const LogString& pattern)
{
//
// If there are more "S" in the pattern than just one "SSS" then
// (for example, "HH:mm:ss,SSS SSS"), then set the expiration to
// one millisecond which should only perform duplicate request caching.
//
const logchar S = 0x53;
const logchar SSS[] = { 0x53, 0x53, 0x53, 0 };
size_t firstS = pattern.find(S);
size_t len = pattern.length();
//
// if there are no S's or
// three that start with the first S and no fourth S in the string
//
if (firstS == LogString::npos ||
(len >= firstS + 3 && pattern.compare(firstS, 3, SSS) == 0
&& (len == firstS + 3 ||
pattern.find(S, firstS + 3) == LogString::npos)))
{
return 1000000;
}
return 1000;
}
/**
* Tests if two string regions are equal.
* @param target target string.
* @param toffset character position in target to start comparison.
* @param other other string.
* @param ooffset character position in other to start comparison.
* @param len length of region.
* @return true if regions are equal.
*/
bool CachedDateFormat::regionMatches(
const LogString& target,
size_t toffset,
const LogString& other,
size_t ooffset,
size_t len)
{
return target.compare(toffset, len, other, ooffset, len) == 0;
}