/* * 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 #include #include #include #include #include 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 }; /** * Expected representation of first magic number. */ const logchar CachedDateFormat::magicString1[] = { 0x36, 0x35, 0x34, 0 }; /** * Expected representation of second magic number. */ 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::min()), cache(50, 0x20), expiration(expiration1), previousTime(std::numeric_limits::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; int magic = magic1; LogString magicString(magicString1); if (millis == magic1) { 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::min(); slotBegin = std::numeric_limits::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; }