org.apache.log4j.pattern.CachedDateFormat Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of reload4j Show documentation
Show all versions of reload4j Show documentation
Reload4j revives EOLed log4j 1.x
/*
* 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.
*/
package org.apache.log4j.pattern;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Date;
import java.util.TimeZone;
/**
* CachedDateFormat optimizes the performance of a wrapped DateFormat. The
* implementation is not thread-safe. If the millisecond pattern is not
* recognized, the class will only use the cache if the same value is requested.
*
*/
public final class CachedDateFormat extends DateFormat {
/**
* Serialization version.
*/
private static final long serialVersionUID = 1;
/**
* Constant used to represent that there was no change observed when changing
* the millisecond count.
*/
public static final int NO_MILLISECONDS = -2;
/**
* 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.
*/
private static final String DIGITS = "0123456789";
/**
* Constant used to represent that there was an observed change, but was an
* expected change.
*/
public static final int UNRECOGNIZED_MILLISECONDS = -1;
/**
* First magic number used to detect the millisecond position.
*/
private static final int MAGIC1 = 654;
/**
* Expected representation of first magic number.
*/
private static final String MAGICSTRING1 = "654";
/**
* Second magic number used to detect the millisecond position.
*/
private static final int MAGIC2 = 987;
/**
* Expected representation of second magic number.
*/
private static final String MAGICSTRING2 = "987";
/**
* Expected representation of 0 milliseconds.
*/
private static final String ZERO_STRING = "000";
/**
* Wrapped formatter.
*/
private final DateFormat formatter;
/**
* Index of initial digit of millisecond pattern or UNRECOGNIZED_MILLISECONDS or
* NO_MILLISECONDS.
*/
private int millisecondStart;
/**
* Integral second preceding the previous convered Date.
*/
private long slotBegin;
/**
* Cache of previous conversion.
*/
private StringBuffer cache = new StringBuffer(50);
/**
* Maximum validity period for the cache. Typically 1, use cache for duplicate
* requests only, or 1000, use cache for requests within the same integral
* second.
*/
private final int expiration;
/**
* Date requested in previous conversion.
*/
private long previousTime;
/**
* Scratch date object used to minimize date object creation.
*/
private final Date tmpDate = new Date(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.
*/
public CachedDateFormat(final DateFormat dateFormat, final int expiration) {
if (dateFormat == null) {
throw new IllegalArgumentException("dateFormat cannot be null");
}
if (expiration < 0) {
throw new IllegalArgumentException("expiration must be non-negative");
}
formatter = dateFormat;
this.expiration = expiration;
millisecondStart = 0;
//
// set the previousTime so the cache will be invalid
// for the next request.
previousTime = Long.MIN_VALUE;
slotBegin = Long.MIN_VALUE;
}
/**
* 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)
*/
public static int findMillisecondStart(final long time, final String formatted, final DateFormat formatter) {
long slotBegin = (time / 1000) * 1000;
if (slotBegin > time) {
slotBegin -= 1000;
}
int millis = (int) (time - slotBegin);
int magic = MAGIC1;
String magicString = MAGICSTRING1;
if (millis == MAGIC1) {
magic = MAGIC2;
magicString = MAGICSTRING2;
}
String plusMagic = formatter.format(new Date(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 (int i = 0; i < formatted.length(); i++) {
if (formatted.charAt(i) != plusMagic.charAt(i)) {
//
// determine the expected digits for the base time
StringBuffer formattedMillis = new StringBuffer("ABC");
millisecondFormat(millis, formattedMillis, 0);
String plusZero = formatter.format(new Date(slotBegin));
// If the next 3 characters match the magic
// string and the expected string
if ((plusZero.length() == formatted.length())
&& magicString.regionMatches(0, plusMagic, i, magicString.length())
&& formattedMillis.toString().regionMatches(0, formatted, i, magicString.length())
&& ZERO_STRING.regionMatches(0, plusZero, i, ZERO_STRING.length())) {
return i;
} else {
return UNRECOGNIZED_MILLISECONDS;
}
}
}
}
return NO_MILLISECONDS;
}
/**
* Formats a Date into a date/time string.
*
* @param date the date to format.
* @param sbuf the string buffer to write to.
* @param fieldPosition remains untouched.
* @return the formatted time string.
*/
public StringBuffer format(Date date, StringBuffer sbuf, FieldPosition fieldPosition) {
format(date.getTime(), sbuf);
return sbuf;
}
/**
* Formats a millisecond count into a date/time string.
*
* @param now Number of milliseconds after midnight 1 Jan 1970 GMT.
* @param buf the string buffer to write to.
* @return the formatted time string.
*/
public StringBuffer format(long now, StringBuffer buf) {
//
// If the current requested time is identical to the previously
// requested time, then append the cache contents.
//
if (now == previousTime) {
buf.append(cache);
return buf;
}
//
// 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.
(now < (slotBegin + expiration)) && (now >= slotBegin) && (now < (slotBegin + 1000L))) {
//
// if there was a millisecond field then update it
//
if (millisecondStart >= 0) {
millisecondFormat((int) (now - slotBegin), cache, millisecondStart);
}
//
// update the previously requested time
// (the slot begin should be unchanged)
previousTime = now;
buf.append(cache);
return buf;
}
//
// could not use previous value.
// Call underlying formatter to format date.
cache.setLength(0);
tmpDate.setTime(now);
cache.append(formatter.format(tmpDate));
buf.append(cache);
previousTime = now;
slotBegin = (previousTime / 1000) * 1000;
if (slotBegin > previousTime) {
slotBegin -= 1000;
}
//
// if the milliseconds field was previous found
// then reevaluate in case it moved.
//
if (millisecondStart >= 0) {
millisecondStart = findMillisecondStart(now, cache.toString(), formatter);
}
return buf;
}
/**
* Formats a count of milliseconds (0-999) into a numeric representation.
*
* @param millis Millisecond coun between 0 and 999.
* @param buf String buffer, may not be null.
* @param offset Starting position in buffer, the length of the buffer must be
* at least offset + 3.
*/
private static void millisecondFormat(final int millis, final StringBuffer buf, final int offset) {
buf.setCharAt(offset, DIGITS.charAt(millis / 100));
buf.setCharAt(offset + 1, DIGITS.charAt((millis / 10) % 10));
buf.setCharAt(offset + 2, DIGITS.charAt(millis % 10));
}
/**
* Set timezone.
*
* Setting the timezone using getCalendar().setTimeZone() will likely cause
* caching to misbehave.
*
* @param timeZone TimeZone new timezone
*/
public void setTimeZone(final TimeZone timeZone) {
formatter.setTimeZone(timeZone);
previousTime = Long.MIN_VALUE;
slotBegin = Long.MIN_VALUE;
}
/**
* This method is delegated to the formatter which most likely returns null.
*
* @param s string representation of date.
* @param pos field position, unused.
* @return parsed date, likely null.
*/
public Date parse(String s, ParsePosition pos) {
return formatter.parse(s, pos);
}
/**
* Gets number formatter.
*
* @return NumberFormat number formatter
*/
public NumberFormat getNumberFormat() {
return formatter.getNumberFormat();
}
/**
* Gets maximum cache validity for the specified SimpleDateTime conversion
* pattern.
*
* @param pattern conversion pattern, may not be null.
* @return Duration in milliseconds from an integral second that the cache will
* return consistent results.
*/
public static int getMaximumCacheValidity(final String 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.
//
int firstS = pattern.indexOf('S');
if ((firstS >= 0) && (firstS != pattern.lastIndexOf("SSS"))) {
return 1;
}
return 1000;
}
}