|
23 | 23 | import java.lang.annotation.Target;
|
24 | 24 | import java.time.Duration;
|
25 | 25 | 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; |
33 | 26 |
|
34 | 27 | /**
|
35 | 28 | * 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}. |
38 | 30 | *
|
39 | 31 | * @author Simon Baslé
|
40 | 32 | * @since 6.1
|
|
51 | 43 | Style style() default Style.ISO8601;
|
52 | 44 |
|
53 | 45 | /**
|
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()} |
55 | 47 | * 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. |
57 | 49 | */
|
58 |
| - Unit defaultUnit() default Unit.MILLIS; |
| 50 | + ChronoUnit defaultUnit() default ChronoUnit.MILLIS; |
59 | 51 |
|
60 | 52 | /**
|
61 | 53 | * Duration format styles.
|
62 |
| - * |
63 |
| - * @author Phillip Webb |
64 |
| - * @author Valentine Wu |
65 | 54 | */
|
66 | 55 | enum Style {
|
67 | 56 |
|
68 | 57 | /**
|
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}. |
70 | 66 | */
|
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, |
96 | 68 |
|
97 | 69 | /**
|
98 | 70 | * ISO-8601 formatting.
|
| 71 | + * <p>This is what the JDK uses in {@link java.time.Duration#parse(CharSequence)} |
| 72 | + * and {@link Duration#toString()}. |
99 | 73 | */
|
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; |
320 | 75 | }
|
321 | 76 |
|
322 |
| - |
323 | 77 | }
|
0 commit comments