All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dua3.utility.lang.Stopwatch Maven / Gradle / Ivy

There is a newer version: 15.0.2
Show newest version
// Copyright (c) 2019 Axel Howind
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

package com.dua3.utility.lang;

import java.time.Duration;
import java.time.Instant;
import java.util.Locale;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * A simple stopwatch class.
 */
public class Stopwatch {

    private final Object name;
    private final Instant start;
    private final Format format = Format.STANDARD;
    private Instant startSplit;

    /**
     * Construct instance.
     *
     * @param name the name for this instance; it is included in {@code toString()}
     */
    protected Stopwatch(String name) {
        this.name = name;
        this.start = this.startSplit = Instant.now();
    }

    /**
     * Construct instance.
     *
     * @param name the name for this instance; it is included in {@code toString()}
     */
    protected Stopwatch(Supplier name) {
        this.name = new LazyName(name);
        this.start = this.startSplit = Instant.now();
    }

    /**
     * Create new instance.
     *
     * @param name the name
     * @return new instance
     */
    public static Stopwatch create(String name) {
        return new Stopwatch(name);
    }

    /**
     * Create new instance.
     *
     * @param name the name supplier
     * @return new instance
     */
    public static Stopwatch create(Supplier name) {
        return new Stopwatch(name);
    }

    /**
     * Create new instance.
     *
     * @param name    the name
     * @param onClose the action to perform when close() is called
     * @return new instance
     */
    public static AutoCloseableStopWatch create(String name, Consumer onClose) {
        return new AutoCloseableStopWatch(name, onClose);
    }

    /**
     * Create new instance.
     *
     * @param name    the name supplier
     * @param onClose the action to perform when close() is called
     * @return new instance
     */
    public static AutoCloseableStopWatch create(Supplier name, Consumer onClose) {
        return new AutoCloseableStopWatch(name, onClose);
    }

    /**
     * Get the name of this StopWatch instance.
     * @return the name of this instance
     */
    public String getName() {
        return name.toString();
    }

    /**
     * Get start instant.
     *
     * @return the instant when this stopwatch was created
     */
    public Instant getStart() {
        return start;
    }

    /**
     * Get start instant of current split.
     *
     * @return the instant of the last split set in this stopwatch or the instant when this stopwatch was created of no split has been set
     */
    public Instant getStartSplit() {
        return startSplit;
    }

    /**
     * Get elapsed time in current split.
     *
     * @param newSplit if true, start a new split
     * @return the duration since the start of the current split
     */
    public Duration elapsedSplit(boolean newSplit) {
        Instant now = Instant.now();
        Duration duration = Duration.between(startSplit, now);
        if (newSplit) {
            startSplit = now;
        }
        return duration;
    }

    /**
     * Get elapsed time.
     *
     * @return the duration since this instance was created
     */
    public Duration elapsed() {
        Instant now = Instant.now();
        return Duration.between(start, now);
    }

    @Override
    public String toString() {
        return "[" + name + "] current split: " + elapsedStringSplit(false) + " total: " + elapsedString();
    }

    /**
     * Get elapsed time as a string.
     *
     * @return string with elapsed time
     */
    public String elapsedString() {
        return format.format(elapsed());
    }

    /**
     * Get elapsed time in current split as a string.
     *
     * @param newSplit if true, reset the stopwatch
     * @return string with elapsed time
     */
    public String elapsedStringSplit(boolean newSplit) {
        return format.format(elapsedSplit(newSplit));
    }

    /**
     * Create a Supplier for use in log messages (formatting is only done when {@link Supplier#get()} is called).
     *
     * @param fmt the format to use
     * @return Supplier that returns the state of the stopwatch at time of invocation
     */
    public Object logElapsed(Format fmt) {
        Instant instant = Instant.now();
        return new Object() {
            @Override
            public String toString() {
                return fmt.format(Duration.between(start, instant));
            }
        };
    }

