+++ /dev/null
-/*
- * 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;
- }
-}