From 8ddb22836b5bfeec584ee47904c1f8d1715e6a99 Mon Sep 17 00:00:00 2001 From: Davide Angelocola Date: Sat, 19 Feb 2022 09:01:58 +0100 Subject: [PATCH] Add support for java.util.Duration in @Value --- .../beans/PropertyEditorRegistrySupport.java | 3 ++ .../beans/propertyeditors/DurationEditor.java | 47 +++++++++++++++++++ .../org/springframework/util/StringUtils.java | 22 +++++++++ .../util/StringUtilsTests.java | 8 ++++ 4 files changed, 80 insertions(+) create mode 100644 spring-beans/src/main/java/org/springframework/beans/propertyeditors/DurationEditor.java diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java index d62fc5d83b07..8c61a62bc669 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java @@ -26,6 +26,7 @@ import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; +import java.time.Duration; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; @@ -57,6 +58,7 @@ import org.springframework.beans.propertyeditors.CustomCollectionEditor; import org.springframework.beans.propertyeditors.CustomMapEditor; import org.springframework.beans.propertyeditors.CustomNumberEditor; +import org.springframework.beans.propertyeditors.DurationEditor; import org.springframework.beans.propertyeditors.FileEditor; import org.springframework.beans.propertyeditors.InputSourceEditor; import org.springframework.beans.propertyeditors.InputStreamEditor; @@ -216,6 +218,7 @@ private void createDefaultEditors() { this.defaultEditors.put(Class.class, new ClassEditor()); this.defaultEditors.put(Class[].class, new ClassArrayEditor()); this.defaultEditors.put(Currency.class, new CurrencyEditor()); + this.defaultEditors.put(Duration.class, new DurationEditor()); this.defaultEditors.put(File.class, new FileEditor()); this.defaultEditors.put(InputStream.class, new InputStreamEditor()); if (!shouldIgnoreXml) { diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/DurationEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/DurationEditor.java new file mode 100644 index 000000000000..06a3219346ba --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/DurationEditor.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 + * + * https://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.springframework.beans.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.time.Duration; +import java.util.Objects; + +import org.springframework.util.StringUtils; + +/** + * Editor for {@code java.time.Duration}, translating durations into + * {@code Duration} objects. Exposes the {@code Duration} ISO as a text + * representation. + * + * @author Davide Angelocola + * @since 3.0 + * @see Duration + */ +public class DurationEditor extends PropertyEditorSupport { + + @Override + public void setAsText(String text) throws IllegalArgumentException { + setValue(StringUtils.parseDuration(text)); + } + + @Override + public String getAsText() { + Duration value = (Duration) getValue(); + return Objects.toString(value, ""); + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index 50014a3d6655..b33531c4a996 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java @@ -18,6 +18,8 @@ import java.io.ByteArrayOutputStream; import java.nio.charset.Charset; +import java.time.Duration; +import java.time.format.DateTimeParseException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -37,6 +39,7 @@ import org.springframework.lang.Nullable; + /** * Miscellaneous {@link String} utility methods. * @@ -891,6 +894,25 @@ public static TimeZone parseTimeZoneString(String timeZoneString) { return timeZone; } + /** + * Parse the given {@code durationString} value into a {@link Duration}. + * @param durationString the duration {@code String}, following ISO 8601 format starting with "PT". It is + * also possible to skip PT prefix (i.e. both "PT10s" and "10s" are accepted). + * @return a corresponding {@link Duration} instance or null if @{code durationString} is null. + * @throws DateTimeParseException in case of an invalid duration + */ + @Nullable + public static Duration parseDuration(@Nullable String durationString) { + if (durationString == null) { + return null; + } + else if (durationString.startsWith("PT")) { + return Duration.parse(durationString); + } + else { + return Duration.parse("PT" + durationString); + } + } //--------------------------------------------------------------------- // Convenience methods for working with String arrays diff --git a/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java b/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java index c484f3f2f12a..6adbc2f03015 100644 --- a/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java @@ -16,6 +16,7 @@ package org.springframework.util; +import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.Locale; @@ -773,4 +774,11 @@ void collectionToDelimitedStringWithNullValuesShouldNotFail() { assertThat(StringUtils.collectionToCommaDelimitedString(Collections.singletonList(null))).isEqualTo("null"); } + @Test + void parseDuration() { + assertThat(StringUtils.parseDuration(null)).isNull(); + assertThat(StringUtils.parseDuration("PT1s")).isEqualTo(Duration.ofSeconds(1)); + assertThat(StringUtils.parseDuration("1s")).isEqualTo(Duration.ofSeconds(1)); + } + }