carnold 2004/12/24 00:28:29 Modified: src/java/org/apache/log4j/pattern CachedDateFormat.java DatePatternConverter.java Removed: src/java/org/apache/log4j/pattern CacheUtil.java Log: Bug 32064: CachedDateFormat rework Revision Changes Path 1.13 +235 -88 logging-log4j/src/java/org/apache/log4j/pattern/CachedDateFormat.java Index: CachedDateFormat.java =================================================================== RCS file: /home/cvs/logging-log4j/src/java/org/apache/log4j/pattern/CachedDateFormat.java,v retrieving revision 1.12 retrieving revision 1.13 diff -u -r1.12 -r1.13 --- CachedDateFormat.java 23 Dec 2004 22:50:48 -0000 1.12 +++ CachedDateFormat.java 24 Dec 2004 08:28:28 -0000 1.13 @@ -27,48 +27,132 @@ /** - * Caches the results of a DateFormat. + * 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. + * * @author Curt Arnold * @since 1.3 */ final class CachedDateFormat extends DateFormat { - private static final int BAD_PATTERN = -1; - private static final int NO_MILLISECONDS = -2; + /* + * 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 final static 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 zeroString = "000"; - // Given that the JVM precision is 1/1000 of a second, 3 digit millisecond - // precision is the best we can ever expect. - private static final int JVM_MAX_MILLI_DIGITS = 3; + /** + * Wrapped formatter. + */ + private final DateFormat formatter; - private DateFormat formatter; + /** + * Index of initial digit of millisecond pattern or + * UNRECOGNIZED_MILLISECONDS or NO_MILLISECONDS. + */ private int millisecondStart; - private StringBuffer cache = new StringBuffer(); + + /** + * Cache of previous conversion. + */ + private StringBuffer cache = new StringBuffer(50); + + /** + * Integral second preceding the previous convered Date. + */ private long slotBegin; - private Date slotBeginDate; - private int milliDigits; - private StringBuffer milliBuf = new StringBuffer(JVM_MAX_MILLI_DIGITS); - private NumberFormat numberFormat; + /** + * 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); - public CachedDateFormat(DateFormat dateFormat) { + /** + * 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"); } formatter = dateFormat; - - numberFormat = new DecimalFormat(); - // numberFormat will zero as necessary - numberFormat.setMinimumIntegerDigits(JVM_MAX_MILLI_DIGITS); + this.expiration = expiration; Date now = new Date(); long nowTime = now.getTime(); slotBegin = (nowTime / 1000L) * 1000L; + // + // if now is before 1970 and slot begin + // was truncated forward + // + if (slotBegin > nowTime) { + slotBegin -= 1000; + } - slotBeginDate = new Date(slotBegin); - String formatted = formatter.format(slotBeginDate); + String formatted = formatter.format(now); cache.append(formatted); - millisecondStart = findMillisecondStart(slotBegin, formatted, formatter); - //System.out.println("millisecondStart="+millisecondStart); + previousTime = nowTime; + millisecondStart = findMillisecondStart(nowTime, formatted, formatter); } @@ -82,88 +166,150 @@ * -1 indicates no millisecond field, -2 indicates unrecognized * field (likely RelativeTimeDateFormat) */ - private int findMillisecondStart( + public static int findMillisecondStart( final long time, final String formatted, final DateFormat formatter) { - String plus987 = formatter.format(new Date(time + 987)); - - //System.out.println("-formatted="+formatted); - //System.out.println("plus987="+plus987); - // find first difference between values - for (int i = 0; i < formatted.length(); i++) { - if (formatted.charAt(i) != plus987.charAt(i)) { - // if one string has "000" and the other "987" - // we have found the millisecond field - if (i + 3 <= formatted.length() - && "000".equals(formatted.substring(i, i + JVM_MAX_MILLI_DIGITS)) - && "987".equals(plus987.substring(i, i + JVM_MAX_MILLI_DIGITS))) { - return i; - } else { - return BAD_PATTERN; - } - } + + 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()) + && zeroString.regionMatches(0, plusZero, i, zeroString.length())) { + return i; + } else { + return UNRECOGNIZED_MILLISECONDS; + } + } + } } return NO_MILLISECONDS; } /** - * Converts a Date utilizing a previously converted - * value if possible. - - @param date the date to format - @param sbuf the string buffer to write to - @param fieldPosition remains untouched + * 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 */ public StringBuffer format( - Date date, StringBuffer sbuf, FieldPosition fieldPosition) { - - if (millisecondStart == BAD_PATTERN) { - return formatter.format(date, sbuf, fieldPosition); + 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 sbuf the string buffer to write to + */ + public StringBuffer format(long now, + StringBuffer buf ){ + + // + // If an identical request, append the buffer and return. + // + if (now == previousTime) { + buf.append(cache); + return buf; } - long now = date.getTime(); - if ((now < (slotBegin + 1000L)) && (now >= slotBegin)) { - //System.out.println("Using cached val:"+date); - - // If there are NO_MILLISECONDS we don't bother computing the millisecs. - if (millisecondStart >= 0) { - int millis = (int) (now - slotBegin); - int cacheLength = cache.length(); - - milliBuf.setLength(0); - numberFormat.format(millis, milliBuf, fieldPosition); - //System.out.println("milliBuf="+milliBuf); - for(int j = 0; j < JVM_MAX_MILLI_DIGITS; j++) { - cache.setCharAt(millisecondStart+j, milliBuf.charAt(j)); - } - } - } else { - //System.out.println("Refreshing the cache: "+date+","+(date.getTime()%1000)); - slotBegin = (now / 1000L) * 1000L; - int prevLength = cache.length(); - cache.setLength(0); - formatter.format(date, cache, fieldPosition); - - // if the length changed then - // recalculate the millisecond position - if (cache.length() != prevLength && (milliDigits > 0)) { - //System.out.println("Recomputing cached len changed oldLen="+prevLength - // +", newLen="+cache.length()); - // - // format the previous integral second - StringBuffer tempBuffer = new StringBuffer(cache.length()); - slotBeginDate.setTime(slotBegin); - formatter.format(slotBeginDate, tempBuffer, fieldPosition); + // + // If not a recognized millisecond pattern, + // use the wrapped formatter to update the cache + // and append the cache to the buffer. + // + if (millisecondStart != UNRECOGNIZED_MILLISECONDS) { + // - // detect the start of the millisecond field - millisecondStart = findMillisecondStart(slotBegin, - tempBuffer.toString(), - formatter); - } + // 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 + 1000L) { + + // + // if there was a millisecond field then update it + // + if (millisecondStart >= 0 ) { + millisecondFormat((int) (now - slotBegin), cache, millisecondStart); + } + 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; + // + // find the start of the millisecond field again + // if we had previously been able to find it. + if (millisecondStart != UNRECOGNIZED_MILLISECONDS) { + millisecondStart = findMillisecondStart(now, cache.toString(), formatter); } - return sbuf.append(cache); + return buf; } /** + * Formats a count of milliseconds (0-999) into a numeric representation. + * @param millis Millisecond coun 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. + */ + 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. * * @remarks Setting the timezone using getCalendar().setTimeZone() @@ -192,4 +338,5 @@ public NumberFormat getNumberFormat() { return formatter.getNumberFormat(); } + } 1.15 +21 -50 logging-log4j/src/java/org/apache/log4j/pattern/DatePatternConverter.java Index: DatePatternConverter.java =================================================================== RCS file: /home/cvs/logging-log4j/src/java/org/apache/log4j/pattern/DatePatternConverter.java,v retrieving revision 1.14 retrieving revision 1.15 diff -u -r1.14 -r1.15 --- DatePatternConverter.java 23 Dec 2004 22:49:11 -0000 1.14 +++ DatePatternConverter.java 24 Dec 2004 08:28:28 -0000 1.15 @@ -38,13 +38,12 @@ // We assume that each PatternConveter instance is unique within a layout, // which is unique within an appender. We further assume that calls to the // appender method are serialized (per appender). - StringBuffer buf; Logger logger = Logger.getLogger(DatePatternConverter.class); - private DateFormat df; - private Date date; - protected FieldPosition pos = new FieldPosition(0); - long lastTimestamp = 0; - boolean alreadyWarned = false; + private CachedDateFormat df; + private final StringBuffer buf = new StringBuffer(30); + public static final String ISO8601_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS"; + public static final String ABSOLUTE_PATTERN = "HH:mm:ss,SSS"; + public static final String DATE_AND_TIME_PATTERN = "dd MMM yyyy HH:mm:ss,SSS"; // public DatePatternConverter(FormattingInfo formattingInfo) { // super(formattingInfo); @@ -52,8 +51,6 @@ // date = new Date(); // } public DatePatternConverter() { - this.buf = new StringBuffer(32); - date = new Date(); } /** @@ -73,68 +70,42 @@ } String pattern; - if (patternOption == null) { - pattern = "yyyy-MM-dd HH:mm:ss,SSS"; + if (patternOption == null || patternOption.equalsIgnoreCase("ISO8601")) { + pattern = ISO8601_PATTERN; } else if ( - patternOption.equalsIgnoreCase(AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT)) { - pattern = "yyyy-MM-dd HH:mm:ss,SSS"; + patternOption.equalsIgnoreCase("ABSOLUTE")) { + pattern = ABSOLUTE_PATTERN; } else if ( - patternOption.equalsIgnoreCase(AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT)) { - pattern = "HH:mm:ss,SSS"; - } else if ( - patternOption.equalsIgnoreCase( - AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT)) { - pattern = "dd MMM yyyy HH:mm:ss,SSS"; + patternOption.equalsIgnoreCase("DATE")) { + pattern = DATE_AND_TIME_PATTERN; } else { pattern = patternOption; } + SimpleDateFormat simpleFormat = null; try { - df = new SimpleDateFormat(pattern); + simpleFormat = new SimpleDateFormat(pattern); } catch (IllegalArgumentException e) { logger.warn( "Could not instantiate SimpleDateFormat with pattern " + patternOption, e); - // detault for the ISO8601 format - pattern = "yyyy-MM-dd HH:mm:ss,SSS"; - df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS"); + // default to the ISO8601 format + simpleFormat = new SimpleDateFormat(ISO8601_PATTERN); } // if the option list contains a TZ option, then set it. if (optionList != null && optionList.size() > 1) { TimeZone tz = TimeZone.getTimeZone((String) optionList.get(1)); - df.setTimeZone(tz); + simpleFormat.setTimeZone(tz); } - - // if we can cache, so much the better - if(CacheUtil.isPatternSafeForCaching(pattern)) { - df = new CachedDateFormat(df); - } + + df = new CachedDateFormat(simpleFormat, 1000); } public StringBuffer convert(LoggingEvent event) { - long timestamp = event.getTimeStamp(); - // if called multiple times within the same milliseconds - // return old value - if(timestamp == lastTimestamp) { - return buf; - } else { - buf.setLength(0); - lastTimestamp = timestamp; - date.setTime(timestamp); - try { - df.format(date, buf, pos); - lastTimestamp = timestamp; - } catch (Exception ex) { - // this should never happen - buf.append("DATE_CONV_ERROR"); - if(!alreadyWarned) { - alreadyWarned = true; - logger.error("Exception while converting date", ex); - } - } - return buf; - } + buf.setLength(0); + df.format(event.getTimeStamp(), buf); + return buf; } public String getName() {
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]