| /* | |
| * 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 | |
| #define NOMINMAX /* tell wnidows not to define min/max macros */ | |
| #include <log4cxx/logstring.h> | |
| #include <log4cxx/helpers/cacheddateformat.h> | |
| #include <log4cxx/helpers/pool.h> | |
| #include <limits> | |
| #include <log4cxx/helpers/exception.h> | |
| using namespace LOG4CXX_NS; | |
| using namespace LOG4CXX_NS::helpers; | |
| using namespace LOG4CXX_NS::pattern; | |
| struct CachedDateFormat::CachedDateFormatPriv | |
| { | |
| CachedDateFormatPriv(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()) | |
| {} | |
| /** | |
| * Wrapped formatter. | |
| */ | |
| LOG4CXX_NS::helpers::DateFormatPtr formatter; | |
| /** | |
| * Index of initial digit of millisecond pattern or | |
| * UNRECOGNIZED_MILLISECONDS or NO_MILLISECONDS. | |
| */ | |
| mutable int millisecondStart; | |
| /** | |
| * Integral second preceding the previous convered Date. | |
| */ | |
| mutable log4cxx_time_t slotBegin; | |
| /** | |
| * Cache of previous conversion. | |
| */ | |
| mutable LogString cache; | |
| /** | |
| * Maximum validity period for the cache. | |
| * Typically 1, use cache for duplicate requests only, or | |
| * 1000000, use cache for requests within the same integral second. | |
| */ | |
| const int expiration; | |
| /** | |
| * Date requested in previous conversion. | |
| */ | |
| mutable log4cxx_time_t previousTime; | |
| }; | |
| /** | |
| * 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 }; | |
| /** | |
| * 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) : | |
| m_priv(std::make_unique<CachedDateFormatPriv>(dateFormat, expiration1)) | |
| { | |
| if (dateFormat == NULL) | |
| { | |
| throw NullPointerException(LOG4CXX_STR("dateFormat")); | |
| } | |
| if (expiration1 < 0) | |
| { | |
| throw IllegalArgumentException(LOG4CXX_STR("expiration must be non-negative")); | |
| } | |
| } | |
| CachedDateFormat::~CachedDateFormat() {} | |
| /** | |
| * 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) | |
| { | |
| log4cxx_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); | |
| /** | |
| * 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); | |
| // 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 = int(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& toAppendTo, log4cxx_time_t tm ) const | |
| { | |
| // | |
| // If the current requested time is identical to the previously | |
| // requested time, then append the cache contents. | |
| // | |
| if (tm == m_priv->previousTime) | |
| { | |
| toAppendTo.append(m_priv->cache); | |
| return; | |
| } | |
| // | |
| // If millisecond pattern was not unrecognized | |
| // (that is if it was found or milliseconds did not appear) | |
| // | |
| if (m_priv->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 (tm < m_priv->slotBegin + m_priv->expiration | |
| && tm >= m_priv->slotBegin | |
| && tm < m_priv->slotBegin + 1000000L) | |
| { | |
| // | |
| // if there was a millisecond field then update it | |
| // | |
| if (m_priv->millisecondStart >= 0) | |
| { | |
| millisecondFormat((int) ((tm - m_priv->slotBegin) / 1000), m_priv->cache, m_priv->millisecondStart); | |
| } | |
| // | |
| // update the previously requested time | |
| // (the slot begin should be unchanged) | |
| m_priv->previousTime = tm; | |
| toAppendTo.append(m_priv->cache); | |
| return; | |
| } | |
| } | |
| // | |
| // could not use previous value. | |
| // Call underlying formatter to format date. | |
| m_priv->cache.erase(m_priv->cache.begin(), m_priv->cache.end()); | |
| m_priv->formatter->format(m_priv->cache, tm); | |
| toAppendTo.append(m_priv->cache); | |
| m_priv->previousTime = tm; | |
| m_priv->slotBegin = (m_priv->previousTime / 1000000) * 1000000; | |
| if (m_priv->slotBegin > m_priv->previousTime) | |
| { | |
| m_priv->slotBegin -= 1000000; | |
| } | |
| // | |
| // if the milliseconds field was previous found | |
| // then reevaluate in case it moved. | |
| // | |
| if (m_priv->millisecondStart >= 0) | |
| { | |
| m_priv->millisecondStart = findMillisecondStart(tm, m_priv->cache, m_priv->formatter); | |
| } | |
| } | |
| /** | |
| * 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) | |
| { | |
| m_priv->formatter->setTimeZone(timeZone); | |
| m_priv->previousTime = std::numeric_limits<log4cxx_time_t>::min(); | |
| m_priv->slotBegin = std::numeric_limits<log4cxx_time_t>::min(); | |
| } | |
| void CachedDateFormat::numberFormat( LogString& toAppendTo, int n ) const | |
| { | |
| m_priv->formatter->numberFormat(toAppendTo, n); | |
| } | |
| /** | |
| * 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; | |
| } | |