/* * Copyright 2004 - 2008 Christian Sprajc, Dennis Waldherr. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder 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. * * PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id$ */ package de.dal33t.powerfolder.util.ui; import java.util.LinkedList; import de.dal33t.powerfolder.Constants; /** * Generic class to estimate progress. * Using an optional sliding window, this class tries to estimate the time at which a given value is reached. * To do this it requires updates on the real value - the estimation is extrapolated using a configurable function. * @author Dennis "Bytekeeper" Waldherr * */ public class TimeEstimator { public enum Function { LINEAR { long estimate(TimeEstimator est, double target) { double tDelta = System.currentTimeMillis() - est.window.getFirst().timeIndex; // Have at least 1ms before calculating anything if (tDelta < 1) { return -1; } TimeStamp first = est.window.getFirst(); TimeStamp last = est.window.getLast(); // Check if there was actually something "measured" if (last.value <= first.value) { return -1; } double tmp = (last.timeIndex - first.timeIndex) * (target - last.value) / (last.value - first.value); return Math.round(tmp); } }; abstract long estimate(TimeEstimator est, double target); } private static final double EPSILON = 0.00001; private final LinkedList<TimeStamp> window; private final long windowMillis; private volatile Function usedFunc = Function.LINEAR; private boolean filterDecreasingValues; /** * Creates a TimeEstimator which uses a window of the given length. * In addition, the estimation function is set to LINEAR * @param windowMillis */ public TimeEstimator(long windowMillis) { window = new LinkedList<TimeStamp>(); this.windowMillis = windowMillis; } /** * Creates a TimeEstimator. * The function used is LINEAR. */ public TimeEstimator() { this(-1); } /** * Sets a new function for estimation. * @param func the function to use */ public void setEstimationFunction(Function func) { if (func == null) { throw new NullPointerException("Function parameter is null."); } usedFunc = func; } /** * Updates the "real" progress. * Time is an implicit parameter, aka it is retrieved using System.currentTimeMillis() * @param val the value reached */ public synchronized void addValue(double val) { if (isFilterDecreasingValues() && !window.isEmpty() && window.getLast().value > val) { return; } TimeStamp t = new TimeStamp(); t.timeIndex = System.currentTimeMillis(); t.value = val; if (window != null) { while (!window.isEmpty() && window.getLast().value > val) { window.removeLast(); } window.add(t); purgeOld(); } } /** * Returns the estimated time in milliseconds at which the value given in the parameter is reached. * @param toValue the value to be reached * @return the time in milliseconds */ public synchronized long estimatedMillis(double toValue) { if (!window.isEmpty() && window.getLast().value + EPSILON > toValue) { return 0; } // We need at least one stamp to calculate anything if (window.size() < Constants.ESTIMATION_MINVALUES) { return -1; } return usedFunc.estimate(this, toValue); } private void purgeOld() { if (windowMillis <= 0) { return; } TimeStamp t = window.getLast(); // Make sure that there's always one stamp left, even if we're out of the window while (window.size() > Constants.ESTIMATION_MINVALUES && window.getFirst().timeIndex + windowMillis < t.timeIndex) { window.removeFirst(); } } /** * Returns true if updates with decreasing values should be ignored. * @return */ public boolean isFilterDecreasingValues() { return filterDecreasingValues; } /** * Sets the value filtering to filter out decreasing value updates. * @param filterDecreasingValues */ public void setFilterDecreasingValues(boolean filterDecreasingValues) { this.filterDecreasingValues = filterDecreasingValues; } private static class TimeStamp { private long timeIndex; private double value; } }