com.sworddance.util.perf.LapTimer.java Source code

Java tutorial

Introduction

Here is the source code for com.sworddance.util.perf.LapTimer.java

Source

/*
 * 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.sworddance.util.perf;

import java.io.*;
import java.rmi.dgc.VMID;
import java.text.FieldPosition;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.commons.lang.StringUtils;

import static com.sworddance.util.CUtilities.*;

/**
 * This class is a lightweight timer that lets developers perform many timing
 * operations including remote transmission of timers to collect data across many servers.
 * <p/>
 * A LapTimer is used to record a series of intervals (laps) between calls to {@link #start()}
 * and {@link #stop()} calls. When the LapTimer's {@link #toString()} is called the lap
 * information will be broken out.
 * <p/>
 * A LapTimer to be attached to a Thread and the static {@link #sLap()} and {@link #sLap(Object...)}
 * can be used to record lap information
 * This code needs to be added to the top-level {@link Serializable} object that is being sent as a message.
 *
 * <pre>
 * private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
 *     out.defaultWriteObject();
 *     com.sworddance.util.LapTimer.send(out);
 * }
 * private void readObject(java.io.ObjectInputStream in)throws java.io.IOException, ClassNotFoundException{
 *     in.defaultReadObject();
 *     com.sworddance.util.LapTimer.receive(in);
 * }
 * </pre>
 * Alternatively {@link #sendToString()} and {@link #receiveFromString(String)} may be used to
 * send and receive the LapTimers attached to the current thread.
 *
 * 06-04-2004 PSM added begin/end methods that roughly mimic the Springframework's StopWatch functionality.
 *                fix csv output so that it is better formated.
 *                Let LapTimer.Collection set how many laps the member LapTimers should keep.
 * ----------------------------------
 * TODO: Use ThreadHistoryTracker. (But need to handle sending LapTimers to other machines)
 * ----------------------------------
 * @author pmoore
 */
public class LapTimer implements Runnable, Serializable {

    private static final long serialVersionUID = 5534508222987180718L;

    /**
     * a simple flag that can be used to decide if LapTimers should be used.
     * This does not do anything within the LapTimer code but can be used by others
     * as a common flag.
     */
    public static final boolean ENABLE = Boolean.getBoolean("lapTimer.enable");

    /**
     * This is used to spot when a serialized LapTimer has been returned.
     */
    private static VMID vmid = new VMID();
    /**
     * This counter always increments. Combined with vmid, this will
     * make a unique enough id for LapTimers that are remoted.
     */
    private static int remotingCounter;
    /**
     * This is a map of all laptimers that are recieved.
     * A LapTimer that is new to this JVM (actually ClassLoader)
     * is stored in this map. When it is sent on (or back) it is removed.
     */
    private static ConcurrentMap<Id, LapTimer> receivedLaptimers = new ConcurrentHashMap<Id, LapTimer>();
    /**
     * This is a map of all LapTimers that are sent out from this JVM/ClassLoader.
     * A LapTimer can never be in both receivedLaptimers and sentLaptimers
     */
    private static ConcurrentMap<Id, LapTimer> sentLaptimers = new ConcurrentHashMap<Id, LapTimer>();
    /**
     * This is used as a way to avoid having to keep track the LapTimer.
     * By attaching it to a thread the LapTimer can be less intrusive.
     */
    private static ThreadLocal<LapTimer> threadLapTimer = new ThreadLocal<LapTimer>();
    private static ThreadLocal<LapTimer> lastThreadLapTimer = new ThreadLocal<LapTimer>();

    /**
     */
    private static ConcurrentMap<Object, LapTimer> keyedTimers = new ConcurrentHashMap<Object, LapTimer>();

    /**
     * The id of the LapTimer. Because it is used to identify serialize LapTimers that
     * are read in from a socket, it must be unique across all machines.
     */
    private Id home;
    private String timerName;
    /**
     * This is used to indicate the start()/stop() state.
     */
    private boolean running;
    /**
     * This is used to indicate that pause() has been called without
     * a matching {@link #cont()}.
     */
    private boolean paused;
    /**
     * This is only used to show when the LapTimer was created
     */
    private long createTime;
    private long startTime;
    private transient Date startDate;
    private transient String startDateStr;
    private long endTime;
    private transient Date endDate;
    private transient String endDateStr;
    /**
     * This is the time that the LapTimer has been running. This is the sum of all the lap() return results.
     */
    private long elapsedTime;
    /**
     * system time that is the start of the most current lap
     */
    private long lastLapTime;
    /**
     * This preserves the time spent in the current lap before a pause() occurred.
     */
    private long lapOffset;
    /**
     * The lap times. This array is filled from the back to the front.
     *
     * @see #getLapHistory()
     */
    private long[] lapHistory;
    /**
     * Lap names. This array is filled from the back to the front.
     *
     * @see #getLapNames()
     */
    private String[] lapNames;
    private int lapHistoryCount;
    /**
     * Usually the same as {@link #lapHistoryCount} except if the lapCount is
     * greater than the size supplied when the LapTimer is created.
     */
    private int lapCount;
    private transient Runnable runnable;
    /**
     * This is used to do timing nesting. When the LapTimer is
     * on the JVM that created it, it is used to chain together
     * LapTimers that have been pushed on to the {@link #threadLapTimer}
     * stack. When the LapTimer is remoted this is used to chain together
     * LapTimers on the {@link #remotedLapTimers} chains.
     */
    private transient LapTimer prevStackMember;
    /**
     * This is used for a LapTimer that is over on a different
     * JVM (It has been sent). This is how the LapTimer is attached to
     * the given thread
     */
    private transient static final ThreadLocal<LapTimer> remotedLapTimers = new ThreadLocal<LapTimer>();
    /**
     * How many child LapTimers were created for this LapTimer.
     */
    private int childCount;
    /**
     * Divide all the lap times by this number. This is used
     * when each lap will span
     */
    private int denominator = 1;
    /**
     * This LapTimer has been sent remotely and thus cannot be sent again until a reply is received.
     */
    private transient boolean sent;
    /**
     * This LapTimer can be sent remotely.
     */
    private boolean remotable;
    /**
     * This counts how many times a LapTimer was sent over the wire. Divide by 2
     * to get number of round trips.
     */
    private int hopCount;
    private static final boolean DEBUG = false;
    private String pendingLapName;

