Skip to content

Commit d0bcb7c

Browse files
committed
Trying different approach without Unit and externalizing parsing code
1 parent 6001d01 commit d0bcb7c

File tree

4 files changed

+234
-274
lines changed

4 files changed

+234
-274
lines changed

spring-context/src/main/java/org/springframework/format/annotation/DurationFormat.java

Lines changed: 16 additions & 262 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,10 @@
2323
import java.lang.annotation.Target;
2424
import java.time.Duration;
2525
import java.time.temporal.ChronoUnit;
26-
import java.util.function.Function;
27-
import java.util.regex.Matcher;
28-
import java.util.regex.Pattern;
29-
30-
import org.springframework.lang.Nullable;
31-
import org.springframework.util.Assert;
32-
import org.springframework.util.StringUtils;
3326

3427
/**
3528
* Declares that a field or method parameter should be formatted as a {@link java.time.Duration},
36-
* either using the default JDK parsing and printing of {@code Duration} or a simplified
37-
* representation.
29+
* according to the specified {@code style}.
3830
*
3931
* @author Simon Baslé
4032
* @since 6.1
@@ -51,273 +43,35 @@
5143
Style style() default Style.ISO8601;
5244

5345
/**
54-
* Define which {@code Unit} to fall back to in case the {@code style()}
46+
* Define which {@code ChronoUnit} to fall back to in case the {@code style()}
5547
* needs a unit for either parsing or printing, and none is explicitly provided in
56-
* the input. Can also be used to convert a number to a {@code Duration}.
48+
* the input.
5749
*/
58-
Unit defaultUnit() default Unit.MILLIS;
50+
ChronoUnit defaultUnit() default ChronoUnit.MILLIS;
5951

