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