001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008 Sun Microsystems, Inc. 025 * Portions Copyright 2015 ForgeRock AS. 026 */ 027package org.forgerock.opendj.config; 028 029import java.util.HashMap; 030import java.util.Map; 031import java.util.regex.Matcher; 032import java.util.regex.Pattern; 033 034/** 035 * This enumeration defines various duration units. 036 */ 037public enum DurationUnit { 038 039 /** A day unit. */ 040 DAYS(24 * 60 * 60 * 1000, "d", "days"), 041 042 /** An hour unit. */ 043 HOURS(60 * 60 * 1000, "h", "hours"), 044 045 /** A millisecond unit. */ 046 MILLI_SECONDS(1L, "ms", "milliseconds"), 047 048 /** A minute unit. */ 049 MINUTES(60 * 1000, "m", "minutes"), 050 051 /** A second unit. */ 052 SECONDS(1000L, "s", "seconds"), 053 054 /** A week unit. */ 055 WEEKS(7 * 24 * 60 * 60 * 1000, "w", "weeks"); 056 057 /** A lookup table for resolving a unit from its name. */ 058 private static final Map<String, DurationUnit> NAME_TO_UNIT = new HashMap<>(); 059 static { 060 061 for (DurationUnit unit : DurationUnit.values()) { 062 NAME_TO_UNIT.put(unit.shortName, unit); 063 NAME_TO_UNIT.put(unit.longName, unit); 064 } 065 } 066 067 /** 068 * Get the unit corresponding to the provided unit name. 069 * 070 * @param s 071 * The name of the unit. Can be the abbreviated or long name and 072 * can contain white space and mixed case characters. 073 * @return Returns the unit corresponding to the provided unit name. 074 * @throws IllegalArgumentException 075 * If the provided name did not correspond to a known duration 076 * unit. 077 */ 078 public static DurationUnit getUnit(String s) { 079 DurationUnit unit = NAME_TO_UNIT.get(s.trim().toLowerCase()); 080 if (unit == null) { 081 throw new IllegalArgumentException("Illegal duration unit \"" + s + "\""); 082 } 083 return unit; 084 } 085 086 /** 087 * Parse the provided duration string and return its equivalent duration in 088 * milliseconds. The duration string must specify the unit e.g. "10s". This 089 * method will parse duration string representations produced from the 090 * {@link #toString(long)} method. Therefore, a duration can comprise of 091 * multiple duration specifiers, for example <code>1d15m25s</code>. 092 * 093 * @param s 094 * The duration string to be parsed. 095 * @return Returns the parsed duration in milliseconds. 096 * @throws NumberFormatException 097 * If the provided duration string could not be parsed. 098 * @see #toString(long) 099 */ 100 public static long parseValue(String s) { 101 return parseValue(s, null); 102 } 103 104 /** 105 * Parse the provided duration string and return its equivalent duration in 106 * milliseconds. This method will parse duration string representations 107 * produced from the {@link #toString(long)} method. Therefore, a duration 108 * can comprise of multiple duration specifiers, for example 109 * <code>1d15m25s</code>. 110 * 111 * @param s 112 * The duration string to be parsed. 113 * @param defaultUnit 114 * The default unit to use if there is no unit specified in the 115 * duration string, or <code>null</code> if the string must 116 * always contain a unit. 117 * @return Returns the parsed duration in milliseconds. 118 * @throws NumberFormatException 119 * If the provided duration string could not be parsed. 120 * @see #toString(long) 121 */ 122 public static long parseValue(String s, DurationUnit defaultUnit) { 123 String ns = s.trim(); 124 if (ns.length() == 0) { 125 throw new NumberFormatException("Empty duration value \"" + s + "\""); 126 } 127 128 Pattern p1 = 129 Pattern.compile("^\\s*((\\d+)\\s*w)?" + "\\s*((\\d+)\\s*d)?" + "\\s*((\\d+)\\s*h)?" 130 + "\\s*((\\d+)\\s*m)?" + "\\s*((\\d+)\\s*s)?" + "\\s*((\\d+)\\s*ms)?\\s*$", Pattern.CASE_INSENSITIVE); 131 Matcher m1 = p1.matcher(ns); 132 if (m1.matches()) { 133 // Value must be of the form produced by toString(long). 134 String weeks = m1.group(2); 135 String days = m1.group(4); 136 String hours = m1.group(6); 137 String minutes = m1.group(8); 138 String seconds = m1.group(10); 139 String ms = m1.group(12); 140 141 long duration = 0; 142 143 try { 144 if (weeks != null) { 145 duration += Long.valueOf(weeks) * WEEKS.getDuration(); 146 } 147 148 if (days != null) { 149 duration += Long.valueOf(days) * DAYS.getDuration(); 150 } 151 152 if (hours != null) { 153 duration += Long.valueOf(hours) * HOURS.getDuration(); 154 } 155 156 if (minutes != null) { 157 duration += Long.valueOf(minutes) * MINUTES.getDuration(); 158 } 159 160 if (seconds != null) { 161 duration += Long.valueOf(seconds) * SECONDS.getDuration(); 162 } 163 164 if (ms != null) { 165 duration += Long.valueOf(ms) * MILLI_SECONDS.getDuration(); 166 } 167 } catch (NumberFormatException e) { 168 throw new NumberFormatException("Invalid duration value \"" + s + "\""); 169 } 170 171 return duration; 172 } else { 173 // Value must be a floating point number followed by a unit. 174 Pattern p2 = Pattern.compile("^\\s*(\\d+(\\.\\d+)?)\\s*(\\w+)?\\s*$"); 175 Matcher m2 = p2.matcher(ns); 176 177 if (!m2.matches()) { 178 throw new NumberFormatException("Invalid duration value \"" + s + "\""); 179 } 180 181 // Group 1 is the float. 182 double d; 183 try { 184 d = Double.valueOf(m2.group(1)); 185 } catch (NumberFormatException e) { 186 throw new NumberFormatException("Invalid duration value \"" + s + "\""); 187 } 188 189 // Group 3 is the unit. 190 String unitString = m2.group(3); 191 DurationUnit unit; 192 if (unitString == null) { 193 if (defaultUnit == null) { 194 throw new NumberFormatException("Invalid duration value \"" + s + "\""); 195 } else { 196 unit = defaultUnit; 197 } 198 } else { 199 try { 200 unit = getUnit(unitString); 201 } catch (IllegalArgumentException e) { 202 throw new NumberFormatException("Invalid duration value \"" + s + "\""); 203 } 204 } 205 206 return unit.toMilliSeconds(d); 207 } 208 } 209 210 /** 211 * Returns a string representation of the provided duration. The string 212 * representation can be parsed using the {@link #parseValue(String)} 213 * method. The string representation is comprised of one or more of the 214 * number of weeks, days, hours, minutes, seconds, and milliseconds. Here 215 * are some examples: 216 * 217 * <pre> 218 * toString(0) // 0 ms 219 * toString(999) // 999 ms 220 * toString(1000) // 1 s 221 * toString(1500) // 1 s 500 ms 222 * toString(3650000) // 1 h 50 s 223 * toString(3700000) // 1 h 1 m 40 s 224 * </pre> 225 * 226 * @param duration 227 * The duration in milliseconds. 228 * @return Returns a string representation of the provided duration. 229 * @throws IllegalArgumentException 230 * If the provided duration is negative. 231 * @see #parseValue(String) 232 * @see #parseValue(String, DurationUnit) 233 */ 234 public static String toString(long duration) { 235 if (duration < 0) { 236 throw new IllegalArgumentException("Negative duration " + duration); 237 } 238 239 if (duration == 0) { 240 return "0 ms"; 241 } 242 243 DurationUnit[] units = new DurationUnit[] { WEEKS, DAYS, HOURS, MINUTES, SECONDS, MILLI_SECONDS }; 244 long remainder = duration; 245 StringBuilder builder = new StringBuilder(); 246 boolean isFirst = true; 247 for (DurationUnit unit : units) { 248 long count = remainder / unit.getDuration(); 249 if (count > 0) { 250 if (!isFirst) { 251 builder.append(' '); 252 } 253 builder.append(count); 254 builder.append(' '); 255 builder.append(unit.getShortName()); 256 remainder = remainder - (count * unit.getDuration()); 257 isFirst = false; 258 } 259 } 260 return builder.toString(); 261 } 262 263 /** The long name of the unit. */ 264 private final String longName; 265 266 /** The abbreviation of the unit. */ 267 private final String shortName; 268 269 /** The size of the unit in milliseconds. */ 270 private final long sz; 271 272 /** Private constructor. */ 273 private DurationUnit(long sz, String shortName, String longName) { 274 this.sz = sz; 275 this.shortName = shortName; 276 this.longName = longName; 277 } 278 279 /** 280 * Converts the specified duration in milliseconds to this unit. 281 * 282 * @param duration 283 * The duration in milliseconds. 284 * @return Returns milliseconds in this unit. 285 */ 286 public double fromMilliSeconds(long duration) { 287 return (double) duration / sz; 288 } 289 290 /** 291 * Get the number of milliseconds that this unit represents. 292 * 293 * @return Returns the number of milliseconds that this unit represents. 294 */ 295 public long getDuration() { 296 return sz; 297 } 298 299 /** 300 * Get the long name of this unit. 301 * 302 * @return Returns the long name of this unit. 303 */ 304 public String getLongName() { 305 return longName; 306 } 307 308 /** 309 * Get the abbreviated name of this unit. 310 * 311 * @return Returns the abbreviated name of this unit. 312 */ 313 public String getShortName() { 314 return shortName; 315 } 316 317 /** 318 * Converts the specified duration in this unit to milliseconds. 319 * 320 * @param duration 321 * The duration as a quantity of this unit. 322 * @return Returns the number of milliseconds that the duration represents. 323 */ 324 public long toMilliSeconds(double duration) { 325 return (long) (sz * duration); 326 } 327 328 /** 329 * {@inheritDoc} 330 * <p> 331 * This implementation returns the abbreviated name of this duration unit. 332 */ 333 @Override 334 public String toString() { 335 return shortName; 336 } 337}