001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.convert; 018 019import java.time.Duration; 020import java.time.temporal.ChronoUnit; 021import java.util.function.Function; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import org.springframework.util.Assert; 026import org.springframework.util.StringUtils; 027 028/** 029 * Duration format styles. 030 * 031 * @author Phillip Webb 032 * @since 2.0.0 033 */ 034public enum DurationStyle { 035 036 /** 037 * Simple formatting, for example '1s'. 038 */ 039 SIMPLE("^([\\+\\-]?\\d+)([a-zA-Z]{0,2})$") { 040 041 @Override 042 public Duration parse(String value, ChronoUnit unit) { 043 try { 044 Matcher matcher = matcher(value); 045 Assert.state(matcher.matches(), "Does not match simple duration pattern"); 046 String suffix = matcher.group(2); 047 return (StringUtils.hasLength(suffix) ? Unit.fromSuffix(suffix) 048 : Unit.fromChronoUnit(unit)).parse(matcher.group(1)); 049 } 050 catch (Exception ex) { 051 throw new IllegalArgumentException( 052 "'" + value + "' is not a valid simple duration", ex); 053 } 054 } 055 056 @Override 057 public String print(Duration value, ChronoUnit unit) { 058 return Unit.fromChronoUnit(unit).print(value); 059 } 060 061 }, 062 063 /** 064 * ISO-8601 formatting. 065 */ 066 ISO8601("^[\\+\\-]?P.*$") { 067 068 @Override 069 public Duration parse(String value, ChronoUnit unit) { 070 try { 071 return Duration.parse(value); 072 } 073 catch (Exception ex) { 074 throw new IllegalArgumentException( 075 "'" + value + "' is not a valid ISO-8601 duration", ex); 076 } 077 } 078 079 @Override 080 public String print(Duration value, ChronoUnit unit) { 081 return value.toString(); 082 } 083 084 }; 085 086 private final Pattern pattern; 087 088 DurationStyle(String pattern) { 089 this.pattern = Pattern.compile(pattern); 090 } 091 092 protected final boolean matches(String value) { 093 return this.pattern.matcher(value).matches(); 094 } 095 096 protected final Matcher matcher(String value) { 097 return this.pattern.matcher(value); 098 } 099 100 /** 101 * Parse the given value to a duration. 102 * @param value the value to parse 103 * @return a duration 104 */ 105 public Duration parse(String value) { 106 return parse(value, null); 107 } 108 109 /** 110 * Parse the given value to a duration. 111 * @param value the value to parse 112 * @param unit the duration unit to use if the value doesn't specify one ({@code null} 113 * will default to ms) 114 * @return a duration 115 */ 116 public abstract Duration parse(String value, ChronoUnit unit); 117 118 /** 119 * Print the specified duration. 120 * @param value the value to print 121 * @return the printed result 122 */ 123 public String print(Duration value) { 124 return print(value, null); 125 } 126 127 /** 128 * Print the specified duration using the given unit. 129 * @param value the value to print 130 * @param unit the value to use for printing 131 * @return the printed result 132 */ 133 public abstract String print(Duration value, ChronoUnit unit); 134 135 /** 136 * Detect the style then parse the value to return a duration. 137 * @param value the value to parse 138 * @return the parsed duration 139 * @throws IllegalStateException if the value is not a known style or cannot be parsed 140 */ 141 public static Duration detectAndParse(String value) { 142 return detectAndParse(value, null); 143 } 144 145 /** 146 * Detect the style then parse the value to return a duration. 147 * @param value the value to parse 148 * @param unit the duration unit to use if the value doesn't specify one ({@code null} 149 * will default to ms) 150 * @return the parsed duration 151 * @throws IllegalStateException if the value is not a known style or cannot be parsed 152 */ 153 public static Duration detectAndParse(String value, ChronoUnit unit) { 154 return detect(value).parse(value, unit); 155 } 156 157 /** 158 * Detect the style from the given source value. 159 * @param value the source value 160 * @return the duration style 161 * @throws IllegalStateException if the value is not a known style 162 */ 163 public static DurationStyle detect(String value) { 164 Assert.notNull(value, "Value must not be null"); 165 for (DurationStyle candidate : values()) { 166 if (candidate.matches(value)) { 167 return candidate; 168 } 169 } 170 throw new IllegalArgumentException("'" + value + "' is not a valid duration"); 171 } 172 173 /** 174 * Units that we support. 175 */ 176 enum Unit { 177 178 /** 179 * Nanoseconds. 180 */ 181 NANOS(ChronoUnit.NANOS, "ns", Duration::toNanos), 182 183 /** 184 * Microseconds. 185 */ 186 MICROS(ChronoUnit.MICROS, "us", (duration) -> duration.toMillis() * 1000L), 187 188 /** 189 * Milliseconds. 190 */ 191 MILLIS(ChronoUnit.MILLIS, "ms", Duration::toMillis), 192 193 /** 194 * Seconds. 195 */ 196 SECONDS(ChronoUnit.SECONDS, "s", Duration::getSeconds), 197 198 /** 199 * Minutes. 200 */ 201 MINUTES(ChronoUnit.MINUTES, "m", Duration::toMinutes), 202 203 /** 204 * Hours. 205 */ 206 HOURS(ChronoUnit.HOURS, "h", Duration::toHours), 207 208 /** 209 * Days. 210 */ 211 DAYS(ChronoUnit.DAYS, "d", Duration::toDays); 212 213 private final ChronoUnit chronoUnit; 214 215 private final String suffix; 216 217 private Function<Duration, Long> longValue; 218 219 Unit(ChronoUnit chronoUnit, String suffix, Function<Duration, Long> toUnit) { 220 this.chronoUnit = chronoUnit; 221 this.suffix = suffix; 222 this.longValue = toUnit; 223 } 224 225 public Duration parse(String value) { 226 return Duration.of(Long.valueOf(value), this.chronoUnit); 227 } 228 229 public String print(Duration value) { 230 return longValue(value) + this.suffix; 231 } 232 233 public long longValue(Duration value) { 234 return this.longValue.apply(value); 235 } 236 237 public static Unit fromChronoUnit(ChronoUnit chronoUnit) { 238 if (chronoUnit == null) { 239 return Unit.MILLIS; 240 } 241 for (Unit candidate : values()) { 242 if (candidate.chronoUnit == chronoUnit) { 243 return candidate; 244 } 245 } 246 throw new IllegalArgumentException("Unknown unit " + chronoUnit); 247 } 248 249 public static Unit fromSuffix(String suffix) { 250 for (Unit candidate : values()) { 251 if (candidate.suffix.equalsIgnoreCase(suffix)) { 252 return candidate; 253 } 254 } 255 throw new IllegalArgumentException("Unknown unit '" + suffix + "'"); 256 } 257 258 } 259 260}