[LANG-1355] TimeZone.getTimeZone() in FastDateParser causes resource

contention</action>. This closes #296.
This commit is contained in:
Chas Honton 2017-10-10 11:58:29 -06:00 committed by Gary Gregory
parent 15d5503215
commit 1dbfaeecbd
13 changed files with 411 additions and 20 deletions

View File

@ -46,6 +46,7 @@ The <action> type attribute can be add,update,fix,remove.
<body>
<release version="3.7" date="2017-MM-DD" description="New features and bug fixes. Requires Java 7.">
<action issue="LANG-1355" type="add" dev="ggregory" due-to="Chas Honton">TimeZone.getTimeZone() in FastDateParser causes resource contention (PR #296.)</action>
<action issue="LANG-1348" type="fix" dev="pschumacher" due-to="mbusso">StackOverflowError on TypeUtils.toString(...) for a generic return type of Enum.valueOf</action>
<action issue="LANG-1346" type="update" dev="pschumacher">Remove deprecation from RandomStringUtils</action>
<action issue="LANG-1350" type="fix" dev="ggregory" due-to="Brett Kail">ConstructorUtils.invokeConstructor(Class, Object...) regression</action>

View File

@ -38,7 +38,7 @@ public class DateFormatUtils {
* The UTC time zone (often referred to as GMT).
* This is private as it is mutable.
*/
private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("GMT");
private static final TimeZone UTC_TIME_ZONE = FastTimeZone.getGmtTimeZone();
/**
* ISO 8601 formatter for date-time without time zone.

View File

@ -888,11 +888,8 @@ public class FastDateParser implements DateParser, Serializable {
*/
@Override
void setCalendar(final FastDateParser parser, final Calendar cal, final String timeZone) {
if (timeZone.charAt(0) == '+' || timeZone.charAt(0) == '-') {
final TimeZone tz = TimeZone.getTimeZone(TimeZones.GMT_ID + timeZone);
cal.setTimeZone(tz);
} else if (timeZone.regionMatches(true, 0, TimeZones.GMT_ID, 0, 3)) {
final TimeZone tz = TimeZone.getTimeZone(timeZone.toUpperCase(Locale.ROOT));
TimeZone tz = FastTimeZone.getGmtTimeZone(timeZone);
if (tz != null) {
cal.setTimeZone(tz);
} else {
final TzInfo tzInfo = tzNames.get(timeZone.toLowerCase(locale));
@ -918,11 +915,7 @@ public class FastDateParser implements DateParser, Serializable {
*/
@Override
void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
if (value.equals("Z")) {
cal.setTimeZone(TimeZone.getTimeZone(TimeZones.GMT_ID));
} else {
cal.setTimeZone(TimeZone.getTimeZone(TimeZones.GMT_ID + value));
}
cal.setTimeZone(FastTimeZone.getGmtTimeZone(value));
}
private static final Strategy ISO_8601_1_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}))");

View File

@ -0,0 +1,95 @@
/*
* 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.commons.lang3.time;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Faster methods to produce custom time zones.
*
* @since 3.7
*/
public class FastTimeZone {
private static final TimeZone GREENWICH = new GmtTimeZone(false, 0, 0);
// do not instantiate
private FastTimeZone() {
}
/**
* Get the GMT TimeZone.
* @return A TimeZone with a raw offset of zero.
*/
public static TimeZone getGmtTimeZone() {
return GREENWICH;
}
/**
* Get a TimeZone, looking first for GMT custom ids, then falling back to Olson ids.
* A GMT custom id can be 'Z', or 'UTC', or has an optional prefix of GMT,
* followed by sign, hours digit(s), optional colon(':'), and optional minutes digits.
* i.e. <em>[GMT] (+|-) Hours [[:] Minutes]</em>
*
* @param id A GMT custom id (or Olson id
* @return A timezone
*/
public static TimeZone getTimeZone(String id) {
TimeZone tz = getGmtTimeZone(id);
if (tz != null) {
return tz;
}
return TimeZone.getTimeZone(id);
}
private static final Pattern GMT_PATTERN = Pattern.compile("^(?:(?i)GMT)?([+-])?(\\d\\d?)?(:?(\\d\\d?))?$");
/**
* Get a TimeZone with GMT offsets. A GMT offset must be either 'Z', or 'UTC', or match
* <em>(GMT)? hh?(:?mm?)?</em>, where h and m are digits representing hours and minutes.
*
* @param pattern The GMT offset
* @return A TimeZone with offset from GMT or null, if pattern does not match.
*/
public static TimeZone getGmtTimeZone(String pattern) {
if ("Z".equals(pattern) || "UTC".equals(pattern)) {
return GREENWICH;
}
Matcher m = GMT_PATTERN.matcher(pattern);
if (m.matches()) {
int hours = parseInt(m.group(2));
int minutes = parseInt(m.group(4));
if (hours == 0 && minutes == 0) {
return GREENWICH;
}
return new GmtTimeZone(parseSign(m.group(1)), hours, minutes);
}
return null;
}
private static int parseInt(String group) {
return group != null ? Integer.parseInt(group) : 0;
}
private static boolean parseSign(String group) {
return group != null && group.charAt(0) == '-';
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.commons.lang3.time;
import java.util.Date;
import java.util.TimeZone;
/**
* Custom timezone that contains offset from GMT.
*
* @since 3.7
*/
class GmtTimeZone extends TimeZone {
private static final int MILLISECONDS_PER_MINUTE = 60 * 1000;
private static final int MINUTES_PER_HOUR = 60;
private static final int HOURS_PER_DAY = 24;
// Serializable!
static final long serialVersionUID = 1L;
private final int offset;
private final String zoneId;
GmtTimeZone(boolean negate, int hours, int minutes) {
if (hours >= HOURS_PER_DAY) {
throw new IllegalArgumentException(hours + " hours out of range");
}
if (minutes >= MINUTES_PER_HOUR) {
throw new IllegalArgumentException(minutes + " minutes out of range");
}
int milliseconds = (minutes + (hours * MINUTES_PER_HOUR)) * MILLISECONDS_PER_MINUTE;
offset = negate ? -milliseconds : milliseconds;
zoneId = twoDigits(
twoDigits(new StringBuilder(9).append("GMT").append(negate ? '-' : '+'), hours)
.append(':'), minutes).toString();
}
private static StringBuilder twoDigits(StringBuilder sb, int n) {
return sb.append((char) ('0' + (n / 10))).append((char) ('0' + (n % 10)));
}
@Override
public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) {
return offset;
}
@Override
public void setRawOffset(int offsetMillis) {
throw new UnsupportedOperationException();
}
@Override
public int getRawOffset() {
return offset;
}
@Override
public String getID() {
return zoneId;
}
@Override
public boolean useDaylightTime() {
return false;
}
@Override
public boolean inDaylightTime(Date date) {
return false;
}
@Override
public String toString() {
return "[GmtTimeZone id=\"" + zoneId + "\",offset=" + offset + ']';
}
@Override
public int hashCode() {
return offset;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof GmtTimeZone)) {
return false;
} else {
return zoneId == ((GmtTimeZone) other).zoneId;
}
}
}

View File

@ -24,6 +24,10 @@ package org.apache.commons.lang3.time;
*/
public class TimeZones {
// do not instantiate
private TimeZones() {
}
/**
* A public version of {@link java.util.TimeZone}'s package private {@code GMT_ID} field.
*/

View File

@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.commons.lang3.time.FastTimeZone;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
@ -46,7 +47,7 @@ public class SystemDefaultsSwitchTest {
TEST_DEFAULT_LOCALE = Locale.getDefault();
DEFAULT_TIMEZONE_BEFORE_TEST = TimeZone.getDefault();
final TimeZone utc = TimeZone.getTimeZone("UTC");
final TimeZone utc = FastTimeZone.getGmtTimeZone();
if (!DEFAULT_TIMEZONE_BEFORE_TEST.equals(utc)) {
TimeZone.setDefault(utc);
} else {

View File

@ -57,7 +57,7 @@ public class DateFormatUtilsTest {
//-----------------------------------------------------------------------
@Test
public void testFormat() {
final Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
final Calendar c = Calendar.getInstance(FastTimeZone.getGmtTimeZone());
c.set(2005, Calendar.JANUARY, 1, 12, 0, 0);
c.setTimeZone(TimeZone.getDefault());
final StringBuilder buffer = new StringBuilder ();
@ -81,7 +81,7 @@ public class DateFormatUtilsTest {
//-----------------------------------------------------------------------
@Test
public void testFormatCalendar() {
final Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
final Calendar c = Calendar.getInstance(FastTimeZone.getGmtTimeZone());
c.set(2005, Calendar.JANUARY, 1, 12, 0, 0);
c.setTimeZone(TimeZone.getDefault());
final StringBuilder buffer = new StringBuilder ();
@ -104,7 +104,7 @@ public class DateFormatUtilsTest {
@Test
public void testFormatUTC() {
final Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
final Calendar c = Calendar.getInstance(FastTimeZone.getGmtTimeZone());
c.set(2005, Calendar.JANUARY, 1, 12, 0, 0);
assertEquals ("2005-01-01T12:00:00", DateFormatUtils.formatUTC(c.getTime(), DateFormatUtils.ISO_DATETIME_FORMAT.getPattern()));
@ -139,7 +139,7 @@ public class DateFormatUtilsTest {
}
private void testUTC(final String expectedValue, final String pattern) {
final TimeZone timeZone = TimeZone.getTimeZone("UTC");
final TimeZone timeZone = FastTimeZone.getGmtTimeZone();
assertFormats(expectedValue, pattern, timeZone, createFebruaryTestDate(timeZone));
}
@ -180,7 +180,7 @@ public class DateFormatUtilsTest {
assertFormats("Sun, 08 Jun 2003 10:11:12 -0300", DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern(),
timeZone, june);
timeZone = TimeZone.getTimeZone("UTC");
timeZone = FastTimeZone.getGmtTimeZone();
june = createJuneTestDate(timeZone);
assertFormats("Sun, 08 Jun 2003 10:11:12 +0000", DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern(),
timeZone, june);

View File

@ -452,7 +452,7 @@ public class DurationFormatUtilsTest {
@Test
public void testEdgeDurations() {
// This test case must use a time zone without DST
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
TimeZone.setDefault(FastTimeZone.getGmtTimeZone());
assertEqualDuration( "01", new int[] { 2006, 0, 15, 0, 0, 0 },
new int[] { 2006, 2, 10, 0, 0, 0 }, "MM");
assertEqualDuration( "12", new int[] { 2005, 0, 15, 0, 0, 0 },

View File

@ -311,7 +311,7 @@ public class FastDateFormatTest {
@Test
public void testLANG_1152() {
final TimeZone utc = TimeZone.getTimeZone("UTC");
final TimeZone utc = FastTimeZone.getGmtTimeZone();
final Date date = new Date(Long.MAX_VALUE);
String dateAsString = FastDateFormat.getInstance("yyyy-MM-dd", utc, Locale.US).format(date);

View File

@ -265,7 +265,7 @@ public class FastDatePrinterTest {
@SystemDefaults(timezone="UTC")
@Test
public void testTimeZoneAsZ() throws Exception {
final Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
final Calendar c = Calendar.getInstance(FastTimeZone.getGmtTimeZone());
final FastDateFormat noColonFormat = FastDateFormat.getInstance("Z");
assertEquals("+0000", noColonFormat.format(c));

View File

@ -0,0 +1,99 @@
/*
* 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.commons.lang3.time;
import org.junit.Assert;
import org.junit.Test;
import java.util.TimeZone;
/**
* Tests for FastTimeZone
*/
public class FastTimeZoneTest {
private static final int HOURS_23 = 23 * 60 * 60 * 1000;
private static final int HOURS_2 = 2 * 60 * 60 * 1000;
private static final int MINUTES_59 = 59 * 60 * 1000;
private static final int MINUTES_5 = 5 * 60 * 1000;
@Test
public void testGetGmtTimeZone() {
Assert.assertEquals(0, FastTimeZone.getGmtTimeZone().getRawOffset());
}
@Test
public void testBareGmt() {
Assert.assertEquals(FastTimeZone.getGmtTimeZone(), FastTimeZone.getTimeZone("GMT"));
}
@Test
public void testZ() {
Assert.assertEquals(FastTimeZone.getGmtTimeZone(), FastTimeZone.getTimeZone("Z"));
}
@Test
public void testUTC() {
Assert.assertEquals(FastTimeZone.getGmtTimeZone(), FastTimeZone.getTimeZone("UTC"));
}
@Test
public void testZeroOffsetsReturnSingleton() {
Assert.assertEquals(FastTimeZone.getGmtTimeZone(), FastTimeZone.getTimeZone("+0"));
Assert.assertEquals(FastTimeZone.getGmtTimeZone(), FastTimeZone.getTimeZone("-0"));
}
@Test
public void testOlson() {
Assert.assertEquals(TimeZone.getTimeZone("America/New_York"), FastTimeZone.getTimeZone("America/New_York"));
}
@Test
public void testGmtPrefix() {
Assert.assertEquals(HOURS_23, FastTimeZone.getGmtTimeZone("GMT+23:00").getRawOffset());
Assert.assertEquals(-HOURS_23, FastTimeZone.getGmtTimeZone("GMT-23:00").getRawOffset());
}
@Test
public void testSign() {
Assert.assertEquals(HOURS_23, FastTimeZone.getGmtTimeZone("+23:00").getRawOffset());
Assert.assertEquals(HOURS_2, FastTimeZone.getGmtTimeZone("+2:00").getRawOffset());
Assert.assertEquals(-HOURS_23, FastTimeZone.getGmtTimeZone("-23:00").getRawOffset());
Assert.assertEquals(-HOURS_2, FastTimeZone.getGmtTimeZone("-2:00").getRawOffset());
}
@Test
public void testHoursColonMinutes() {
Assert.assertEquals(HOURS_23, FastTimeZone.getGmtTimeZone("23:00").getRawOffset());
Assert.assertEquals(HOURS_2, FastTimeZone.getGmtTimeZone("2:00").getRawOffset());
Assert.assertEquals(MINUTES_59, FastTimeZone.getGmtTimeZone("00:59").getRawOffset());
Assert.assertEquals(MINUTES_5, FastTimeZone.getGmtTimeZone("00:5").getRawOffset());
Assert.assertEquals(HOURS_23+MINUTES_59, FastTimeZone.getGmtTimeZone("23:59").getRawOffset());
Assert.assertEquals(HOURS_2+MINUTES_5, FastTimeZone.getGmtTimeZone("2:5").getRawOffset());
}
@Test
public void testHoursMinutes() {
Assert.assertEquals(HOURS_23, FastTimeZone.getGmtTimeZone("2300").getRawOffset());
Assert.assertEquals(HOURS_2, FastTimeZone.getGmtTimeZone("0200").getRawOffset());
Assert.assertEquals(MINUTES_59, FastTimeZone.getGmtTimeZone("0059").getRawOffset());
Assert.assertEquals(MINUTES_5, FastTimeZone.getGmtTimeZone("0005").getRawOffset());
Assert.assertEquals(HOURS_23+MINUTES_59, FastTimeZone.getGmtTimeZone("2359").getRawOffset());
Assert.assertEquals(HOURS_2+MINUTES_5, FastTimeZone.getGmtTimeZone("0205").getRawOffset());
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.commons.lang3.time;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests for GmtTimeZone
*/
public class GmtTimeZoneTest {
@Test(expected = IllegalArgumentException.class)
public void hoursOutOfRange() {
new GmtTimeZone(false, 24, 0);
}
@Test
public void hoursInRange() {
Assert.assertEquals(23 * 60 * 60 * 1000, new GmtTimeZone(false, 23, 0).getRawOffset());
}
@Test(expected = IllegalArgumentException.class)
public void minutesOutOfRange() {
new GmtTimeZone(false, 0, 60);
}
@Test
public void minutesInRange() {
Assert.assertEquals(59 * 60 * 1000, new GmtTimeZone(false, 0, 59).getRawOffset());
}
@Test
public void getOffset() {
Assert.assertEquals(0, new GmtTimeZone(false, 0, 0).getOffset(234304));
}
@Test(expected = UnsupportedOperationException.class)
public void setRawOffset() {
new GmtTimeZone(false, 0, 0).setRawOffset(0);
}
@Test
public void getRawOffset() {
Assert.assertEquals(0, new GmtTimeZone(false, 0, 0).getRawOffset());
}
@Test
public void getID() {
Assert.assertEquals("GMT+00:00", new GmtTimeZone(false, 0, 0).getID());
Assert.assertEquals("GMT+01:02", new GmtTimeZone(false, 1, 2).getID());
Assert.assertEquals("GMT+11:22", new GmtTimeZone(false, 11, 22).getID());
Assert.assertEquals("GMT-01:02", new GmtTimeZone(true, 1, 2).getID());
Assert.assertEquals("GMT-11:22", new GmtTimeZone(true, 11, 22).getID());
}
@Test
public void useDaylightTime() {
Assert.assertFalse(new GmtTimeZone(false, 0, 0).useDaylightTime());
}
@Test
public void inDaylightTime() {
Assert.assertFalse(new GmtTimeZone(false, 0, 0).useDaylightTime());
}
@Test
public void testToString() {
Assert.assertEquals("[GmtTimeZone id=\"GMT-12:00\",offset=-43200000]",
new GmtTimeZone(true, 12, 0).toString());
}
@Test
public void testGetOffset() {
Assert.assertEquals(-(6 * 60 + 30) * 60 * 1000,
new GmtTimeZone(true, 6, 30).getOffset(1, 1, 1, 1, 1, 1));
}
}