Java tutorial
/* * Copyright (C) 2011 Google 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 com.google.caliper.util; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.primitives.Longs; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.Collections; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; /** * Represents a nonnegative duration from 0 to 100 days, with picosecond precision. * Contrast with Joda-Time's duration class, which has only millisecond precision but can * represent durations of millions of years. */ public abstract class ShortDuration implements Comparable<ShortDuration> { // Factories public static ShortDuration of(long duration, TimeUnit unit) { if (duration == 0) { return ZERO; } checkArgument(duration >= 0, "negative duration: %s", duration); checkArgument(duration <= MAXES.get(unit), "ShortDuration cannot exceed 100 days: %s %s", duration, unit); long nanos = TimeUnit.NANOSECONDS.convert(duration, unit); return new PositiveShortDuration(nanos * 1000); } public static ShortDuration of(BigDecimal duration, TimeUnit unit) { // convert to picoseconds first, to minimize rounding BigDecimal picos = duration.multiply(ONE_IN_PICOS.get(unit)); return ofPicos(toLong(picos, RoundingMode.HALF_UP)); } public static ShortDuration valueOf(String s) { if ("0".equals(s)) { return ZERO; } Matcher matcher = PATTERN.matcher(s); checkArgument(matcher.matches(), "Invalid ShortDuration: %s", s); BigDecimal value = new BigDecimal(matcher.group(1)); String abbrev = matcher.group(2); TimeUnit unit = ABBREV_TO_UNIT.get(abbrev); checkArgument(unit != null, "Unrecognized time unit: %s", abbrev); return of(value, unit); } public static ShortDuration zero() { return ZERO; } // fortunately no abbreviation starts with 'e', so this should work private static final Pattern PATTERN = Pattern.compile("^([0-9.eE+-]+) ?(\\S+)$"); private static ShortDuration ofPicos(long picos) { if (picos == 0) { return ZERO; } checkArgument(picos > 0); return new PositiveShortDuration(picos); } // TODO(kevinb): we sure seem to convert back and forth with BigDecimal a lot. // Why not just *make* this a BigDecimal? final long picos; ShortDuration(long picos) { this.picos = picos; } public long toPicos() { return picos; } public long to(TimeUnit unit) { return to(unit, RoundingMode.HALF_UP); } public abstract long to(TimeUnit unit, RoundingMode roundingMode); /* * In Guava, this will probably implement an interface called Quantity, and the following methods * will come from there, so they won't have to be defined here. */ /** * Returns an instance of this type that represents the sum of this value and {@code * addend}. */ public abstract ShortDuration plus(ShortDuration addend); /** * Returns an instance of this type that represents the difference of this value and * {@code subtrahend}. */ public abstract ShortDuration minus(ShortDuration subtrahend); /** * Returns an instance of this type that represents the product of this value and the * integral value {@code multiplicand}. */ public abstract ShortDuration times(long multiplicand); /** * Returns an instance of this type that represents the product of this value and {@code * multiplicand}, rounded according to {@code roundingMode} if necessary. * * <p>If this class represents an amount that is "continuous" rather than discrete, the * implementation of this method may simply ignore the rounding mode. */ public abstract ShortDuration times(BigDecimal multiplicand, RoundingMode roundingMode); /** * Returns an instance of this type that represents this value divided by the integral * value {@code divisor}, rounded according to {@code roundingMode} if necessary. * * <p>If this class represents an amount that is "continuous" rather than discrete, the * implementation of this method may simply ignore the rounding mode. */ public abstract ShortDuration dividedBy(long divisor, RoundingMode roundingMode); /** * Returns an instance of this type that represents this value divided by {@code * divisor}, rounded according to {@code roundingMode} if necessary. * * <p>If this class represents an amount that is "continuous" rather than discrete, the * implementation of this method may simply ignore the rounding mode. */ public abstract ShortDuration dividedBy(BigDecimal divisor, RoundingMode roundingMode); // Zero private static ShortDuration ZERO = new ShortDuration(0) { @Override public long to(TimeUnit unit, RoundingMode roundingMode) { return 0; } @Override public ShortDuration plus(ShortDuration addend) { return addend; } @Override public ShortDuration minus(ShortDuration subtrahend) { checkArgument(this == subtrahend); return this; } @Override public ShortDuration times(long multiplicand) { return this; } @Override public ShortDuration times(BigDecimal multiplicand, RoundingMode roundingMode) { return this; } @Override public ShortDuration dividedBy(long divisor, RoundingMode roundingMode) { return dividedBy(new BigDecimal(divisor), roundingMode); } @Override public ShortDuration dividedBy(BigDecimal divisor, RoundingMode roundingMode) { checkArgument(divisor.compareTo(BigDecimal.ZERO) != 0); return this; } @Override public int compareTo(ShortDuration that) { if (this == that) { return 0; } checkNotNull(that); return -1; } @Override public boolean equals(@Nullable Object that) { return this == that; } @Override public int hashCode() { return 0; } @Override public String toString() { return "0s"; } }; // Non-zero private static class PositiveShortDuration extends ShortDuration { private PositiveShortDuration(long picos) { super(picos); checkArgument(picos > 0); } @Override public long to(TimeUnit unit, RoundingMode roundingMode) { BigDecimal divisor = ONE_IN_PICOS.get(unit); return toLong(new BigDecimal(picos).divide(divisor), roundingMode); } @Override public ShortDuration plus(ShortDuration addend) { return new PositiveShortDuration(picos + addend.picos); } @Override public ShortDuration minus(ShortDuration subtrahend) { return ofPicos(picos - subtrahend.picos); } @Override public ShortDuration times(long multiplicand) { if (multiplicand == 0) { return ZERO; } checkArgument(multiplicand >= 0, "negative multiplicand: %s", multiplicand); checkArgument(multiplicand <= Long.MAX_VALUE / picos, "product of %s and %s would overflow", this, multiplicand); return new PositiveShortDuration(picos * multiplicand); } @Override public ShortDuration times(BigDecimal multiplicand, RoundingMode roundingMode) { BigDecimal product = BigDecimal.valueOf(picos).multiply(multiplicand); return ofPicos(toLong(product, roundingMode)); } @Override public ShortDuration dividedBy(long divisor, RoundingMode roundingMode) { return dividedBy(new BigDecimal(divisor), roundingMode); } @Override public ShortDuration dividedBy(BigDecimal divisor, RoundingMode roundingMode) { BigDecimal product = BigDecimal.valueOf(picos).divide(divisor, roundingMode); return ofPicos(product.longValueExact()); } @Override public int compareTo(ShortDuration that) { return Longs.compare(this.picos, that.picos); } @Override public boolean equals(Object object) { if (object instanceof PositiveShortDuration) { PositiveShortDuration that = (PositiveShortDuration) object; return this.picos == that.picos; } return false; } @Override public int hashCode() { return Longs.hashCode(picos); } @Override public String toString() { TimeUnit bestUnit = TimeUnit.NANOSECONDS; for (TimeUnit unit : TimeUnit.values()) { if (picosIn(unit) > picos) { break; } bestUnit = unit; } BigDecimal divisor = ONE_IN_PICOS.get(bestUnit); return new BigDecimal(picos).divide(divisor, ROUNDER) + preferredAbbrev(bestUnit); } private static final MathContext ROUNDER = new MathContext(4); } // Private parts private static String preferredAbbrev(TimeUnit bestUnit) { return ABBREVIATIONS.get(bestUnit).get(0); } private static final ImmutableListMultimap<TimeUnit, String> ABBREVIATIONS = createAbbreviations(); private static ImmutableListMultimap<TimeUnit, String> createAbbreviations() { ImmutableListMultimap.Builder<TimeUnit, String> builder = ImmutableListMultimap.builder(); builder.putAll(TimeUnit.NANOSECONDS, "ns", "nanos"); builder.putAll(TimeUnit.MICROSECONDS, "\u03bcs" /*s*/, "us", "micros"); builder.putAll(TimeUnit.MILLISECONDS, "ms", "millis"); builder.putAll(TimeUnit.SECONDS, "s", "sec"); // Do the rest in a JDK5-safe way TimeUnit[] allUnits = TimeUnit.values(); if (allUnits.length >= 7) { builder.putAll(allUnits[4], "m", "min"); builder.putAll(allUnits[5], "h", "hr"); builder.putAll(allUnits[6], "d"); } for (TimeUnit unit : TimeUnit.values()) { builder.put(unit, Ascii.toLowerCase(unit.name())); } return builder.build(); } private static final Map<String, TimeUnit> ABBREV_TO_UNIT = createAbbrevToUnitMap(); private static Map<String, TimeUnit> createAbbrevToUnitMap() { ImmutableMap.Builder<String, TimeUnit> builder = ImmutableMap.builder(); for (Map.Entry<TimeUnit, String> entry : ABBREVIATIONS.entries()) { builder.put(entry.getValue(), entry.getKey()); } return builder.build(); } private static final Map<TimeUnit, BigDecimal> ONE_IN_PICOS = createUnitToPicosMap(); private static Map<TimeUnit, BigDecimal> createUnitToPicosMap() { Map<TimeUnit, BigDecimal> map = Maps.newEnumMap(TimeUnit.class); for (TimeUnit unit : TimeUnit.values()) { map.put(unit, new BigDecimal(picosIn(unit))); } return Collections.unmodifiableMap(map); } private static final Map<TimeUnit, Long> MAXES = createMaxesMap(); private static Map<TimeUnit, Long> createMaxesMap() { Map<TimeUnit, Long> map = Maps.newEnumMap(TimeUnit.class); for (TimeUnit unit : TimeUnit.values()) { // Max is 100 days map.put(unit, unit.convert(100L * 24 * 60 * 60, TimeUnit.SECONDS)); } return Collections.unmodifiableMap(map); } private static long toLong(BigDecimal bd, RoundingMode roundingMode) { // setScale does not really mutate the BigDecimal return bd.setScale(0, roundingMode).longValueExact(); } private static long picosIn(TimeUnit unit) { return unit.toNanos(1000); } }