6052
/**
6153
* Duration format styles.
62-
*
63-
* @author Phillip Webb
64-
* @author Valentine Wu
6554
*/
6655
enum Style {
6756

6857
/**
69-
* Simple formatting, for example '1s'.
58+
* Simple formatting based on a short suffix, for example '1s'.
59+
* Supported unit suffixes are: {@code ns, us, ms, s, m, h, d}.
60+
* This corresponds to nanoseconds, microseconds, milliseconds, seconds,
61+
* minutes, hours and days respectively.
62+
* <p>Note that when printing a {@code Duration}, this style can be lossy if the
63+
* selected unit is bigger than the resolution of the duration. For example,
64+
* {@code Duration.ofMillis(5).plusNanos(1234)} would get truncated to {@code "5ms"}
65+
* when printing using {@code ChronoUnit.MILLIS}.
7066
*/
71-
SIMPLE("^([+-]?\\d+)([a-zA-Z]{0,2})$") {
72-
73-
@Override
74-
public Duration parse(String value, @Nullable Unit unit) {
75-
try {
76-
Matcher matcher = matcher(value);
77-
Assert.state(matcher.matches(), "Does not match simple duration pattern");
78-
String suffix = matcher.group(2);
79-
Unit parsingUnit = (unit == null ? Unit.MILLIS : unit);
80-
if (StringUtils.hasLength(suffix)) {
81-
parsingUnit = Unit.fromSuffix(suffix);
82-
}
83-
return parsingUnit.parse(matcher.group(1));
84-
}
85-
catch (Exception ex) {
86-
throw new IllegalArgumentException("'" + value + "' is not a valid simple duration", ex);
87-
}
88-
}
89-
90-
@Override
91-
public String print(Duration value, @Nullable Unit unit) {
92-
return (unit == null ? Unit.MILLIS : unit).print(value);
93-
}
94-
95-
},
67+
SIMPLE,
9668

9769
/**
9870
* ISO-8601 formatting.
71+
* <p>This is what the JDK uses in {@link java.time.Duration#parse(CharSequence)}
72+
* and {@link Duration#toString()}.
9973
*/
100-
ISO8601("^[+-]?[pP].*$") {
101-
@Override
102-
public Duration parse(String value) {
103-
try {
104-
return Duration.parse(value);
105-
}
106-
catch (Exception ex) {
107-
throw new IllegalArgumentException("'" + value + "' is not a valid ISO-8601 duration", ex);
108-
}
109-
}
110-
111-
@Override
112-
public Duration parse(String value, @Nullable Unit unit) {
113-
return parse(value);
114-
}
115-
116-
@Override
117-
public String print(Duration value, @Nullable Unit unit) {
118-
return value.toString();
119-
}
120-
121-
};
122-
123-
private final Pattern pattern;
124-
125-
Style(String pattern) {
126-
this.pattern = Pattern.compile(pattern);
127-
}
128-
129-
protected final boolean matches(String value) {
130-
return this.pattern.matcher(value).matches();
131-
}
132-
133-
protected final Matcher matcher(String value) {
134-
return this.pattern.matcher(value);
135-
}
136-
137-
/**
138-
* Parse the given value to a duration.
139-
* @param value the value to parse
140-
* @return a duration
141-
*/
142-
public Duration parse(String value) {
143-
return parse(value, null);
144-
}
145-
146-
/**
147-
* Parse the given value to a duration.
148-
* @param value the value to parse
149-
* @param unit the duration unit to use if the value doesn't specify one ({@code null}
150-
* will default to ms)
151-
* @return a duration
152-
*/
153-
public abstract Duration parse(String value, @Nullable Unit unit);
154-
155-
/**
156-
* Print the specified duration.
157-
* @param value the value to print
158-
* @return the printed result
159-
*/
160-
public String print(Duration value) {
161-
return print(value, null);
162-
}
163-
164-
/**
165-
* Print the specified duration using the given unit.
166-
* @param value the value to print
167-
* @param unit the value to use for printing, if relevant
168-
* @return the printed result
169-
*/
170-
public abstract String print(Duration value, @Nullable Unit unit);
171-
172-
/**
173-
* Detect the style then parse the value to return a duration.
174-
* @param value the value to parse
175-
* @return the parsed duration
176-
* @throws IllegalArgumentException if the value is not a known style or cannot be
177-
* parsed
178-
*/
179-
public static Duration detectAndParse(String value) {
180-
return detectAndParse(value, null);
181-
}
182-
183-
/**
184-
* Detect the style then parse the value to return a duration.
185-
* @param value the value to parse
186-
* @param unit the duration unit to use if the value doesn't specify one ({@code null}
187-
* will default to ms)
188-
* @return the parsed duration
189-
* @throws IllegalArgumentException if the value is not a known style or cannot be
190-
* parsed
191-
*/
192-
public static Duration detectAndParse(String value, @Nullable Unit unit) {
193-
return detect(value).parse(value, unit);
194-
}
195-
196-
/**
197-
* Detect the style from the given source value.
198-
* @param value the source value
199-
* @return the duration style
200-
* @throws IllegalArgumentException if the value is not a known style
201-
*/
202-
public static Style detect(String value) {
203-
Assert.notNull(value, "Value must not be null");
204-
for (Style candidate : values()) {
205-
if (candidate.matches(value)) {
206-
return candidate;
207-
}
208-
}
209-
throw new IllegalArgumentException("'" + value + "' is not a valid duration");
210-
}
211-
}
212-
213-
/**
214-
* Duration format units, similar to {@code ChronoUnit} with additional meta-data like
215-
* a short String {@link #asSuffix()}.
216-
* <p>This enum helps to deal with units when {@link #parse(String) parsing} or
217-
* {@link #print(Duration) printing} {@code Durations}, allows conversion {@link #asChronoUnit() to}
218-
* and {@link #fromChronoUnit(ChronoUnit) from} {@code ChronoUnit}, as well as to a
219-
* {@link #toLongValue(Duration) long} representation.
220-
* <p>The short suffix in particular is mostly relevant in the {@link Style#SIMPLE SIMPLE}
221-
* {@code Style}.
222-
*
223-
* @author Phillip Webb
224-
* @author Valentine Wu
225-
* @author Simon Baslé
226-
*/
227-
enum Unit { //TODO increase javadoc coverage
228-
229-
/**
230-
* Nanoseconds.
231-
*/
232-
NANOS(ChronoUnit.NANOS, "ns", Duration::toNanos),
233-
234-
/**
235-
* Microseconds.
236-
*/
237-
MICROS(ChronoUnit.MICROS, "us", duration -> duration.toNanos() / 1000L),
238-
239-
/**
240-
* Milliseconds.
241-
*/
242-
MILLIS(ChronoUnit.MILLIS, "ms", Duration::toMillis),
243-
244-
/**
245-
* Seconds.
246-
*/
247-
SECONDS(ChronoUnit.SECONDS, "s", Duration::getSeconds),
248-
249-
/**
250-
* Minutes.
251-
*/
252-
MINUTES(ChronoUnit.MINUTES, "m", Duration::toMinutes),
253-
254-
/**
255-
* Hours.
256-
*/
257-
HOURS(ChronoUnit.HOURS, "h", Duration::toHours),
258-
259-
/**
260-
* Days.
261-
*/
262-
DAYS(ChronoUnit.DAYS, "d", Duration::toDays);
263-
264-
private final ChronoUnit chronoUnit;
265-
266-
private final String suffix;
267-
268-
private final Function<Duration, Long> longValue;
269-
270-
Unit(ChronoUnit chronoUnit, String suffix, Function<Duration, Long> toUnit) {
271-
this.chronoUnit = chronoUnit;
272-
this.suffix = suffix;
273-
this.longValue = toUnit;
274-
}
275-
276-
//note: newly defined accessors
277-
public ChronoUnit asChronoUnit() {
278-
return this.chronoUnit;
279-
}
280-
281-
public String asSuffix() {
282-
return this.suffix;
283-
}
284-
285-
public Duration parse(String value) {
286-
return Duration.of(Long.parseLong(value), this.chronoUnit);
287-
}
288-
289-
public String print(Duration value) {
290-
return toLongValue(value) + this.suffix;
291-
}
292-
293-
public long toLongValue(Duration value) {
294-
// Note: This method signature / name is similar but not exactly equal to Boot's version.
295-
// There's no way to have the Boot enum inherit this one, so we just need to maintain a compatible feature set
296-
return this.longValue.apply(value);
297-
}
298-
299-
public static Unit fromChronoUnit(@Nullable ChronoUnit chronoUnit) {
300-
if (chronoUnit == null) {
301-
return Unit.MILLIS;
302-
}
303-
for (Unit candidate : values()) {
304-
if (candidate.chronoUnit == chronoUnit) {
305-
return candidate;
306-
}
307-
}
308-
throw new IllegalArgumentException("Unknown unit " + chronoUnit);
309-
}
310-
311-
public static Unit fromSuffix(String suffix) {
312-
for (Unit candidate : values()) {
313-
if (candidate.suffix.equalsIgnoreCase(suffix)) {
314-
return candidate;
315-
}
316-
}
317-
throw new IllegalArgumentException("Unknown unit '" + suffix + "'");
318-
}
319-
74+
ISO8601;
32075
}
32176

