com.google.caliper.util.ShortDuration.java Source code

Java tutorial

Introduction

Here is the source code for com.google.caliper.util.ShortDuration.java

Source

/*
 * 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);
    }
}