This commit is contained in:
jendave
2011-08-09 19:34:12 +00:00
parent 277a6f255f
commit d9a785ee3a
1028 changed files with 267358 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
The files in this directory and in all subdirectories of it (the "Files") are
Copyright 2011 Braids Cabal-Conjurer. They are available under either Forge's
main license (the GNU Public License; see LICENSE.txt in Forge's top directory)
or under the Apache License, as explained below.
The Files are additionally licensed under the Apache License, Version 2.0 (the
"Apache License"); you may not use the files in this directory except in
compliance with one of its two licenses. You may obtain a copy of the Apache
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the Apache License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the Apache License for the specific language governing permissions and
limitations under the Apache License.

View File

@@ -0,0 +1,8 @@
package net.slightlymagic.braids.util;
/**
* Like Runnable, but it can throw any Exception.
*/
public interface ClumsyRunnable {
public void run() throws Exception;
}

View File

@@ -0,0 +1,60 @@
package net.slightlymagic.braids.util;
import java.util.Iterator;
/**
* Acts as both immutable Iterator and Iterable; remove method always throws
* exception.
*/
public class ImmutableIterableFrom<T> implements Iterable<T>, Iterator<T> {
private Iterator<T> iterator;
/**
* Wrap an iterable so that it cannot be changed via
* the remove method.
*
* @param iterable the iterable to wrap
*/
public ImmutableIterableFrom(Iterable<T> iterable) {
this.iterator = iterable.iterator();
}
/**
* Wrap an iterator so that its container cannot be changed via
* the remove method.
*
* @param iterator the iterator to wrap
*/
public ImmutableIterableFrom(Iterator<T> iterator) {
this.iterator = iterator;
}
/**
* This class acts as both an Iterable and an Iterator.
*/
public Iterator<T> iterator() {
return this;
}
/**
* Returns hasNext from the wrapped [object's] iterator.
*/
public boolean hasNext() {
return iterator.hasNext();
}
/**
* Returns next from the wrapped [object's] iterator.
*/
public T next() {
return iterator.next();
}
/**
* Never succeeeds.
* @throws UnsupportedOperationException always.
*/
public void remove() {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,59 @@
package net.slightlymagic.braids.util;
/**
* This exception indicates the particular method (or part of a method) being
* called has not been implemented; getting this exception is generally
* considered a programming error.
*
* Throwing this exception does not necessarily mean the method will be
* implemented at any point in the future.
*/
public class NotImplementedError extends RuntimeException {
private static final long serialVersionUID = -6714022569997781370L;
/**
* No-arg constructor; this usually means the entire method or block from
* which it is thrown has not been implemented.
*/
public NotImplementedError() {
super();
}
/**
* Indicates what has not been implemented.
*
* @param message indicates what exactly has not been implemented.
* May include information about future plans to implement the described
* section of code.
*/
public NotImplementedError(final String message) {
super(message);
}
/**
* Like the no-arg constructor, but with a cause parameter.
*
* @param cause the exception that caused this one to be thrown
*
* @see #NotImplementedError()
*/
public NotImplementedError(final Throwable cause) {
super(cause);
}
/**
* Like the String constructor, but with a cause parameter.
*
* @param message indicates what exactly has not been implemented.
* May include information about future plans to implement the described
* section of code.
*
* @param cause the exception that caused this one to be thrown
*
* @see #NotImplementedError(String)
*/
public NotImplementedError(final String message, final Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,267 @@
package net.slightlymagic.braids.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Some general-purpose functions.
*/
public final class UtilFunctions {
/**
* Do not instantiate.
*/
private UtilFunctions() {;}
/**
* Throws a NullPointerException if param is null.
*
* @param paramName the name of the parameter; may be null
* @param param the parameter to test
*/
public static void checkNotNull(String paramName, Object param) {
if (param != null) return;
NullPointerException exn = null;
if (paramName != null) {
exn = new NullPointerException(paramName + " must not be null");
}
else {
//
exn = new NullPointerException();
}
// Doctor the exception to appear to come from the caller.
StackTraceElement[] trace = exn.getStackTrace();
int len = getSliceLength(trace, 1);
exn.setStackTrace(slice(new StackTraceElement[len], trace, 1));
throw exn;
}
/**
* Create an array from the (rest of) an iterator's output;
* this function is horribly inefficient.
*
* Please, only use it on small iterators.
*
* @param iter the iterator to traverse
*
* @return an array of (the rest of) the iterator's values
*/
public static <T> T[] iteratorToArray(Iterator<T> iter) {
ArrayList<T> list = new ArrayList<T>();
T item;
while (iter.hasNext()) {
item = iter.next();
list.add(item);
}
@SuppressWarnings("unchecked")
T[] result = (T[]) list.toArray();
return result;
}
/**
* Returns the rightmost portion of an array, Python-style.
*
* @param <T> (inferred automatically)
*
* @param srcArray the array to copy (shallowly)
*
* @param startIndex if positive, the index (from the left) at which to
* start copying; if negative, we treat this as the index from the right.
* For example, calling this with startIndex = -2 returns the last two
* items in the array, if it has that many.
*
* @return a shallow copy of array starting at startIndex; this may return
* an empty array if the startIndex is out of bounds.
*/
public static <T extends Object> T[] slice(T[] dstArray, T[] srcArray,
int startIndex)
{
if (startIndex < 0) {
startIndex = srcArray.length + startIndex;
if (startIndex < 0) startIndex = 0;
}
if (dstArray == null) {
throw new NullPointerException();
}
if (srcArray == null) {
throw new NullPointerException();
}
int resultLength = getSliceLength(srcArray, startIndex);
if (dstArray.length != resultLength) {
throw new ArrayIndexOutOfBoundsException(
"First parameter must have length " + resultLength + ", but length is " + dstArray.length + ".");
}
int srcIx = startIndex;
for (int dstIx = 0;
dstIx < resultLength && srcIx < srcArray.length;
dstIx++, srcIx++)
{
dstArray[dstIx] = srcArray[srcIx];
}
return dstArray;
}
/**
* Get a slice's length in preparation for taking a slice.
*
* I do not like the fact that I have to use this function, but
* Java left me with little choice.
*
* @see #slice(Object[], Object[], int)
*
* @return the length of the array that would result from calling
* slice(Object[], Object[], int) with the given srcArray and
* startIndex.
*/
public static <T> int getSliceLength(T[] srcArray, int startIndex) {
if (startIndex < 0) {
startIndex = srcArray.length + startIndex;
if (startIndex < 0) startIndex = 0;
}
int resultLength = srcArray.length - startIndex;
return resultLength;
}
/**
* Handles the boilerplate null and isinstance check for an equals method.
*
* Example:
* <pre>
* public boolean equals(Object obj) {
* MyClassName that = checkNullOrNotInstance(this, obj);
* if (that == null) {
* return false;
* }
* //...
* }
* </pre>
*
* @param goodInstance a non-null instance of type T; looks neater than
* passing in goodInstance.getClass()
*
* @param obj the object to test
*
* @return null if obj is null or not an instance of goodInstance's class;
* otherwise, we return obj cast to goodInstance's type
*/
public static <T> T checkNullOrNotInstance(T goodInstance, Object obj) {
if (goodInstance == null) {
throw new NullPointerException("first parameter must not be null");
}
@SuppressWarnings("unchecked")
Class<T> classT = (Class<T>) goodInstance.getClass();
boolean viable = true;
if (obj == null) viable = false;
else if (!(classT.isInstance(obj))) viable = false;
if (viable) {
return classT.cast(obj);
}
return null;
}
/**
* Safely converts an object to a String.
*
* @param obj to convert; may be null
*
* @return "null" if obj is null, obj.toString() otherwise
*/
public static String safeToString(Object obj) {
if (obj == null) {
return "null";
}
else {
return obj.toString();
}
}
/**
* Remove nulls and duplicate items from the list.
*
* This may change the list's ordering. It uses the items' equals methods to
* determine equality.
*
* Advantages over HashSet: This consumes no unnecessary heap-memory, nor
* does it require objects to implement hashCode. It is OK if
* (o1.equals(o2) does not imply o1.hashCode() == o2.hashCode()).
*
* Advantages over TreeSet: This does not require a comparator.
*
* Disadvantages over HashSet and TreeSet: This runs in O(n*n) time.
*
* @param list the list to modify; this is fastest with ArrayList.
*/
public static <T> void smartRemoveDuplicatesAndNulls(List<T> list)
{
// Get rid of pesky leading nulls.
smartRemoveDuplicatesAndNullsHelper(list, 0, null);
for (int earlierIx = 0; earlierIx < list.size(); earlierIx++)
{
for (int laterIx = earlierIx + 1; laterIx < list.size(); laterIx++)
{
T itemAtEarlierIx = list.get(earlierIx);
smartRemoveDuplicatesAndNullsHelper(list, laterIx,
itemAtEarlierIx);
}
}
}
/**
* Helper method for smartRemoveDuplicatesAndNulls that is subject to
* change; if you call this directly, you do so at your own risk!
*
* @param list the list to modify; if all items from startIx to the end
* are either null or equal to objSeenPreviously, then we truncate the
* list just before startIx.
*
* @param startIx the index to examine; we only move items within the range
* of [startIx, list.size()-1].
*
* @param objSeenPreviously the object with which to compare list[startIx];
* may be null.
*/
public static <T> void smartRemoveDuplicatesAndNullsHelper(
List<T> list, int startIx, T objSeenPreviously)
{
while (startIx < list.size() &&
(list.get(startIx) == null ||
list.get(startIx) == objSeenPreviously ||
list.get(startIx).equals(objSeenPreviously)))
{
int lastItemIx = list.size()-1;
// Overwrite the item at laterIx with the one at the end,
// then delete the one at the end.
list.set(startIx, list.get(lastItemIx));
list.remove(lastItemIx);
}
}
}

View File

@@ -0,0 +1,61 @@
/** Licensed under both the GPL and the Apache 2.0 License. */
package net.slightlymagic.braids.util.generator;
import java.io.File;
import com.google.code.jyield.Generator;
import com.google.code.jyield.Yieldable;
/**
* This is a generator over all of the non-directories residing in a given
* starting directory and all subdirectories of it that do NOT start with a
* dot; this prevents the code from descending into .svn directories.
*
* For documentation on Java-Yield and its generators, see
* {@link http://code.google.com/p/java-yield/}
*/
public class FindNonDirectoriesSkipDotDirectoriesGenerator implements Generator<File> {
private File startDir;
/**
* Create a generator at a given starting directory.
*
* One can invoke this generator more than once by calling its generate
* method.
*
* @param startDir the directory to start in; we ignore this directory's
* name, so if it starts with a dot, we treat it as if it didn't.
*/
public FindNonDirectoriesSkipDotDirectoriesGenerator(File startDir) {
this.startDir = startDir;
}
/**
* Standard generate method.
*
* <p>Yields results to the given Yieldable. Convert Generator instances to
* Iterables with YieldUtils.toIterable.</p>
*
* See {@link https://java-yield.googlecode.com/hg/docs/com/google/code/jyield/YieldUtils.html#toIterable(com.google.code.jyield.Generator)}
*/
public void generate(Yieldable<File> yy) {
String[] list = startDir.list();
for (String filename : list) {
File entry = new File(startDir, filename);
if (entry.isDirectory()) {
if (!filename.startsWith(".")) {
FindNonDirectoriesSkipDotDirectoriesGenerator child = new FindNonDirectoriesSkipDotDirectoriesGenerator(entry);
child.generate(yy);
child = null;
}
// else do nothing, because it's a dot directory
}
else {
// Use this instead of a return statement.
yy.yield(entry);
}
}
}
}

View File

@@ -0,0 +1,36 @@
package net.slightlymagic.braids.util.generator;
import com.google.code.jyield.Generator;
import com.google.code.jyield.Yieldable;
/**
* Creates a Generator from an array; generators are a handy
* substitute for passing around and creating temporary
* lists, collections, and arrays.
*
* @see http://code.google.com/p/jyield/
*/
public class GeneratorFromArray<T> implements Generator<T> {
private T[] array;
/**
* Create a Generator from an array
*
* @param array from which to generate items
*/
public GeneratorFromArray(T[] array) {
this.array = array;
}
@Override
/**
* Submits all of the array's elements to the yieldable.
*
* @param yy the yieldable which receives the elements
*/
public void generate(Yieldable<T> yy) {
for (T item : array) {
yy.yield(item);
}
}
}

View File

@@ -0,0 +1,205 @@
/** Licensed under both the GPL and the Apache 2.0 License. */
package net.slightlymagic.braids.util.generator;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import net.slightlymagic.braids.util.lambda.Lambda1;
import com.google.code.jyield.Generator;
import com.google.code.jyield.YieldUtils;
import com.google.code.jyield.Yieldable;
/**
* For documentation on Java-Yield and its generators, see
* {@link http://code.google.com/p/java-yield/}
*/
public final class GeneratorFunctions {
/**
* Do not instantiate.
*/
private GeneratorFunctions() {
;
}
/**
* Estimate the number of items in this generator by traversing all of its
* elements.
*
* Note this only works on a generator that can be reinstantiated once it
* has been traversed. This is only an estimate, because a generator's size
* may vary been traversals. This is especially true if the generator
* relies on external resources, such as a file system.
*
* If you call this on an infinite generator, this method will never
* return.
*
* @return the estimated number of items provided by this generator
*/
public static <T> long estimateSize(Generator<T> gen) {
long result = 0;
for (@SuppressWarnings("unused") T ignored : YieldUtils.toIterable(gen))
{
result++;
}
return result;
}
/**
* Highly efficient means of filtering a long or infinite sequence.
*
* @param <T> any type
*
* @param predicate a Lambda (function) whose apply method takes an object
* of type <T> and returns a Boolean. If it returns false or null, the
* item from the inputGenerator is not yielded by this Generator;
* if predicate.apply returns true, then this Generator <i>does</i>
* yield the value.
*
* @param inputGenerator the sequence upon which we operate
*
* @return a generator which produces a subset <= the inputGenerator
*/
public static <T> Generator<T> filterGenerator(
final Lambda1<Boolean,T> predicate, final Generator<T> inputGenerator)
{
Generator<T> result = new Generator<T>() {
@Override
public void generate(final Yieldable<T> outputYield) {
Yieldable<T> inputYield = new Yieldable<T>() {
Boolean pResult;
@Override
public void yield(T input) {
pResult = predicate.apply(input);
if (pResult != null && pResult) {
outputYield.yield(input);
}
}
};
inputGenerator.generate(inputYield);
}
};
return result;
}
/**
* Highly efficient means of applying a transform to a long or infinite
* sequence.
*
* @param <T> any type
*
* @param transform a Lambda (function) whose apply method takes an object
* of type <T> and returns an object of the same type. This transforms
* the values from the inputGenerator into this Generator.
*
* @param inputGenerator the sequence upon which we operate
*
* @return a generator that yields transform.apply's return value for
* each item in the inputGenerator
*/
public static <T> Generator<T> transformGenerator(
final Lambda1<T,T> transform, final Generator<T> inputGenerator)
{
Generator<T> result = new Generator<T>() {
@Override
public void generate(final Yieldable<T> outputYield) {
Yieldable<T> inputYield = new Yieldable<T>() {
@Override
public void yield(T input) {
outputYield.yield(transform.apply(input));
}
};
inputGenerator.generate(inputYield);
}
};
return result;
}
/**
* Forces a generator to be completely evaluated into a temporary data
* structure, then returns the generator over that same structure.
*
* This effectively returns the same Generator, but it is a faster one.
* This trades away heap space for reduced CPU intensity. This is
* particuarly helpful if you know that a Generator is going to be
* totally evaluated more than once in the near future.
*
* @param <T> inferred automatically
*
* @param unevaluated a Generator of T instances
*
* @return the equivalent Generator, except that the result's generate
* method can be invoked multiple times for fast results.
*/
public static <T> Generator<T> solidify(Generator<T> unevaluated) {
ArrayList<T> solidTmp = YieldUtils.toArrayList(unevaluated);
solidTmp.trimToSize();
return YieldUtils.toGenerator(solidTmp);
}
/**
* Select an item at random from a Generator; this causes the entire
* Generator to be evaluated once, but only once.
*
* @param generator
* the generator from which to select a random item
*
* @return an item chosen at random from the generator; this may be null, if
* the generator contains null items.
*
* @throws NoSuchElementException
* if the generator has no contents
*/
public static <T> T selectRandom(Generator<T> generator)
throws NoSuchElementException
{
/*
* This algorithm requires some explanation. Each time we encounter a
* new item from the generator, we determine via random chance if the
* item is the one we select. At the end of each iteration, we have a
* candidate, and we have a count of the number of items encountered so
* far. Each iteration has a 1/n chance of replacing the candidate with
* the current item, where n is the number of items encountered so far.
* This allows us to randomly select an item from the generated contents
* with an equal distribution; and we don't have to count the number of
* items first!
*/
int n = 0;
T candidate = null;
for (T item : YieldUtils.toIterable(generator)) {
n++;
int rand = (int) (Math.random() * n);
// At this point, 0 <= rand < n.
rand++; // Now, 1 <= rand <= n.
if (rand == 1) {
// We rolled a 1 on an n-sided die. We have a new candidate!
// Note that on the first iteration, this always happens,
// because n = 1.
candidate = item;
}
}
if (n == 0) {
// There were no items in the generator!
throw new NoSuchElementException("generator is empty");
}
return candidate;
}
}

View File

@@ -0,0 +1,11 @@
/**
* Utilities to be used with Java-Yield Generators.
*
* Files in this package and all subpackages are Copyright 2011 Braids Cabal-Conjurer.
* They are licensed under both the GPL and the Apache 2.0 license.
*
* For documentation on Java-Yield and its generators, see
* {@link http://code.google.com/p/java-yield/} .
*/
package net.slightlymagic.braids.util.generator;

View File

@@ -0,0 +1,45 @@
package net.slightlymagic.braids.util.lambda;
import net.slightlymagic.braids.util.UtilFunctions;
import static net.slightlymagic.braids.util.UtilFunctions.checkNotNull;
/**
* This embodies a promise to invoke a certain method at a later time; the
* FrozenCall remembers the arguments to use and the return type.
*
* @param <T> the return type of apply
*
* @see Thunk
*/
public class FrozenCall<T> implements Thunk<T> {
private Lambda<T> proc;
private Object[] args;
public FrozenCall(Lambda<T> proc, Object[] args) {
checkNotNull("proc", proc);
checkNotNull("args", args);
this.proc = proc;
this.args = args;
}
public T apply() {
return proc.apply(args);
}
@Override
public boolean equals(Object obj) {
FrozenCall<T> that = UtilFunctions.checkNullOrNotInstance(this, obj);
if (that == null) return false;
else if (!this.proc.equals(that.proc)) return false;
else if (this.args.length != that.args.length) return false;
for (int i = 0; i < args.length; i++) {
if (this.args[i] == null && that.args[i] != null) return false;
else if (!this.args[i].equals(that.args[i])) return false;
}
return true;
}
}

View File

@@ -0,0 +1,5 @@
package net.slightlymagic.braids.util.lambda;
public interface Lambda<R> {
public abstract R apply(Object[] args);
}

View File

@@ -0,0 +1,13 @@
package net.slightlymagic.braids.util.lambda;
public abstract class Lambda1<R,A1> implements Lambda<R> {
public abstract R apply(A1 arg1);
@SuppressWarnings("unchecked")
//TODO @Override
public R apply(Object[] args) {
return apply((A1) args[0]);
}
}

View File

@@ -0,0 +1,13 @@
package net.slightlymagic.braids.util.lambda;
public abstract class Lambda2<R,A1,A2> implements Lambda<R> {
public abstract R apply(A1 arg1, A2 arg2);
@SuppressWarnings("unchecked")
//TODO @Override
public R apply(Object[] args) {
return apply((A1) args[0], (A2) args[1]);
}
}

View File

@@ -0,0 +1,13 @@
package net.slightlymagic.braids.util.lambda;
public abstract class Lambda3<R,A1,A2,A3> implements Lambda<R> {
public abstract R apply(A1 arg1, A2 arg2, A3 arg3);
@SuppressWarnings("unchecked")
//TODO @Override
public R apply(Object[] args) {
return apply((A1) args[0], (A2) args[1], (A3) args[2]);
}
}

View File

@@ -0,0 +1,9 @@
package net.slightlymagic.braids.util.lambda;
/**
* Do not try to instantiate this class, because you cannot; this is simply
* a marker for the null literal.
*/
public final class Null {
private Null() {;}
}

View File

@@ -0,0 +1,5 @@
package net.slightlymagic.braids.util.lambda;
public interface Thunk<T> {
public abstract T apply();
}

View File

@@ -0,0 +1,2 @@
/** Forge Card Game */
package net.slightlymagic.braids.util.lambda;

View File

@@ -0,0 +1,2 @@
/** Forge Card Game */
package net.slightlymagic.braids.util;

View File

@@ -0,0 +1,528 @@
package net.slightlymagic.braids.util.progress_monitor;
import java.util.Date;
import com.esotericsoftware.minlog.Log;
/**
* This base class also acts as a "null" progress monitor; it doesn't display
* anything when updated.
*
* Absolute times are measured in seconds, in congruence with ProgressMonitor.
*
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor
*/
public class BaseProgressMonitor implements BraidsProgressMonitor {
private int numPhases;
private int currentPhase;
private long totalUnitsThisPhase;
private long unitsCompletedSoFarThisPhase;
private float minUIUpdateIntervalSec;
private long lastUIUpdateTime;
private long phaseOneStartTime;
private long currentPhaseStartTime;
private float currentPhaseExponent;
private long[] phaseDurationHistorySecList;
private float[] phaseWeights;
public final int SECONDS_PER_MINUTE = 60;
public final int SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
public final int SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
/**
* Convenience for
* BaseProgressMonitor(numPhases, totalUnitsFirstPhase, 2.0f, null).
*
* @see #BaseProgressMonitor(int,long,float,float[])
*/
public BaseProgressMonitor(int numPhases, long totalUnitsFirstPhase) {
this(numPhases, totalUnitsFirstPhase, 2.0f, null);
}
/**
* Convenience for
* BaseProgressMonitor(numPhases, totalUnitsFirstPhase,
* minUIUpdateIntervalSec, null).
*
* @see #BaseProgressMonitor(int,long,float,float[])
*/
public BaseProgressMonitor(int numPhases, long totalUnitsFirstPhase,
float minUIUpdateIntervalSec)
{
this(numPhases, totalUnitsFirstPhase, minUIUpdateIntervalSec, null);
}
/**
* Initializes fields and starts the timers.
*
* @param numPhases the total number of phases we will monitor
*
* @param totalUnitsFirstPhase how many units to expect in phase 1
*
* @param minUIUpdateIntervalSec the approximate interval at which we
* update the user interface, in seconds
*
* @param phaseWeights may be null; if not null, this indicates the
* relative weight of each phase in terms of time to complete all phases.
* Index 0 of this array indicates phase 1's weight, index 1 indicates
* the weight of phase 2, and so on. If null, all phases are considered to
* take an equal amount of time to complete, which is equivalent to setting
* all phase weights to 1.0f. For example, if there are two phases, and
* the phase weights are set to {2.0f, 1.0f}, then the methods that compute
* the final ETA (Estimated Time of Arrival or completion) will assume that
* phase 2 takes half as long as phase 1. In other words, the operation
* will spend 67% of its time in phase 1, and 33% of its time in phase 2.
*/
public BaseProgressMonitor(int numPhases, long totalUnitsFirstPhase,
float minUIUpdateIntervalSec, float[] phaseWeights)
{
this.numPhases = numPhases;
this.currentPhase = 1;
this.unitsCompletedSoFarThisPhase = 0L;
this.minUIUpdateIntervalSec = minUIUpdateIntervalSec;
this.lastUIUpdateTime = 0L;
this.phaseOneStartTime = new Date().getTime()/1000;
this.currentPhaseStartTime = this.phaseOneStartTime;
this.currentPhaseExponent = 1;
this.phaseDurationHistorySecList = new long[numPhases];
if (phaseWeights == null) {
this.phaseWeights = new float[numPhases];
for (int ix = 0; ix < numPhases; ix++) {
this.phaseWeights[ix] = 1.0f;
}
}
else {
this.phaseWeights = phaseWeights;
}
setTotalUnitsThisPhase(totalUnitsFirstPhase);
}
/**
* Does nothing.
*/
public void dispose() {
;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getNumPhases()
*/
public int getNumPhases() {
return this.numPhases;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getMinUpdateIntervalSec()
*/
public float getMinUpdateIntervalSec() {
return this.minUIUpdateIntervalSec;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getCurrentPhase()
*/
public int getCurrentPhase() {
return this.currentPhase;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getUnitsCompletedSoFarThisPhase()
*/
public long getUnitsCompletedSoFarThisPhase() {
return this.unitsCompletedSoFarThisPhase;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getTotalUnitsThisPhase()
*/
public long getTotalUnitsThisPhase() {
return this.totalUnitsThisPhase;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getLastUIUpdateTime()
*/
public long getLastUIUpdateTime() {
return this.lastUIUpdateTime;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getPhaseOneStartTime()
*/
public long getPhaseOneStartTime() {
return this.phaseOneStartTime;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getCurrentPhaseStartTime()
*/
public long getCurrentPhaseStartTime() {
return this.currentPhaseStartTime;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#setMinUpdateIntervalSec(float)
*/
public void setMinUpdateIntervalSec(float value) {
this.minUIUpdateIntervalSec = value;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#setTotalUnitsThisPhase(long)
*/
public void setTotalUnitsThisPhase(long value) {
this.totalUnitsThisPhase = value;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getPercentCompleteOfThisPhaseAsString()
*/
public String getPercentCompleteOfThisPhaseAsString() {
Float percent = getPercentCompleteOfThisPhaseAsFloat();
if (percent != null) {
return Integer.toString((int) (float) percent);
}
else {
return "??";
}
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getTotalPercentCompleteAsString()
*/
public String getTotalPercentCompleteAsString() {
Float percent = getTotalPercentCompleteAsFloat();
if (percent == null) {
return "??";
}
else {
return Integer.toString((int) (float) percent);
}
}
/**
* Convenience for getRelativeETAAsString(false), meaning to compute the
* value for the end of the last phase.
*
* @see #getRelativeETAAsString(boolean)
*/
public String getRelativeETAAsString() {
return getRelativeETAAsString(false);
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getRelativeETAAsString(boolean)
*/
public String getRelativeETAAsString(boolean thisPhaseOnly) {
Integer etaSec = getRelativeETASec(thisPhaseOnly);
if (etaSec == null) {
return "unknown";
}
String result = "";
if (etaSec > SECONDS_PER_DAY) {
result += Integer.toString(etaSec / SECONDS_PER_DAY);
result += " da, ";
etaSec %= SECONDS_PER_DAY; // Shave off the portion recorded.
}
if (result.length() > 0 || etaSec > SECONDS_PER_HOUR) {
result += Integer.toString(etaSec / SECONDS_PER_HOUR);
result += " hr, ";
etaSec %= SECONDS_PER_HOUR; // Shave off the portion recorded.
}
if (result.length() > 0 || etaSec > SECONDS_PER_MINUTE) {
result += Integer.toString(etaSec / SECONDS_PER_MINUTE);
result += " min, ";
etaSec %= SECONDS_PER_MINUTE; // Shave off the portion recorded.
}
result += Integer.toString(etaSec);
result += " sec";
return result;
}
/**
* Convenience for getAbsoluteETAAsLocalTimeString(false), meaning to
* compute the value for the end of the last phase.
*
* @see #getAbsoluteETAAsLocalTimeString(boolean)
*/
public String getAbsoluteETAAsLocalTimeString() {
return getAbsoluteETAAsLocalTimeString(false);
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getAbsoluteETAAsLocalTimeString(boolean)
*/
public String getAbsoluteETAAsLocalTimeString(boolean thisPhaseOnly) {
Long etaTime = getAbsoluteETATime(thisPhaseOnly);
if (etaTime == null) {
return "unknown";
}
return (new Date(etaTime*1000).toString());
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#incrementUnitsCompletedThisPhase(long)
*/
public void incrementUnitsCompletedThisPhase(long numUnits) {
this.unitsCompletedSoFarThisPhase += numUnits;
}
/**
* Subclasses must call this immediately after updating the UI, to
* preserve the integrity of the shouldUpdateUI method.
*/
protected void justUpdatedUI() {
this.lastUIUpdateTime = new Date().getTime()/1000;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#shouldUpdateUI()
*/
public boolean shouldUpdateUI() {
doctorStartTimes();
long nowTime = (new Date().getTime()/1000);
if (nowTime - this.lastUIUpdateTime >= this.minUIUpdateIntervalSec ||
(this.getUnitsCompletedSoFarThisPhase() ==
this.getTotalUnitsThisPhase()))
{
return true;
}
else {
return false;
}
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#markCurrentPhaseAsComplete(long)
*/
public void markCurrentPhaseAsComplete(long totalUnitsNextPhase) {
if ((this.currentPhase > this.numPhases)) {
String message = "The phase just completed (";
message += this.currentPhase;
message += ") is greater than the total number ";
message += "of anticipated phases (";
message += this.numPhases;
message += "); the latter is probably incorrect.";
Log.warn(message);
}
this.currentPhase += 1;
this.unitsCompletedSoFarThisPhase = 0;
setTotalUnitsThisPhase(totalUnitsNextPhase);
this.currentPhaseExponent = 1;
long nowTime = (new Date().getTime()/1000);
long durationOfThisPhaseSec = nowTime - this.currentPhaseStartTime;
if (durationOfThisPhaseSec < 0) {
durationOfThisPhaseSec = 0;
}
if (0 <= currentPhase-2 && currentPhase-2 < phaseDurationHistorySecList.length) {
this.phaseDurationHistorySecList[currentPhase-2] = durationOfThisPhaseSec;
}
this.currentPhaseStartTime = nowTime;
if (this.currentPhase >= this.numPhases) {
String message = "Actual individual phase durations: [";
for (int ix = 0 ; ix < phaseDurationHistorySecList.length ; ix++) {
message += phaseDurationHistorySecList[ix] + ", ";
}
Log.info(message + ']');
}
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#sendMessage(java.lang.String)
*/
public void sendMessage(String message) {
;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#setCurrentPhaseAsExponential(float)
*/
public void setCurrentPhaseAsExponential(float value) {
this.currentPhaseExponent = value;
}
/**
* @see net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor#getCurrentPhaseExponent()
*/
public float getCurrentPhaseExponent() {
return this.currentPhaseExponent;
}
/**
* @return number in range [0.0, 100.0] or null.
*/
protected Float getPercentCompleteOfThisPhaseAsFloat() {
if (this.totalUnitsThisPhase < 1 ||
this.unitsCompletedSoFarThisPhase > this.totalUnitsThisPhase) {
return null;
}
else {
float ratio = ((float) (this.unitsCompletedSoFarThisPhase)) /
((float) this.totalUnitsThisPhase);
ratio = (float) Math.pow(ratio, this.getCurrentPhaseExponent());
return (ratio * 100.0f);
}
}
/**
* Returns number in range [0.0, 100.0] or null.
*/
protected Float getTotalPercentCompleteAsFloat() {
long totalPoints = 0;
for (float weight : this.phaseWeights) {
totalPoints += weight * 100;
}
Float percentThisPhase = getPercentCompleteOfThisPhaseAsFloat();
if (percentThisPhase == null) {
// If we can't know the percentage for this phase, use a
// conservative estimate.
percentThisPhase = 0.0f;
}
long pointsSoFar = 0;
for (int ix = 0; ix < this.currentPhase-1; ix++) {
// We get full points for (all the phases completed prior to this one.
pointsSoFar += phaseWeights[ix] * 100;
}
pointsSoFar += percentThisPhase * this.phaseWeights[this.currentPhase-1];
if (totalPoints <= 0.0 || pointsSoFar > totalPoints) {
return null;
}
else {
return (100.0f * pointsSoFar) / totalPoints;
}
}
/**
* Convenience for getRelativeETASec(false), meaning to compute the value
* for the end of the last phase.
*
* @see #getRelativeETASec(boolean)
*/
protected Integer getRelativeETASec() {
return getRelativeETASec(false);
}
/**
* @return estimated seconds until completion for either thisPhaseOnly
* or for the entire operation. May return null if unknown.
*/
protected Integer getRelativeETASec(boolean thisPhaseOnly) {
Long absoluteETATime = getAbsoluteETATime(thisPhaseOnly);
if (absoluteETATime == null) {
return null;
}
return (int) (absoluteETATime - (new Date().getTime()/1000));
}
/**
* Convenience for getAbsoluteETATime(false), meaning to compute the value
* for the end of all phases.
*
* @see #getAbsoluteETATime(boolean)
*/
protected Long getAbsoluteETATime() {
return getAbsoluteETATime(false);
}
/**
* @return the estimated time (in absolute seconds) at which thisPhaseOnly
* or the entire operation will be completed. May return null if (unknown.
*/
protected Long getAbsoluteETATime(boolean thisPhaseOnly) {
doctorStartTimes();
// If we're in the last phase, the overall ETA is the same as the ETA
// for (this particular phase.
if (this.getCurrentPhase() >= this.getNumPhases()) {
thisPhaseOnly = true;
}
Float percentDone = null;
long startTime = 0L;
if (thisPhaseOnly) {
percentDone = getPercentCompleteOfThisPhaseAsFloat();
startTime = this.currentPhaseStartTime;
}
else {
percentDone = getTotalPercentCompleteAsFloat();
startTime = this.phaseOneStartTime;
}
if (percentDone == null || percentDone <= 0.001) {
return null;
}
// Elapsed time is to percent done as total time is to total done =>
// elapsed/percentDone == totalTime/100.0 =>
long totalTime = (long) (100.0f * ((new Date().getTime()/1000) - startTime) / percentDone);
return totalTime + startTime;
}
/**
* Repair the start times in case the system clock has been moved
* backwards.
*/
protected void doctorStartTimes() {
long nowTime = (new Date().getTime()/1000);
if (this.lastUIUpdateTime > nowTime) {
this.lastUIUpdateTime = 0;
}
if (this.phaseOneStartTime > nowTime) {
this.phaseOneStartTime = nowTime;
}
if (this.currentPhaseStartTime > nowTime) {
this.currentPhaseStartTime = nowTime;
}
}
}

View File

@@ -0,0 +1,152 @@
package net.slightlymagic.braids.util.progress_monitor;
/**
* Interface for a progress monitor that can have multiple phases
* and periodically update its UI.
*
* All times must be in seconds; absolute times are measured in seconds since
* 01 Jan 1970 00:00:00 UTC (GMT) a la (new Date().getTime()/1000).
*/
public interface BraidsProgressMonitor {
/**
* Destroy this progress monitor, making it no longer usable and/or
* visible.
*/
public void dispose();
/**
* @return the total number of phases monitored by this object.
*/
public abstract int getNumPhases();
/**
* @return the approximate minimum interval in seconds at which the UI
* should be updated.
*/
public abstract float getMinUpdateIntervalSec();
/**
* @return the current phase number; this is never less than 1 (one).
*/
public abstract int getCurrentPhase();
/**
* @return the number of units (an intentionally vague amount) completed
* so far in the current phase.
*/
public abstract long getUnitsCompletedSoFarThisPhase();
/**
* @return the total units we expect to process in this phase
*/
public abstract long getTotalUnitsThisPhase();
/**
* @return the time in absolute seconds since the UI was last updated
*/
public abstract long getLastUIUpdateTime();
/**
* @return the time in absolute seconds at which the first phase started
*/
public abstract long getPhaseOneStartTime();
/**
* @return the time in absolute seconds at which the current phase started
*/
public abstract long getCurrentPhaseStartTime();
/**
* @param value
* the approximate time in relative seconds at which the UI
* should be updated periodically
*/
public abstract void setMinUpdateIntervalSec(float value);
/**
* @param value the total number of units expected to processed in this
* phase
*/
public abstract void setTotalUnitsThisPhase(long value);
/**
* Resulting string does not contain a percent sign.
*
* @return the percentage completion of this phase as a String with no
* percent sign.
*/
public abstract String getPercentCompleteOfThisPhaseAsString();
/**
* Resulting string does not contain a percent sign.
*
* @return the percentage completion at this point, taking into account all
* phases and phase-weights, as a String with no percent sign.
*/
public abstract String getTotalPercentCompleteAsString();
/**
* May return "unknown"
*/
public abstract String getRelativeETAAsString(boolean thisPhaseOnly);
/**
* May return "unknown"
*/
public abstract String getAbsoluteETAAsLocalTimeString(boolean thisPhaseOnly);
/**
* Note this will NOT advance the phase.
* To do that, use markCurrentPhaseAsComplete().
*/
public abstract void incrementUnitsCompletedThisPhase(long numUnits);
/**
* Returns a boolean, whether or not to display the updated information.
* This throttles the update so it doesn't refresh so fast that it is
* unreadable. Implementers should call this method from their own
* incrementUnitsCompletedThisPhase method.
*
* If we have just reached 100% for (the current phase, we return true,
* even if it would otherwise be too soon to update the UI.
*/
public abstract boolean shouldUpdateUI();
/**
* This is the only way to advance the phase number.
* It automatically "starts the clock" for the next phase.
*
* @param totalUnitsNextPhase if unknown, use zero (0), and be sure to call
* setTotalUnitsThisPhase() soon after.
*/
public abstract void markCurrentPhaseAsComplete(long totalUnitsNextPhase);
/**
* Attempt to display a message to the user; not all implementations
* support this.
*
* If they do not, they may silently ignore this call.
*
* @param message the message to display
*/
public abstract void sendMessage(String message);
/**
* Mark the current phase as having an exponential rate; such phases
* reach their totalUnits slower and slower as they process more units.
*
* By default, a phase is considered to be linear, meaning this value is
* 1.0f.
*
* @param value usually less than 1.0f; often determined empirically.
*/
public abstract void setCurrentPhaseAsExponential(float value);
/**
* @return the exponent for this phase
*/
public abstract float getCurrentPhaseExponent();
}

View File

@@ -0,0 +1,81 @@
package net.slightlymagic.braids.util.progress_monitor;
public class StderrProgressMonitor extends BaseProgressMonitor {
/**
* @see BaseProgressMonitor(int,long)
*/
public StderrProgressMonitor(int numPhases, long totalUnitsFirstPhase) {
this(numPhases, totalUnitsFirstPhase, 2.0f, null);
}
/**
* @see BaseProgressMonitor(int,long,float)
*/
public StderrProgressMonitor(int numPhases, long totalUnitsFirstPhase,
float minUpdateIntervalSec)
{
this(numPhases, totalUnitsFirstPhase, minUpdateIntervalSec, null);
}
/**
* @see BaseProgressMonitor(int,long,float,float[])
*/
public StderrProgressMonitor(int numPhases, long totalUnitsFirstPhase,
float minUpdateIntervalSec, float[] phaseWeights)
{
super(numPhases, totalUnitsFirstPhase, minUpdateIntervalSec, phaseWeights);
}
@Override
/**
* @see BaseProgressMonitor#incrementUnitsCompletedThisPhase(long)
*/
public void incrementUnitsCompletedThisPhase(long numUnits) {
super.incrementUnitsCompletedThisPhase(numUnits);
if (shouldUpdateUI()) {
if ((getNumPhases() > 1)) {
printUpdate(
"Phase " + getCurrentPhase() + ": " +
getUnitsCompletedSoFarThisPhase() + " units processed. " +
"Overall: " + getTotalPercentCompleteAsString() + "% complete, " +
"ETA in " + getRelativeETAAsString() + "."
);
}
else {
printUpdate(
"Overall: " +
getUnitsCompletedSoFarThisPhase() + " units processed " +
"(" + getTotalPercentCompleteAsString() + "%); " +
"ETA in " + getRelativeETAAsString() + "."
);
}
}
}
/**
* Displays a message to stderr, overwriting the current estimate; calls
* from outside this class should provide a newline character at the
* end of the messsage.
*
* @param message the message to display
*/
public void printUpdate(String message) {
while (message.length() < 79) {
message += ' ';
}
System.err.print("\r");
System.err.print(message);
if (message.length() > 79) {
System.err.print("\n");
}
justUpdatedUI();
}
}

View File

@@ -0,0 +1,2 @@
/** Forge Card Game */
package net.slightlymagic.braids.util.progress_monitor;