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