Java tutorial
/* * Copyright 2014 Bevbot LLC <info@bevbot.com> * * This file is part of the Kegtab package from the Kegbot project. For * more information on Kegtab or Kegbot, see <http://kegbot.org/>. * * Kegtab is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation, version 2. * * Kegtab is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with Kegtab. If not, see <http://www.gnu.org/licenses/>. */ package org.kegbot.app.util; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import java.util.List; /** * Represents a time series, a vector of (time, value) pairs. * * @author mike wakerly (opensource@hoho.com) */ public class TimeSeries { /** Immutable bean representing a single data point. */ public static final class Point { public final long time; public final long value; public Point(long time, long value) { this.time = time; this.value = value; } @Override public String toString() { return String.format("(%s, %s)", Long.valueOf(time), Long.valueOf(value)); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (time ^ (time >>> 32)); result = prime * result + (int) (value ^ (value >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Point other = (Point) obj; return time == other.time && value == other.value; } } /** Internal immutable list of points. */ private final List<Point> mPoints; public static TimeSeries.Builder newBuilder(int maxResolution, boolean rebaseTimes) { return new TimeSeries.Builder(maxResolution, rebaseTimes); } public static TimeSeries fromString(String s) { Builder b = TimeSeries.newBuilder(0, false); for (String pairString : Splitter.onPattern("\\s+").split(s)) { String[] items = pairString.split(":"); if (items.length != 2) { throw new IllegalArgumentException("Pair did not contain exactly two items."); } long time; try { time = Long.valueOf(items[0]).longValue(); } catch (NumberFormatException e) { throw new IllegalArgumentException("Error parsing time: " + e, e); } long value; try { value = Long.valueOf(items[1]).longValue(); } catch (NumberFormatException e) { throw new IllegalArgumentException("Error parsing value: " + e, e); } b.add(time, value); } return b.build(); } private TimeSeries(List<Point> points) { mPoints = ImmutableList.copyOf(points); } public List<Point> getPoints() { return mPoints; } @Override public String toString() { return asString(); } public String asString() { StringBuilder builder = new StringBuilder(); boolean firstPoint = true; for (final Point point : mPoints) { if (!firstPoint) { builder.append(' '); } firstPoint = false; builder.append(point.time); builder.append(':'); builder.append(point.value); } return builder.toString(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + mPoints.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TimeSeries other = (TimeSeries) obj; return mPoints.equals(other.mPoints); } public static class Builder { private final long mMinResolution; private final boolean mRebaseTimes; private final List<Point> mPoints = Lists.newArrayList(); private long mBaseTime = 0; /** * Constructor. * * @param minResolution minimum time delta between data points. Adjacent points will be * coalesced until a point is added whose {@code time} exceeds the last * value by at least this much. The value {@code 0} disables coalescing. * @param rebaseTimes if {@code true}, all timestamps will be relative to the first data * point's time, which will be stored as {@code 0}. */ public Builder(long minResolution, boolean rebaseTimes) { mMinResolution = minResolution; mRebaseTimes = rebaseTimes; } /** * Adds a new data point. {@code time} values must be added in increasing order. * * @param time the event time * @param value the event value * @return {@code this}, for chaining */ public Builder add(long time, long value) { // Add immediately if no reason to coalesce. if (mPoints.isEmpty()) { if (mRebaseTimes) { mPoints.add(new Point(0, value)); mBaseTime = time; } else { mPoints.add(new Point(time, value)); } return this; } if (mRebaseTimes) { time -= mBaseTime; } final int lastLocation = mPoints.size() - 1; final Point lastPoint = mPoints.get(lastLocation); if (time == lastPoint.time || mMinResolution > 0 && (lastPoint.time + mMinResolution) > time) { // Last point is recent; coalesce. mPoints.remove(lastLocation); mPoints.add(new Point(lastPoint.time, lastPoint.value + value)); } else { mPoints.add(new Point(time, value)); } return this; } public int size() { return mPoints.size(); } public TimeSeries build() { return new TimeSeries(mPoints); } } }