You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-dev@logging.apache.org by ce...@apache.org on 2004/12/23 13:16:22 UTC

cvs commit: logging-log4j/tests/src/java/org/apache/log4j/pattern CachedDateFormatTest.java

ceki        2004/12/23 04:16:22

  Modified:    tests    build.xml
  Added:       src/java/org/apache/log4j/pattern CachedDateFormat.java
                        CacheUtil.java
               tests/src/java/org/apache/log4j/pattern
                        CachedDateFormatTest.java
  Removed:     src/java/org/apache/log4j/helpers CachedDateFormat.java
               tests/src/java/org/apache/log4j/helpers
                        CachedDateFormatTestCase.java
  Log:
  - Moved CachedDateFormat to o.a.l.pattern
  - Moved o.a.l.helpers.CachedDateFormatTestCase to o.a.l.pattern, renamed it as CachedDateFormatTest
  
  Revision  Changes    Path
  1.79      +1 -1      logging-log4j/tests/build.xml
  
  Index: build.xml
  ===================================================================
  RCS file: /home/cvs/logging-log4j/tests/build.xml,v
  retrieving revision 1.78
  retrieving revision 1.79
  diff -u -r1.78 -r1.79
  --- build.xml	22 Dec 2004 20:29:01 -0000	1.78
  +++ build.xml	23 Dec 2004 12:16:22 -0000	1.79
  @@ -627,7 +627,7 @@
   	<junit printsummary="yes" fork="yes" haltonfailure="yes">
   	  <classpath refid="tests.classpath"/>
   	  <formatter type="plain" usefile="false"/>
  -	  <test name="org.apache.log4j.helpers.CachedDateFormatTestCase" />
  +	  <test name="org.apache.log4j.pattern.CachedDateFormatTest" />
       </junit>
     </target>
   	    
  
  
  
  1.1                  logging-log4j/src/java/org/apache/log4j/pattern/CachedDateFormat.java
  
  Index: CachedDateFormat.java
  ===================================================================
  /*
   * Copyright 1999,2004 The Apache Software Foundation.
   *
   * Licensed 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.util.Date;
  import java.text.FieldPosition;
  import java.text.ParsePosition;
  import java.text.DateFormat;
  import java.text.NumberFormat;
  import java.util.TimeZone;
  
  /**
   * Caches the results of a DateFormat.
   *  @author Curt Arnold
   *  @since 1.3
   */
  final class CachedDateFormat extends DateFormat {
    private DateFormat formatter;
    private int millisecondStart;
    private StringBuffer cache = new StringBuffer();
    private long previousTime;
    private NumberFormat numberFormat;
    private static final int UNRECOGNIZED_MILLISECOND_PATTERN = -2;
    private static final int NO_MILLISECOND_PATTERN = -1;
  
  
    
  
    
    public CachedDateFormat(final DateFormat formatter) {
      if (formatter == null) {
        throw new NullPointerException("formatter");
      }
      this.formatter = formatter;
      numberFormat = formatter.getNumberFormat();
      if (numberFormat == null) {
        throw new NullPointerException("numberFormat");
      }
      Date now = new Date();
      long nowTime = now.getTime();
      previousTime = (nowTime / 1000L) * 1000L;
  
      Date lastSecond = new Date(previousTime);
      String formatted = formatter.format(lastSecond);
      cache.append(formatted);
      millisecondStart = findMillisecondStart(previousTime, formatted,
                                              formatter);
    }
  
    /**
     * 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)
     */
    private static int findMillisecondStart(final long time,
                                            final String formatted,
                                            final DateFormat formatter) {
        String plus987 = formatter.format(new Date(time + 987));
        //
        //    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() 
                && formatted.substring(i, i + 3) == "000" 
                && plus987.substring(i, i + 3) == "987") {
              return i;
            } else {
              return UNRECOGNIZED_MILLISECOND_PATTERN;
            }
          }
        }
      return NO_MILLISECOND_PATTERN;
  }
  
  
    /**
     * 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
     */
    public
        StringBuffer format(Date date, StringBuffer sbuf,
                            FieldPosition fieldPosition) {
  
      if (millisecondStart == UNRECOGNIZED_MILLISECOND_PATTERN) {
        return formatter.format(date, sbuf, fieldPosition);
      }
      long now = date.getTime();
      if (now < previousTime + 1000L && now >= previousTime) {
        if (millisecondStart >= 0) {
          cache.delete(millisecondStart, millisecondStart + 3);
          int millis = (int) (now - previousTime);
          int cacheLength = cache.length();
          //
          //   append milliseconds to the end of the cache
          numberFormat.format(millis, cache, fieldPosition);
          int milliLength = cache.length() - cacheLength;
          //
          //   if it didn't belong at the end, then move it
          if (cacheLength != millisecondStart) {
            String milli = cache.substring(cacheLength);
            cache.setLength(cacheLength);
            cache.insert(millisecondStart, milli);
          }
          for (int i = milliLength; i < 3; i++) {
            cache.insert(millisecondStart, "0");
          }
        }
        sbuf.append(cache);
      } else {
        previousTime = (now / 1000L) * 1000L;
        //
        //   if earlier than 1970 and rounded toward 1970
        //      then move back one second
        if (now - previousTime < 0) {
          previousTime -= 1000;
        }
        cache.setLength(0);
        formatter.format(new Date(previousTime), cache, fieldPosition);
        millisecondStart = findMillisecondStart(previousTime, cache.toString(), formatter);
        //
        //  calling ourself should be safe and faster
        //     but why risk it
        formatter.format(date, sbuf, fieldPosition);
      }
      return sbuf;
    }
  
  
    /**
     * Set timezone.
     *
     * @remarks 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);
      int prevLength = cache.length();
      cache.setLength(0);
      cache.append(formatter.format(new Date(previousTime)));
      millisecondStart = findMillisecondStart(previousTime,
                                                cache.toString(),
                                                formatter);
    }
  
    /**
       This method is delegated to the formatter which most
       likely returns 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();
    }
  }
  
  
  
  1.1                  logging-log4j/src/java/org/apache/log4j/pattern/CacheUtil.java
  
  Index: CacheUtil.java
  ===================================================================
  /*
   * Copyright 1999,2004 The Apache Software Foundation.
   *
   * Licensed 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;
  
  
  /**
   * Utility methods to detect patterns unsafe for caching by {@ CacheDateFormat},
   * and other utility methods.
   * 
   * @author Ceki Gulcu
   */
  public class CacheUtil {
    private static final int REGULAR_STATE = 0;
    private static final int IN_QUOTE_STATE = 1;
    
    /**
     * Remove all literal text from the pattern, return only the letters a-z or 
     * A-Z placed ouside quotes.
     * @param pattern
     * @return
     */
    static String removeLiterals(String pattern) {
      StringBuffer pbuf = new StringBuffer(pattern.length());
      int state = REGULAR_STATE;
      for(int i = 0; i < pattern.length(); i++) {
        char c = pattern.charAt(i);
        switch(state) {
        case REGULAR_STATE:
          if(c == '\'') {
            state = IN_QUOTE_STATE;
          } else if( (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
            pbuf.append(c);
          }
        case IN_QUOTE_STATE:
          if(c == '\'') {
            state = REGULAR_STATE;
          }
        }
      }
      return pbuf.toString();
    }
    
    /**
     * Pattern unsafe only in case both the long text form of E (4 or more 
     * successive Es) and the long text form of M (4 or more successive Ms) are 
     * present. 
     * 
     * Another uncacheable pattern is that of disjoint Ss, e.g. "YYYY-MM SSE E SSS"
     * a non-sensical pattern, but unsafe nonetheless.
     */
    static boolean isPatternSafeForCaching(String pattern) {
      // this code assumes that literals have been removed from the pattern
      if(pattern.indexOf("EEEE") != -1 && pattern.indexOf("MMMM") != -1) {
        return false;
      }
      if(disjointS(pattern)) {
        return false;
      }
      return true;
    }  
    
    int computeSuccessiveS(String pattern) {
      // this code assumes that literals have been removed from the pattern
      int len = pattern.length();
      int i = pattern.indexOf('S');
      
      if(i != -1)
        return 0;
      
      int count = 1;
      while(i < len && pattern.charAt(i++) == 'S') {
        count++;
      }
      return count;
    }
    
    /**
     * Are there any disjoint S in the pattern? Examples of disjointS:
     * <p>YYYY SSS EE SSS
     * <p>SSS EE SSSS
     * 
     * @param pattern
     * @return
     */
    static boolean disjointS(String pattern) {
      int len = pattern.length();
      int i = pattern.indexOf('S');
      
      if(i != -1)
        return false;
      
      // skip any  ajoining S
      while(i < len && pattern.charAt(i++) == 'S') {
      }
      
      // i now points to a character different than 'S'
      // the first possible occurence of S must come after i, hence the i++;
      i++;
      if(i >= len )
        return false;
      else {
        // if there are other S present, then we are in the presence of
        // disjoint S
        return pattern.indexOf('S', i) != 1;
      }
    }
  }
  
  
  
  1.1                  logging-log4j/tests/src/java/org/apache/log4j/pattern/CachedDateFormatTest.java
  
  Index: CachedDateFormatTest.java
  ===================================================================
  /*
   * Copyright 2004 The Apache Software Foundation.
   *
   * Licensed 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 junit.framework.TestCase;
  import org.apache.log4j.helpers.AbsoluteTimeDateFormat;
  import org.apache.log4j.pattern.CachedDateFormat;
  
  import java.text.DateFormat;
  import java.util.TimeZone;
  import java.util.Date;
  import java.text.SimpleDateFormat;
  import java.util.Locale;
  import java.util.Calendar;
  
  /**
     Unit test {@link AbsoluteTimeDateFormat}.
     @author Curt Arnold
     @since 1.3.0 */
  public final class CachedDateFormatTest
      extends TestCase {
  
    /**
     * Test constructor
     * @param name String test name
     */
    public CachedDateFormatTest(String name) {
      super(name);
    }
  
    /**
     * Asserts that formatting the provided date results
     * in the expected string.
     *
     * @param date Date date
     * @param timeZone TimeZone timezone for conversion
     * @param expected String expected string
     */
    private final void assertFormattedTime(Date date,
                                           TimeZone timeZone,
                                           String expected) {
      DateFormat formatter = new AbsoluteTimeDateFormat(timeZone);
      String actual = formatter.format(date);
      assertEquals(expected, actual);
    }
  
    /**
     * Timezone representing GMT.
     */
    private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
  
    /**
     * Timezone for Chicago, Ill. USA.
     */
    private static final TimeZone CHICAGO = TimeZone.getTimeZone(
        "America/Chicago");
  
    /**
     * Test multiple calls in close intervals.
     */
    public void test1() {
      //   subsequent calls within one minute
      //     are optimized to reuse previous formatted value
      //     make a couple of nearly spaced calls
      DateFormat gmtFormat = new CachedDateFormat(new AbsoluteTimeDateFormat(GMT));
      long ticks = 12601L * 86400000L;
      Date jul1 = new Date(ticks);
      assertEquals("00:00:00,000", gmtFormat.format(jul1));
      Date plus8ms = new Date(ticks + 8);
      assertEquals("00:00:00,008", gmtFormat.format(plus8ms));
      Date plus17ms = new Date(ticks + 17);
      assertEquals("00:00:00,017", gmtFormat.format(plus17ms));
      Date plus237ms = new Date(ticks + 237);
      assertEquals("00:00:00,237", gmtFormat.format(plus237ms));
      Date plus1415ms = new Date(ticks + 1415);
      assertEquals("00:00:01,415", gmtFormat.format(plus1415ms));
    }
  
    /**
     *  Check for interaction between caches.
     */
  
    public void test2() {
        Date jul2 = new Date(12602L * 86400000L);
        DateFormat gmtFormat = new CachedDateFormat(new AbsoluteTimeDateFormat(GMT));
        DateFormat chicagoFormat = new CachedDateFormat(new AbsoluteTimeDateFormat(CHICAGO));
        assertEquals("00:00:00,000", gmtFormat.format(jul2));
        assertEquals("19:00:00,000", chicagoFormat.format(jul2));
        assertEquals("00:00:00,000", gmtFormat.format(jul2));
    }
  
    /**
     * Test multiple calls in close intervals prior to 1 Jan 1970.
     */
    public void test3() {
      //   subsequent calls within one minute
      //     are optimized to reuse previous formatted value
      //     make a couple of nearly spaced calls
      DateFormat gmtFormat = new CachedDateFormat(
         new AbsoluteTimeDateFormat(GMT));
      long ticks = -7L * 86400000L;
      Date jul1 = new Date(ticks);
      assertEquals("00:00:00,000", gmtFormat.format(jul1));
      Date plus8ms = new Date(ticks + 8);
      assertEquals("00:00:00,008", gmtFormat.format(plus8ms));
      Date plus17ms = new Date(ticks + 17);
      assertEquals("00:00:00,017", gmtFormat.format(plus17ms));
      Date plus237ms = new Date(ticks + 237);
      assertEquals("00:00:00,237", gmtFormat.format(plus237ms));
      Date plus1415ms = new Date(ticks + 1415);
      assertEquals("00:00:01,415", gmtFormat.format(plus1415ms));
    }
  
    public void test4() {
      //   subsequent calls within one minute
      //     are optimized to reuse previous formatted value
      //     make a couple of nearly spaced calls
      SimpleDateFormat baseFormat =
           new SimpleDateFormat("EEE, MMM dd, HH:mm:ss.SSS Z", Locale.ENGLISH);
      DateFormat cachedFormat = new CachedDateFormat(baseFormat);
      //
      //   use a date in 2000 to attempt to confuse the millisecond locator
      long ticks = 11141L * 86400000L;
      Date jul1 = new Date(ticks);
      assertEquals(baseFormat.format(jul1), cachedFormat.format(jul1));
      Date plus8ms = new Date(ticks + 8);
      String base = baseFormat.format(plus8ms);
      String cached = cachedFormat.format(plus8ms);
      assertEquals(baseFormat.format(plus8ms), cachedFormat.format(plus8ms));
      Date plus17ms = new Date(ticks + 17);
      assertEquals(baseFormat.format(plus17ms), cachedFormat.format(plus17ms));
      Date plus237ms = new Date(ticks + 237);
      assertEquals(baseFormat.format(plus237ms), cachedFormat.format(plus237ms));
      Date plus1415ms = new Date(ticks + 1415);
      assertEquals(baseFormat.format(plus1415ms), cachedFormat.format(plus1415ms));
    }
  
    public void test5() {
      //   subsequent calls within one minute
      //     are optimized to reuse previous formatted value
      //     make a couple of nearly spaced calls
      Locale thai = new Locale("th");
      SimpleDateFormat baseFormat =
           new SimpleDateFormat("EEE, MMM dd, HH:mm:ss.SSS Z", thai);
      DateFormat cachedFormat = new CachedDateFormat(baseFormat);
      //
      //   use a date in 2000 to attempt to confuse the millisecond locator
      long ticks = 11141L * 86400000L;
      Date jul1 = new Date(ticks);
      assertEquals(baseFormat.format(jul1), cachedFormat.format(jul1));
      Date plus8ms = new Date(ticks + 8);
      assertEquals(baseFormat.format(plus8ms), cachedFormat.format(plus8ms));
      Date plus17ms = new Date(ticks + 17);
      assertEquals(baseFormat.format(plus17ms), cachedFormat.format(plus17ms));
      Date plus237ms = new Date(ticks + 237);
      assertEquals(baseFormat.format(plus237ms), cachedFormat.format(plus237ms));
      Date plus1415ms = new Date(ticks + 1415);
      assertEquals(baseFormat.format(plus1415ms), cachedFormat.format(plus1415ms));
    }
  
    /**
     * Checks that getNumberFormat does not return null.
     */
    public void test6() {
      assertNotNull(new CachedDateFormat(new SimpleDateFormat()).getNumberFormat());
    }
  
    /**
     * Attempt to cache a RelativeTimeDateFormat which isn't compatible
     * with caching.  Should just delegate to the RelativeTimeDateFormat.
     */
  //  public void test7() {
  //    DateFormat baseFormat = new RelativeTimeDateFormat();
  //    DateFormat cachedFormat = new CachedDateFormat(baseFormat);
  //    long ticks = 12603L * 86400000L;
  //    Date jul3 = new Date(ticks);
  //    assertEquals(baseFormat.format(jul3), cachedFormat.format(jul3));
  //    Date plus8ms = new Date(ticks + 8);
  //    assertEquals(baseFormat.format(plus8ms), cachedFormat.format(plus8ms));
  //    Date plus17ms = new Date(ticks + 17);
  //    assertEquals(baseFormat.format(plus17ms), cachedFormat.format(plus17ms));
  //    Date plus237ms = new Date(ticks + 237);
  //    assertEquals(baseFormat.format(plus237ms), cachedFormat.format(plus237ms));
  //    Date plus1415ms = new Date(ticks + 1415);
  //    assertEquals(baseFormat.format(plus1415ms), cachedFormat.format(plus1415ms));
  //  }
  
    /**
     * Set time zone on cached and check that it is effective.
     */
    public void test8() {
      DateFormat baseFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
      DateFormat cachedFormat = new CachedDateFormat(baseFormat);
      cachedFormat.setTimeZone(TimeZone.getTimeZone("GMT-6"));
      Date jul4 = new Date(12603L * 86400000L);
      assertEquals("2004-07-03 18:00:00,000", cachedFormat.format(jul4));
    }
  
  
    /**
     * Test of caching when less than three millisecond digits are specified.
     */
    public void test9() {
      DateFormat baseFormat = new SimpleDateFormat("yyyy-MMMM-dd HH:mm:ss,SS Z", Locale.US);
      DateFormat cachedFormat = new CachedDateFormat(baseFormat);
      TimeZone cet = TimeZone.getTimeZone("GMT+1");
      cachedFormat.setTimeZone(cet);
      
      Calendar c = Calendar.getInstance();
      c.set(2004, Calendar.DECEMBER, 12, 20, 0);
      c.set(Calendar.SECOND, 37);
      c.set(Calendar.MILLISECOND, 23);
      c.setTimeZone(cet);
  
      String s = cachedFormat.format(c.getTime());
      assertEquals("2004-December-12 20:00:37,23 +0100", s);
  
      c.set(2005, Calendar.JANUARY, 1, 0, 0);
      c.set(Calendar.SECOND, 13);
      c.set(Calendar.MILLISECOND, 905);
  
      s = cachedFormat.format(c.getTime());
      assertEquals("2005-January-01 00:00:13,905 +0100", s);
    }
    
  
    /**
     * Test when millisecond position moves but length remains constant.
     */
    public void test10() {
      DateFormat baseFormat = new SimpleDateFormat("MMMM SSS EEEEEE", Locale.US);
      DateFormat cachedFormat = new CachedDateFormat(baseFormat);
      TimeZone cet = TimeZone.getTimeZone("GMT+1");
      cachedFormat.setTimeZone(cet);
      
      Calendar c = Calendar.getInstance();
      c.set(2004, Calendar.OCTOBER, 5, 20, 0);
      c.set(Calendar.SECOND, 37);
      c.set(Calendar.MILLISECOND, 23);
      c.setTimeZone(cet);
  
      String s = cachedFormat.format(c.getTime());
      assertEquals("October 023 Tuesday", s);
  
      c.set(2004, Calendar.NOVEMBER, 1, 0, 0);
      c.set(Calendar.MILLISECOND, 23);
      s = cachedFormat.format(c.getTime());
      assertEquals("November 023 Monday", s);
  
  
      c.set(Calendar.MILLISECOND, 984);
      s = cachedFormat.format(c.getTime());
      assertEquals("November 984 Monday", s);
    }
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
For additional commands, e-mail: log4j-dev-help@logging.apache.org