    /**
     * Create a LapTimer with no name and an allowed lapHistory of 100
     * Must also be available for the serialization of LapTimers.
     *
     * @see #LapTimer(String,Runnable,int)
     */
    public LapTimer() {
        this(null, null, 100);
        this.timerName = "LapTimer#" + System.identityHashCode(this);
    }

    /**
     * Create a LapTimer with the supplied name and an allowed lapHistory of 100
     *
     * @param timerName
     * @see #LapTimer(String,Runnable,int)
     */
    public LapTimer(String timerName) {
        this(timerName, null, 100);
    }

    /**
     * Creates a new instance of LapTimer
     *
     * @param actual
     */
    public LapTimer(Runnable actual) {
        this(null, actual, 100);
        this.timerName = "LapTimer#" + System.identityHashCode(this);
    }

    /**
     * @param actual
     * @param lapHistorySize
     */
    public LapTimer(Runnable actual, int lapHistorySize) {
        this(null, actual, lapHistorySize);
        this.timerName = "LapTimer#" + System.identityHashCode(this);
    }

    /**
     * @param timerName
     * @param actual
     * @param lapHistorySize
     */
    public LapTimer(String timerName, Runnable actual, int lapHistorySize) {
        if (lapHistorySize < 2) {
            lapHistorySize = 2;
        }
        this.timerName = timerName;
        this.lapHistory = new long[lapHistorySize];
        this.lapNames = new String[lapHistorySize];
        this.runnable = actual;
        this.createTime = getTimeInMillis();
    }

    public void setRemotable(boolean remotable) {
        this.remotable = remotable;
    }

    /**
     * @return timerName
     */
    public String getName() {
        return this.timerName;
    }

    /**
     * @return true this LapTimer can be sent over to another VM? False by default.
     */
    public boolean isRemotable() {
        return remotable;
    }

    /**
     * When the result returned by 'Per' methods,i.e. lapPerStr, are divided
     * by this number
     *
     * @param denominator
     */
    public void setDenominator(int denominator) {
        this.denominator = denominator;
    }

    /**
     * @param runthis
     */
    public void setRunnable(Runnable runthis) {
        this.runnable = runthis;
    }

    /**
     * Starts the LapTimer running. This will reset the start timer.
     *
     * @return this
     */
    public LapTimer start() {
        if (this.isRunning()) {
            return this;
        }
        this.lastLapTime = this.startTime = getTimeInMillis();
        this.lapOffset = 0;
        this.running = true;
        return this;
    }

    /**
     * @return
     */
    private long getTimeInMillis() {
        return System.currentTimeMillis();
    }

    /**
     * Like reset() but discards Runnable
     *
     * @see #reset()
     * @return this
     */
    public LapTimer clear() {
        this.runnable = null;
        return this.reset();
    }

    /**
     * Sets all information to 0 - but does not drop the Runnable.
     * Use clear() to do reset() and discard Runnable.
     * @return this
     */
    public LapTimer reset() {
        this.running = false;
        this.startTime = 0;
        this.startDate = null;
        this.startDateStr = null;
        this.endTime = 0;
        this.lastLapTime = 0;
        Arrays.fill(this.lapHistory, 0);
        this.lapHistoryCount = 0;
        this.lapCount = 0;
        this.elapsedTime = 0;
        return this;
    }

    /**
     * @return true this LapTimer has been {@link #start()}ed but not {@link #stop()}ped
     */
    public boolean isRunning() {
        return this.running;
    }

    public boolean isPaused() {
        return this.paused;
    }

    /**
     * This method in combination with {@link #endLap()}. A laptimer using
     * begin/end will not in general use the lap() methods. But combining
     * the two will not interfere with each other.
     * @param lapName tag marking this "lap".
     */
    public void beginNewLap(String lapName) {
        // maybe paused from previous end()
        this.cont();
        this.pendingLapName = lapName;
    }

    /**
     * Equivalent to {@link #lap(String)} where the lap name is the
     * name passed in on the {@link #beginNewLap(String)}
     *
     */
    @Deprecated // combine with lap()
    public void endLap() {
        this.endLap(true);
    }