322-
32377
}

spring-context/src/main/java/org/springframework/format/datetime/standard/DurationFormatter.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.text.ParseException;
2020
import java.time.Duration;
21+
import java.time.temporal.ChronoUnit;
2122
import java.util.Locale;
2223

2324
import org.springframework.format.Formatter;
@@ -27,18 +28,18 @@
2728
/**
2829
* {@link Formatter} implementation for a JSR-310 {@link Duration},
2930
* following JSR-310's parsing rules for a Duration by default and
30-
* supporting additional {@code DurationStyle} styles.
31+
* supporting additional {@code DurationFormat.Style} styles.
3132
*
3233
* @author Juergen Hoeller
3334
* @since 4.2.4
34-
* @see Duration#parse
35+
* @see DurationFormatterUtils
3536
* @see DurationFormat.Style
3637
*/
3738
class DurationFormatter implements Formatter<Duration> { //TODO why is this one package-private ? make public and change since taglet ?
3839

3940
private final DurationFormat.Style style;
4041
@Nullable
41-
private final DurationFormat.Unit defaultUnit;
42+
private final ChronoUnit defaultUnit;
4243

4344
/**
4445
* Create a {@code DurationFormatter} following JSR-310's parsing rules for a Duration
@@ -59,17 +60,17 @@ public DurationFormatter(DurationFormat.Style style) {
5960

6061
/**
6162
* Create a {@code DurationFormatter} in a specific {@link DurationFormat.Style} with an
62-
* optional {@code DurationFormat.Unit}.
63+
* optional {@code ChronoUnit}.
6364
* <p>If a {@code defaultUnit} is specified, it may be used in parsing cases when no
6465
* unit is present in the string (provided the style allows for such a case). It will
6566
* also be used as the representation's resolution when printing in the
6667
* {@link DurationFormat.Style#SIMPLE} style. Otherwise, the style defines its default
6768
* unit.
6869
*
6970
* @param style the {@code DurationStyle} to use
70-
* @param defaultUnit the {@code DurationFormat.Unit} to fall back to when parsing and printing
71+
* @param defaultUnit the {@code ChronoUnit} to fall back to when parsing and printing
7172
*/
72-
public DurationFormatter(DurationFormat.Style style, @Nullable DurationFormat.Unit defaultUnit) {
73+
public DurationFormatter(DurationFormat.Style style, @Nullable ChronoUnit defaultUnit) {
7374
this.style = style;
7475
this.defaultUnit = defaultUnit;
7576
}
@@ -78,18 +79,18 @@ public DurationFormatter(DurationFormat.Style style, @Nullable DurationFormat.Un
7879
public Duration parse(String text, Locale locale) throws ParseException {
7980
if (this.defaultUnit == null) {
8081
//delegate to the style
81-
return this.style.parse(text);
82+
return DurationFormatterUtils.parse(text, this.style);
8283
}
83-
return this.style.parse(text, this.defaultUnit);
84+
return DurationFormatterUtils.parse(text, this.style, this.defaultUnit);
8485
}
8586

8687
@Override
8788
public String print(Duration object, Locale locale) {
8889
if (this.defaultUnit == null) {
8990
//delegate the ultimate of the default unit to the style
90-
return this.style.print(object);
91+
return DurationFormatterUtils.print(object, this.style);
9192
}
92-
return this.style.print(object, this.defaultUnit);
93+
return DurationFormatterUtils.print(object, this.style, this.defaultUnit);
9394
}
9495

9596
}

0 commit comments

Comments
 (0)