You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2015/07/09 06:57:36 UTC
[2/3] logging-log4j2 git commit: LOG4J2-812 - Improve performance of
date formatting in multi-threaded systems
LOG4J2-812 - Improve performance of date formatting in multi-threaded systems
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/051f7019
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/051f7019
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/051f7019
Branch: refs/heads/master
Commit: 051f701907497633504a7079f0bf16189ab61c22
Parents: 483d2ca
Author: Ralph Goers <rg...@nextiva.com>
Authored: Wed Jul 8 21:51:00 2015 -0700
Committer: Ralph Goers <rg...@nextiva.com>
Committed: Wed Jul 8 21:51:00 2015 -0700
----------------------------------------------------------------------
.../core/pattern/DatePatternConverter.java | 61 +-
.../log4j/core/util/datetime/DateParser.java | 102 ++
.../log4j/core/util/datetime/DatePrinter.java | 127 ++
.../core/util/datetime/FastDateFormat.java | 578 ++++++++
.../core/util/datetime/FastDateParser.java | 926 ++++++++++++
.../core/util/datetime/FastDatePrinter.java | 1330 ++++++++++++++++++
.../log4j/core/util/datetime/Format.java | 28 +
.../log4j/core/util/datetime/FormatCache.java | 265 ++++
.../perf/jmh/SimpleDateFormatBenchmark.java | 88 +-
9 files changed, 3482 insertions(+), 23 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/051f7019/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
index d8a6467..03e954e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
@@ -19,9 +19,11 @@ package org.apache.logging.log4j.core.pattern;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.util.datetime.FastDateFormat;
/**
* Converts and formats the event's date in a StringBuilder.
@@ -30,6 +32,18 @@ import org.apache.logging.log4j.core.config.plugins.Plugin;
@ConverterKeys({ "d", "date" })
public final class DatePatternConverter extends LogEventPatternConverter implements ArrayPatternConverter {
+ private class CurrentTime {
+ public long timestamp;
+ public String formatted;
+
+ public CurrentTime(long timestamp) {
+ this.timestamp = timestamp;
+ this.formatted = formatter.format(this.timestamp);
+ }
+ }
+
+ private AtomicReference<CurrentTime> currentTime;
+
private abstract static class Formatter {
abstract String format(long time);
@@ -39,20 +53,20 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
}
private static class PatternFormatter extends Formatter {
- private final SimpleDateFormat simpleDateFormat;
+ private final FastDateFormat fastDateFormat;
- PatternFormatter(final SimpleDateFormat simpleDateFormat) {
- this.simpleDateFormat = simpleDateFormat;
+ PatternFormatter(final FastDateFormat fastDateFormat) {
+ this.fastDateFormat = fastDateFormat;
}
@Override
String format(final long time) {
- return simpleDateFormat.format(Long.valueOf(time));
+ return fastDateFormat.format(time);
}
@Override
public String toPattern() {
- return simpleDateFormat.toPattern();
+ return fastDateFormat.toPattern();
}
}
@@ -203,25 +217,29 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
}
if (pattern != null) {
- SimpleDateFormat tempFormat;
+ FastDateFormat tempFormat;
+
+ TimeZone tz = null;
+
+ // if the option list contains a TZ option, then set it.
+ if (options != null && options.length > 1) {
+ tz = TimeZone.getTimeZone(options[1]);
+ }
try {
- tempFormat = new SimpleDateFormat(pattern);
+ tempFormat = FastDateFormat.getInstance(pattern, tz);
} catch (final IllegalArgumentException e) {
LOGGER.warn("Could not instantiate SimpleDateFormat with pattern " + patternOption, e);
// default to the DEFAULT format
- tempFormat = new SimpleDateFormat(DEFAULT_PATTERN);
+ tempFormat = FastDateFormat.getInstance(DEFAULT_PATTERN);
}
- // if the option list contains a TZ option, then set it.
- if (options != null && options.length > 1) {
- final TimeZone tz = TimeZone.getTimeZone(options[1]);
- tempFormat.setTimeZone(tz);
- }
+
tempFormatter = new PatternFormatter(tempFormat);
}
formatter = tempFormatter;
+ currentTime = new AtomicReference<>(new CurrentTime(System.currentTimeMillis()));
}
/**
@@ -233,9 +251,7 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
* buffer to which formatted date is appended.
*/
public void format(final Date date, final StringBuilder toAppendTo) {
- synchronized (this) {
toAppendTo.append(formatter.format(date.getTime()));
- }
}
/**
@@ -244,14 +260,17 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
@Override
public void format(final LogEvent event, final StringBuilder output) {
final long timestamp = event.getTimeMillis();
-
- synchronized (this) {
- if (timestamp != lastTimestamp) {
- lastTimestamp = timestamp;
- cachedDateString = formatter.format(timestamp);
+ CurrentTime current = currentTime.get();
+ if (timestamp != current.timestamp) {
+ final CurrentTime newTime = new CurrentTime(timestamp);
+ if (currentTime.compareAndSet(current, newTime)) {
+ current = newTime;
+ } else {
+ current = currentTime.get();
}
}
- output.append(cachedDateString);
+
+ output.append(current.formatted);
}
/**
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/051f7019/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DateParser.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DateParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DateParser.java
new file mode 100644
index 0000000..666ef81
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DateParser.java
@@ -0,0 +1,102 @@
+/*
+ * 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.logging.log4j.core.util.datetime;
+
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Copied from Commons Lang 3
+ */
+public interface DateParser {
+
+ /**
+ * Equivalent to DateFormat.parse(String).
+ *
+ * See {@link java.text.DateFormat#parse(String)} for more information.
+ * @param source A <code>String</code> whose beginning should be parsed.
+ * @return A <code>Date</code> parsed from the string
+ * @throws ParseException if the beginning of the specified string cannot be parsed.
+ */
+ Date parse(String source) throws ParseException;
+
+ /**
+ * Equivalent to DateFormat.parse(String, ParsePosition).
+ *
+ * See {@link java.text.DateFormat#parse(String, ParsePosition)} for more information.
+ *
+ * @param source A <code>String</code>, part of which should be parsed.
+ * @param pos A <code>ParsePosition</code> object with index and error index information
+ * as described above.
+ * @return A <code>Date</code> parsed from the string. In case of error, returns null.
+ * @throws NullPointerException if text or pos is null.
+ */
+ Date parse(String source, ParsePosition pos);
+
+ // Accessors
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Get the pattern used by this parser.</p>
+ *
+ * @return the pattern, {@link java.text.SimpleDateFormat} compatible
+ */
+ String getPattern();
+
+ /**
+ * <p>
+ * Get the time zone used by this parser.
+ * </p>
+ *
+ * <p>
+ * The default {@link TimeZone} used to create a {@link Date} when the {@link TimeZone} is not specified by
+ * the format pattern.
+ * </p>
+ *
+ * @return the time zone
+ */
+ TimeZone getTimeZone();
+
+ /**
+ * <p>Get the locale used by this parser.</p>
+ *
+ * @return the locale
+ */
+ Locale getLocale();
+
+ /**
+ * Parses text from a string to produce a Date.
+ *
+ * @param source A <code>String</code> whose beginning should be parsed.
+ * @return a <code>java.util.Date</code> object
+ * @throws ParseException if the beginning of the specified string cannot be parsed.
+ * @see java.text.DateFormat#parseObject(String)
+ */
+ Object parseObject(String source) throws ParseException;
+
+ /**
+ * Parse a date/time string according to the given parse position.
+ *
+ * @param source A <code>String</code> whose beginning should be parsed.
+ * @param pos the parse position
+ * @return a <code>java.util.Date</code> object
+ * @see java.text.DateFormat#parseObject(String, ParsePosition)
+ */
+ Object parseObject(String source, ParsePosition pos);
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/051f7019/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DatePrinter.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DatePrinter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DatePrinter.java
new file mode 100644
index 0000000..e82d9a6
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DatePrinter.java
@@ -0,0 +1,127 @@
+/*
+ * 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.logging.log4j.core.util.datetime;
+
+import java.text.FieldPosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Copied from Commons Lang 3
+ */
+public interface DatePrinter {
+
+ /**
+ * <p>Formats a millisecond {@code long} value.</p>
+ *
+ * @param millis the millisecond value to format
+ * @return the formatted string
+ * @since 2.1
+ */
+ String format(long millis);
+
+ /**
+ * <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
+ *
+ * @param date the date to format
+ * @return the formatted string
+ */
+ String format(Date date);
+
+ /**
+ * <p>Formats a {@code Calendar} object.</p>
+ * The TimeZone set on the Calendar is only used to adjust the time offset.
+ * The TimeZone specified during the construction of the Parser will determine the TimeZone
+ * used in the formatted string.
+ *
+ * @param calendar the calendar to format.
+ * @return the formatted string
+ */
+ String format(Calendar calendar);
+
+ /**
+ * <p>Formats a milliseond {@code long} value into the
+ * supplied {@code StringBuilder}.</p>
+ *
+ * @param millis the millisecond value to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ */
+ StringBuilder format(long millis, StringBuilder buf);
+
+ /**
+ * <p>Formats a {@code Date} object into the
+ * supplied {@code StringBuilder} using a {@code GregorianCalendar}.</p>
+ *
+ * @param date the date to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ */
+ StringBuilder format(Date date, StringBuilder buf);
+
+ /**
+ * <p>Formats a {@code Calendar} object into the supplied {@code StringBuilder}.</p>
+ * The TimeZone set on the Calendar is only used to adjust the time offset.
+ * The TimeZone specified during the construction of the Parser will determine the TimeZone
+ * used in the formatted string.
+ *
+ * @param calendar the calendar to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ */
+ StringBuilder format(Calendar calendar, StringBuilder buf);
+
+ // Accessors
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets the pattern used by this printer.</p>
+ *
+ * @return the pattern, {@link java.text.SimpleDateFormat} compatible
+ */
+ String getPattern();
+
+ /**
+ * <p>Gets the time zone used by this printer.</p>
+ *
+ * <p>This zone is always used for {@code Date} printing. </p>
+ *
+ * @return the time zone
+ */
+ TimeZone getTimeZone();
+
+ /**
+ * <p>Gets the locale used by this printer.</p>
+ *
+ * @return the locale
+ */
+ Locale getLocale();
+
+ /**
+ * <p>Formats a {@code Date}, {@code Calendar} or
+ * {@code Long} (milliseconds) object.</p>
+ *
+ * See {@link java.text.DateFormat#format(Object, StringBuilder, FieldPosition)}
+ *
+ * @param obj the object to format
+ * @param toAppendTo the buffer to append to
+ * @param pos the position - ignored
+ * @return the buffer passed in
+ */
+ StringBuilder format(Object obj, StringBuilder toAppendTo, FieldPosition pos);
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/051f7019/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java
new file mode 100644
index 0000000..888014f
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java
@@ -0,0 +1,578 @@
+/*
+ * 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.logging.log4j.core.util.datetime;
+
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * This is a copy of Commons Lang's Fast Date Formatter.
+ */
+public class FastDateFormat extends Format implements DatePrinter, DateParser {
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 2L;
+
+ /**
+ * FULL locale dependent date or time style.
+ */
+ public static final int FULL = DateFormat.FULL;
+ /**
+ * LONG locale dependent date or time style.
+ */
+ public static final int LONG = DateFormat.LONG;
+ /**
+ * MEDIUM locale dependent date or time style.
+ */
+ public static final int MEDIUM = DateFormat.MEDIUM;
+ /**
+ * SHORT locale dependent date or time style.
+ */
+ public static final int SHORT = DateFormat.SHORT;
+
+ private static final FormatCache<FastDateFormat> cache= new FormatCache<FastDateFormat>() {
+ @Override
+ protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
+ return new FastDateFormat(pattern, timeZone, locale);
+ }
+ };
+
+ private final FastDatePrinter printer;
+ private final FastDateParser parser;
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets a formatter instance using the default pattern in the
+ * default locale.</p>
+ *
+ * @return a date/time formatter
+ */
+ public static FastDateFormat getInstance() {
+ return cache.getInstance();
+ }
+
+ /**
+ * <p>Gets a formatter instance using the specified pattern in the
+ * default locale.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @return a pattern based date/time formatter
+ * @throws IllegalArgumentException if pattern is invalid
+ */
+ public static FastDateFormat getInstance(final String pattern) {
+ return cache.getInstance(pattern, null, null);
+ }
+
+ /**
+ * <p>Gets a formatter instance using the specified pattern and
+ * time zone.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @return a pattern based date/time formatter
+ * @throws IllegalArgumentException if pattern is invalid
+ */
+ public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone) {
+ return cache.getInstance(pattern, timeZone, null);
+ }
+
+ /**
+ * <p>Gets a formatter instance using the specified pattern and
+ * locale.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @param locale optional locale, overrides system locale
+ * @return a pattern based date/time formatter
+ * @throws IllegalArgumentException if pattern is invalid
+ */
+ public static FastDateFormat getInstance(final String pattern, final Locale locale) {
+ return cache.getInstance(pattern, null, locale);
+ }
+
+ /**
+ * <p>Gets a formatter instance using the specified pattern, time zone
+ * and locale.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @param locale optional locale, overrides system locale
+ * @return a pattern based date/time formatter
+ * @throws IllegalArgumentException if pattern is invalid
+ * or {@code null}
+ */
+ public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
+ return cache.getInstance(pattern, timeZone, locale);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets a date formatter instance using the specified style in the
+ * default time zone and locale.</p>
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateInstance(final int style) {
+ return cache.getDateInstance(style, null, null);
+ }
+
+ /**
+ * <p>Gets a date formatter instance using the specified style and
+ * locale in the default time zone.</p>
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateInstance(final int style, final Locale locale) {
+ return cache.getDateInstance(style, null, locale);
+ }
+
+ /**
+ * <p>Gets a date formatter instance using the specified style and
+ * time zone in the default locale.</p>
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) {
+ return cache.getDateInstance(style, timeZone, null);
+ }
+
+ /**
+ * <p>Gets a date formatter instance using the specified style, time
+ * zone and locale.</p>
+ *
+ * @param style date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard date formatter
+ * @throws IllegalArgumentException if the Locale has no date
+ * pattern defined
+ */
+ public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone, final Locale locale) {
+ return cache.getDateInstance(style, timeZone, locale);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets a time formatter instance using the specified style in the
+ * default time zone and locale.</p>
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getTimeInstance(final int style) {
+ return cache.getTimeInstance(style, null, null);
+ }
+
+ /**
+ * <p>Gets a time formatter instance using the specified style and
+ * locale in the default time zone.</p>
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getTimeInstance(final int style, final Locale locale) {
+ return cache.getTimeInstance(style, null, locale);
+ }
+
+ /**
+ * <p>Gets a time formatter instance using the specified style and
+ * time zone in the default locale.</p>
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted time
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone) {
+ return cache.getTimeInstance(style, timeZone, null);
+ }
+
+ /**
+ * <p>Gets a time formatter instance using the specified style, time
+ * zone and locale.</p>
+ *
+ * @param style time style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted time
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard time formatter
+ * @throws IllegalArgumentException if the Locale has no time
+ * pattern defined
+ */
+ public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone, final Locale locale) {
+ return cache.getTimeInstance(style, timeZone, locale);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets a date/time formatter instance using the specified style
+ * in the default time zone and locale.</p>
+ *
+ * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
+ * @return a localized standard date/time formatter
+ * @throws IllegalArgumentException if the Locale has no date/time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle) {
+ return cache.getDateTimeInstance(dateStyle, timeStyle, null, null);
+ }
+
+ /**
+ * <p>Gets a date/time formatter instance using the specified style and
+ * locale in the default time zone.</p>
+ *
+ * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard date/time formatter
+ * @throws IllegalArgumentException if the Locale has no date/time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final Locale locale) {
+ return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale);
+ }
+
+ /**
+ * <p>Gets a date/time formatter instance using the specified style and
+ * time zone in the default locale.</p>
+ *
+ * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @return a localized standard date/time formatter
+ * @throws IllegalArgumentException if the Locale has no date/time
+ * pattern defined
+ * @since 2.1
+ */
+ public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone) {
+ return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
+ }
+ /**
+ * <p>Gets a date/time formatter instance using the specified style,
+ * time zone and locale.</p>
+ *
+ * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
+ * @param timeZone optional time zone, overrides time zone of
+ * formatted date
+ * @param locale optional locale, overrides system locale
+ * @return a localized standard date/time formatter
+ * @throws IllegalArgumentException if the Locale has no date/time
+ * pattern defined
+ */
+ public static FastDateFormat getDateTimeInstance(
+ final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
+ return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
+ }
+
+ // Constructor
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Constructs a new FastDateFormat.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
+ * @param timeZone non-null time zone to use
+ * @param locale non-null locale to use
+ * @throws NullPointerException if pattern, timeZone, or locale is null.
+ */
+ protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) {
+ this(pattern, timeZone, locale, null);
+ }
+
+ // Constructor
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Constructs a new FastDateFormat.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
+ * @param timeZone non-null time zone to use
+ * @param locale non-null locale to use
+ * @param centuryStart The start of the 100 year period to use as the "default century" for 2 digit year parsing. If centuryStart is null, defaults to now - 80 years
+ * @throws NullPointerException if pattern, timeZone, or locale is null.
+ */
+ protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
+ printer= new FastDatePrinter(pattern, timeZone, locale);
+ parser= new FastDateParser(pattern, timeZone, locale, centuryStart);
+ }
+
+ // Format methods
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Formats a {@code Date}, {@code Calendar} or
+ * {@code Long} (milliseconds) object.</p>
+ *
+ * @param obj the object to format
+ * @param toAppendTo the buffer to append to
+ * @param pos the position - ignored
+ * @return the buffer passed in
+ */
+ @Override
+ public StringBuilder format(final Object obj, final StringBuilder toAppendTo, final FieldPosition pos) {
+ return printer.format(obj, toAppendTo, pos);
+ }
+
+ /**
+ * <p>Formats a millisecond {@code long} value.</p>
+ *
+ * @param millis the millisecond value to format
+ * @return the formatted string
+ * @since 2.1
+ */
+ @Override
+ public String format(final long millis) {
+ return printer.format(millis);
+ }
+
+ /**
+ * <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
+ *
+ * @param date the date to format
+ * @return the formatted string
+ */
+ @Override
+ public String format(final Date date) {
+ return printer.format(date);
+ }
+
+ /**
+ * <p>Formats a {@code Calendar} object.</p>
+ *
+ * @param calendar the calendar to format
+ * @return the formatted string
+ */
+ @Override
+ public String format(final Calendar calendar) {
+ return printer.format(calendar);
+ }
+
+ /**
+ * <p>Formats a millisecond {@code long} value into the
+ * supplied {@code StringBuilder}.</p>
+ *
+ * @param millis the millisecond value to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ * @since 2.1
+ */
+ @Override
+ public StringBuilder format(final long millis, final StringBuilder buf) {
+ return printer.format(millis, buf);
+ }
+
+ /**
+ * <p>Formats a {@code Date} object into the
+ * supplied {@code StringBuilder} using a {@code GregorianCalendar}.</p>
+ *
+ * @param date the date to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ */
+ @Override
+ public StringBuilder format(final Date date, final StringBuilder buf) {
+ return printer.format(date, buf);
+ }
+
+ /**
+ * <p>Formats a {@code Calendar} object into the
+ * supplied {@code StringBuilder}.</p>
+ *
+ * @param calendar the calendar to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ */
+ @Override
+ public StringBuilder format(final Calendar calendar, final StringBuilder buf) {
+ return printer.format(calendar, buf);
+ }
+
+ // Parsing
+ //-----------------------------------------------------------------------
+
+
+ /* (non-Javadoc)
+ * @see DateParser#parse(java.lang.String)
+ */
+ @Override
+ public Date parse(final String source) throws ParseException {
+ return parser.parse(source);
+ }
+
+ /* (non-Javadoc)
+ * @see DateParser#parse(java.lang.String, java.text.ParsePosition)
+ */
+ @Override
+ public Date parse(final String source, final ParsePosition pos) {
+ return parser.parse(source, pos);
+ }
+
+ /* (non-Javadoc)
+ * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
+ */
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ return parser.parseObject(source, pos);
+ }
+
+ // Accessors
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Gets the pattern used by this formatter.</p>
+ *
+ * @return the pattern, {@link java.text.SimpleDateFormat} compatible
+ */
+ @Override
+ public String getPattern() {
+ return printer.getPattern();
+ }
+
+ /**
+ * <p>Gets the time zone used by this formatter.</p>
+ *
+ * <p>This zone is always used for {@code Date} formatting. </p>
+ *
+ * @return the time zone
+ */
+ @Override
+ public TimeZone getTimeZone() {
+ return printer.getTimeZone();
+ }
+
+ /**
+ * <p>Gets the locale used by this formatter.</p>
+ *
+ * @return the locale
+ */
+ @Override
+ public Locale getLocale() {
+ return printer.getLocale();
+ }
+
+ /**
+ * <p>Gets an estimate for the maximum string length that the
+ * formatter will produce.</p>
+ *
+ * <p>The actual formatted length will almost always be less than or
+ * equal to this amount.</p>
+ *
+ * @return the maximum formatted length
+ */
+ public int getMaxLengthEstimate() {
+ return printer.getMaxLengthEstimate();
+ }
+
+ public String toPattern() {
+ return printer.getPattern();
+ }
+
+ // Basics
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Compares two objects for equality.</p>
+ *
+ * @param obj the object to compare to
+ * @return {@code true} if equal
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof FastDateFormat == false) {
+ return false;
+ }
+ final FastDateFormat other = (FastDateFormat) obj;
+ // no need to check parser, as it has same invariants as printer
+ return printer.equals(other.printer);
+ }
+
+ /**
+ * <p>Returns a hashcode compatible with equals.</p>
+ *
+ * @return a hashcode compatible with equals
+ */
+ @Override
+ public int hashCode() {
+ return printer.hashCode();
+ }
+
+ /**
+ * <p>Gets a debugging string version of this formatter.</p>
+ *
+ * @return a debugging string
+ */
+ @Override
+ public String toString() {
+ return "FastDateFormat[" + printer.getPattern() + "," + printer.getLocale() + "," + printer.getTimeZone().getID() + "]";
+ }
+
+
+ /**
+ * <p>Performs the formatting by applying the rules to the
+ * specified calendar.</p>
+ *
+ * @param calendar the calendar to format
+ * @param buf the buffer to format into
+ * @return the specified string buffer
+ */
+ protected StringBuilder applyRules(final Calendar calendar, final StringBuilder buf) {
+ return printer.applyRules(calendar, buf);
+ }
+
+
+}
+
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/051f7019/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateParser.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateParser.java
new file mode 100644
index 0000000..a30e6ae
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateParser.java
@@ -0,0 +1,926 @@
+/*
+ * 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.logging.log4j.core.util.datetime;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.text.DateFormatSymbols;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Copied from Commons Lang 3
+ */
+public class FastDateParser implements DateParser, Serializable {
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = 3L;
+
+ static final Locale JAPANESE_IMPERIAL = new Locale("ja","JP","JP");
+
+ // defining fields
+ private final String pattern;
+ private final TimeZone timeZone;
+ private final Locale locale;
+ private final int century;
+ private final int startYear;
+ private final boolean lenient;
+
+ // derived fields
+ private transient Pattern parsePattern;
+ private transient Strategy[] strategies;
+
+ // dynamic fields to communicate with Strategy
+ private transient String currentFormatField;
+ private transient Strategy nextStrategy;
+
+ /**
+ * <p>Constructs a new FastDateParser.</p>
+ *
+ * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the
+ * factory methods of {@link FastDateFormat} to get a cached FastDateParser instance.
+ *
+ * @param pattern non-null {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @param timeZone non-null time zone to use
+ * @param locale non-null locale
+ */
+ protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale) {
+ this(pattern, timeZone, locale, null, true);
+ }
+
+ /**
+ * <p>Constructs a new FastDateParser.</p>
+ *
+ * @param pattern non-null {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @param timeZone non-null time zone to use
+ * @param locale non-null locale
+ * @param centuryStart The start of the century for 2 digit year parsing
+ *
+ * @since 3.3
+ */
+ protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
+ this(pattern, timeZone, locale, centuryStart, true);
+ }
+
+ /**
+ * <p>Constructs a new FastDateParser.</p>
+ *
+ * @param pattern non-null {@link java.text.SimpleDateFormat} compatible
+ * pattern
+ * @param timeZone non-null time zone to use
+ * @param locale non-null locale
+ * @param centuryStart The start of the century for 2 digit year parsing
+ * @param lenient if true, non-standard values for Calendar fields should be accepted;
+ * if false, non-standard values will cause a ParseException to be thrown {@link CalendaretLenient(boolean)}
+ *
+ * @since 3.5
+ */
+ protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale,
+ final Date centuryStart, final boolean lenient) {
+ this.pattern = pattern;
+ this.timeZone = timeZone;
+ this.locale = locale;
+ this.lenient = lenient;
+
+ final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
+
+ int centuryStartYear;
+ if(centuryStart!=null) {
+ definingCalendar.setTime(centuryStart);
+ centuryStartYear= definingCalendar.get(Calendar.YEAR);
+ }
+ else if(locale.equals(JAPANESE_IMPERIAL)) {
+ centuryStartYear= 0;
+ }
+ else {
+ // from 80 years ago to 20 years from now
+ definingCalendar.setTime(new Date());
+ centuryStartYear= definingCalendar.get(Calendar.YEAR)-80;
+ }
+ century= centuryStartYear / 100 * 100;
+ startYear= centuryStartYear - century;
+
+ init(definingCalendar);
+ }
+
+ /**
+ * Initialize derived fields from defining fields.
+ * This is called from constructor and from readObject (de-serialization)
+ *
+ * @param definingCalendar the {@link java.util.Calendar} instance used to initialize this FastDateParser
+ */
+ private void init(final Calendar definingCalendar) {
+
+ final StringBuilder regex= new StringBuilder();
+ final List<Strategy> collector = new ArrayList<Strategy>();
+
+ final Matcher patternMatcher= formatPattern.matcher(pattern);
+ if(!patternMatcher.lookingAt()) {
+ throw new IllegalArgumentException(
+ "Illegal pattern character '" + pattern.charAt(patternMatcher.regionStart()) + "'");
+ }
+
+ currentFormatField= patternMatcher.group();
+ Strategy currentStrategy= getStrategy(currentFormatField, definingCalendar);
+ for(;;) {
+ patternMatcher.region(patternMatcher.end(), patternMatcher.regionEnd());
+ if(!patternMatcher.lookingAt()) {
+ nextStrategy = null;
+ break;
+ }
+ final String nextFormatField= patternMatcher.group();
+ nextStrategy = getStrategy(nextFormatField, definingCalendar);
+ if(currentStrategy.addRegex(this, regex)) {
+ collector.add(currentStrategy);
+ }
+ currentFormatField= nextFormatField;
+ currentStrategy= nextStrategy;
+ }
+ if (patternMatcher.regionStart() != patternMatcher.regionEnd()) {
+ throw new IllegalArgumentException("Failed to parse \""+pattern+"\" ; gave up at index "+patternMatcher.regionStart());
+ }
+ if(currentStrategy.addRegex(this, regex)) {
+ collector.add(currentStrategy);
+ }
+ currentFormatField= null;
+ strategies= collector.toArray(new Strategy[collector.size()]);
+ parsePattern= Pattern.compile(regex.toString());
+ }
+
+ // Accessors
+ //-----------------------------------------------------------------------
+ /* (non-Javadoc)
+ * @see org.apache.commons.lang3.time.DateParser#getPattern()
+ */
+ @Override
+ public String getPattern() {
+ return pattern;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.lang3.time.DateParser#getTimeZone()
+ */
+ @Override
+ public TimeZone getTimeZone() {
+ return timeZone;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.lang3.time.DateParser#getLocale()
+ */
+ @Override
+ public Locale getLocale() {
+ return locale;
+ }
+
+ /**
+ * Returns the generated pattern (for testing purposes).
+ *
+ * @return the generated pattern
+ */
+ Pattern getParsePattern() {
+ return parsePattern;
+ }
+
+ // Basics
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Compare another object for equality with this object.</p>
+ *
+ * @param obj the object to compare to
+ * @return <code>true</code>if equal to this instance
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (! (obj instanceof FastDateParser) ) {
+ return false;
+ }
+ final FastDateParser other = (FastDateParser) obj;
+ return pattern.equals(other.pattern)
+ && timeZone.equals(other.timeZone)
+ && locale.equals(other.locale);
+ }
+
+ /**
+ * <p>Return a hashcode compatible with equals.</p>
+ *
+ * @return a hashcode compatible with equals
+ */
+ @Override
+ public int hashCode() {
+ return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode());
+ }
+
+ /**
+ * <p>Get a string version of this formatter.</p>
+ *
+ * @return a debugging string
+ */
+ @Override
+ public String toString() {
+ return "FastDateParser[" + pattern + "," + locale + "," + timeZone.getID() + "]";
+ }
+
+ // Serializing
+ //-----------------------------------------------------------------------
+ /**
+ * Create the object after serialization. This implementation reinitializes the
+ * transient properties.
+ *
+ * @param in ObjectInputStream from which the object is being deserialized.
+ * @throws IOException if there is an IO issue.
+ * @throws ClassNotFoundException if a class cannot be found.
+ */
+ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+
+ final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
+ init(definingCalendar);
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String)
+ */
+ @Override
+ public Object parseObject(final String source) throws ParseException {
+ return parse(source);
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String)
+ */
+ @Override
+ public Date parse(final String source) throws ParseException {
+ final Date date= parse(source, new ParsePosition(0));
+ if(date==null) {
+ // Add a note re supported date range
+ if (locale.equals(JAPANESE_IMPERIAL)) {
+ throw new ParseException(
+ "(The " +locale + " locale does not support dates before 1868 AD)\n" +
+ "Unparseable date: \""+source+"\" does not match "+parsePattern.pattern(), 0);
+ }
+ throw new ParseException("Unparseable date: \""+source+"\" does not match "+parsePattern.pattern(), 0);
+ }
+ return date;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.lang3.time.DateParser#parseObject(java.lang.String, java.text.ParsePosition)
+ */
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ return parse(source, pos);
+ }
+
+ /**
+ * This implementation updates the ParsePosition if the parse succeeeds.
+ * However, unlike the method {@link java.text.SimpleDateFormat#parse(String, ParsePosition)}
+ * it is not able to set the error Index - i.e. {@link ParsePosition#getErrorIndex()} - if the parse fails.
+ * <p>
+ * To determine if the parse has succeeded, the caller must check if the current parse position
+ * given by {@link ParsePosition#getIndex()} has been updated. If the input buffer has been fully
+ * parsed, then the index will point to just after the end of the input buffer.
+ *
+ * @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String, java.text.ParsePosition)
+ * {@inheritDoc}
+ */
+ @Override
+ public Date parse(final String source, final ParsePosition pos) {
+ final int offset= pos.getIndex();
+ final Matcher matcher= parsePattern.matcher(source.substring(offset));
+ if(!matcher.lookingAt()) {
+ return null;
+ }
+ // timing tests indicate getting new instance is 19% faster than cloning
+ final Calendar cal= Calendar.getInstance(timeZone, locale);
+ cal.clear();
+ cal.setLenient(lenient);
+
+ for(int i=0; i<strategies.length;) {
+ final Strategy strategy= strategies[i++];
+ strategy.setCalendar(this, cal, matcher.group(i));
+ }
+ pos.setIndex(offset+matcher.end());
+ return cal.getTime();
+ }
+
+ // Support for strategies
+ //-----------------------------------------------------------------------
+
+ private static StringBuilder simpleQuote(final StringBuilder sb, final String value) {
+ for(int i= 0; i<value.length(); ++i) {
+ char c= value.charAt(i);
+ switch(c) {
+ case '\\':
+ case '^':
+ case '$':
+ case '.':
+ case '|':
+ case '?':
+ case '*':
+ case '+':
+ case '(':
+ case ')':
+ case '[':
+ case '{':
+ sb.append('\\');
+ default:
+ sb.append(c);
+ }
+ }
+ return sb;
+ }
+
+ /**
+ * Escape constant fields into regular expression
+ * @param regex The destination regex
+ * @param value The source field
+ * @param unquote If true, replace two success quotes ('') with single quote (')
+ * @return The <code>StringBuilder</code>
+ */
+ private static StringBuilder escapeRegex(final StringBuilder regex, final String value, final boolean unquote) {
+ regex.append("\\Q");
+ for(int i= 0; i<value.length(); ++i) {
+ char c= value.charAt(i);
+ switch(c) {
+ case '\'':
+ if(unquote) {
+ if(++i==value.length()) {
+ return regex;
+ }
+ c= value.charAt(i);
+ }
+ break;
+ case '\\':
+ if(++i==value.length()) {
+ break;
+ }
+ /*
+ * If we have found \E, we replace it with \E\\E\Q, i.e. we stop the quoting,
+ * quote the \ in \E, then restart the quoting.
+ *
+ * Otherwise we just output the two characters.
+ * In each case the initial \ needs to be output and the final char is done at the end
+ */
+ regex.append(c); // we always want the original \
+ c = value.charAt(i); // Is it followed by E ?
+ if (c == 'E') { // \E detected
+ regex.append("E\\\\E\\"); // see comment above
+ c = 'Q'; // appended below
+ }
+ break;
+ default:
+ break;
+ }
+ regex.append(c);
+ }
+ regex.append("\\E");
+ return regex;
+ }
+
+
+ /**
+ * Get the short and long values displayed for a field
+ * @param field The field of interest
+ * @param definingCalendar The calendar to obtain the short and long values
+ * @param locale The locale of display names
+ * @return A Map of the field key / value pairs
+ */
+ private static Map<String, Integer> getDisplayNames(final int field, final Calendar definingCalendar, final Locale locale) {
+ return definingCalendar.getDisplayNames(field, Calendar.ALL_STYLES, locale);
+ }
+
+ /**
+ * Adjust dates to be within appropriate century
+ * @param twoDigitYear The year to adjust
+ * @return A value between centuryStart(inclusive) to centuryStart+100(exclusive)
+ */
+ private int adjustYear(final int twoDigitYear) {
+ final int trial= century + twoDigitYear;
+ return twoDigitYear>=startYear ?trial :trial+100;
+ }
+
+ /**
+ * Is the next field a number?
+ * @return true, if next field will be a number
+ */
+ boolean isNextNumber() {
+ return nextStrategy!=null && nextStrategy.isNumber();
+ }
+
+ /**
+ * What is the width of the current field?
+ * @return The number of characters in the current format field
+ */
+ int getFieldWidth() {
+ return currentFormatField.length();
+ }
+
+ /**
+ * A strategy to parse a single field from the parsing pattern
+ */
+ private static abstract class Strategy {
+
+ /**
+ * Is this field a number?
+ * The default implementation returns false.
+ *
+ * @return true, if field is a number
+ */
+ boolean isNumber() {
+ return false;
+ }
+
+ /**
+ * Set the Calendar with the parsed field.
+ *
+ * The default implementation does nothing.
+ *
+ * @param parser The parser calling this strategy
+ * @param cal The <code>Calendar</code> to set
+ * @param value The parsed field to translate and set in cal
+ */
+ void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
+
+ }
+
+ /**
+ * Generate a <code>Pattern</code> regular expression to the <code>StringBuilder</code>
+ * which will accept this field
+ * @param parser The parser calling this strategy
+ * @param regex The <code>StringBuilder</code> to append to
+ * @return true, if this field will set the calendar;
+ * false, if this field is a constant value
+ */
+ abstract boolean addRegex(FastDateParser parser, StringBuilder regex);
+
+ }
+
+ /**
+ * A <code>Pattern</code> to parse the user supplied SimpleDateFormat pattern
+ */
+ private static final Pattern formatPattern= Pattern.compile(
+ "D+|E+|F+|G+|H+|K+|M+|S+|W+|X+|Z+|a+|d+|h+|k+|m+|s+|w+|y+|z+|''|'[^']++(''[^']*+)*+'|[^'A-Za-z]++");
+
+ /**
+ * Obtain a Strategy given a field from a SimpleDateFormat pattern
+ * @param formatField A sub-sequence of the SimpleDateFormat pattern
+ * @param definingCalendar The calendar to obtain the short and long values
+ * @return The Strategy that will handle parsing for the field
+ */
+ private Strategy getStrategy(final String formatField, final Calendar definingCalendar) {
+ switch(formatField.charAt(0)) {
+ case '\'':
+ if(formatField.length()>2) {
+ return new CopyQuotedStrategy(formatField.substring(1, formatField.length()-1));
+ }
+ //$FALL-THROUGH$
+ default:
+ return new CopyQuotedStrategy(formatField);
+ case 'D':
+ return DAY_OF_YEAR_STRATEGY;
+ case 'E':
+ return getLocaleSpecificStrategy(Calendar.DAY_OF_WEEK, definingCalendar);
+ case 'F':
+ return DAY_OF_WEEK_IN_MONTH_STRATEGY;
+ case 'G':
+ return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar);
+ case 'H': // Hour in day (0-23)
+ return HOUR_OF_DAY_STRATEGY;
+ case 'K': // Hour in am/pm (0-11)
+ return HOUR_STRATEGY;
+ case 'M':
+ return formatField.length()>=3 ?getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) :NUMBER_MONTH_STRATEGY;
+ case 'S':
+ return MILLISECOND_STRATEGY;
+ case 'W':
+ return WEEK_OF_MONTH_STRATEGY;
+ case 'a':
+ return getLocaleSpecificStrategy(Calendar.AM_PM, definingCalendar);
+ case 'd':
+ return DAY_OF_MONTH_STRATEGY;
+ case 'h': // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
+ return HOUR12_STRATEGY;
+ case 'k': // Hour in day (1-24), i.e. midnight is 24, not 0
+ return HOUR24_OF_DAY_STRATEGY;
+ case 'm':
+ return MINUTE_STRATEGY;
+ case 's':
+ return SECOND_STRATEGY;
+ case 'w':
+ return WEEK_OF_YEAR_STRATEGY;
+ case 'y':
+ return formatField.length()>2 ?LITERAL_YEAR_STRATEGY :ABBREVIATED_YEAR_STRATEGY;
+ case 'X':
+ return ISO8601TimeZoneStrategy.getStrategy(formatField.length());
+ case 'Z':
+ if (formatField.equals("ZZ")) {
+ return ISO_8601_STRATEGY;
+ }
+ //$FALL-THROUGH$
+ case 'z':
+ return getLocaleSpecificStrategy(Calendar.ZONE_OFFSET, definingCalendar);
+ }
+ }
+
+ @SuppressWarnings("unchecked") // OK because we are creating an array with no entries
+ private static final ConcurrentMap<Locale, Strategy>[] caches = new ConcurrentMap[Calendar.FIELD_COUNT];
+
+ /**
+ * Get a cache of Strategies for a particular field
+ * @param field The Calendar field
+ * @return a cache of Locale to Strategy
+ */
+ private static ConcurrentMap<Locale, Strategy> getCache(final int field) {
+ synchronized(caches) {
+ if(caches[field]==null) {
+ caches[field]= new ConcurrentHashMap<Locale,Strategy>(3);
+ }
+ return caches[field];
+ }
+ }
+
+ /**
+ * Construct a Strategy that parses a Text field
+ * @param field The Calendar field
+ * @param definingCalendar The calendar to obtain the short and long values
+ * @return a TextStrategy for the field and Locale
+ */
+ private Strategy getLocaleSpecificStrategy(final int field, final Calendar definingCalendar) {
+ final ConcurrentMap<Locale,Strategy> cache = getCache(field);
+ Strategy strategy= cache.get(locale);
+ if(strategy==null) {
+ strategy= field==Calendar.ZONE_OFFSET
+ ? new TimeZoneStrategy(locale)
+ : new CaseInsensitiveTextStrategy(field, definingCalendar, locale);
+ final Strategy inCache= cache.putIfAbsent(locale, strategy);
+ if(inCache!=null) {
+ return inCache;
+ }
+ }
+ return strategy;
+ }
+
+ /**
+ * A strategy that copies the static or quoted field in the parsing pattern
+ */
+ private static class CopyQuotedStrategy extends Strategy {
+ private final String formatField;
+
+ /**
+ * Construct a Strategy that ensures the formatField has literal text
+ * @param formatField The literal text to match
+ */
+ CopyQuotedStrategy(final String formatField) {
+ this.formatField= formatField;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ boolean isNumber() {
+ char c= formatField.charAt(0);
+ if(c=='\'') {
+ c= formatField.charAt(1);
+ }
+ return Character.isDigit(c);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
+ escapeRegex(regex, formatField, true);
+ return false;
+ }
+ }
+
+ /**
+ * A strategy that handles a text field in the parsing pattern
+ */
+ private static class CaseInsensitiveTextStrategy extends Strategy {
+ private final int field;
+ private final Locale locale;
+ private final Map<String, Integer> lKeyValues;
+
+ /**
+ * Construct a Strategy that parses a Text field
+ * @param field The Calendar field
+ * @param definingCalendar The Calendar to use
+ * @param locale The Locale to use
+ */
+ CaseInsensitiveTextStrategy(final int field, final Calendar definingCalendar, final Locale locale) {
+ this.field= field;
+ this.locale= locale;
+ final Map<String, Integer> keyValues = getDisplayNames(field, definingCalendar, locale);
+ this.lKeyValues= new HashMap<String,Integer>();
+
+ for(final Map.Entry<String, Integer> entry : keyValues.entrySet()) {
+ lKeyValues.put(entry.getKey().toLowerCase(locale), entry.getValue());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
+ regex.append("((?iu)");
+ for(final String textKeyValue : lKeyValues.keySet()) {
+ simpleQuote(regex, textKeyValue).append('|');
+ }
+ regex.setCharAt(regex.length()-1, ')');
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
+ final Integer iVal = lKeyValues.get(value.toLowerCase(locale));
+ if(iVal == null) {
+ final StringBuilder sb= new StringBuilder(value);
+ sb.append(" not in (");
+ for(final String textKeyValue : lKeyValues.keySet()) {
+ sb.append(textKeyValue).append(' ');
+ }
+ sb.setCharAt(sb.length()-1, ')');
+ throw new IllegalArgumentException(sb.toString());
+ }
+ cal.set(field, iVal.intValue());
+ }
+ }
+
+
+ /**
+ * A strategy that handles a number field in the parsing pattern
+ */
+ private static class NumberStrategy extends Strategy {
+ private final int field;
+
+ /**
+ * Construct a Strategy that parses a Number field
+ * @param field The Calendar field
+ */
+ NumberStrategy(final int field) {
+ this.field= field;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ boolean isNumber() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
+ // See LANG-954: We use {Nd} rather than {IsNd} because Android does not support the Is prefix
+ if(parser.isNextNumber()) {
+ regex.append("(\\p{Nd}{").append(parser.getFieldWidth()).append("}+)");
+ }
+ else {
+ regex.append("(\\p{Nd}++)");
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
+ cal.set(field, modify(Integer.parseInt(value)));
+ }
+
+ /**
+ * Make any modifications to parsed integer
+ * @param iValue The parsed integer
+ * @return The modified value
+ */
+ int modify(final int iValue) {
+ return iValue;
+ }
+ }
+
+ private static final Strategy ABBREVIATED_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR) {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
+ int iValue= Integer.parseInt(value);
+ if(iValue<100) {
+ iValue= parser.adjustYear(iValue);
+ }
+ cal.set(Calendar.YEAR, iValue);
+ }
+ };
+
+ /**
+ * A strategy that handles a timezone field in the parsing pattern
+ */
+ static class TimeZoneStrategy extends Strategy {
+ private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}";
+ private static final String GMT_OPTION= "GMT[+-]\\d{1,2}:\\d{2}";
+
+ private final Locale locale;
+ private final Map<String, TimeZone> tzNames= new HashMap<String, TimeZone>();
+ private final String validTimeZoneChars;
+
+ /**
+ * Index of zone id
+ */
+ private static final int ID = 0;
+
+ /**
+ * Construct a Strategy that parses a TimeZone
+ * @param locale The Locale
+ */
+ TimeZoneStrategy(final Locale locale) {
+ this.locale = locale;
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append('(' + RFC_822_TIME_ZONE + "|(?iu)" + GMT_OPTION );
+
+ final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
+ for (final String[] zoneNames : zones) {
+ final String tzId = zoneNames[ID];
+ if (tzId.equalsIgnoreCase("GMT")) {
+ continue;
+ }
+ final TimeZone tz = TimeZone.getTimeZone(tzId);
+ for(int i= 1; i<zoneNames.length; ++i) {
+ String zoneName = zoneNames[i].toLowerCase(locale);
+ if (!tzNames.containsKey(zoneName)){
+ tzNames.put(zoneName, tz);
+ simpleQuote(sb.append('|'), zoneName);
+ }
+ }
+ }
+
+ sb.append(')');
+ validTimeZoneChars = sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
+ regex.append(validTimeZoneChars);
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
+ TimeZone tz;
+ if(value.charAt(0)=='+' || value.charAt(0)=='-') {
+ tz= TimeZone.getTimeZone("GMT"+value);
+ }
+ else if(value.regionMatches(true, 0, "GMT", 0, 3)) {
+ tz= TimeZone.getTimeZone(value.toUpperCase());
+ }
+ else {
+ tz= tzNames.get(value.toLowerCase(locale));
+ if(tz==null) {
+ throw new IllegalArgumentException(value + " is not a supported timezone name");
+ }
+ }
+ cal.setTimeZone(tz);
+ }
+ }
+
+ private static class ISO8601TimeZoneStrategy extends Strategy {
+ // Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm
+ private final String pattern;
+
+ /**
+ * Construct a Strategy that parses a TimeZone
+ * @param pattern The Pattern
+ */
+ ISO8601TimeZoneStrategy(String pattern) {
+ this.pattern = pattern;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ boolean addRegex(FastDateParser parser, StringBuilder regex) {
+ regex.append(pattern);
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void setCalendar(FastDateParser parser, Calendar cal, String value) {
+ if (value.equals("Z")) {
+ cal.setTimeZone(TimeZone.getTimeZone("UTC"));
+ } else {
+ cal.setTimeZone(TimeZone.getTimeZone("GMT" + value));
+ }
+ }
+
+ private static final Strategy ISO_8601_1_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}))");
+ private static final Strategy ISO_8601_2_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}\\d{2}))");
+ private static final Strategy ISO_8601_3_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::)\\d{2}))");
+
+ /**
+ * Factory method for ISO8601TimeZoneStrategies.
+ *
+ * @param tokenLen a token indicating the length of the TimeZone String to be formatted.
+ * @return a ISO8601TimeZoneStrategy that can format TimeZone String of length {@code tokenLen}. If no such
+ * strategy exists, an IllegalArgumentException will be thrown.
+ */
+ static Strategy getStrategy(int tokenLen) {
+ switch(tokenLen) {
+ case 1:
+ return ISO_8601_1_STRATEGY;
+ case 2:
+ return ISO_8601_2_STRATEGY;
+ case 3:
+ return ISO_8601_3_STRATEGY;
+ default:
+ throw new IllegalArgumentException("invalid number of X");
+ }
+ }
+ }
+
+ private static final Strategy NUMBER_MONTH_STRATEGY = new NumberStrategy(Calendar.MONTH) {
+ @Override
+ int modify(final int iValue) {
+ return iValue-1;
+ }
+ };
+ private static final Strategy LITERAL_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR);
+ private static final Strategy WEEK_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_YEAR);
+ private static final Strategy WEEK_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_MONTH);
+ private static final Strategy DAY_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.DAY_OF_YEAR);
+ private static final Strategy DAY_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_MONTH);
+ private static final Strategy DAY_OF_WEEK_IN_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK_IN_MONTH);
+ private static final Strategy HOUR_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY);
+ private static final Strategy HOUR24_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY) {
+ @Override
+ int modify(final int iValue) {
+ return iValue == 24 ? 0 : iValue;
+ }
+ };
+ private static final Strategy HOUR12_STRATEGY = new NumberStrategy(Calendar.HOUR) {
+ @Override
+ int modify(final int iValue) {
+ return iValue == 12 ? 0 : iValue;
+ }
+ };
+ private static final Strategy HOUR_STRATEGY = new NumberStrategy(Calendar.HOUR);
+ private static final Strategy MINUTE_STRATEGY = new NumberStrategy(Calendar.MINUTE);
+ private static final Strategy SECOND_STRATEGY = new NumberStrategy(Calendar.SECOND);
+ private static final Strategy MILLISECOND_STRATEGY = new NumberStrategy(Calendar.MILLISECOND);
+ private static final Strategy ISO_8601_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::?\\d{2})?))");
+
+
+}