    @Deprecated // combine with lap()
    public void endLap(boolean pauseTimer) {
        this.lap(this.pendingLapName);
        if (pauseTimer) {
            this.pause();
        }
    }

    /**
     * get time since last call to lap (or start). This will start the lap timer if it is not already started.
     *
     * @return how long this lap was in milliseconds
     */
    public long lap() {
        if (!this.isRunning()) {
            this.start();
            return 0;
        }

        System.arraycopy(this.lapHistory, 1, this.lapHistory, 0, this.lapHistory.length - 1);
        System.arraycopy(this.lapNames, 1, this.lapNames, 0, this.lapNames.length - 1);
        long tmp = getTimeInMillis();
        if (!this.isPaused()) {
            this.lapHistory[this.lapHistory.length - 1] = this.lapOffset + (tmp - this.lastLapTime);
        } else {
            // if LapTimer is paused, then just the preserved lapOffset is the time of the lap
            // otherwise, we need to add in the time that has passed.
            this.lapHistory[this.lapHistory.length - 1] = this.lapOffset;
        }
        this.elapsedTime += this.lapHistory[this.lapHistory.length - 1];
        this.lapOffset = 0;
        this.lastLapTime = tmp;
        this.lapCount++;
        return this.lapHistory[this.lapHistory.length - 1];
    }

    public static void sBegin(Object... lapInfo) {
        LapTimerIterator iter = new LapTimerIterator(remotedLapTimers.get());
        if (iter.hasNext() || hasThreadTimer()) {
            String lapName = StringUtils.join(lapInfo);
            while (iter.hasNext()) {
                LapTimer timer = iter.next();
                timer.beginNewLap(lapName);
            }
            LapTimer timer = _getThreadTimer();
            if (timer != null) {
                timer.beginNewLap(lapName);
            }
        }
    }

    public static void sEnd() {
        for (LapTimerIterator iter = new LapTimerIterator(remotedLapTimers.get()); iter.hasNext();) {
            LapTimer timer = iter.next();
            timer.endLap();
        }
        LapTimer timer = _getThreadTimer();
        if (timer != null) {
            timer.endLap();
        }
    }

    /**
     * This will call the current thread's LapTimer' lap(String). It will quietly do nothing is there
     * no current LapTimer, nothing is done.
     * All LapTimers that have been received by this thread from another JVM are also stamped.
     *
     * @param lapInfo Objects that are converted to strings and concatenated. Resultant string is associated with this lap.
     * @return -1 if there is no current LapTimer otherwise the results of {@link #getThreadTimer()}{@link #lap(String)} are returned.
     */
    public static long sLap(Object... lapInfo) {
        LapTimerIterator iter = new LapTimerIterator(remotedLapTimers.get());
        if (iter.hasNext() || hasThreadTimer()) {
            String lapName = StringUtils.join(lapInfo);
            while (iter.hasNext()) {
                LapTimer timer = iter.next();
                timer.lap(lapName);
            }
            LapTimer timer = _getThreadTimer();
            if (timer != null) {
                return timer.lap(lapName);
            }
        }
        return -1;
    }

    /**
     * This will call the current thread's LapTimer' lap(). It will quietly do nothing is there
     * no current LapTimer, nothing is done.
     * All LapTimers that have been received by this thread from another JVM are also stamped.
     *
     * @return -1 if there is no current LapTimer otherwise the results of lap() are returned.
     */
    public static long sLap() {
        for (LapTimerIterator iter = new LapTimerIterator(remotedLapTimers.get()); iter.hasNext();) {
            LapTimer timer = iter.next();
            timer.lap();
        }
        LapTimer timer = _getThreadTimer();
        if (timer == null) {
            return -1;
        } else {
            return timer.lap();
        }
    }

    /**
     * get time since last call to lap (or start). This will start the lap timer if it is not already started.
     *
     * @param lapName The string that is associated with this lap.
     * @return how long this lap was in milliseconds
     */
    public long lap(String lapName) {
        long time = this.lap();
        this.lapNames[this.lapNames.length - 1] = lapName;
        return time;
    }

    /**
     * get time since last call to lap (or start) as a string. This will start the lap timer if it is not already started.
     *
     * @return how long this lap was in milliseconds formated as a string with "ms " appended.
     */
    public String lapStr() {
        return lap() + "ms ";
    }

    /**
     * get time since last call to lap (or start) as a string. This will start the lap timer if it is not already started.
     *
     * @param lapName The string that is associated with this lap.
     * @return how long this lap was in milliseconds formated as a string with "ms " appended.
     */
    public String lapStr(String lapName) {
        return lap(lapName) + "ms ";
    }

    public float lapPer() {
        return lapPer(this.denominator);
    }

    public String lapPerStr() {
        return lapPerStr(denominator);
    }

    /**
     * Call lap() and return time of this lap divided by denominator
     *
     * @param denominatorOverride
     * @return time of this lap divided by denominator
     */
    public float lapPer(int denominatorOverride) {
        return ((float) this.lap()) / denominatorOverride;
    }

    public String lapPerStr(int denominatorOverride) {
        return lapPer(denominatorOverride) + "ms";
    }

    public float lapPer(String lapName, int denominatorOverride) {
        return ((float) this.lap(lapName)) / denominatorOverride;
    }

    public String lapPerStr(String lapName, int denominatorOverride) {
        return lapPer(lapName, denominatorOverride) + "ms";
    }

    /**
     * includes the time while paused or stopped() if a cont() has occurred.
     * @return elapsed time in milliseconds.
     */
    public long elapsed() {
        // total sum so far + time so far into the current lap + pause offset.
        if (this.isRunning()) {
            long t = this.elapsedTime + this.lapOffset;
            return this.isPaused() ? t : t + (getTimeInMillis() - this.lastLapTime);
        } else {
            return this.elapsedTime;
        }
    }

    public float elapsedPer() {
        return elapsedPer(this.denominator);
    }

    public float elapsedPer(int denominatorOverride) {
        return ((float) elapsed()) / denominatorOverride;
    }

    public String elapsedPerStr(int denominatorOverride) {
        return elapsedPer(denominatorOverride) + "ms";
    }

    /**
     * Return the time in milliseconds since the LapTimer was created
     * if the timer was started and is still running. This will be different than
     * the elapsed() time because it includes time that the LapTimer was paused.
     * Will be inaccurate if this LapTimer has been remoted to another machne.
     *
     * @return time since the LapTimer was created.
     */
    public long elapsedSinceCreate() {
        if (this.isStarted()) {
            if (this.isRunning()) {
                return getTimeInMillis() - this.createTime;
            } else {
                return this.endTime - this.createTime;
            }
        } else {
            // created but never started.
            return getTimeInMillis() - this.createTime;
        }
    }

    /**
     * Calls lap() and then stops the timer and records the end time.
     * Does nothing if the LapTimer is already stopped.
     * @param lapName final lapName.
     * @return this
     */
    public LapTimer stop(String lapName) {
        if (this.isRunning()) {
            this.lap(lapName);
            this.running = false;
            this.endTime = getTimeInMillis();
        }
        return this;
    }

    /**
     * @return this
     * @see #stop(String)
     */
    public LapTimer stop() {
        return this.stop(null);
    }

    public boolean isDone() {
        return this.endTime != 0;
    }

    /**
     * temporarily pauses the LapTimer - does not cause a lap to be done.
     * While paused the elapsed time will not increase.
     * @return this
     */
    public LapTimer pause() {
        if (!this.paused) {
            long tmp = getTimeInMillis();
            this.lapOffset += tmp - this.lastLapTime;
            this.paused = true;
        }
        return this;
    }

    /**
     * continue after a {@link #pause()} or a {@link #stop()}
     * If the LapTimer has not yet been {@link #start()}ed then it
     * will be.
     * @return this
     */
    public LapTimer cont() {
        if (!this.isStarted()) {
            this.start();
        } else if (!this.isRunning()) {
            this.running = true;
            this.paused = false;
            this.endTime = 0;
            this.lastLapTime = getTimeInMillis();
        } else if (this.isPaused()) {
            this.paused = false;
            this.lastLapTime = getTimeInMillis() - this.lapOffset;
            this.lapOffset = 0;
        }
        return this;
    }

    /**
     * Get a copy of lap times.
     *
     * @return an array of lap time durations
     */
    public long[] getLapHistory() {
        int size = this.getLapHistoryCount();
        long[] tmp = new long[size];
        System.arraycopy(this.lapHistory, this.lapHistory.length - size, tmp, 0, size);
        return tmp;
    }

    /**
     * Get the lap time from the previous call to lap() or stop()
     *
     * @return the last lap's time.
     */
    public long getPrevLapTime() {
        return this.lapHistory[this.lapHistory.length - 1];
    }

    /**
     *
     * @return a copy of the lap names
     */
    public String[] getLapNames() {
        int size = this.getLapHistoryCount();
        String[] tmp = new String[size];
        System.arraycopy(this.lapNames, this.lapNames.length - size, tmp, 0, size);
        return tmp;
    }

    public boolean isStarted() {
        return this.startTime != 0;
    }

    /**
     * @return number of valid entries in the getLapHistory() array
     */
    public int getLapHistoryCount() {
        return this.lapCount >= this.lapHistory.length ? this.lapHistory.length : this.lapCount;
    }

    /**
     * @return number of times that lap has been called since the most recent start()/clear()
     */
    public int getLapCount() {
        return this.lapCount;
    }

    public long getStartTime() {
        return this.startTime;
    }

    /**
     * @return the date/time that this LapTimer was started
     */
    public Date getStartDate() {
        if (this.startDate == null && this.isStarted()) {
            this.startDate = new Date(this.startTime);
        }
        return this.startDate;
    }

    /**
     * Get the human readable version of the starting time string.
     *
     * @return {@link #getStartDate()} converted to a String
     */
    public String getStartDateStr() {
        if (!this.isStarted()) {
            return "not started";
        } else if (this.startDateStr == null) {
            this.startDateStr = new SimpleDateFormat("HH:mm:ss.SSS").format(this.getStartDate());
        }
        return this.startDateStr;
    }

    /**
     * @return the date/time that this LapTimer was stopped
     */
    public Date getEndDate() {
        if (this.endDate == null && this.isDone()) {
            this.endDate = new Date(this.endTime);
        }
        return this.endDate;
    }

    /**
     * Get the human readable version of the starting time string.
     *
     * @return {@link #getEndDate()} converted to a String
     */
    public String getEndDateStr() {
        if (!this.isDone()) {
            return "not done";
        } else if (this.endDateStr == null) {
            this.endDateStr = new SimpleDateFormat("HH:mm:ss.SSS").format(this.getEndDate());
        }
        return this.endDateStr;
    }

    /**
     * @see #run(Runnable)
     * @see #setRunnable(Runnable)
     * @see java.lang.Runnable#run()
     */
    public void run() {
        this.run(this.runnable);
    }

    /**
     * If this LapTimer has been started it will 'lap' around the execution
     * of the runnable. Otherwise it will start and stop the LapTimer around the runnable.
     * @param runthis runnable to time as a unit.
     */
    public void run(Runnable runthis) {
        boolean started = this.isRunning();
        if (!started) {
            this.start();
        } else {
            this.lap();
        }
        try {
            runthis.run();
        } finally {
            if (!started) {
                this.stop();
            } else {
                this.lap();
            }
        }
    }

    /**
     * Get the LapTimer associated with this thread. Create a new LapTimer if none exists.
     *
     * @return the LapTimer assigned to this thread
     */
    public static LapTimer getThreadTimer() {
        LapTimer timer = _getThreadTimer();
        if (timer == null) {
            timer = new LapTimer();
            threadLapTimer.set(timer);
        }
        return timer;
    }

    public static LapTimer getExistingThreadTimer() {
        return LapTimer.hasThreadTimer() ? _getThreadTimer() : null;
    }

    private static LapTimer _getThreadTimer() {
        return threadLapTimer.get();
    }

    /**
     * This method checks to see if there is a thread timer. This is
     * useful in conjunction with {#sLap(String)} as it lets a caller avoid an
     * unnecessary string creation if there is no attached timer.
     *
     * @return true if there is a thread timer.
     */
    public static boolean hasThreadTimer() {
        return threadLapTimer.get() != null;
    }

    /**
     * @return the most recent LapTimer popped off the Threadstack.
     * @see #popThreadTimer(LapTimer)
     */
    public static LapTimer getLastThreadTimer() {
        return lastThreadLapTimer.get();
    }

    /**
     * Disassociate this thread from any LapTimer. {@link #getThreadTimer()}
     * can be used to new LapTimer.
     */
    public static void clearThreadTimer() {
        threadLapTimer.set(null);
    }

    /**
     * push the given LapTimer on to the Threadbased stack.
     * The pushed Timer is started.
     * @param newTimer the supplied timer.
     * @return newTimer
     */
    public static LapTimer pushThreadTimerAndStart(LapTimer newTimer) {
        LapTimer timer = _getThreadTimer();
        newTimer.prevStackMember = timer;
        threadLapTimer.set(newTimer);
        newTimer.start();
        return newTimer;
    }

    /**
     * Create a new LapTimer that will be pushed on to this Thread's stack. This Thread's
     * previously attached LapTimer is saved. The previous LapTimer is restored with a call
     * to {@link #popThreadTimer(LapTimer)}. The new LapTimer is automatically started.
     *
     * @return a started LapTimer
     */
    public static LapTimer pushNewStartedThreadTimer() {
        LapTimer timer = _getThreadTimer();
        String name;
        if (timer != null) {
            name = timer.timerName + '.' + (++timer.childCount);
        } else {
            name = null;
        }
        return pushThreadTimerAndStart(new LapTimer(name));
    }

    /**
     * Create a new LapTimer that will be pushed on to this Thread's stack. This Thread's
     * previously attached LapTimer is saved. The previous LapTimer is restored with a call
     * to {@link #popThreadTimer(LapTimer)}. The new LapTimer is automatically started.
     *
     * @param name
     * @return a started LapTimer
     */
    public static LapTimer pushNewThreadTimer(String name) {
        return pushThreadTimerAndStart(new LapTimer(name));
    }

    /**
     * This Thread's current LapTimer is stopped and popped off of the Thread's stack.
     * The previous LapTimer attached to this thread is restored. The popped LapTimer
     * is stopped (can be restarted by a call to {@link #cont()}).
     * @param expected the laptimer to be popped off. (this ensures that only the expected laptimer is removed)
     *
     * @return expected
     */
    public static LapTimer popThreadTimer(LapTimer expected) {
        return LapTimer.popThreadTimer(null, expected);
    }

    /**
     * This Thread's current LapTimer is stopped and popped off of the Thread's stack.
     * The previous LapTimer attached to this thread is restored. The popped LapTimer
     * is {@link #stop(String)}'ed (can be restarted by a call to {@link #cont()}).
     *
     * @param lapName name passed to stop
     * @param expected the laptimer to be popped off. (this ensures that only the expected laptimer is removed)
     * @return The LapTimer formerly attached to this thread.
     */
    public static LapTimer popThreadTimer(String lapName, LapTimer expected) {
        LapTimer timer = _getThreadTimer();
        if (timer != null && expected == timer) {
            timer.stop(lapName);
            threadLapTimer.set(timer.prevStackMember);
            lastThreadLapTimer.set(timer);
        }
        return timer;
    }