    /**
     * Create a Supplier for use in log messages (formatting is only done when {@link Supplier#get()} is called).
     *
     * @param fmt      the format to use
     * @param newSplit if true, start a new split
     * @return Supplier that returns the state of the stopwatch at time of invocation
     */
    public Object logElapsedSplit(Format fmt, boolean newSplit) {
        Instant startOfSplit = startSplit;
        Instant instant = Instant.now();
        return new ToStringProxy(fmt, startOfSplit, instant);
    }

    /**
     * Enum defining the different output formats.
     */
    public enum Format {
        /**
         * standard format, same as {@link #HOURS_MINUTES_SECONDS_MILLIS}.
         */
        STANDARD {
            @Override
            public String format(Duration d) {
                boolean negative = d.isNegative();

                long seconds = d.getSeconds();
                long absSeconds = Math.abs(seconds);
                int nano = Math.abs(d.getNano());

                long hr = absSeconds / 3600;
                long min = (absSeconds % 3600) / 60;
                double sec = (absSeconds % 60) + nano / 1_000_000_000.0;

                String positive = String.format(
                        Locale.ROOT,
                        "%d:%02d:%06.3f",
                        hr,
                        min,
                        sec);

                return negative ? "-" + positive : positive;
            }
        },
        /**
         * h:mm:ss.sss.
         */
        HOURS_MINUTES_SECONDS_MILLIS {
            @Override
            public String format(Duration d) {
                return STANDARD.format(d);
            }
        },
        /**
         * m:ss.sss.
         */
        MINUTES_SECONDS_MILLIS {
            @Override
            public String format(Duration d) {
                boolean negative = d.isNegative();

                long seconds = d.getSeconds();
                long absSeconds = Math.abs(seconds);
                int nano = Math.abs(d.getNano());

                long min = absSeconds / 60;
                double sec = (absSeconds % 60) + nano / 1_000_000_000.0;

                String positive = String.format(
                        Locale.ROOT,
                        "%dm:%06.3fs",
                        min,
                        sec);

                return negative ? "-" + positive : positive;
            }
        },
        /**
         * Seconds formatted as floating point value.
         */
        SECONDS_MILLIS {
            @Override
            public String format(Duration d) {
                boolean negative = d.isNegative();

                long seconds = d.getSeconds();
                long absSeconds = Math.abs(seconds);
                int nano = Math.abs(d.getNano());

                double sec = absSeconds + nano / 1_000_000_000.0;

                String positive = String.format(
                        Locale.ROOT,
                        "%.3fs",
                        sec);

                return negative ? "-" + positive : positive;
            }
        },
        /**
         * Milliseconds formatted as floating point value.
         */
        MILLIS {
            @Override
            public String format(Duration d) {
                boolean negative = d.isNegative();

                long seconds = d.getSeconds();
                long absSeconds = Math.abs(seconds);
                int nano = Math.abs(d.getNano());

                double millis = (absSeconds + nano / 1_000_000_000.0) * 1000.0;

                String positive = String.format(
                        Locale.ROOT,
                        "%.3fms",
                        millis);

                return negative ? "-" + positive : positive;
            }
        };

        /**
         * Format a duration.
         *
         * @param d the duration
         * @return d formatted as a string
         */
        public abstract String format(Duration d);
    }

    /**
     * A stopwatch that can be used in a try-with-resources block and automatically closed.
     */
    public static class AutoCloseableStopWatch extends Stopwatch implements AutoCloseable {
        private final Consumer onClose;

        protected AutoCloseableStopWatch(String name, Consumer onClose) {
            super(name);
            this.onClose = onClose;
        }

        protected AutoCloseableStopWatch(Supplier name, Consumer onClose) {
            super(name);
            this.onClose = onClose;
        }

        @Override
        public void close() {
            onClose.accept(this);
        }
    }

    private static class LazyName {
        private final Supplier name;
        String n;

        public LazyName(Supplier name) {
            this.name = name;
        }

        @Override
        public String toString() {
            if (n == null) {
                n = name.get();
            }
            return n;
        }
    }

    private record ToStringProxy(Format fmt, Instant startOfSplit, Instant instant) {
        @Override
        public String toString() {
            return fmt.format(Duration.between(startOfSplit, instant));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy