/*
* Copyright 2011 Yahoo! Inc
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.commons.jconfig.datatype;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
/**
* Physical quantities with units of time, and 64-bit integer values.
*
* @author sgrennan
*
*/
public final class TimeValue implements java.io.Serializable, Comparable<TimeValue> {
/**
* Creates a time quantity with the specified numeric value and units.
*
* @param value the magnitude of the time interval (positive, negative or zero)
* @param timeUnit the units of the interval
*/
public TimeValue(final long value, final TimeUnit timeUnit) {
super();
this.value = value;
this.timeUnit = timeUnit;
}
/**
* Returns a time value equal to the current JVM time.
*
* @return the current time
*/
public static TimeValue now() {
return new TimeValue(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
/**
* Compares this value to {@code o}, returning 1, 0 or -1 as this value is greater
* than, equal to or less than {@code o}, respectively. (Implementation of
* {@link Comparable}.
*
* @return -1, 0 or 1 as this value is less than, equal to or greater than {@code o}.
* @throws IllegalArgumentException if {@code o} is null.
*/
@Override
public int compareTo(@Nonnull TimeValue o) {
if (o == null)
throw new IllegalArgumentException("Cannot compare to null TimeValue.");
// Same units? Just compare values.
int unitCompare = timeUnit.compareTo(o.timeUnit);
if (unitCompare == 0) {
if (this.value < o.value) return -1;
if (this.value > o.value) return 1;
return 0;
}
// Convert larger-united quantity to smaller units.
TimeValue bigU = (unitCompare < 0) ? o : this;
TimeValue smallU = (unitCompare < 0) ? this : o;
long convertedValue = smallU.timeUnit.convert(bigU.value, bigU.timeUnit);
/*
* Is the converted value now greater or smaller? Overflow or underflow means the
* larger-united quantity cannot be represented in the smaller units, which means
* it has to be greater or smaller, respectively.
* NB: it is not possible for Long.MAX_VALUE or Long.MIN-VALUE to be generated
* by any unit conversion, as they are not evenly divisible by 24, 60 or 1000.
*/
if (convertedValue == Long.MAX_VALUE || convertedValue > smallU.value)
return (bigU == this) ? 1 : -1;
if (convertedValue == Long.MIN_VALUE || convertedValue < smallU.value)
return (bigU == this) ? -1 : 1;
return 0;
}
/**
* A regular expression separating the configuration string into a whole number
* and units, in such a way that the number will parse as a long without
* exception, and the units specifier cannot be null or missing (but may be
* any case).
* TODO fsg 110504 Add floating-point.
*/
private static Pattern timeValueRegex = Pattern.compile("\\s*(\\d+)(\\s*)([a-zA-Z]+)\\s*");
private final long value;
private final TimeUnit timeUnit;
public long getValue() {
return value;
}
public TimeUnit getTimeUnit() {
return timeUnit;
}
public long toDays() {
return timeUnit.toDays(value);
}
public long toHours() {
return timeUnit.toHours(value);
}
public long toMicros() {
return timeUnit.toMicros(value);
}
public long toMillis() {
return timeUnit.toMillis(value);
}
public long toMinutes() {
return timeUnit.toMinutes(value);
}
public long toSeconds() {
return timeUnit.toSeconds(value);
}
@Override
public boolean equals(final Object that) {
if (!(that instanceof TimeValue))
return false;
return compareTo((TimeValue)that) == 0;
}
public static TimeValue parse(final String value) throws TypeFormatException {
Matcher matcher = timeValueRegex.matcher(value);
if (matcher.find()) {
long longVal = Long.parseLong(matcher.group(1));
TimeUnit units = abbreviationToUnit.get(matcher.group(3).toLowerCase(Locale.US));
if (units != null) {
return new TimeValue(longVal, units);
} else {
throw new TypeFormatException("Time units [d,h,m,s,ms,us,ns] missing from '" + value + "'");
}
} else {
throw new TypeFormatException("Unable to parse time 'NUMBER d|h|m|s|ms|us|ns' from '" + value + "'");
}
}
@Override
public String toString() {
return value + " " + unitToAbbreviation.get(timeUnit);
}
private static final HashMap<TimeUnit,String> unitToAbbreviation = new HashMap<TimeUnit,String>();
static {
unitToAbbreviation.put(TimeUnit.NANOSECONDS, "ns");
unitToAbbreviation.put(TimeUnit.MICROSECONDS, "us");
unitToAbbreviation.put(TimeUnit.MILLISECONDS, "ms");
unitToAbbreviation.put(TimeUnit.SECONDS, "s");
unitToAbbreviation.put(TimeUnit.MINUTES, "m");
unitToAbbreviation.put(TimeUnit.HOURS, "h");
unitToAbbreviation.put(TimeUnit.DAYS, "d");
}
private static final HashMap<String,TimeUnit> abbreviationToUnit = new HashMap<String,TimeUnit>();
static {
abbreviationToUnit.put("ns", TimeUnit.NANOSECONDS);
abbreviationToUnit.put("us", TimeUnit.MICROSECONDS);
abbreviationToUnit.put("ms", TimeUnit.MILLISECONDS);
abbreviationToUnit.put("s", TimeUnit.SECONDS);
abbreviationToUnit.put("m", TimeUnit.MINUTES);
abbreviationToUnit.put("h", TimeUnit.HOURS);
abbreviationToUnit.put("d", TimeUnit.DAYS);
}
/** Required by Serializable. */
private static final long serialVersionUID = 8439678036575756604L;
}