    /**
     * Get the LapTimer associated with this key. If none exists, a new LapTimer is created and started.
     *
     * @param key
     * @return the LapTimer associated with this Key
     */
    public static LapTimer getKeyedTimer(Object key) {
        LapTimer timer = new LapTimer();
        LapTimer t = get(keyedTimers, key, timer);
        if (timer == t) {
            timer.start();
        }
        return timer;
    }

    /**
     * remove the LapTimer associated with this key. The LapTimer is not stopped.
     *
     * @param key
     * @return The just removed LapTimer
     */
    public static LapTimer removeKeyedTimer(Object key) {
        return keyedTimers.remove(key);
    }

    public static Object getProxy(Object obj) {
        return ProxyWrapper.getProxyWrapper(obj);
    }

    public static Object getProxy(Object obj, LapTimer timer) {
        return ProxyWrapper.getProxyWrapper(obj, timer);
    }

    /**
     * Execute a toLapString(null) and create a nice short string.
     *
     * @return this.{@link #toLapString(String)}(null)
     *
     */
    public String toLapString() {
        return this.toLapString(null);
    }

    /**
     * Execute a lap(name) and create a nice string for printing that shows this last lap's info.
     *
     * @param name
     * @return {@link #lap(String)}
     */
    public String toLapString(String name) {
        long lapTime = this.lap(name);
        StringBuilder sb = new StringBuilder(50);
        if (this.timerName != null) {
            sb.append(this.timerName).append(':');
        }

        if (name != null) {
            sb.append('(').append(name).append(')');
        }
        sb.append("[start=").append(this.getStartDateStr()).append("; elapsed=").append(elapsed());
        sb.append("ms; last lap=").append(lapTime).append("ms]");
        return sb.toString();
    }

    /**
     * Create a nice string that shows all the information about this LapTimer
     *
     * @see java.lang.Object#toString()
     */

    public String toString() {
        StringBuilder sb = new StringBuilder(100);
        if (this.timerName != null) {
            sb.append(this.timerName).append(':');
        }
        sb.append("[start=").append(this.getStartDateStr());
        if (!this.isRunning()) {
            sb.append("(stopped)");
        }
        sb.append("; elapsed=").append(elapsed()).append("ms; hops=");
        sb.append(this.hopCount).append(" laps=(\n");
        long[] history = this.getLapHistory();
        String[] names = this.getLapNames();
        for (int i = 0; i < history.length; i++) {
            sb.append("spent ").append(history[i]).append("ms until:");
            if (names[i] != null) {
                sb.append(' ').append(names[i]).append(';');
            }
            sb.append("\n");
        }
        sb.append(")]");
        return sb.toString();
    }

    /**
     * This returns an excel CVS line. The first cell is the name of this LapTimer.
     *
     * @return a comma separated value line.
     */
    public String toCSV() {
        StringBuffer sb = new StringBuffer(50);
        if (this.timerName != null) {
            sb.append(this.timerName);
        }
        sb.append(',');
        new SimpleDateFormat("MM/dd/yyyy HH:mm:ss.SSS").format(this.getStartDate(), sb, new FieldPosition(0));
        sb.append(',').append(this.hopCount);
        sb.append(',').append(this.elapsed());
        long[] history = this.getLapHistory();
        for (long element : history) {
            sb.append(',').append(element);
            if (this.denominator != 1.0f) {
                sb.append(',').append(element / this.denominator);
            }
        }
        return sb.toString();
    }

    /**
     * This creates a string version of the output of LapTimer.send(ObjectOutputStream)
     * @see LapTimer#send(ObjectOutputStream)
     * @see LapTimer#receiveFromString(String)
     * @return a Base64 encoded string suitable for all methods of transport
     */
    public static String sendToString() {
        ByteArrayOutputStream b = new ByteArrayOutputStream();
        ObjectOutputStream os;
        String s = null;
        try {
            os = new ObjectOutputStream(b);
            LapTimer.send(os);
            s = Base64.byteArrayToBase64(b.toByteArray());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return s;
    }

    /**
     * This method is normally called from within a Serializable's writeObject(ObjectOutputStream)
     * It should be the last method called just in case the receipient has
     * not been modified to handle LapTimers. The LapTimer will be screwed up
     * but at least nothing else will be.
     * A LapTimer may only be sent out once. After that LapTimer is paused
     * until the response is received with the data in it.
     *
     * @param out
     * @throws IOException
     */
    public static void send(ObjectOutputStream out) throws IOException {
        for (LapTimerIterator iter = new LapTimerIterator(_getThreadTimer()); iter.hasNext();) {
            LapTimer timer = iter.next();
            if (timer.sent || !timer.isRemotable()) {
                // this timer has already be sent out, hopefully on this request
                continue;
            } else {
                timer.sent = true;
            }
            if (DEBUG) {
                System.out.println("SENDING LAPTIMER=" + timer);
            }
            out.writeObject(timer);
        }
        for (LapTimerIterator iter = new LapTimerIterator(remotedLapTimers.get()); iter.hasNext();) {
            LapTimer timer = iter.next();
            if (DEBUG) {
                System.out.println("SENDING REMOTED LAPTIMER=" + timer);
            }
            out.writeObject(timer);
        }
        // Once a JVM has passed off non-local LapTimers to a different JVM,
        // that other JVM needs to return them if need be. Because there
        // is no way of knowing if it is being passed to new JVM or
        // back to the originator.
        LapTimer.remotedLapTimers.set(null);

        // signals end of timers
        out.writeObject(null);
    }

    public static void receiveFromString(String s) {
        if (s == null) {
            return;
        }
        try {
            byte[] b = Base64.base64ToByteArray(s);
            receive(new ObjectInputStream(new ByteArrayInputStream(b)));
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * This method is called from within a Serializable's readObject(ObjectInputStream)
     * It should be the last method called just in case the receipient has
     * not been modified to handle LapTimers. The LapTimer will be screwed up
     * but at least nothing else will be.
     *
     * @param in
     */
    public static void receive(ObjectInputStream in) {
        // we swallow exceptions because if the sender was not modified to
        // send a LapTimer, that is o.k. and should not impact the business
        // logic.

        try {
            LapTimer timer;
            for (; (timer = (LapTimer) in.readObject()) != null;) {
                // it can now be sent again.
                timer.sent = false;
                if (DEBUG) {
                    System.out.println("RECVING LAPTIMER=" + timer);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        boolean skipCheck = this.home == null;

        // check to see if this LapTimer was received remotely.
        // if its homeVMID was not set then this JVM/ClassLoader
        // is created it and is sending it out.
        if (skipCheck) {
            synchronized (vmid) {
                this.home = new Id(vmid, remotingCounter++);
            }
        }
        if (skipCheck || LapTimer.receivedLaptimers.remove(this.home) == null) {
            LapTimer.sentLaptimers.put(this.home, this);
        }
        boolean restart = this.isRunning();
        if (restart) {
            this.beginNewLap("XMIT");
            //            this.pause(); TODO how to handle staying on same machine?
        }
        out.writeBoolean(restart);
        out.defaultWriteObject();
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        boolean restart = in.readBoolean();
        in.defaultReadObject();
        this.hopCount++;
        if (restart) {
            this.cont();
            //TODO do we continue?
            this.endLap(); //("EndXMIT");
        }
    }

    @SuppressWarnings("unused")
    private Object readResolve() throws ObjectStreamException {
        LapTimer t = sentLaptimers.remove(this.home);
        // the server that this is coming back from may have changed it's value
        this.setRemotable(true);
        if (t == null) {
            receivedLaptimers.put(this.home, this);
            // TODO islocal should just be false
            //            if ( this.islocal )
            //                throw new ObjectStreamException();
            pushRemotedLapTimer(this);
            return this;
        } else {
            // this is the reply to an early send. Copy the remote results back.
            t.copyFrom(this);
            // TODO islocal should just be true
            //            if ( !this.islocal )
            //                throw new RuntimeException();
            return t;
        }
    }

    public static void pushRemotedLapTimer(LapTimer timer) {
        timer.prevStackMember = remotedLapTimers.get();
        remotedLapTimers.set(timer);
    }

    /**
     * @param timer
     */
    private void copyFrom(LapTimer timer) {
        this.childCount = timer.childCount;
        this.createTime = timer.createTime;
        this.denominator = timer.denominator;
        this.elapsedTime = timer.elapsedTime;
        this.endTime = timer.endTime;
        this.hopCount = timer.hopCount;
        // home is the key for maps as such it should also not be copied.
        //        this.home = timer.home;
        this.lapCount = timer.lapCount;
        this.lapHistory = timer.lapHistory;
        this.lapHistoryCount = timer.lapHistoryCount;
        this.lapNames = timer.lapNames;
        this.lapOffset = timer.lapOffset;
        this.lastLapTime = timer.lastLapTime;
        this.paused = timer.paused;
        // this.prevStackMember is preserved because the prevStackMember
        // was not sent across the wire.
        this.remotable = timer.remotable;
        // this.runnable was not sent because there is no way to know
        // if the runnable was serializable.
        this.running = timer.running;
        this.startDate = timer.startDate;
        this.startDateStr = timer.startDateStr;
        this.startTime = timer.startTime;
        this.timerName = timer.timerName;
    }

    public static class LapTimerIterator implements Iterator<LapTimer> {
        private LapTimer nextTimer;

        LapTimerIterator(LapTimer startingLapTimer) {
            this.nextTimer = startingLapTimer;
        }

        /**
         * @see java.util.Iterator#hasNext()
         */

        public boolean hasNext() {
            return nextTimer != null && nextTimer.prevStackMember != null;
        }

        /**
         * @see java.util.Iterator#next()
         */

        public LapTimer next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            } else {
                this.nextTimer = this.nextTimer.prevStackMember;
            }
            return this.nextTimer;
        }

        /**
         * @see java.util.Iterator#remove()
         */

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

    private static class Id implements Serializable {

        private static final long serialVersionUID = -4147276090981031760L;

        final int counter;
        final VMID vmidentity;

        /**
         * @param vmidentity
         * @param counter
         */
        Id(VMID vmidentity, int counter) {
            this.vmidentity = vmidentity;
            this.counter = counter;
        }

        public boolean equals(Object o) {
            if (o instanceof Id) {
                return this.counter == ((Id) o).counter && vmidentity.equals(((Id) o).vmidentity);
            } else {
                return false;
            }
        }

        public int hashCode() {
            return this.vmidentity.hashCode() + this.counter;
        }
    }

    /**
     * This comparator sorts LapTimers by how long the lap indicated took.
     *
     * @author pat
     */
    public static class LongestLapComparator implements Comparator<LapTimer> {
        private int index;

        /**
         * @param index which lap are we sorting on.
         */
        LongestLapComparator(int index) {
            this.index = index;
        }

        public int compare(LapTimer lt0, LapTimer lt1) {
            if (lt0.lapHistory[this.index] < lt1.lapHistory[this.index]) {
                return -1;
            } else if (lt0.lapHistory[this.index] > lt1.lapHistory[this.index]) {
                return 1;
            } else {
                return 0;
            }
        }

    }

    private static class Base64 {
        private static byte[] base64ToByteArray(String s) {
            byte[] alphaToInt = base64ToInt;
            int sLen = s.length();
            int numGroups = sLen / 4;
            if (4 * numGroups != sLen) {
                throw new IllegalArgumentException("String length must be a multiple of four.");
            }
            int missingBytesInLastGroup = 0;
            int numFullGroups = numGroups;
            if (sLen != 0) {
                if (s.charAt(sLen - 1) == '=') {
                    missingBytesInLastGroup++;
                    numFullGroups--;
                }
                if (s.charAt(sLen - 2) == '=') {
                    missingBytesInLastGroup++;
                }
            }
            byte[] result = new byte[3 * numGroups - missingBytesInLastGroup];

            // Translate all full groups from base64 to byte array elements
            int inCursor = 0, outCursor = 0;
            for (int i = 0; i < numFullGroups; i++) {
                int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt);
                int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt);
                int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt);
                int ch3 = base64toInt(s.charAt(inCursor++), alphaToInt);
                result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4));
                result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2));
                result[outCursor++] = (byte) ((ch2 << 6) | ch3);
            }

            // Translate partial group, if present
            if (missingBytesInLastGroup != 0) {
                int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt);
                int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt);
                result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4));

                if (missingBytesInLastGroup == 1) {
                    int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt);
                    result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2));
                }
            }
            // assert inCursor == s.length()-missingBytesInLastGroup;
            // assert outCursor == result.length;
            return result;
        }

        private static String byteArrayToBase64(byte[] a) {
            int aLen = a.length;
            int numFullGroups = aLen / 3;
            int numBytesInPartialGroup = aLen - 3 * numFullGroups;
            int resultLen = 4 * ((aLen + 2) / 3);
            StringBuilder result = new StringBuilder(resultLen);
            char[] intToAlpha = intToBase64;

            // Translate all full groups from byte array elements to Base64
            int inCursor = 0;
            for (int i = 0; i < numFullGroups; i++) {
                int byte0 = a[inCursor++] & 0xff;
                int byte1 = a[inCursor++] & 0xff;
                int byte2 = a[inCursor++] & 0xff;
                result.append(intToAlpha[byte0 >> 2]);
                result.append(intToAlpha[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
                result.append(intToAlpha[(byte1 << 2) & 0x3f | (byte2 >> 6)]);
                result.append(intToAlpha[byte2 & 0x3f]);
            }

            // Translate partial group if present
            if (numBytesInPartialGroup != 0) {
                int byte0 = a[inCursor++] & 0xff;
                result.append(intToAlpha[byte0 >> 2]);
                if (numBytesInPartialGroup == 1) {
                    result.append(intToAlpha[(byte0 << 4) & 0x3f]);
                    result.append("==");
                } else {
                    // assert numBytesInPartialGroup == 2;
                    int byte1 = a[inCursor++] & 0xff;
                    result.append(intToAlpha[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
                    result.append(intToAlpha[(byte1 << 2) & 0x3f]);
                    result.append('=');
                }
            }
            // assert inCursor == a.length;
            // assert result.length() == resultLen;
            return result.toString();
        }

        /**
         * This array is a lookup table that translates 6-bit positive integer
         * index values into their "Base64 Alphabet" equivalents as specified
         * in Table 1 of RFC 2045.
         */
        private static final char intToBase64[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
                'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
                'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
                '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };

        /**
         * Translates the specified character, which is assumed to be in the
         * "Base 64 Alphabet" into its equivalent 6-bit positive integer.
         *
         * @throw IllegalArgumentException or ArrayOutOfBoundsException if
         * c is not in the Base64 Alphabet.
         */
        private static int base64toInt(char c, byte[] alphaToInt) {
            int result = alphaToInt[c];
            if (result < 0) {
                throw new IllegalArgumentException("Illegal character " + c);
            }
            return result;
        }

        /**
         * This array is a lookup table that translates unicode characters
         * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045)
         * into their 6-bit positive integer equivalents.  Characters that
         * are not in the Base64 alphabet but fall within the bounds of the
         * array are translated to -1.
         */
        private static final byte base64ToInt[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0,
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1,
                -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
                47, 48, 49, 50, 51 };
    }
}