Initial source import.
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
package mightypork.utils;
|
||||
|
||||
|
||||
/**
|
||||
* Utility for converting Object to data types; Can also convert strings to data
|
||||
* types.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class Convert {
|
||||
|
||||
/**
|
||||
* Get INTEGER
|
||||
*
|
||||
* @param o object
|
||||
* @param def default value
|
||||
* @return integer
|
||||
*/
|
||||
public static int toInteger(Object o, Integer def)
|
||||
{
|
||||
try {
|
||||
if (o == null) return def;
|
||||
if (o instanceof Number) return ((Number) o).intValue();
|
||||
if (o instanceof String) return (int) Math.round(Double.parseDouble((String) o));
|
||||
if (o instanceof Boolean) return ((Boolean) o) ? 1 : 0;
|
||||
} catch (final NumberFormatException e) {}
|
||||
return def;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get DOUBLE
|
||||
*
|
||||
* @param o object
|
||||
* @param def default value
|
||||
* @return double
|
||||
*/
|
||||
public static double toDouble(Object o, Double def)
|
||||
{
|
||||
try {
|
||||
if (o == null) return def;
|
||||
if (o instanceof Number) return ((Number) o).doubleValue();
|
||||
if (o instanceof String) return Double.parseDouble((String) o);
|
||||
if (o instanceof Boolean) return ((Boolean) o) ? 1 : 0;
|
||||
} catch (final NumberFormatException e) {}
|
||||
return def;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get FLOAT
|
||||
*
|
||||
* @param o object
|
||||
* @param def default value
|
||||
* @return float
|
||||
*/
|
||||
public static double toFloat(Object o, Float def)
|
||||
{
|
||||
try {
|
||||
if (o == null) return def;
|
||||
if (o instanceof Number) return ((Number) o).floatValue();
|
||||
} catch (final NumberFormatException e) {}
|
||||
return def;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get BOOLEAN
|
||||
*
|
||||
* @param o object
|
||||
* @param def default value
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean toBoolean(Object o, Boolean def)
|
||||
{
|
||||
if (o == null) return def;
|
||||
if (o instanceof Boolean) return ((Boolean) o).booleanValue();
|
||||
if (o instanceof Number) return ((Number) o).intValue() != 0;
|
||||
|
||||
if (o instanceof String) {
|
||||
final String s = ((String) o).trim().toLowerCase();
|
||||
if (s.equals("0")) return false;
|
||||
if (s.equals("1")) return true;
|
||||
try {
|
||||
final double n = Double.parseDouble(s);
|
||||
return n != 0;
|
||||
} catch (final NumberFormatException e) {}
|
||||
|
||||
if (s.equals("true")) return true;
|
||||
if (s.equals("yes")) return true;
|
||||
if (s.equals("y")) return true;
|
||||
if (s.equals("a")) return true;
|
||||
if (s.equals("enabled")) return true;
|
||||
|
||||
if (s.equals("false")) return false;
|
||||
if (s.equals("no")) return false;
|
||||
if (s.equals("n")) return false;
|
||||
if (s.equals("disabled")) return true;
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get STRING
|
||||
*
|
||||
* @param o object
|
||||
* @param def default value
|
||||
* @return String
|
||||
*/
|
||||
public static String toString(Object o, String def)
|
||||
{
|
||||
if (o == null) return def;
|
||||
if (o instanceof String) return ((String) o);
|
||||
|
||||
if (o instanceof Float) return Support.str((float) o);
|
||||
|
||||
if (o instanceof Double) return Support.str((double) o);
|
||||
|
||||
if (o instanceof Class<?>) {
|
||||
return Support.str(o);
|
||||
}
|
||||
|
||||
return o.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get RANGE
|
||||
*
|
||||
* @param o object
|
||||
* @param def default value
|
||||
* @return AiCoord
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Get INTEGER
|
||||
*
|
||||
* @param o object
|
||||
* @return integer
|
||||
*/
|
||||
public static int toInteger(Object o)
|
||||
{
|
||||
return toInteger(o, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get DOUBLE
|
||||
*
|
||||
* @param o object
|
||||
* @return double
|
||||
*/
|
||||
public static double toDouble(Object o)
|
||||
{
|
||||
return toDouble(o, 0d);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get FLOAT
|
||||
*
|
||||
* @param o object
|
||||
* @return float
|
||||
*/
|
||||
public static double toFloat(Object o)
|
||||
{
|
||||
return toFloat(o, 0f);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get BOOLEAN
|
||||
*
|
||||
* @param o object
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean toBoolean(Object o)
|
||||
{
|
||||
return toBoolean(o, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get STRING
|
||||
*
|
||||
* @param o object
|
||||
* @return String
|
||||
*/
|
||||
public static String toString(Object o)
|
||||
{
|
||||
return toString(o, "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package mightypork.utils;
|
||||
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
|
||||
/**
|
||||
* Map sorting utils
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class MapSort {
|
||||
|
||||
/**
|
||||
* Sort a map by keys, maintaining key-value pairs.
|
||||
*
|
||||
* @param map map to be sorted
|
||||
* @param comparator a comparator, or null for natural ordering
|
||||
* @return linked hash map with sorted entries
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public static <K extends Comparable, V extends Comparable> Map<K, V> sortByKeys(Map<K, V> map, final Comparator<K> comparator)
|
||||
{
|
||||
final List<K> keys = new LinkedList<>(map.keySet());
|
||||
|
||||
if (comparator == null) {
|
||||
Collections.sort(keys);
|
||||
} else {
|
||||
Collections.sort(keys, comparator);
|
||||
}
|
||||
|
||||
// LinkedHashMap will keep the keys in the order they are inserted
|
||||
// which is currently sorted on natural ordering
|
||||
final Map<K, V> sortedMap = new LinkedHashMap<>();
|
||||
for (final K key : keys) {
|
||||
sortedMap.put(key, map.get(key));
|
||||
}
|
||||
|
||||
return sortedMap;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort a map by values, maintaining key-value pairs.
|
||||
*
|
||||
* @param map map to be sorted
|
||||
* @param comparator a comparator, or null for natural ordering
|
||||
* @return linked hash map with sorted entries
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static <K extends Comparable, V extends Comparable> Map<K, V> sortByValues(Map<K, V> map, final Comparator<V> comparator)
|
||||
{
|
||||
final List<Map.Entry<K, V>> entries = new LinkedList<>(map.entrySet());
|
||||
|
||||
Collections.sort(entries, new Comparator<Map.Entry<K, V>>() {
|
||||
|
||||
@Override
|
||||
public int compare(Entry<K, V> o1, Entry<K, V> o2)
|
||||
{
|
||||
if (comparator == null) return o1.getValue().compareTo(o2.getValue());
|
||||
return comparator.compare(o1.getValue(), o2.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
// LinkedHashMap will keep the keys in the order they are inserted
|
||||
// which is currently sorted on natural ordering
|
||||
final Map<K, V> sortedMap = new LinkedHashMap<>();
|
||||
|
||||
for (final Map.Entry<K, V> entry : entries) {
|
||||
sortedMap.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return sortedMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package mightypork.utils;
|
||||
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
|
||||
/**
|
||||
* Miscelanous reflection-related utilities
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class Reflect {
|
||||
|
||||
/**
|
||||
* Get annotation of given type from an object
|
||||
*
|
||||
* @param tested the examined object
|
||||
* @param annotation annotation we want
|
||||
* @return the anotation on that object, or null
|
||||
*/
|
||||
public static <T extends Annotation> T getAnnotation(Object tested, Class<T> annotation)
|
||||
{
|
||||
return tested.getClass().getAnnotation(annotation);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if an object has an annotation of given trype
|
||||
*
|
||||
* @param tested the examined object
|
||||
* @param annotation annotation we want
|
||||
* @return true if the annotation is present on the object
|
||||
*/
|
||||
public static boolean hasAnnotation(Object tested, Class<? extends Annotation> annotation)
|
||||
{
|
||||
return tested.getClass().isAnnotationPresent(annotation);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
package mightypork.utils;
|
||||
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import mightypork.utils.annotations.Alias;
|
||||
|
||||
|
||||
/**
|
||||
* Miscelanous utilities
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public final class Support {
|
||||
|
||||
/**
|
||||
* Create a new thread of the runnable, and start it.
|
||||
*
|
||||
* @param r runnable
|
||||
* @return the thread started
|
||||
*/
|
||||
public static Thread runAsThread(Runnable r)
|
||||
{
|
||||
final Thread t = new Thread(r);
|
||||
t.start();
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick first non-null option
|
||||
*
|
||||
* @param options options
|
||||
* @return the selected option
|
||||
*/
|
||||
public static Object fallback(Object... options)
|
||||
{
|
||||
for (final Object o : options) {
|
||||
if (o != null) return o;
|
||||
}
|
||||
|
||||
return null; // all null
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get current time/date for given format.
|
||||
*
|
||||
* @param format format, according to {@link DateFormat}.
|
||||
* @return the formatted time/date
|
||||
*/
|
||||
public static String getTime(String format)
|
||||
{
|
||||
return (new SimpleDateFormat(format)).format(new Date());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse array of vararg key, value pairs to a LinkedHashMap.<br>
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
* Object[] array = {
|
||||
* "one", 1,
|
||||
* "two", 4,
|
||||
* "three", 9,
|
||||
* "four", 16
|
||||
* };
|
||||
*
|
||||
* Map<String, Integer> args = parseVarArgs(array);
|
||||
* </pre>
|
||||
*
|
||||
* @param args varargs
|
||||
* @return LinkedHashMap
|
||||
* @throws ClassCastException in case of incompatible type in the array
|
||||
* @throws IllegalArgumentException in case of invalid array length (odd)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <K, V> Map<K, V> parseVarArgs(Object... args) throws ClassCastException, IllegalArgumentException
|
||||
{
|
||||
final LinkedHashMap<K, V> attrs = new LinkedHashMap<>();
|
||||
|
||||
if (args.length % 2 != 0) {
|
||||
throw new IllegalArgumentException("Odd number of elements in varargs map!");
|
||||
}
|
||||
|
||||
K key = null;
|
||||
for (final Object o : args) {
|
||||
if (key == null) {
|
||||
if (o == null) throw new RuntimeException("Key cannot be NULL in varargs map.");
|
||||
key = (K) o;
|
||||
} else {
|
||||
attrs.put(key, (V) o);
|
||||
key = null;
|
||||
}
|
||||
}
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get if an Object is in array (using equals)
|
||||
*
|
||||
* @param needle checked Object
|
||||
* @param haystack array of Objects
|
||||
* @return is in array
|
||||
*/
|
||||
public static boolean isInArray(Object needle, Object... haystack)
|
||||
{
|
||||
for (final Object s : haystack) {
|
||||
if (needle.equals(s)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get if string is in array
|
||||
*
|
||||
* @param needle checked string
|
||||
* @param case_sensitive case sensitive comparision
|
||||
* @param haystack array of possible values
|
||||
* @return is in array
|
||||
*/
|
||||
public static boolean isInArray(String needle, boolean case_sensitive, String... haystack)
|
||||
{
|
||||
if (case_sensitive) {
|
||||
return isInArray(needle, (Object[]) haystack);
|
||||
} else {
|
||||
for (final String s : haystack) {
|
||||
if (needle.equalsIgnoreCase(s)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make enumeration iterable
|
||||
*
|
||||
* @param enumeration enumeration
|
||||
* @return iterable wrapper
|
||||
*/
|
||||
public static <T> Iterable<T> enumIter(Enumeration<? extends T> enumeration)
|
||||
{
|
||||
return new IterableEnumerationWrapper<>(enumeration);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper class for iterationg over an {@link Enumeration}
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
* @param <T> target element type (will be cast)
|
||||
*/
|
||||
private static class IterableEnumerationWrapper<T> implements Iterable<T> {
|
||||
|
||||
private final Enumeration<? extends T> enumeration;
|
||||
|
||||
|
||||
/**
|
||||
* @param enumeration the iterated enumeration
|
||||
*/
|
||||
public IterableEnumerationWrapper(Enumeration<? extends T> enumeration)
|
||||
{
|
||||
this.enumeration = enumeration;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator()
|
||||
{
|
||||
return new Iterator<T>() {
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return enumeration.hasMoreElements();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public T next()
|
||||
{
|
||||
return enumeration.nextElement();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void remove()
|
||||
{
|
||||
throw new UnsupportedOperationException("Operation not supported.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a class to string, preserving name and outer class, but excluding
|
||||
* path.
|
||||
*
|
||||
* @param cls
|
||||
* @return
|
||||
*/
|
||||
public static String str(Class<?> cls)
|
||||
{
|
||||
final Alias ln = cls.getAnnotation(Alias.class);
|
||||
if (ln != null) {
|
||||
return ln.name();
|
||||
}
|
||||
|
||||
String name = cls.getName();
|
||||
|
||||
String sep = "";
|
||||
|
||||
if (name.contains("$")) {
|
||||
name = name.substring(name.lastIndexOf("$") + 1);
|
||||
sep = "$";
|
||||
} else {
|
||||
name = name.substring(name.lastIndexOf(".") + 1);
|
||||
sep = ".";
|
||||
}
|
||||
|
||||
final Class<?> enclosing = cls.getEnclosingClass();
|
||||
|
||||
return (enclosing == null ? "" : Support.str(enclosing) + sep) + name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert double to string, remove the mess at the end.
|
||||
*
|
||||
* @param d double
|
||||
* @return string
|
||||
*/
|
||||
public static String str(Double d)
|
||||
{
|
||||
String s = d.toString();
|
||||
s = s.replace(',', '.');
|
||||
s = s.replaceAll("([0-9]+\\.[0-9]+)00+[0-9]+", "$1");
|
||||
s = s.replaceAll("0+$", "");
|
||||
s = s.replaceAll("\\.$", "");
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert float to string, remove the mess at the end.
|
||||
*
|
||||
* @param f float
|
||||
* @return string
|
||||
*/
|
||||
public static String str(Float f)
|
||||
{
|
||||
String s = f.toString();
|
||||
s = s.replaceAll("([0-9]+\\.[0-9]+)00+[0-9]+", "$1");
|
||||
s = s.replaceAll("0+$", "");
|
||||
s = s.replaceAll("\\.$", "");
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert object to string. If the object overrides toString(), it is
|
||||
* caled. Otherwise it's class name is converted to string.
|
||||
*
|
||||
* @param o object
|
||||
* @return string representation
|
||||
*/
|
||||
public static String str(Object o)
|
||||
{
|
||||
if (o == null) return "<null>";
|
||||
|
||||
boolean hasToString = false;
|
||||
|
||||
try {
|
||||
hasToString = (o.getClass().getMethod("toString").getDeclaringClass() != Object.class);
|
||||
} catch (final Throwable t) {
|
||||
// oh well..
|
||||
}
|
||||
|
||||
if (hasToString) {
|
||||
return o.toString();
|
||||
} else {
|
||||
|
||||
return str(o.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package mightypork.utils.annotations;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* Specify pretty name to be used when logging / converting class name to
|
||||
* string.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Alias {
|
||||
|
||||
String name();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package mightypork.utils.annotations;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* Marked method can be safely overriden; it's left blank (or with default
|
||||
* implementation) as a convenience.<br>
|
||||
* This is a description annotation and has no other function.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(value = { ElementType.METHOD })
|
||||
public @interface DefaultImpl {
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package mightypork.utils.annotations;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* Marks a static factory method. This is a description annotation and has no
|
||||
* other function.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
public @interface FactoryMethod {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.utils.eventbus;
|
||||
|
||||
|
||||
/**
|
||||
* Access to an {@link EventBus} instance
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface BusAccess {
|
||||
|
||||
/**
|
||||
* @return event bus
|
||||
*/
|
||||
EventBus getEventBus();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package mightypork.utils.eventbus;
|
||||
|
||||
|
||||
import mightypork.utils.eventbus.events.flags.DelayedEvent;
|
||||
import mightypork.utils.eventbus.events.flags.DirectEvent;
|
||||
import mightypork.utils.eventbus.events.flags.NonConsumableEvent;
|
||||
import mightypork.utils.eventbus.events.flags.NotLoggedEvent;
|
||||
import mightypork.utils.eventbus.events.flags.SingleReceiverEvent;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Event that can be handled by HANDLER, subscribing to the event bus.
|
||||
* </p>
|
||||
* <p>
|
||||
* Can be annotated as {@link SingleReceiverEvent} to be delivered once only,
|
||||
* and {@link DelayedEvent} or {@link DirectEvent} to specify default sending
|
||||
* mode. When marked as {@link NotLoggedEvent}, it will not appear in detailed
|
||||
* bus logging (useful for very frequent events, such as UpdateEvent).
|
||||
* </p>
|
||||
* <p>
|
||||
* Events annotated as {@link NonConsumableEvent} will throw an exception upon
|
||||
* an attempt to consume them.
|
||||
* </p>
|
||||
* <p>
|
||||
* Default sending mode (if not changed by annotations) is <i>queued</i> with
|
||||
* zero delay.
|
||||
* </p>
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
* @param <HANDLER> handler type
|
||||
*/
|
||||
public abstract class BusEvent<HANDLER> {
|
||||
|
||||
private boolean consumed;
|
||||
private boolean served;
|
||||
|
||||
|
||||
/**
|
||||
* Ask handler to handle this message.
|
||||
*
|
||||
* @param handler handler instance
|
||||
*/
|
||||
protected abstract void handleBy(HANDLER handler);
|
||||
|
||||
|
||||
/**
|
||||
* Consume the event, so no other clients will receive it.
|
||||
*
|
||||
* @throws UnsupportedOperationException if the {@link NonConsumableEvent}
|
||||
* annotation is present.
|
||||
*/
|
||||
public final void consume()
|
||||
{
|
||||
if (consumed) throw new IllegalStateException("Already consumed.");
|
||||
|
||||
if (getClass().isAnnotationPresent(NonConsumableEvent.class)) {
|
||||
throw new UnsupportedOperationException("Not consumable.");
|
||||
}
|
||||
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deliver to a handler using the handleBy method.
|
||||
*
|
||||
* @param handler handler instance
|
||||
*/
|
||||
final void deliverTo(HANDLER handler)
|
||||
{
|
||||
handleBy(handler);
|
||||
|
||||
if (!served) {
|
||||
if (getClass().isAnnotationPresent(SingleReceiverEvent.class)) {
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
served = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the event is consumed. Consumed event is not served to other
|
||||
* clients.
|
||||
*
|
||||
* @return true if consumed
|
||||
*/
|
||||
public final boolean isConsumed()
|
||||
{
|
||||
return consumed;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the event was served to at least 1 client
|
||||
*/
|
||||
final boolean wasServed()
|
||||
{
|
||||
return served;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear "served" and "consumed" flags before dispatching.
|
||||
*/
|
||||
final void clearFlags()
|
||||
{
|
||||
served = false;
|
||||
consumed = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called after all clients have received the event.
|
||||
*
|
||||
* @param bus event bus instance
|
||||
*/
|
||||
public void onDispatchComplete(EventBus bus)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
package mightypork.utils.eventbus;
|
||||
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.DelayQueue;
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import mightypork.utils.Reflect;
|
||||
import mightypork.utils.Support;
|
||||
import mightypork.utils.eventbus.clients.DelegatingClient;
|
||||
import mightypork.utils.eventbus.events.flags.DelayedEvent;
|
||||
import mightypork.utils.eventbus.events.flags.DirectEvent;
|
||||
import mightypork.utils.eventbus.events.flags.NotLoggedEvent;
|
||||
import mightypork.utils.interfaces.Destroyable;
|
||||
import mightypork.utils.logging.Log;
|
||||
|
||||
|
||||
/**
|
||||
* An event bus, accommodating multiple EventChannels.<br>
|
||||
* Channel will be created when an event of type is first encountered.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
final public class EventBus implements Destroyable, BusAccess {
|
||||
|
||||
/**
|
||||
* Queued event holder
|
||||
*/
|
||||
private class DelayQueueEntry implements Delayed {
|
||||
|
||||
private final long due;
|
||||
private final BusEvent<?> evt;
|
||||
|
||||
|
||||
public DelayQueueEntry(double seconds, BusEvent<?> event)
|
||||
{
|
||||
super();
|
||||
this.due = System.currentTimeMillis() + (long) (seconds * 1000);
|
||||
this.evt = event;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int compareTo(Delayed o)
|
||||
{
|
||||
return Long.valueOf(getDelay(TimeUnit.MILLISECONDS)).compareTo(o.getDelay(TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getDelay(TimeUnit unit)
|
||||
{
|
||||
return unit.convert(due - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
public BusEvent<?> getEvent()
|
||||
{
|
||||
return evt;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread handling queued events
|
||||
*/
|
||||
private class QueuePollingThread extends Thread {
|
||||
|
||||
public volatile boolean stopped = false;
|
||||
|
||||
|
||||
public QueuePollingThread()
|
||||
{
|
||||
super("Queue Polling Thread");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
DelayQueueEntry evt;
|
||||
|
||||
while (!stopped) {
|
||||
evt = null;
|
||||
|
||||
try {
|
||||
evt = sendQueue.take();
|
||||
} catch (final InterruptedException ignored) {
|
||||
//
|
||||
}
|
||||
|
||||
if (evt != null) {
|
||||
try {
|
||||
dispatch(evt.getEvent());
|
||||
} catch (final Throwable t) {
|
||||
Log.w(logMark + "Error while dispatching event: ", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final String logMark = "(bus) ";
|
||||
|
||||
|
||||
private static Class<?> getEventListenerClass(BusEvent<?> event)
|
||||
{
|
||||
// BEHOLD, MAGIC!
|
||||
|
||||
final Type evtc = event.getClass().getGenericSuperclass();
|
||||
|
||||
if (evtc instanceof ParameterizedType) {
|
||||
if (((ParameterizedType) evtc).getRawType() == BusEvent.class) {
|
||||
final Type[] types = ((ParameterizedType) evtc).getActualTypeArguments();
|
||||
for (final Type genericType : types) {
|
||||
return (Class<?>) genericType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Could not detect event listener type.");
|
||||
}
|
||||
|
||||
/** Log detailed messages (debug) */
|
||||
public boolean detailedLogging = false;
|
||||
|
||||
/** Queue polling thread */
|
||||
private final QueuePollingThread busThread;
|
||||
|
||||
/** Registered clients */
|
||||
private final Set<Object> clients = Collections.newSetFromMap(new ConcurrentHashMap<Object, Boolean>());
|
||||
|
||||
/** Whether the bus was destroyed */
|
||||
private boolean dead = false;
|
||||
|
||||
/** Message channels */
|
||||
private final Set<EventChannel<?, ?>> channels = Collections.newSetFromMap(new ConcurrentHashMap<EventChannel<?, ?>, Boolean>());
|
||||
|
||||
/** Messages queued for delivery */
|
||||
private final DelayQueue<DelayQueueEntry> sendQueue = new DelayQueue<>();
|
||||
|
||||
|
||||
/**
|
||||
* Make a new bus and start it's queue thread.
|
||||
*/
|
||||
public EventBus()
|
||||
{
|
||||
busThread = new QueuePollingThread();
|
||||
busThread.setDaemon(true);
|
||||
busThread.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Halt bus thread and reject any future events.
|
||||
*/
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
assertLive();
|
||||
|
||||
busThread.stopped = true;
|
||||
dead = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send based on annotation
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
public void send(BusEvent<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
final DelayedEvent adelay = Reflect.getAnnotation(event, DelayedEvent.class);
|
||||
if (adelay != null) {
|
||||
sendDelayed(event, adelay.delay());
|
||||
return;
|
||||
}
|
||||
|
||||
if (Reflect.hasAnnotation(event, DirectEvent.class)) {
|
||||
sendDirect(event);
|
||||
return;
|
||||
}
|
||||
|
||||
sendQueued(event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add event to a queue
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
public void sendQueued(BusEvent<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
sendDelayed(event, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add event to a queue, scheduled for given time.
|
||||
*
|
||||
* @param event event
|
||||
* @param delay delay before event is dispatched
|
||||
*/
|
||||
public void sendDelayed(BusEvent<?> event, double delay)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
final DelayQueueEntry dm = new DelayQueueEntry(delay, event);
|
||||
|
||||
if (shallLog(event)) {
|
||||
Log.f3(logMark + "Qu [" + Support.str(event) + "]" + (delay == 0 ? "" : (", delay: " + delay + "s")));
|
||||
}
|
||||
|
||||
sendQueue.add(dm);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send immediately.<br>
|
||||
* Should be used for real-time events that require immediate response, such
|
||||
* as timing events.
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
public void sendDirect(BusEvent<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
if (shallLog(event)) Log.f3(logMark + "Di [" + Support.str(event) + "]");
|
||||
|
||||
dispatch(event);
|
||||
}
|
||||
|
||||
|
||||
public void sendDirectToChildren(DelegatingClient delegatingClient, BusEvent<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
if (shallLog(event)) Log.f3(logMark + "Di->sub [" + Support.str(event) + "]");
|
||||
|
||||
doDispatch(delegatingClient.getChildClients(), event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Connect a client to the bus. The client will be connected to all current
|
||||
* and future channels, until removed from the bus.
|
||||
*
|
||||
* @param client the client
|
||||
*/
|
||||
public void subscribe(Object client)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
if (client == null) return;
|
||||
|
||||
clients.add(client);
|
||||
|
||||
if (detailedLogging) Log.f3(logMark + "Client joined: " + Support.str(client));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnect a client from the bus.
|
||||
*
|
||||
* @param client the client
|
||||
*/
|
||||
public void unsubscribe(Object client)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
clients.remove(client);
|
||||
|
||||
if (detailedLogging) Log.f3(logMark + "Client left: " + Support.str(client));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean addChannelForEvent(BusEvent<?> event)
|
||||
{
|
||||
try {
|
||||
if (detailedLogging) {
|
||||
Log.f3(logMark + "Setting up channel for new event type: " + Support.str(event.getClass()));
|
||||
}
|
||||
|
||||
final Class<?> listener = getEventListenerClass(event);
|
||||
final EventChannel<?, ?> ch = EventChannel.create(event.getClass(), listener);
|
||||
|
||||
if (ch.canBroadcast(event)) {
|
||||
|
||||
channels.add(ch);
|
||||
//channels.flush();
|
||||
|
||||
if (detailedLogging) {
|
||||
Log.f3(logMark + "Created new channel: " + Support.str(event.getClass()) + " -> " + Support.str(listener));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
Log.w(logMark + "Could not create channel for event " + Support.str(event.getClass()));
|
||||
}
|
||||
|
||||
} catch (final Throwable t) {
|
||||
Log.w(logMark + "Error while trying to add channel for event.", t);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make sure the bus is not destroyed.
|
||||
*
|
||||
* @throws IllegalStateException if the bus is dead.
|
||||
*/
|
||||
private void assertLive() throws IllegalStateException
|
||||
{
|
||||
if (dead) throw new IllegalStateException("EventBus is dead.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send immediately.<br>
|
||||
* Should be used for real-time events that require immediate response, such
|
||||
* as timing events.
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
private synchronized void dispatch(BusEvent<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
doDispatch(clients, event);
|
||||
event.onDispatchComplete(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send to a set of clients
|
||||
*
|
||||
* @param clients clients
|
||||
* @param event event
|
||||
*/
|
||||
private synchronized void doDispatch(Collection<?> clients, BusEvent<?> event)
|
||||
{
|
||||
boolean accepted = false;
|
||||
|
||||
event.clearFlags();
|
||||
|
||||
for (int i = 0; i < 2; i++) { // two tries.
|
||||
|
||||
for (final EventChannel<?, ?> b : channels) {
|
||||
if (b.canBroadcast(event)) {
|
||||
accepted = true;
|
||||
b.broadcast(event, clients);
|
||||
}
|
||||
|
||||
if (event.isConsumed()) break;
|
||||
}
|
||||
|
||||
if (!accepted) if (addChannelForEvent(event)) continue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!accepted) Log.e(logMark + "Not accepted by any channel: " + Support.str(event));
|
||||
if (!event.wasServed() && shallLog(event)) Log.w(logMark + "Not delivered: " + Support.str(event));
|
||||
}
|
||||
|
||||
|
||||
private boolean shallLog(BusEvent<?> event)
|
||||
{
|
||||
if (!detailedLogging) return false;
|
||||
if (Reflect.hasAnnotation(event, NotLoggedEvent.class)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public EventBus getEventBus()
|
||||
{
|
||||
return this; // just for compatibility use-case
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package mightypork.utils.eventbus;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import mightypork.utils.Reflect;
|
||||
import mightypork.utils.Support;
|
||||
import mightypork.utils.eventbus.clients.DelegatingClient;
|
||||
import mightypork.utils.eventbus.clients.ToggleableClient;
|
||||
import mightypork.utils.eventbus.events.flags.NonRejectableEvent;
|
||||
import mightypork.utils.logging.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Event delivery channel, module of {@link EventBus}
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
* @param <EVENT> event type
|
||||
* @param <CLIENT> client (subscriber) type
|
||||
*/
|
||||
class EventChannel<EVENT extends BusEvent<CLIENT>, CLIENT> {
|
||||
|
||||
private final Class<CLIENT> clientClass;
|
||||
private final Class<EVENT> eventClass;
|
||||
|
||||
|
||||
/**
|
||||
* Create a channel
|
||||
*
|
||||
* @param eventClass event class
|
||||
* @param clientClass client class
|
||||
*/
|
||||
public EventChannel(Class<EVENT> eventClass, Class<CLIENT> clientClass)
|
||||
{
|
||||
|
||||
if (eventClass == null || clientClass == null) {
|
||||
throw new NullPointerException("Null Event or Client class.");
|
||||
}
|
||||
|
||||
this.clientClass = clientClass;
|
||||
this.eventClass = eventClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to broadcast a event.<br>
|
||||
* If event is of wrong type, <code>false</code> is returned.
|
||||
*
|
||||
* @param event a event to be sent
|
||||
* @param clients collection of clients
|
||||
*/
|
||||
public void broadcast(BusEvent<?> event, Collection<?> clients)
|
||||
{
|
||||
if (!canBroadcast(event)) return;
|
||||
|
||||
doBroadcast(eventClass.cast(event), clients, new HashSet<>());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send the event
|
||||
*
|
||||
* @param event sent event
|
||||
* @param clients subscribing clients
|
||||
* @param processed clients already processed
|
||||
*/
|
||||
private void doBroadcast(final EVENT event, final Collection<?> clients, final Collection<Object> processed)
|
||||
{
|
||||
for (final Object client : clients) {
|
||||
|
||||
// exclude obvious non-clients
|
||||
if (!isClientValid(client)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// avoid executing more times
|
||||
if (processed.contains(client)) {
|
||||
Log.w(EventBus.logMark + "Client already served: " + Support.str(client));
|
||||
continue;
|
||||
}
|
||||
processed.add(client);
|
||||
|
||||
final boolean must_deliver = Reflect.hasAnnotation(event, NonRejectableEvent.class);
|
||||
|
||||
// opt-out
|
||||
if (client instanceof ToggleableClient) {
|
||||
if (!must_deliver && !((ToggleableClient) client).isListening()) continue;
|
||||
}
|
||||
|
||||
sendTo(client, event);
|
||||
|
||||
if (event.isConsumed()) return;
|
||||
|
||||
// pass on to delegated clients
|
||||
if (client instanceof DelegatingClient) {
|
||||
if (must_deliver || ((DelegatingClient) client).doesDelegate()) {
|
||||
|
||||
final Collection<?> children = ((DelegatingClient) client).getChildClients();
|
||||
|
||||
if (children != null && children.size() > 0) {
|
||||
doBroadcast(event, children, processed);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send an event to a client.
|
||||
*
|
||||
* @param client target client
|
||||
* @param event event to send
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void sendTo(Object client, EVENT event)
|
||||
{
|
||||
if (isClientOfChannelType(client)) {
|
||||
((BusEvent<CLIENT>) event).deliverTo((CLIENT) client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the given event can be broadcasted by this channel
|
||||
*
|
||||
* @param event event object
|
||||
* @return can be broadcasted
|
||||
*/
|
||||
public boolean canBroadcast(BusEvent<?> event)
|
||||
{
|
||||
return event != null && eventClass.isInstance(event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance for given types
|
||||
*
|
||||
* @param eventClass event class
|
||||
* @param clientClass client class
|
||||
* @return the broadcaster
|
||||
*/
|
||||
public static <F_EVENT extends BusEvent<F_CLIENT>, F_CLIENT> EventChannel<F_EVENT, F_CLIENT> create(Class<F_EVENT> eventClass, Class<F_CLIENT> clientClass)
|
||||
{
|
||||
return new EventChannel<>(eventClass, clientClass);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if client is of channel type
|
||||
*
|
||||
* @param client client
|
||||
* @return is of type
|
||||
*/
|
||||
private boolean isClientOfChannelType(Object client)
|
||||
{
|
||||
return clientClass.isInstance(client);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the channel is compatible with given
|
||||
*
|
||||
* @param client client
|
||||
* @return is supported
|
||||
*/
|
||||
public boolean isClientValid(Object client)
|
||||
{
|
||||
return isClientOfChannelType(client) || (client instanceof DelegatingClient);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 13;
|
||||
int result = 1;
|
||||
result = prime * result + ((clientClass == null) ? 0 : clientClass.hashCode());
|
||||
result = prime * result + ((eventClass == null) ? 0 : eventClass.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (!(obj instanceof EventChannel)) return false;
|
||||
final EventChannel<?, ?> other = (EventChannel<?, ?>) obj;
|
||||
if (clientClass == null) {
|
||||
if (other.clientClass != null) return false;
|
||||
} else if (!clientClass.equals(other.clientClass)) return false;
|
||||
if (eventClass == null) {
|
||||
if (other.eventClass != null) return false;
|
||||
} else if (!eventClass.equals(other.eventClass)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "{ " + Support.str(eventClass) + " => " + Support.str(clientClass) + " }";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package mightypork.utils.eventbus.clients;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import mightypork.utils.eventbus.BusAccess;
|
||||
import mightypork.utils.eventbus.EventBus;
|
||||
|
||||
|
||||
/**
|
||||
* Client that can be attached to the {@link EventBus}, or added as a child
|
||||
* client to another {@link DelegatingClient}
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public abstract class BusNode implements BusAccess, ClientHub {
|
||||
|
||||
private final BusAccess busAccess;
|
||||
|
||||
private final Set<Object> clients = new LinkedHashSet<>();
|
||||
private boolean listening = true;
|
||||
private boolean delegating = true;
|
||||
|
||||
|
||||
/**
|
||||
* @param busAccess access to bus
|
||||
*/
|
||||
public BusNode(BusAccess busAccess)
|
||||
{
|
||||
this.busAccess = busAccess;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<Object> getChildClients()
|
||||
{
|
||||
return clients;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean doesDelegate()
|
||||
{
|
||||
return delegating;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isListening()
|
||||
{
|
||||
return listening;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a child subscriber to the {@link EventBus}.<br>
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
@Override
|
||||
public void addChildClient(Object client)
|
||||
{
|
||||
if (client instanceof RootBusNode) {
|
||||
throw new IllegalArgumentException("Cannot nest RootBusNode.");
|
||||
}
|
||||
|
||||
clients.add(client);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a child subscriber
|
||||
*
|
||||
* @param client subscriber to remove
|
||||
*/
|
||||
@Override
|
||||
public void removeChildClient(Object client)
|
||||
{
|
||||
if (client != null) {
|
||||
clients.remove(client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether events should be received.
|
||||
*
|
||||
* @param listening receive events
|
||||
*/
|
||||
public void setListening(boolean listening)
|
||||
{
|
||||
this.listening = listening;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether events should be passed on to child nodes
|
||||
*
|
||||
* @param delegating
|
||||
*/
|
||||
public void setDelegating(boolean delegating)
|
||||
{
|
||||
this.delegating = delegating;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public EventBus getEventBus()
|
||||
{
|
||||
return busAccess.getEventBus();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package mightypork.utils.eventbus.clients;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import mightypork.utils.eventbus.EventBus;
|
||||
|
||||
|
||||
/**
|
||||
* Common methods for client hubs (ie delegating vlient implementations)
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface ClientHub extends DelegatingClient, ToggleableClient {
|
||||
|
||||
@Override
|
||||
public boolean doesDelegate();
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<Object> getChildClients();
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isListening();
|
||||
|
||||
|
||||
/**
|
||||
* Add a child subscriber to the {@link EventBus}.<br>
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
public void addChildClient(Object client);
|
||||
|
||||
|
||||
/**
|
||||
* Remove a child subscriber
|
||||
*
|
||||
* @param client subscriber to remove
|
||||
*/
|
||||
void removeChildClient(Object client);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package mightypork.utils.eventbus.clients;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
/**
|
||||
* Array-list with varargs constructor, intended to wrap fre clients for
|
||||
* delegating client.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class ClientList extends ArrayList<Object> {
|
||||
|
||||
public ClientList(Object... clients)
|
||||
{
|
||||
for (final Object c : clients) {
|
||||
super.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package mightypork.utils.eventbus.clients;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
|
||||
/**
|
||||
* Client containing child clients. According to the contract, if the collection
|
||||
* of clients is ordered, the clients will be served in that order. In any case,
|
||||
* the {@link DelegatingClient} itself will be served beforehand.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface DelegatingClient {
|
||||
|
||||
/**
|
||||
* @return collection of child clients. Can not be null.
|
||||
*/
|
||||
public Collection<?> getChildClients();
|
||||
|
||||
|
||||
/**
|
||||
* @return true if delegating is active
|
||||
*/
|
||||
public boolean doesDelegate();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package mightypork.utils.eventbus.clients;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import mightypork.utils.interfaces.Enableable;
|
||||
|
||||
|
||||
/**
|
||||
* List of clients, that can be used as a delegating client.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class DelegatingList extends ClientList implements DelegatingClient, Enableable {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
|
||||
public DelegatingList(Object... clients)
|
||||
{
|
||||
super(clients);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<?> getChildClients()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean doesDelegate()
|
||||
{
|
||||
return isEnabled();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean yes)
|
||||
{
|
||||
enabled = yes;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package mightypork.utils.eventbus.clients;
|
||||
|
||||
|
||||
import mightypork.utils.annotations.DefaultImpl;
|
||||
import mightypork.utils.eventbus.BusAccess;
|
||||
import mightypork.utils.interfaces.Destroyable;
|
||||
|
||||
|
||||
/**
|
||||
* Bus node that should be directly attached to the bus.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public abstract class RootBusNode extends BusNode implements Destroyable {
|
||||
|
||||
/**
|
||||
* @param busAccess access to bus
|
||||
*/
|
||||
public RootBusNode(BusAccess busAccess)
|
||||
{
|
||||
super(busAccess);
|
||||
|
||||
getEventBus().subscribe(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void destroy()
|
||||
{
|
||||
deinit();
|
||||
|
||||
getEventBus().unsubscribe(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deinitialize the subsystem<br>
|
||||
* (called during destruction)
|
||||
*/
|
||||
@DefaultImpl
|
||||
protected void deinit()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.utils.eventbus.clients;
|
||||
|
||||
|
||||
/**
|
||||
* Client that can toggle receiving messages.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface ToggleableClient {
|
||||
|
||||
/**
|
||||
* @return true if the client wants to receive messages
|
||||
*/
|
||||
public boolean isListening();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package mightypork.utils.eventbus.events;
|
||||
|
||||
|
||||
import mightypork.utils.eventbus.BusEvent;
|
||||
import mightypork.utils.eventbus.events.flags.DirectEvent;
|
||||
import mightypork.utils.eventbus.events.flags.NonConsumableEvent;
|
||||
import mightypork.utils.interfaces.Destroyable;
|
||||
|
||||
|
||||
/**
|
||||
* Invoke destroy() method of all subscribers. Used to deinit a system.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
@DirectEvent
|
||||
@NonConsumableEvent
|
||||
public class DestroyEvent extends BusEvent<Destroyable> {
|
||||
|
||||
@Override
|
||||
public void handleBy(Destroyable handler)
|
||||
{
|
||||
handler.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package mightypork.utils.eventbus.events;
|
||||
|
||||
|
||||
import mightypork.utils.eventbus.BusEvent;
|
||||
import mightypork.utils.eventbus.events.flags.DirectEvent;
|
||||
import mightypork.utils.eventbus.events.flags.NonConsumableEvent;
|
||||
import mightypork.utils.eventbus.events.flags.NotLoggedEvent;
|
||||
import mightypork.utils.interfaces.Updateable;
|
||||
|
||||
|
||||
/**
|
||||
* Delta timing update event. Not logged.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
@NotLoggedEvent
|
||||
@DirectEvent
|
||||
@NonConsumableEvent
|
||||
public class UpdateEvent extends BusEvent<Updateable> {
|
||||
|
||||
private final double deltaTime;
|
||||
|
||||
|
||||
/**
|
||||
* @param deltaTime time since last update (sec)
|
||||
*/
|
||||
public UpdateEvent(double deltaTime)
|
||||
{
|
||||
this.deltaTime = deltaTime;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleBy(Updateable handler)
|
||||
{
|
||||
handler.update(deltaTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package mightypork.utils.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* Event that should be queued with given delay (default: 0);
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface DelayedEvent {
|
||||
|
||||
/**
|
||||
* @return event dispatch delay [seconds]
|
||||
*/
|
||||
double delay() default 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package mightypork.utils.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* Event that should not be queued.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface DirectEvent {}
|
||||
@@ -0,0 +1,21 @@
|
||||
package mightypork.utils.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* Event that cannot be consumed
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface NonConsumableEvent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package mightypork.utils.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* Event that is forcibly delivered to all clients (bypass Toggleable etc)
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface NonRejectableEvent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package mightypork.utils.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* Event that's not worth logging, unless there was an error with it.<br>
|
||||
* Useful for common events that would otherwise clutter the log.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface NotLoggedEvent {}
|
||||
@@ -0,0 +1,21 @@
|
||||
package mightypork.utils.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* Handled only by the first client, then discarded.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface SingleReceiverEvent {}
|
||||
@@ -0,0 +1,37 @@
|
||||
package mightypork.utils.exceptions;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown when data could not be read successfully.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class CorruptDataException extends IOException {
|
||||
|
||||
public CorruptDataException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
public CorruptDataException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
||||
public CorruptDataException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
public CorruptDataException(Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package mightypork.utils.exceptions;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown when a invalid value is given to a method, or found in a data object /
|
||||
* file etc
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class IllegalValueException extends RuntimeException {
|
||||
|
||||
public IllegalValueException()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public IllegalValueException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
public IllegalValueException(Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
}
|
||||
|
||||
|
||||
public IllegalValueException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package mightypork.utils.exceptions;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown by a map-like class when the key specified is already taken.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class KeyAlreadyExistsException extends RuntimeException {
|
||||
|
||||
public KeyAlreadyExistsException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
public KeyAlreadyExistsException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
||||
public KeyAlreadyExistsException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
public KeyAlreadyExistsException(Throwable cause)
|
||||
{
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package mightypork.utils.files;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
|
||||
|
||||
/**
|
||||
* File filter for certain suffixes
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class FileSuffixFilter implements FileFilter {
|
||||
|
||||
/** Array of allowed suffixes */
|
||||
private String[] suffixes = null;
|
||||
|
||||
|
||||
/**
|
||||
* Suffix filter
|
||||
*
|
||||
* @param suffixes var-args allowed suffixes, case insensitive
|
||||
*/
|
||||
public FileSuffixFilter(String... suffixes)
|
||||
{
|
||||
this.suffixes = suffixes;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean accept(File pathname)
|
||||
{
|
||||
if (!pathname.isFile()) return false;
|
||||
|
||||
final String fname = pathname.getName().toLowerCase().trim();
|
||||
|
||||
for (final String suffix : suffixes) {
|
||||
if (fname.endsWith(suffix.toLowerCase().trim())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package mightypork.utils.files;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.zip.Adler32;
|
||||
import java.util.zip.CheckedInputStream;
|
||||
import java.util.zip.Checksum;
|
||||
|
||||
import mightypork.utils.logging.Log;
|
||||
|
||||
|
||||
public class FileTreeDiff {
|
||||
|
||||
private static final byte[] BUFFER = new byte[2048];
|
||||
private final Checksum ck1 = new Adler32();
|
||||
private final Checksum ck2 = new Adler32();
|
||||
|
||||
private boolean logging = true;
|
||||
|
||||
private final List<Tuple<File>> compared = new ArrayList<>();
|
||||
private final Comparator<File> fileFirstSorter = new Comparator<File>() {
|
||||
|
||||
@Override
|
||||
public int compare(File o1, File o2)
|
||||
{
|
||||
if (!o1.isDirectory() && o2.isDirectory()) return -1;
|
||||
if (o1.isDirectory() && !o2.isDirectory()) return 1;
|
||||
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public void enableLogging(boolean state)
|
||||
{
|
||||
logging = state;
|
||||
}
|
||||
|
||||
|
||||
public boolean areEqual(File dir1, File dir2)
|
||||
{
|
||||
if (logging) Log.f3("Comparing directory trees:\n 1. " + dir1 + "\n 2. " + dir2);
|
||||
|
||||
try {
|
||||
compared.clear();
|
||||
buildList(dir1, dir2);
|
||||
|
||||
calcChecksum();
|
||||
|
||||
if (logging) Log.f3("No difference found.");
|
||||
|
||||
return true;
|
||||
|
||||
} catch (final NotEqualException e) {
|
||||
if (logging) Log.f3("Difference found:\n" + e.getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void calcChecksum() throws NotEqualException
|
||||
{
|
||||
|
||||
for (final Tuple<File> pair : compared) {
|
||||
ck1.reset();
|
||||
ck2.reset();
|
||||
|
||||
try(FileInputStream in1 = new FileInputStream(pair.a);
|
||||
FileInputStream in2 = new FileInputStream(pair.b)) {
|
||||
|
||||
try(CheckedInputStream cin1 = new CheckedInputStream(in1, ck1);
|
||||
CheckedInputStream cin2 = new CheckedInputStream(in2, ck2)) {
|
||||
|
||||
while (true) {
|
||||
final int read1 = cin1.read(BUFFER);
|
||||
final int read2 = cin2.read(BUFFER);
|
||||
|
||||
if (read1 != read2 || ck1.getValue() != ck2.getValue()) {
|
||||
throw new NotEqualException("Bytes differ:\n" + pair.a + "\n" + pair.b);
|
||||
}
|
||||
|
||||
if (read1 == -1) break;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (final IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void buildList(File f1, File f2) throws NotEqualException
|
||||
{
|
||||
if (f1.isDirectory() != f2.isDirectory()) throw new NotEqualException("isDirectory differs:\n" + f1 + "\n" + f2);
|
||||
|
||||
if (f1.isFile() && f2.isFile()) {
|
||||
if (f1.length() != f2.length()) throw new NotEqualException("Sizes differ:\n" + f1 + "\n" + f2);
|
||||
}
|
||||
|
||||
if (f1.isDirectory()) {
|
||||
final File[] children1 = f1.listFiles();
|
||||
final File[] children2 = f2.listFiles();
|
||||
|
||||
Arrays.sort(children1, fileFirstSorter);
|
||||
Arrays.sort(children2, fileFirstSorter);
|
||||
|
||||
if (children1.length != children2.length) throw new NotEqualException("Child counts differ:\n" + f1 + "\n" + f2);
|
||||
|
||||
for (int i = 0; i < children1.length; i++) {
|
||||
final File ch1 = children1[i];
|
||||
final File ch2 = children2[i];
|
||||
|
||||
if (!ch1.getName().equals(ch2.getName())) throw new NotEqualException("Filenames differ:\n" + ch1 + "\n" + ch2);
|
||||
|
||||
buildList(ch1, ch2);
|
||||
}
|
||||
|
||||
} else {
|
||||
compared.add(new Tuple<>(f1, f2));
|
||||
}
|
||||
}
|
||||
|
||||
private class NotEqualException extends Exception {
|
||||
|
||||
public NotEqualException(String msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class Tuple<T> {
|
||||
|
||||
public T a;
|
||||
public T b;
|
||||
|
||||
|
||||
public Tuple(T a, T b)
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
package mightypork.utils.files;
|
||||
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import mightypork.utils.logging.Log;
|
||||
import mightypork.utils.string.StringUtil;
|
||||
import mightypork.utils.string.validation.StringFilter;
|
||||
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
/**
|
||||
* Copy directory recursively.
|
||||
*
|
||||
* @param source source file
|
||||
* @param target target file
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public static void copyDirectory(File source, File target) throws IOException
|
||||
{
|
||||
copyDirectory(source, target, null, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy directory recursively - advanced variant.
|
||||
*
|
||||
* @param source source file
|
||||
* @param target target file
|
||||
* @param filter filter accepting only files and dirs to be copied
|
||||
* @param filesCopied list into which all the target files will be added
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public static void copyDirectory(File source, File target, FileFilter filter, List<File> filesCopied) throws IOException
|
||||
{
|
||||
if (!source.exists()) return;
|
||||
|
||||
if (source.isDirectory()) {
|
||||
if (!target.exists() && !target.mkdir()) {
|
||||
throw new IOException("Could not open destination directory.");
|
||||
}
|
||||
|
||||
final String[] children = source.list();
|
||||
for (final String element : children) {
|
||||
copyDirectory(new File(source, element), new File(target, element), filter, filesCopied);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (filter != null && !filter.accept(source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (filesCopied != null) filesCopied.add(target);
|
||||
copyFile(source, target);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List directory recursively
|
||||
*
|
||||
* @param source source file
|
||||
* @param filter filter accepting only files and dirs to be copied (or null)
|
||||
* @param files list of the found files
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public static void listDirectoryRecursive(File source, StringFilter filter, List<File> files) throws IOException
|
||||
{
|
||||
if (source.isDirectory()) {
|
||||
final String[] children = source.list();
|
||||
for (final String element : children) {
|
||||
listDirectoryRecursive(new File(source, element), filter, files);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (filter != null && !filter.isValid(source.getAbsolutePath())) {
|
||||
return;
|
||||
}
|
||||
|
||||
files.add(source);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy file using streams. Make sure target directory exists!
|
||||
*
|
||||
* @param source source file
|
||||
* @param target target file
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public static void copyFile(File source, File target) throws IOException
|
||||
{
|
||||
|
||||
try(InputStream in = new FileInputStream(source);
|
||||
OutputStream out = new FileOutputStream(target)) {
|
||||
|
||||
copyStream(in, out);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy bytes from input to output stream, leaving out stream open
|
||||
*
|
||||
* @param in input stream
|
||||
* @param out output stream
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public static void copyStream(InputStream in, OutputStream out) throws IOException
|
||||
{
|
||||
if (in == null) {
|
||||
throw new NullPointerException("Input stream is null");
|
||||
}
|
||||
|
||||
if (out == null) {
|
||||
throw new NullPointerException("Output stream is null");
|
||||
}
|
||||
|
||||
final byte[] buf = new byte[2048];
|
||||
int len;
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Improved delete
|
||||
*
|
||||
* @param path deleted path
|
||||
* @param recursive recursive delete
|
||||
* @return success
|
||||
*/
|
||||
public static boolean delete(File path, boolean recursive)
|
||||
{
|
||||
if (!path.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!recursive || !path.isDirectory()) return path.delete();
|
||||
|
||||
final String[] list = path.list();
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
if (!delete(new File(path, list[i]), true)) return false;
|
||||
}
|
||||
|
||||
return path.delete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read entire file to a string.
|
||||
*
|
||||
* @param file file
|
||||
* @return file contents
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String fileToString(File file) throws IOException
|
||||
{
|
||||
try(FileInputStream fin = new FileInputStream(file)) {
|
||||
|
||||
return streamToString(fin);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get files in a folder (create folder if needed)
|
||||
*
|
||||
* @param dir folder
|
||||
* @return list of files
|
||||
*/
|
||||
public static List<File> listDirectory(File dir)
|
||||
{
|
||||
return FileUtils.listDirectory(dir, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get files in a folder (create folder if needed)
|
||||
*
|
||||
* @param dir folder
|
||||
* @param filter file filter
|
||||
* @return list of files
|
||||
*/
|
||||
public static List<File> listDirectory(File dir, FileFilter filter)
|
||||
{
|
||||
dir.mkdir();
|
||||
|
||||
final List<File> list = new ArrayList<>();
|
||||
|
||||
for (final File f : dir.listFiles(filter)) {
|
||||
list.add(f);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove extension.
|
||||
*
|
||||
* @param file file
|
||||
* @return filename without extension
|
||||
*/
|
||||
public static String[] getFilenameParts(File file)
|
||||
{
|
||||
return getFilenameParts(file.getName());
|
||||
}
|
||||
|
||||
|
||||
public static String getExtension(File file)
|
||||
{
|
||||
return getExtension(file.getName());
|
||||
}
|
||||
|
||||
|
||||
public static String getExtension(String file)
|
||||
{
|
||||
return StringUtil.fromLastChar(file, '.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove extension.
|
||||
*
|
||||
* @param filename
|
||||
* @return filename and extension
|
||||
*/
|
||||
public static String[] getFilenameParts(String filename)
|
||||
{
|
||||
String ext, name;
|
||||
|
||||
try {
|
||||
ext = StringUtil.fromLastDot(filename);
|
||||
} catch (final StringIndexOutOfBoundsException e) {
|
||||
ext = "";
|
||||
}
|
||||
|
||||
try {
|
||||
name = StringUtil.toLastDot(filename);
|
||||
} catch (final StringIndexOutOfBoundsException e) {
|
||||
name = "";
|
||||
Log.w("Error extracting extension from file " + filename);
|
||||
}
|
||||
|
||||
return new String[] { name, ext };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read entire input stream to a string, and close it.
|
||||
*
|
||||
* @param in input stream
|
||||
* @return file contents
|
||||
*/
|
||||
public static String streamToString(InputStream in)
|
||||
{
|
||||
return streamToString(in, -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read input stream to a string, and close it.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param lines max number of lines (-1 to disable limit)
|
||||
* @return file contents
|
||||
*/
|
||||
public static String streamToString(InputStream in, int lines)
|
||||
{
|
||||
if (in == null) {
|
||||
Log.e(new NullPointerException("Null stream to be converted to String."));
|
||||
return ""; // to avoid NPE's
|
||||
}
|
||||
|
||||
BufferedReader br = null;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
String line;
|
||||
try {
|
||||
int cnt = 0;
|
||||
br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
|
||||
while ((line = br.readLine()) != null && (cnt < lines || lines <= 0)) {
|
||||
sb.append(line + "\n");
|
||||
cnt++;
|
||||
}
|
||||
|
||||
if (cnt == lines && lines > 0) {
|
||||
sb.append("--- end of preview ---\n");
|
||||
}
|
||||
|
||||
} catch (final IOException e) {
|
||||
Log.e(e);
|
||||
} finally {
|
||||
try {
|
||||
if (br != null) br.close();
|
||||
} catch (final IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public static InputStream stringToStream(String text)
|
||||
{
|
||||
if (text == null) return null;
|
||||
|
||||
try {
|
||||
return new ByteArrayInputStream(text.getBytes("UTF-8"));
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
Log.e(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static InputStream getResource(String path)
|
||||
{
|
||||
final InputStream in = FileUtils.class.getResourceAsStream(path);
|
||||
|
||||
if (in != null) return in;
|
||||
|
||||
try {
|
||||
return new FileInputStream(new File(".", path));
|
||||
} catch (final FileNotFoundException e) {
|
||||
// error
|
||||
Log.w("Could not open resource stream: " + path);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static String getResourceAsString(String path)
|
||||
{
|
||||
return streamToString(FileUtils.class.getResourceAsStream(path));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save string to file
|
||||
*
|
||||
* @param file file
|
||||
* @param text string
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public static void stringToFile(File file, String text) throws IOException
|
||||
{
|
||||
try(PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8")) {
|
||||
|
||||
out.print(text);
|
||||
|
||||
out.flush();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void deleteEmptyDirs(File base) throws IOException
|
||||
{
|
||||
for (final File f : listDirectory(base)) {
|
||||
if (!f.isDirectory()) continue;
|
||||
|
||||
deleteEmptyDirs(f);
|
||||
|
||||
final List<File> children = listDirectory(f);
|
||||
if (children.size() == 0) {
|
||||
if (!f.delete()) throw new IOException("Could not delete a directory: " + f);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static String getBasename(String name)
|
||||
{
|
||||
return StringUtil.toLastChar(StringUtil.fromLastChar(name, '/'), '.');
|
||||
}
|
||||
|
||||
|
||||
public static String getFilename(String name)
|
||||
{
|
||||
return StringUtil.fromLastChar(name, '/');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy resource to file
|
||||
*
|
||||
* @param resname resource name
|
||||
* @param file out file
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void resourceToFile(String resname, File file) throws IOException
|
||||
{
|
||||
try(InputStream in = FileUtils.getResource(resname);
|
||||
OutputStream out = new FileOutputStream(file)) {
|
||||
|
||||
FileUtils.copyStream(in, out);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get resource as string, safely closing streams.
|
||||
*
|
||||
* @param resname resource name
|
||||
* @return resource as string, empty string on failure
|
||||
* @throws IOException on fail
|
||||
*/
|
||||
public static String resourceToString(String resname) throws IOException
|
||||
{
|
||||
try(InputStream in = FileUtils.getResource(resname)) {
|
||||
return streamToString(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package mightypork.utils.files;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileLock;
|
||||
|
||||
|
||||
/**
|
||||
* Instance lock (avoid running twice)
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class InstanceLock {
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
public static boolean onFile(final File lockFile)
|
||||
{
|
||||
try {
|
||||
lockFile.getParentFile().mkdirs();
|
||||
final RandomAccessFile randomAccessFile = new RandomAccessFile(lockFile, "rw");
|
||||
|
||||
final FileLock fileLock = randomAccessFile.getChannel().tryLock();
|
||||
if (fileLock != null) {
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try {
|
||||
fileLock.release();
|
||||
randomAccessFile.close();
|
||||
if (!lockFile.delete()) throw new IOException();
|
||||
} catch (final Throwable t) {
|
||||
System.err.println("Unable to remove lock file.");
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (final IOException e) {
|
||||
System.err.println("IO error while obtaining lock.");
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package mightypork.utils.files;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
public class OsUtils {
|
||||
|
||||
public static enum EnumOS
|
||||
{
|
||||
linux, macos, solaris, unknown, windows;
|
||||
|
||||
public boolean isLinux()
|
||||
{
|
||||
return this == linux || this == solaris;
|
||||
}
|
||||
|
||||
|
||||
public boolean isMac()
|
||||
{
|
||||
return this == macos;
|
||||
}
|
||||
|
||||
|
||||
public boolean isWindows()
|
||||
{
|
||||
return this == windows;
|
||||
}
|
||||
}
|
||||
|
||||
private static EnumOS cachedOs;
|
||||
|
||||
|
||||
public static File getHomeWorkDir(String dirname)
|
||||
{
|
||||
final String userhome = System.getProperty("user.home", ".");
|
||||
File file;
|
||||
|
||||
switch (getOs()) {
|
||||
case linux:
|
||||
case solaris:
|
||||
file = new File(userhome, dirname + '/');
|
||||
break;
|
||||
|
||||
case windows:
|
||||
final String appdata = System.getenv("APPDATA");
|
||||
|
||||
if (appdata != null) {
|
||||
file = new File(appdata, dirname + '/');
|
||||
} else {
|
||||
file = new File(userhome, dirname + '/');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case macos:
|
||||
file = new File(userhome, "Library/Application Support/" + dirname);
|
||||
break;
|
||||
|
||||
default:
|
||||
file = new File(userhome, dirname + "/");
|
||||
break;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
public static EnumOS getOs()
|
||||
{
|
||||
if (cachedOs != null) return cachedOs;
|
||||
|
||||
final String s = System.getProperty("os.name").toLowerCase();
|
||||
|
||||
if (s.contains("win")) {
|
||||
cachedOs = EnumOS.windows;
|
||||
|
||||
} else if (s.contains("mac")) {
|
||||
cachedOs = EnumOS.macos;
|
||||
|
||||
} else if (s.contains("linux") || s.contains("unix")) {
|
||||
cachedOs = EnumOS.linux;
|
||||
|
||||
} else if (s.contains("solaris") || s.contains("sunos")) {
|
||||
cachedOs = EnumOS.solaris;
|
||||
|
||||
} else {
|
||||
cachedOs = EnumOS.unknown;
|
||||
}
|
||||
|
||||
return cachedOs;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package mightypork.utils.files.config;
|
||||
|
||||
|
||||
import mightypork.utils.Convert;
|
||||
|
||||
|
||||
public abstract class Property<T extends Object> {
|
||||
|
||||
private final String comment;
|
||||
private final String key;
|
||||
|
||||
private T value;
|
||||
private final T defaultValue;
|
||||
|
||||
|
||||
public Property(String key, T defaultValue, String comment)
|
||||
{
|
||||
super();
|
||||
this.comment = comment;
|
||||
this.key = key;
|
||||
this.value = defaultValue;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
|
||||
public final void parse(String string)
|
||||
{
|
||||
setValue(decode(string, defaultValue));
|
||||
}
|
||||
|
||||
|
||||
public abstract T decode(String string, T defval);
|
||||
|
||||
|
||||
public String encode(T value)
|
||||
{
|
||||
return Convert.toString(value, Convert.toString(defaultValue));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final String toString()
|
||||
{
|
||||
return encode(value);
|
||||
}
|
||||
|
||||
|
||||
public T getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setValue(Object value)
|
||||
{
|
||||
this.value = (T) value;
|
||||
}
|
||||
|
||||
|
||||
public String getComment()
|
||||
{
|
||||
return comment;
|
||||
}
|
||||
|
||||
|
||||
public String getKey()
|
||||
{
|
||||
return key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
package mightypork.utils.files.config;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import mightypork.utils.Convert;
|
||||
import mightypork.utils.logging.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Property manager with advanced formatting and value checking.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class PropertyManager {
|
||||
|
||||
private class BooleanProperty extends Property<Boolean> {
|
||||
|
||||
public BooleanProperty(String key, Boolean defaultValue, String comment)
|
||||
{
|
||||
super(key, defaultValue, comment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Boolean decode(String string, Boolean defval)
|
||||
{
|
||||
return Convert.toBoolean(string, defval);
|
||||
}
|
||||
}
|
||||
|
||||
private class IntegerProperty extends Property<Integer> {
|
||||
|
||||
public IntegerProperty(String key, Integer defaultValue, String comment)
|
||||
{
|
||||
super(key, defaultValue, comment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Integer decode(String string, Integer defval)
|
||||
{
|
||||
return Convert.toInteger(string, defval);
|
||||
}
|
||||
}
|
||||
|
||||
private class DoubleProperty extends Property<Double> {
|
||||
|
||||
public DoubleProperty(String key, Double defaultValue, String comment)
|
||||
{
|
||||
super(key, defaultValue, comment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Double decode(String string, Double defval)
|
||||
{
|
||||
return Convert.toDouble(string, defval);
|
||||
}
|
||||
}
|
||||
|
||||
private class StringProperty extends Property<String> {
|
||||
|
||||
public StringProperty(String key, String defaultValue, String comment)
|
||||
{
|
||||
super(key, defaultValue, comment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String decode(String string, String defval)
|
||||
{
|
||||
return Convert.toString(string, defval);
|
||||
}
|
||||
}
|
||||
|
||||
/** put newline before entry comments */
|
||||
private boolean cfgNewlineBeforeComments = true;
|
||||
|
||||
/** Put newline between sections. */
|
||||
private boolean cfgSeparateSections = true;
|
||||
|
||||
private final File file;
|
||||
private String fileComment = "";
|
||||
|
||||
private final TreeMap<String, Property<?>> entries;
|
||||
private final TreeMap<String, String> renameTable;
|
||||
private SortedProperties props = new SortedProperties();
|
||||
|
||||
|
||||
/**
|
||||
* Create property manager from file path and an initial comment.
|
||||
*
|
||||
* @param file file with the props
|
||||
* @param comment the initial comment. Use \n in it if you want.
|
||||
*/
|
||||
public PropertyManager(File file, String comment)
|
||||
{
|
||||
this.file = file;
|
||||
this.entries = new TreeMap<>();
|
||||
this.renameTable = new TreeMap<>();
|
||||
this.fileComment = comment;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load, fix and write to file.
|
||||
*/
|
||||
public void load()
|
||||
{
|
||||
if (!file.getParentFile().mkdirs()) {
|
||||
if (!file.getParentFile().exists()) {
|
||||
throw new RuntimeException("Cound not create config file.");
|
||||
}
|
||||
}
|
||||
|
||||
try(FileInputStream fis = new FileInputStream(file)) {
|
||||
props.load(fis);
|
||||
} catch (final IOException e) {
|
||||
props = new SortedProperties();
|
||||
}
|
||||
|
||||
props.cfgBlankRowBetweenSections = cfgSeparateSections;
|
||||
props.cfgBlankRowBeforeComment = cfgNewlineBeforeComments;
|
||||
|
||||
// rename keys
|
||||
for (final Entry<String, String> entry : renameTable.entrySet()) {
|
||||
|
||||
final String pr = props.getProperty(entry.getKey());
|
||||
|
||||
if (pr == null) continue;
|
||||
|
||||
props.remove(entry.getKey());
|
||||
props.setProperty(entry.getValue(), pr);
|
||||
}
|
||||
|
||||
for (final Property<?> entry : entries.values()) {
|
||||
entry.parse(props.getProperty(entry.getKey()));
|
||||
}
|
||||
|
||||
renameTable.clear();
|
||||
}
|
||||
|
||||
|
||||
public void save()
|
||||
{
|
||||
try {
|
||||
final ArrayList<String> keyList = new ArrayList<>();
|
||||
|
||||
// validate entries one by one, replace with default when needed
|
||||
for (final Property<?> entry : entries.values()) {
|
||||
keyList.add(entry.getKey());
|
||||
|
||||
if (entry.getComment() != null) {
|
||||
props.setKeyComment(entry.getKey(), entry.getComment());
|
||||
}
|
||||
|
||||
props.setProperty(entry.getKey(), entry.toString());
|
||||
}
|
||||
|
||||
// removed unused props
|
||||
for (final String propname : props.keySet().toArray(new String[props.size()])) {
|
||||
if (!keyList.contains(propname)) {
|
||||
props.remove(propname);
|
||||
}
|
||||
}
|
||||
|
||||
try(FileOutputStream fos = new FileOutputStream(file)) {
|
||||
|
||||
props.store(fos, fileComment);
|
||||
}
|
||||
} catch (final IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param newlineBeforeComments put newline before comments
|
||||
*/
|
||||
public void cfgNewlineBeforeComments(boolean newlineBeforeComments)
|
||||
{
|
||||
this.cfgNewlineBeforeComments = newlineBeforeComments;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param separateSections do separate sections by newline
|
||||
*/
|
||||
public void cfgSeparateSections(boolean separateSections)
|
||||
{
|
||||
this.cfgSeparateSections = separateSections;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a property entry (rarely used)
|
||||
*
|
||||
* @param k key
|
||||
* @return the entry
|
||||
*/
|
||||
public Property<?> getProperty(String k)
|
||||
{
|
||||
try {
|
||||
return entries.get(k);
|
||||
} catch (final Exception e) {
|
||||
Log.w(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get boolean property
|
||||
*
|
||||
* @param k key
|
||||
* @return the boolean found, or false
|
||||
*/
|
||||
public Boolean getBoolean(String k)
|
||||
{
|
||||
return Convert.toBoolean(getProperty(k).getValue());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get numeric property
|
||||
*
|
||||
* @param k key
|
||||
* @return the int found, or null
|
||||
*/
|
||||
public Integer getInteger(String k)
|
||||
{
|
||||
return Convert.toInteger(getProperty(k).getValue());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get numeric property as double
|
||||
*
|
||||
* @param k key
|
||||
* @return the double found, or null
|
||||
*/
|
||||
public Double getDouble(String k)
|
||||
{
|
||||
return Convert.toDouble(getProperty(k).getValue());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get string property
|
||||
*
|
||||
* @param k key
|
||||
* @return the string found, or null
|
||||
*/
|
||||
public String getString(String k)
|
||||
{
|
||||
return Convert.toString(getProperty(k).getValue());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get arbitrary property. Make sure it's of the right type!
|
||||
*
|
||||
* @param k key
|
||||
* @return the prioperty found
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getValue(String k)
|
||||
{
|
||||
try {
|
||||
return ((Property<T>) getProperty(k)).getValue();
|
||||
} catch (final ClassCastException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a boolean property
|
||||
*
|
||||
* @param k key
|
||||
* @param d default value
|
||||
* @param comment the in-file comment
|
||||
*/
|
||||
public void putBoolean(String k, boolean d, String comment)
|
||||
{
|
||||
putProperty(new BooleanProperty(k, d, comment));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a numeric property (double)
|
||||
*
|
||||
* @param k key
|
||||
* @param d default value
|
||||
* @param comment the in-file comment
|
||||
*/
|
||||
public void putDouble(String k, double d, String comment)
|
||||
{
|
||||
putProperty(new DoubleProperty(k, d, comment));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a numeric property
|
||||
*
|
||||
* @param k key
|
||||
* @param d default value
|
||||
* @param comment the in-file comment
|
||||
*/
|
||||
public void putInteger(String k, int d, String comment)
|
||||
{
|
||||
putProperty(new IntegerProperty(k, d, comment));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a string property
|
||||
*
|
||||
* @param k key
|
||||
* @param d default value
|
||||
* @param comment the in-file comment
|
||||
*/
|
||||
public void putString(String k, String d, String comment)
|
||||
{
|
||||
putProperty(new StringProperty(k, d, comment));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a range property
|
||||
*
|
||||
* @param prop property to put
|
||||
*/
|
||||
public <T> void putProperty(Property<T> prop)
|
||||
{
|
||||
entries.put(prop.getKey(), prop);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rename key before loading; value is preserved
|
||||
*
|
||||
* @param oldKey old key
|
||||
* @param newKey new key
|
||||
*/
|
||||
public void renameKey(String oldKey, String newKey)
|
||||
{
|
||||
renameTable.put(oldKey, newKey);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set value saved to certain key.
|
||||
*
|
||||
* @param key key
|
||||
* @param value the saved value
|
||||
*/
|
||||
public void setValue(String key, Object value)
|
||||
{
|
||||
getProperty(key).setValue(value);
|
||||
}
|
||||
|
||||
|
||||
public void setFileComment(String fileComment)
|
||||
{
|
||||
this.fileComment = fileComment;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package mightypork.utils.files.config;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import mightypork.utils.files.FileUtils;
|
||||
import mightypork.utils.logging.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Utility for parsing simple config files<br>
|
||||
* # and // mark a comment<br>
|
||||
* empty lines and lines without "=" are ignored<br>
|
||||
* lines with "=" must have "key = value" format, or a warning is logged.<br>
|
||||
* use "NULL" to create empty value.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class SimpleConfig {
|
||||
|
||||
/**
|
||||
* Load list from file
|
||||
*
|
||||
* @param file file
|
||||
* @return map of keys and values
|
||||
* @throws IOException
|
||||
*/
|
||||
public static List<String> listFromFile(File file) throws IOException
|
||||
{
|
||||
final String fileText = FileUtils.fileToString(file);
|
||||
|
||||
return listFromString(fileText);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load map from file
|
||||
*
|
||||
* @param file file
|
||||
* @return map of keys and values
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Map<String, String> mapFromFile(File file) throws IOException
|
||||
{
|
||||
final String fileText = FileUtils.fileToString(file);
|
||||
|
||||
return mapFromString(fileText);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load list from string
|
||||
*
|
||||
* @param text text of the file
|
||||
* @return map of keys and values
|
||||
*/
|
||||
public static List<String> listFromString(String text)
|
||||
{
|
||||
final List<String> list = new ArrayList<>();
|
||||
|
||||
final String[] groupsLines = text.split("\n");
|
||||
|
||||
for (String s : groupsLines) {
|
||||
// ignore invalid lines
|
||||
if (s.length() == 0) continue;
|
||||
if (s.startsWith("#") || s.startsWith("//")) continue;
|
||||
|
||||
// NULL value
|
||||
if (s.equalsIgnoreCase("NULL")) s = null;
|
||||
|
||||
if (s != null) s = s.replace("\\n", "\n");
|
||||
|
||||
// save extracted key-value pair
|
||||
list.add(s);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load map from string
|
||||
*
|
||||
* @param text text of the file
|
||||
* @return map of keys and values
|
||||
*/
|
||||
public static Map<String, String> mapFromString(String text)
|
||||
{
|
||||
final LinkedHashMap<String, String> pairs = new LinkedHashMap<>();
|
||||
|
||||
final String[] groupsLines = text.split("\n");
|
||||
|
||||
for (final String s : groupsLines) {
|
||||
// ignore invalid lines
|
||||
if (s.length() == 0) continue;
|
||||
if (s.startsWith("#") || s.startsWith("//")) continue;
|
||||
if (!s.contains("=")) continue;
|
||||
|
||||
// split and trim
|
||||
String[] parts = s.split("=");
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
parts[i] = parts[i].trim();
|
||||
}
|
||||
|
||||
// check if both parts are valid
|
||||
if (parts.length == 0) {
|
||||
Log.w("Bad line in config file: " + s);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parts.length == 1) {
|
||||
parts = new String[] { parts[0], "" };
|
||||
}
|
||||
|
||||
if (parts.length != 2) {
|
||||
Log.w("Bad line in config file: " + s);
|
||||
continue;
|
||||
}
|
||||
|
||||
// NULL value
|
||||
if (parts[0].equalsIgnoreCase("NULL")) parts[0] = null;
|
||||
if (parts[1].equalsIgnoreCase("NULL")) parts[1] = null;
|
||||
|
||||
if (parts[0] != null) parts[0] = parts[0].replace("\\n", "\n");
|
||||
if (parts[1] != null) parts[1] = parts[1].replace("\\n", "\n");
|
||||
|
||||
// save extracted key-value pair
|
||||
pairs.put(parts[0], parts[1]);
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save map to file
|
||||
*
|
||||
* @param target
|
||||
* @param data
|
||||
* @param allowNulls allow nulls.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void mapToFile(File target, Map<String, String> data, boolean allowNulls) throws IOException
|
||||
{
|
||||
final List<String> lines = new ArrayList<>();
|
||||
|
||||
for (final Entry<String, String> e : data.entrySet()) {
|
||||
String key = e.getKey();
|
||||
String value = e.getValue();
|
||||
|
||||
if (!allowNulls && (key == null || value == null || key.length() == 0 || value.length() == 0)) continue;
|
||||
|
||||
if (key == null) key = "NULL";
|
||||
if (value == null) value = "NULL";
|
||||
|
||||
key = key.replace("\n", "\\n");
|
||||
value = value.replace("\n", "\\n");
|
||||
|
||||
lines.add(key + " = " + value);
|
||||
}
|
||||
|
||||
String text = ""; // # File written by SimpleConfig
|
||||
|
||||
for (final String s : lines) {
|
||||
if (text.length() > 0) text += "\n";
|
||||
|
||||
text += s;
|
||||
}
|
||||
|
||||
FileUtils.stringToFile(target, text);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save list to file
|
||||
*
|
||||
* @param target
|
||||
* @param data
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void listToFile(File target, List<String> data) throws IOException
|
||||
{
|
||||
String text = ""; // # File written by SimpleConfig
|
||||
|
||||
for (String s : data) {
|
||||
if (text.length() > 0) text += "\n";
|
||||
|
||||
if (s == null) s = "NULL";
|
||||
|
||||
s = s.replace("\n", "\\n");
|
||||
|
||||
text += s;
|
||||
}
|
||||
|
||||
FileUtils.stringToFile(target, text);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package mightypork.utils.files.config;
|
||||
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
|
||||
|
||||
/**
|
||||
* Properties stored in file, alphabetically sorted.<br>
|
||||
* Uses UTF-8 encoding and each property can have it's own comment.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class SortedProperties extends java.util.Properties {
|
||||
|
||||
/** Option: put empty line before each comment. */
|
||||
public boolean cfgBlankRowBeforeComment = true;
|
||||
|
||||
/**
|
||||
* Option: Separate sections by newline<br>
|
||||
* Section = string before first dot in key.
|
||||
*/
|
||||
public boolean cfgBlankRowBetweenSections = true;
|
||||
|
||||
/** Comments for individual keys */
|
||||
private final Hashtable<String, String> keyComments = new Hashtable<>();
|
||||
|
||||
|
||||
private static void writeComments(BufferedWriter bw, String comm) throws IOException
|
||||
{
|
||||
final String comments = comm.replace("\n\n", "\n \n");
|
||||
|
||||
final int len = comments.length();
|
||||
int current = 0;
|
||||
int last = 0;
|
||||
final char[] uu = new char[6];
|
||||
uu[0] = '\\';
|
||||
uu[1] = 'u';
|
||||
while (current < len) {
|
||||
final char c = comments.charAt(current);
|
||||
if (c > '\u00ff' || c == '\n' || c == '\r') {
|
||||
if (last != current) {
|
||||
bw.write("# " + comments.substring(last, current));
|
||||
}
|
||||
|
||||
if (c > '\u00ff') {
|
||||
uu[2] = hexDigit(c, 12);
|
||||
uu[3] = hexDigit(c, 8);
|
||||
uu[4] = hexDigit(c, 4);
|
||||
uu[5] = hexDigit(c, 0);
|
||||
bw.write(new String(uu));
|
||||
} else {
|
||||
bw.newLine();
|
||||
if (c == '\r' && current != len - 1 && comments.charAt(current + 1) == '\n') {
|
||||
current++;
|
||||
}
|
||||
}
|
||||
last = current + 1;
|
||||
}
|
||||
current++;
|
||||
}
|
||||
if (last != current) {
|
||||
bw.write("# " + comments.substring(last, current));
|
||||
}
|
||||
|
||||
bw.newLine();
|
||||
bw.newLine();
|
||||
bw.newLine();
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Override
|
||||
public synchronized Enumeration keys()
|
||||
{
|
||||
final Enumeration keysEnum = super.keys();
|
||||
final Vector keyList = new Vector();
|
||||
while (keysEnum.hasMoreElements()) {
|
||||
keyList.add(keysEnum.nextElement());
|
||||
}
|
||||
Collections.sort(keyList); //sort!
|
||||
return keyList.elements();
|
||||
}
|
||||
|
||||
|
||||
private static String saveConvert(String theString, boolean escapeSpace, boolean escapeUnicode)
|
||||
{
|
||||
final int len = theString.length();
|
||||
int bufLen = len * 2;
|
||||
if (bufLen < 0) {
|
||||
bufLen = Integer.MAX_VALUE;
|
||||
}
|
||||
final StringBuffer result = new StringBuffer(bufLen);
|
||||
|
||||
for (int x = 0; x < len; x++) {
|
||||
final char ch = theString.charAt(x);
|
||||
|
||||
// Handle common case first, selecting largest block that
|
||||
// avoids the specials below
|
||||
if ((ch > 61) && (ch < 127)) {
|
||||
if (ch == '\\') {
|
||||
result.append('\\');
|
||||
result.append('\\');
|
||||
continue;
|
||||
}
|
||||
result.append(ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
if (x == 0 || escapeSpace) {
|
||||
result.append('\\');
|
||||
}
|
||||
result.append(' ');
|
||||
break;
|
||||
|
||||
case '\t':
|
||||
result.append('\\');
|
||||
result.append('t');
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
result.append('\\');
|
||||
result.append('n');
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
result.append('\\');
|
||||
result.append('r');
|
||||
break;
|
||||
|
||||
case '\f':
|
||||
result.append('\\');
|
||||
result.append('f');
|
||||
break;
|
||||
|
||||
case '=': // Fall through
|
||||
case ':': // Fall through
|
||||
case '#': // Fall through
|
||||
case '!':
|
||||
result.append('\\');
|
||||
result.append(ch);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (((ch < 0x0020) || (ch > 0x007e)) & escapeUnicode) {
|
||||
result.append('\\');
|
||||
result.append('u');
|
||||
result.append(hexDigit(ch, 12));
|
||||
result.append(hexDigit(ch, 8));
|
||||
result.append(hexDigit(ch, 4));
|
||||
result.append(hexDigit(ch, 0));
|
||||
} else {
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set additional comment to a key
|
||||
*
|
||||
* @param key key for comment
|
||||
* @param comment the comment
|
||||
*/
|
||||
public void setKeyComment(String key, String comment)
|
||||
{
|
||||
keyComments.put(key, comment);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void store(OutputStream out, String comments) throws IOException
|
||||
{
|
||||
final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
|
||||
|
||||
final boolean escUnicode = false;
|
||||
boolean firstEntry = true;
|
||||
String lastSectionBeginning = "";
|
||||
|
||||
if (comments != null) {
|
||||
writeComments(bw, comments);
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
for (final Enumeration e = keys(); e.hasMoreElements();) {
|
||||
boolean wasNewLine = false;
|
||||
|
||||
String key = (String) e.nextElement();
|
||||
String val = (String) get(key);
|
||||
key = saveConvert(key, true, escUnicode);
|
||||
val = saveConvert(val, false, escUnicode);
|
||||
|
||||
if (cfgBlankRowBetweenSections && !lastSectionBeginning.equals(key.split("[.]")[0])) {
|
||||
if (!firstEntry) {
|
||||
bw.newLine();
|
||||
bw.newLine();
|
||||
}
|
||||
|
||||
wasNewLine = true;
|
||||
lastSectionBeginning = key.split("[.]")[0];
|
||||
}
|
||||
|
||||
if (keyComments.containsKey(key)) {
|
||||
String cm = keyComments.get(key);
|
||||
cm = cm.replace("\r", "\n");
|
||||
cm = cm.replace("\r\n", "\n");
|
||||
cm = cm.replace("\n\n", "\n \n");
|
||||
|
||||
final String[] cmlines = cm.split("\n");
|
||||
|
||||
if (!wasNewLine && !firstEntry && cfgBlankRowBeforeComment) {
|
||||
bw.newLine();
|
||||
}
|
||||
|
||||
for (final String cmline : cmlines) {
|
||||
bw.write("# " + cmline);
|
||||
bw.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
bw.write(key + " = " + val);
|
||||
bw.newLine();
|
||||
|
||||
firstEntry = false;
|
||||
}
|
||||
}
|
||||
bw.flush();
|
||||
}
|
||||
|
||||
|
||||
private static String escapifyStr(String str)
|
||||
{
|
||||
final StringBuilder result = new StringBuilder();
|
||||
|
||||
final int len = str.length();
|
||||
for (int x = 0; x < len; x++) {
|
||||
final char ch = str.charAt(x);
|
||||
if (ch <= 0x007e) {
|
||||
result.append(ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
result.append('\\');
|
||||
result.append('u');
|
||||
result.append(hexDigit(ch, 12));
|
||||
result.append(hexDigit(ch, 8));
|
||||
result.append(hexDigit(ch, 4));
|
||||
result.append(hexDigit(ch, 0));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
private static char hexDigit(char ch, int offset)
|
||||
{
|
||||
final int val = (ch >> offset) & 0xF;
|
||||
if (val <= 9) {
|
||||
return (char) ('0' + val);
|
||||
}
|
||||
|
||||
return (char) ('A' + val - 10);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void load(InputStream is) throws IOException
|
||||
{
|
||||
load(is, "utf-8");
|
||||
}
|
||||
|
||||
|
||||
public void load(InputStream is, String encoding) throws IOException
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final InputStreamReader isr = new InputStreamReader(is, encoding);
|
||||
while (true) {
|
||||
final int temp = isr.read();
|
||||
if (temp < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
final char c = (char) temp;
|
||||
sb.append(c);
|
||||
}
|
||||
|
||||
final String read = sb.toString().replaceAll("(#|;|//|--)[^\n]*\n", "\n");
|
||||
|
||||
final String inputString = escapifyStr(read);
|
||||
final byte[] bs = inputString.getBytes("ISO-8859-1");
|
||||
final ByteArrayInputStream bais = new ByteArrayInputStream(bs);
|
||||
|
||||
super.load(bais);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package mightypork.utils.files.zip;
|
||||
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import mightypork.utils.files.FileUtils;
|
||||
import mightypork.utils.logging.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Class for building a zip file
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class ZipBuilder {
|
||||
|
||||
private final ZipOutputStream out;
|
||||
private final HashSet<String> included = new HashSet<>();
|
||||
|
||||
|
||||
/**
|
||||
* @param target target zip file
|
||||
* @throws IOException if the file is directory or cannot be created
|
||||
*/
|
||||
public ZipBuilder(File target) throws IOException
|
||||
{
|
||||
|
||||
if (!target.getParentFile().mkdirs()) throw new IOException("Could not create output directory.");
|
||||
|
||||
final FileOutputStream dest = new FileOutputStream(target);
|
||||
out = new ZipOutputStream(new BufferedOutputStream(dest));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add stream to a path
|
||||
*
|
||||
* @param path path
|
||||
* @param in stream
|
||||
* @throws IOException
|
||||
*/
|
||||
public void addStream(String path, InputStream in) throws IOException
|
||||
{
|
||||
path = preparePath(path);
|
||||
if (included.contains(path)) {
|
||||
Log.f3("Zip already contains file " + path + ", skipping.");
|
||||
return; // ignore
|
||||
}
|
||||
included.add(path);
|
||||
|
||||
out.putNextEntry(new ZipEntry(path));
|
||||
|
||||
FileUtils.copyStream(in, out);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add string as a file
|
||||
*
|
||||
* @param path path
|
||||
* @param text text to write
|
||||
* @throws IOException
|
||||
*/
|
||||
public void addString(String path, String text) throws IOException
|
||||
{
|
||||
path = preparePath(path);
|
||||
if (included.contains(path)) return; // ignore
|
||||
included.add(path);
|
||||
|
||||
out.putNextEntry(new ZipEntry(path));
|
||||
|
||||
try(InputStream in = FileUtils.stringToStream(text)) {
|
||||
FileUtils.copyStream(in, out);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add resource obtained via FileUtils.getResource()
|
||||
*
|
||||
* @param path path
|
||||
* @param resPath resource path
|
||||
* @throws IOException
|
||||
*/
|
||||
public void addResource(String path, String resPath) throws IOException
|
||||
{
|
||||
path = preparePath(path);
|
||||
if (included.contains(path)) return; // ignore
|
||||
included.add(path);
|
||||
|
||||
out.putNextEntry(new ZipEntry(path));
|
||||
|
||||
try(InputStream in = FileUtils.getResource(resPath)) {
|
||||
FileUtils.copyStream(in, out);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalize path
|
||||
*
|
||||
* @param path original path
|
||||
* @return normalized path
|
||||
*/
|
||||
private static String preparePath(String path)
|
||||
{
|
||||
path = path.replace("\\", "/");
|
||||
|
||||
if (path.charAt(0) == '/') path = path.substring(1);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Close the zip stream
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void close() throws IOException
|
||||
{
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package mightypork.utils.files.zip;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import mightypork.utils.files.FileUtils;
|
||||
import mightypork.utils.logging.Log;
|
||||
import mightypork.utils.string.validation.StringFilter;
|
||||
|
||||
|
||||
/**
|
||||
* Utilities for manipulating zip files
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class ZipUtils {
|
||||
|
||||
private static final int BUFFER_SIZE = 2048;
|
||||
|
||||
|
||||
/**
|
||||
* Extract zip file to target directory
|
||||
*
|
||||
* @param file zip file
|
||||
* @param outputDir target directory
|
||||
* @param filter string filter (will be used to test entry names (paths))
|
||||
* @return list of entries extracted (paths)
|
||||
* @throws IOException
|
||||
*/
|
||||
public static List<String> extractZip(File file, File outputDir, StringFilter filter) throws IOException
|
||||
{
|
||||
try(ZipFile zip = new ZipFile(file)) {
|
||||
return extractZip(zip, outputDir, filter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract zip file to target directory
|
||||
*
|
||||
* @param zip open zip file
|
||||
* @param outputDir target directory
|
||||
* @param filter string filter (will be used to test entry names (paths))
|
||||
* @return list of entries extracted (paths)
|
||||
* @throws IOException if the file)s) cannot be created
|
||||
*/
|
||||
public static List<String> extractZip(ZipFile zip, File outputDir, StringFilter filter) throws IOException
|
||||
{
|
||||
final ArrayList<String> files = new ArrayList<>();
|
||||
|
||||
if (!outputDir.mkdirs()) throw new IOException("Could not create output directory.");
|
||||
|
||||
final Enumeration<? extends ZipEntry> zipFileEntries = zip.entries();
|
||||
|
||||
// process each entry
|
||||
while (zipFileEntries.hasMoreElements()) {
|
||||
final ZipEntry entry = zipFileEntries.nextElement();
|
||||
|
||||
// parse filename and path
|
||||
final String entryPath = entry.getName();
|
||||
final File destFile = new File(outputDir, entryPath);
|
||||
final File destinationParent = destFile.getParentFile();
|
||||
|
||||
if (entry.isDirectory() || (filter != null && !filter.isValid(entryPath))) continue;
|
||||
|
||||
// make sure directories exist
|
||||
if (!destinationParent.mkdirs()) throw new IOException("Could not create directory.");
|
||||
|
||||
if (!entry.isDirectory()) {
|
||||
extractZipEntry(zip, entry, destFile);
|
||||
files.add(entryPath);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read zip entries and add their paths to a list
|
||||
*
|
||||
* @param zipFile open zip file
|
||||
* @return list of entry names
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public static List<String> listZip(File zipFile) throws IOException
|
||||
{
|
||||
try(ZipFile zip = new ZipFile(zipFile)) {
|
||||
return listZip(zip);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read zip entries and add their paths to a list
|
||||
*
|
||||
* @param zip open zip file
|
||||
* @return list of entry names
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public static List<String> listZip(ZipFile zip) throws IOException
|
||||
{
|
||||
final ArrayList<String> files = new ArrayList<>();
|
||||
|
||||
final Enumeration<? extends ZipEntry> zipFileEntries = zip.entries();
|
||||
|
||||
// process each entry
|
||||
while (zipFileEntries.hasMoreElements()) {
|
||||
final ZipEntry entry = zipFileEntries.nextElement();
|
||||
|
||||
if (!entry.isDirectory()) {
|
||||
files.add(entry.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract one zip entry to target file
|
||||
*
|
||||
* @param zip open zip file
|
||||
* @param entry entry from the zip file
|
||||
* @param destFile destination file ((NOT directory!)
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public static void extractZipEntry(ZipFile zip, ZipEntry entry, File destFile) throws IOException
|
||||
{
|
||||
if (!destFile.getParentFile().mkdirs()) throw new IOException("Could not create output directory.");
|
||||
|
||||
try(InputStream in = zip.getInputStream(entry);
|
||||
BufferedInputStream is = new BufferedInputStream(in);
|
||||
FileOutputStream fos = new FileOutputStream(destFile);
|
||||
BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER_SIZE)) {
|
||||
|
||||
FileUtils.copyStream(is, dest);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load zip entry to String
|
||||
*
|
||||
* @param zip open zip file
|
||||
* @param entry entry from the zip file
|
||||
* @return loaded string
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public static String zipEntryToString(ZipFile zip, ZipEntry entry) throws IOException
|
||||
{
|
||||
BufferedInputStream is = null;
|
||||
try {
|
||||
is = new BufferedInputStream(zip.getInputStream(entry));
|
||||
final String s = FileUtils.streamToString(is);
|
||||
return s;
|
||||
} finally {
|
||||
try {
|
||||
if (is != null) is.close();
|
||||
} catch (final IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean entryExists(File selectedFile, String string)
|
||||
{
|
||||
try(ZipFile zf = new ZipFile(selectedFile)) {
|
||||
return zf.getEntry(string) != null;
|
||||
} catch (final IOException | RuntimeException e) {
|
||||
Log.w("Error reading zip.", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package mightypork.utils.interfaces;
|
||||
|
||||
|
||||
/**
|
||||
* Object that can be destroyed (free resources etc)
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface Destroyable {
|
||||
|
||||
/**
|
||||
* Destroy this object
|
||||
*/
|
||||
public void destroy();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package mightypork.utils.interfaces;
|
||||
|
||||
|
||||
/**
|
||||
* Can be enabled or disabled.<br>
|
||||
* Implementations should take appropriate action (ie. stop listening to events,
|
||||
* updating etc.)
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface Enableable {
|
||||
|
||||
/**
|
||||
* Change enabled state
|
||||
*
|
||||
* @param yes enabled
|
||||
*/
|
||||
public void setEnabled(boolean yes);
|
||||
|
||||
|
||||
/**
|
||||
* @return true if enabled
|
||||
*/
|
||||
public boolean isEnabled();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package mightypork.utils.interfaces;
|
||||
|
||||
|
||||
/**
|
||||
* Can be paused & resumed
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface Pauseable {
|
||||
|
||||
/**
|
||||
* Pause operation
|
||||
*/
|
||||
public void pause();
|
||||
|
||||
|
||||
/**
|
||||
* Resume operation
|
||||
*/
|
||||
public void resume();
|
||||
|
||||
|
||||
/**
|
||||
* @return paused state
|
||||
*/
|
||||
public boolean isPaused();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package mightypork.utils.interfaces;
|
||||
|
||||
|
||||
/**
|
||||
* Can be asked to update it's state
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface Pollable {
|
||||
|
||||
/**
|
||||
* Update internal state
|
||||
*/
|
||||
void poll();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mightypork.utils.interfaces;
|
||||
|
||||
|
||||
/**
|
||||
* Uses delta timing
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface Updateable {
|
||||
|
||||
/**
|
||||
* Update item state based on elapsed time
|
||||
*
|
||||
* @param delta time elapsed since last update, in seconds
|
||||
*/
|
||||
public void update(double delta);
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
package mightypork.utils.ion;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
|
||||
/**
|
||||
* Universal data storage system (main API class)
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class Ion {
|
||||
|
||||
private static final int RESERVED_LOW = 0;
|
||||
private static final int RESERVED_HIGH = 49;
|
||||
|
||||
private static final int RANGE_LOW = 0;
|
||||
private static final int RANGE_HIGH = 255;
|
||||
|
||||
// marks for object saving
|
||||
/** Null mark */
|
||||
static final int NULL = 0;
|
||||
/** Boolean mark */
|
||||
static final int BOOLEAN = 1;
|
||||
/** Byte mark */
|
||||
static final int BYTE = 2;
|
||||
/** Character mark */
|
||||
static final int CHAR = 3;
|
||||
/** Short mark */
|
||||
static final int SHORT = 4;
|
||||
/** Integer mark */
|
||||
static final int INT = 5;
|
||||
/** Long mark */
|
||||
static final int LONG = 6;
|
||||
/** Float mark */
|
||||
static final int FLOAT = 7;
|
||||
/** Double mark */
|
||||
static final int DOUBLE = 8;
|
||||
/** String mark */
|
||||
static final int STRING = 9;
|
||||
/** Boolean array mark */
|
||||
static final int BOOLEAN_ARRAY = 10;
|
||||
/** Byte array mark */
|
||||
static final int BYTE_ARRAY = 11;
|
||||
/** Character array mark */
|
||||
static final int CHAR_ARRAY = 12;
|
||||
/** Short array mark */
|
||||
static final int SHORT_ARRAY = 13;
|
||||
/** Integer array mark */
|
||||
static final int INT_ARRAY = 14;
|
||||
/** Long array mark */
|
||||
static final int LONG_ARRAY = 15;
|
||||
/** Float array mark */
|
||||
static final int FLOAT_ARRAY = 16;
|
||||
/** Double array mark */
|
||||
static final int DOUBLE_ARRAY = 17;
|
||||
/** String array mark */
|
||||
static final int STRING_ARRAY = 18;
|
||||
/** Entry mark - start of map or sequence entry */
|
||||
static final int ENTRY = 19;
|
||||
/** End mark - end of sequence or map */
|
||||
static final int END = 20;
|
||||
/** Bundle */
|
||||
static final int ION_BUNDLE = 21;
|
||||
/** Sequence wrapper for bundle */
|
||||
static final int SEQUENCE_WRAPPER = 22;
|
||||
/** Map wrapper for bundle */
|
||||
static final int MAP_WRAPPER = 23;
|
||||
/** Sequence saved as object */
|
||||
public static final int SEQUENCE = 24;
|
||||
/** Map saved as object */
|
||||
public static final int MAP = 25;
|
||||
/** Array of arbitrary objects */
|
||||
public static final int OBJECT_ARRAY = 26;
|
||||
|
||||
|
||||
/** Ionizables<Mark, Class> */
|
||||
private static Map<Integer, Class<?>> markToClass = new HashMap<>();
|
||||
private static Map<Class<?>, Integer> classToMark = new HashMap<>();
|
||||
|
||||
private static boolean reservedMarkChecking;
|
||||
|
||||
static {
|
||||
reservedMarkChecking = false;
|
||||
|
||||
// register built-ins
|
||||
register(ION_BUNDLE, IonBundle.class);
|
||||
register(SEQUENCE_WRAPPER, IonSequenceWrapper.class);
|
||||
register(MAP_WRAPPER, IonMapWrapper.class);
|
||||
|
||||
reservedMarkChecking = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register new {@link IonObjBinary} class for writing/loading.
|
||||
*
|
||||
* @param mark mark to be used 50..255, unless internal
|
||||
* @param objClass class of the registered object
|
||||
*/
|
||||
public static void register(int mark, Class<?> objClass)
|
||||
{
|
||||
// negative marks are allowed.
|
||||
if (mark > RANGE_HIGH) throw new IllegalArgumentException("Mark must be < 256.");
|
||||
if (mark < RANGE_LOW) throw new IllegalArgumentException("Mark must be positive.");
|
||||
|
||||
if (reservedMarkChecking && isMarkReserved(mark)) {
|
||||
throw new IllegalArgumentException("Marks " + RESERVED_LOW + ".." + RESERVED_HIGH + " are reserved.");
|
||||
}
|
||||
|
||||
if (markToClass.containsKey(objClass)) {
|
||||
throw new IllegalArgumentException("Mark " + mark + " is already in use.");
|
||||
}
|
||||
|
||||
if (classToMark.containsKey(objClass)) {
|
||||
throw new IllegalArgumentException(objClass + " is already registered.");
|
||||
}
|
||||
|
||||
if (!IonObjBundled.class.isAssignableFrom(objClass)) {
|
||||
if (!IonObjBinary.class.isAssignableFrom(objClass)) {
|
||||
throw new IllegalArgumentException(objClass + " cannot be registered to Ion.");
|
||||
}
|
||||
}
|
||||
|
||||
// make sure the type has implicit constructor
|
||||
try {
|
||||
objClass.getConstructor();
|
||||
} catch (NoSuchMethodException | SecurityException e) {
|
||||
throw new RuntimeException("Class " + objClass + " doesn't have an implicit constructor.");
|
||||
}
|
||||
|
||||
markToClass.put(mark, objClass);
|
||||
classToMark.put(objClass, mark);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to register the type using a static final ION_MARK int field.
|
||||
*
|
||||
* @param objClass type class
|
||||
*/
|
||||
public static void register(Class<?> objClass)
|
||||
{
|
||||
try {
|
||||
final Field fld = objClass.getDeclaredField("ION_MARK");
|
||||
|
||||
final int modif = fld.getModifiers();
|
||||
|
||||
if (!Modifier.isFinal(modif) || !Modifier.isStatic(modif)) {
|
||||
throw new RuntimeException("The ION_MARK field must be static and final.");
|
||||
}
|
||||
|
||||
fld.setAccessible(true);
|
||||
final int mark = fld.getInt(null);
|
||||
|
||||
register(mark, objClass);
|
||||
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException("Could not register " + objClass + " using an ION_MARK field.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load binary from file and cast.
|
||||
*/
|
||||
public static <T> T fromFile(String path) throws IOException
|
||||
{
|
||||
return fromFile(new File(path));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load binary from file and cast.
|
||||
*/
|
||||
public static <T> T fromFile(File file) throws IOException
|
||||
{
|
||||
try(InputStream in = new FileInputStream(file)) {
|
||||
return fromStream(in);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write binary to file with mark.
|
||||
*/
|
||||
public static void toFile(String path, Object obj) throws IOException
|
||||
{
|
||||
toFile(new File(path), obj);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write binary to file with mark.
|
||||
*/
|
||||
public static void toFile(File file, Object obj) throws IOException
|
||||
{
|
||||
try(OutputStream out = new FileOutputStream(file)) {
|
||||
|
||||
toStream(out, obj);
|
||||
|
||||
out.flush();
|
||||
} catch (final Exception e) {
|
||||
throw new IOException("Error writing to ION file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load object from stream based on mark, try to cast.
|
||||
*/
|
||||
public static <T> T fromStream(InputStream in) throws IOException
|
||||
{
|
||||
try(final IonInput inp = new IonInput(in)) {
|
||||
return (T) inp.readObject();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write object to output with a mark.
|
||||
*/
|
||||
public static void toStream(OutputStream out, Object obj) throws IOException
|
||||
{
|
||||
try(IonOutput iout = new IonOutput(out)) {
|
||||
iout.writeObject(obj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get ion input
|
||||
*
|
||||
* @param path file path to read
|
||||
* @return input
|
||||
* @throws IOException
|
||||
*/
|
||||
public static IonInput getInput(String path) throws IOException
|
||||
{
|
||||
return getInput(new File(path));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get ion input
|
||||
*
|
||||
* @param file file to read
|
||||
* @return input
|
||||
* @throws IOException
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public static IonInput getInput(File file) throws IOException
|
||||
{
|
||||
return new IonInput(new FileInputStream(file));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get ion output
|
||||
*
|
||||
* @param path file path to write
|
||||
* @return output
|
||||
* @throws IOException
|
||||
*/
|
||||
public static IonOutput getOutput(String path) throws IOException
|
||||
{
|
||||
return getOutput(new File(path));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get ion output
|
||||
*
|
||||
* @param file file to write
|
||||
* @return output
|
||||
* @throws IOException
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public static IonOutput getOutput(File file) throws IOException
|
||||
{
|
||||
return new IonOutput(new FileOutputStream(file));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create new bundle and write the object to it.
|
||||
*/
|
||||
public static IonBundle wrapBundled(IonObjBundled content) throws IOException
|
||||
{
|
||||
final IonBundle ib = new IonBundle();
|
||||
content.save(ib);
|
||||
return ib;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to unwrap an object from bundle. The object class must have implicit
|
||||
* accessible constructor.
|
||||
*
|
||||
* @param bundle unwrapped bundle
|
||||
* @param objClass class of desired object
|
||||
* @return the object unwrapped
|
||||
* @throws IOException
|
||||
*/
|
||||
public static <T extends IonObjBundled> T unwrapBundled(IonBundle bundle, Class<? extends T> objClass) throws IOException
|
||||
{
|
||||
try {
|
||||
final T inst = objClass.newInstance();
|
||||
inst.load(bundle);
|
||||
return inst;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new IOException("Could not instantiate " + objClass + ".");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static Class<?> getClassForMark(int mark)
|
||||
{
|
||||
return markToClass.get(mark);
|
||||
}
|
||||
|
||||
|
||||
public static int getMark(Object object)
|
||||
{
|
||||
assertRegistered(object);
|
||||
|
||||
return classToMark.get(object.getClass());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the mark is for a registered {@link IonObjBinary} object
|
||||
*/
|
||||
static boolean isMarkForBinary(int mark)
|
||||
{
|
||||
if (!markToClass.containsKey(mark)) return false;
|
||||
|
||||
return IonObjBinary.class.isAssignableFrom(markToClass.get(mark));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the mark is for a registered {@link IonObjBinary} object
|
||||
*/
|
||||
static boolean isMarkForBundled(int mark)
|
||||
{
|
||||
if (!markToClass.containsKey(mark)) return false;
|
||||
|
||||
return IonObjBundled.class.isAssignableFrom(markToClass.get(mark));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the mark is reserved for internal use
|
||||
*/
|
||||
static boolean isMarkReserved(int mark)
|
||||
{
|
||||
return mark >= RESERVED_LOW && mark <= RESERVED_HIGH;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the mark is for a registered {@link IonObjBinary} object
|
||||
*/
|
||||
static boolean isRegistered(Object object)
|
||||
{
|
||||
return classToMark.containsKey(object.getClass());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make sure object is registered in the table.
|
||||
*
|
||||
* @throws IOException if not registered or class mismatch
|
||||
*/
|
||||
static void assertRegistered(Object obj)
|
||||
{
|
||||
if (!isRegistered(obj)) {
|
||||
throw new RuntimeException("Type not registered: " + (obj.getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For get all external registered types - just like if the class was
|
||||
* freshly loaded. Can be used for unit tests.
|
||||
*/
|
||||
public static void reset()
|
||||
{
|
||||
final List<Integer> toRemove = new ArrayList<>();
|
||||
for (final Entry<Integer, Class<?>> e : markToClass.entrySet()) {
|
||||
final int mark = e.getKey();
|
||||
|
||||
if (!isMarkReserved(mark)) {
|
||||
toRemove.add(mark);
|
||||
}
|
||||
}
|
||||
|
||||
for (final int i : toRemove) {
|
||||
classToMark.remove(markToClass.remove(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
package mightypork.utils.ion;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Ion data bundle - simplified Map with facilities for storing maps and
|
||||
* sequences.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class IonBundle implements IonObjBinary {
|
||||
|
||||
private final Map<String, Object> backingMap = new HashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Clear & fill a provided bundle with elements from a bundle value
|
||||
*
|
||||
* @param key key
|
||||
* @param filled bundle to fill
|
||||
*/
|
||||
public void loadBundle(String key, IonBundle filled)
|
||||
{
|
||||
if (!containsKey(key)) return;
|
||||
|
||||
final IonBundle ib = get(key, new IonBundle());
|
||||
|
||||
filled.clear();
|
||||
filled.putAll(ib);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a key is used in the bundle
|
||||
*
|
||||
* @param key key to check
|
||||
* @return true if this key is used in the bundle
|
||||
*/
|
||||
public boolean containsKey(Object key)
|
||||
{
|
||||
return backingMap.containsKey(key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a value is contained in the bundle
|
||||
*
|
||||
* @param value value to check
|
||||
* @return true if this value is contained in the bundle
|
||||
*/
|
||||
public boolean containsValue(Object value)
|
||||
{
|
||||
return backingMap.containsValue(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a map value
|
||||
*
|
||||
* @param key key
|
||||
* @return a new Map with elements from that value
|
||||
*/
|
||||
public <K, V> Map<K, V> getMap(String key)
|
||||
{
|
||||
return loadMap(key, new LinkedHashMap<K, V>());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear & fill the provided map with elements from a map value
|
||||
*
|
||||
* @param key key
|
||||
* @param filled Map to fill
|
||||
*/
|
||||
public <K, V> Map<K, V> loadMap(String key, Map<K, V> filled)
|
||||
{
|
||||
final IonMapWrapper imw = get(key, null);
|
||||
if (imw == null) throw new RuntimeException("No such key: " + key);
|
||||
filled.clear();
|
||||
imw.fill(filled);
|
||||
return filled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a sequence value
|
||||
*
|
||||
* @param key key
|
||||
* @return a new Collection with elements from that value
|
||||
*/
|
||||
public <E> Collection<E> getSequence(String key)
|
||||
{
|
||||
return loadSequence(key, new ArrayList<E>());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear & fill the provided Collection with elements from a sequence value
|
||||
*
|
||||
* @param key key
|
||||
* @param filled Collection to fill
|
||||
* @return the filled collection
|
||||
*/
|
||||
public <E> Collection<E> loadSequence(String key, Collection<E> filled)
|
||||
{
|
||||
final IonSequenceWrapper isw = get(key, null);
|
||||
if (isw == null) throw new RuntimeException("No such key: " + key);
|
||||
filled.clear();
|
||||
isw.fill(filled);
|
||||
|
||||
return filled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load a bundled object from a bundle value.<br>
|
||||
* The object does not have to be registered.
|
||||
*
|
||||
* @param key key
|
||||
* @param loaded loaded object
|
||||
* @return the loaded object
|
||||
*/
|
||||
public <T extends IonObjBundled> T loadBundled(String key, T loaded)
|
||||
{
|
||||
final IonBundle bu = get(key, null);
|
||||
if (bu == null) throw new RuntimeException("No such key: " + key);
|
||||
|
||||
loaded.load(bu);
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save a bundled object to a bundle value.<br>
|
||||
* The object does not have to be registered.
|
||||
*
|
||||
* @param key key
|
||||
* @param saved saved object
|
||||
*/
|
||||
public void putBundled(String key, IonObjBundled saved)
|
||||
{
|
||||
final IonBundle bu = new IonBundle();
|
||||
saved.save(bu);
|
||||
put(key, bu);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get value, or fallback (if none found of with bad type).
|
||||
*
|
||||
* @param key
|
||||
* @param fallback value
|
||||
* @return value
|
||||
*/
|
||||
public <T> T get(String key, T fallback)
|
||||
{
|
||||
try {
|
||||
final T itm = (T) backingMap.get(key);
|
||||
if (itm == null) return fallback;
|
||||
return itm;
|
||||
} catch (final ClassCastException e) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get value, or null (if none found of with bad type).
|
||||
*
|
||||
* @param key
|
||||
* @return value
|
||||
*/
|
||||
public <T> T get(String key)
|
||||
{
|
||||
return get(key, (T) null);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, IonObjBundled value)
|
||||
{
|
||||
if (key == null || value == null) return;
|
||||
if (!Ion.isRegistered(value)) throw new IllegalArgumentException("Cannot add to bundle, not registered: " + value);
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, IonObjBinary value)
|
||||
{
|
||||
if (key == null || value == null) return;
|
||||
if (!Ion.isRegistered(value)) throw new IllegalArgumentException("Cannot add to bundle, not registered: " + value);
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, boolean value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, byte value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, char value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, short value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, int value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, long value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, double value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, float value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, String value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, boolean[] value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, char[] value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, short[] value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, int[] value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, long[] value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, double[] value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, float[] value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, String[] value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
public void put(String key, Object[] value)
|
||||
{
|
||||
backingMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put a sequence to the bundle.
|
||||
*
|
||||
* @param key key
|
||||
* @param c value (Collection)
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void putSequence(String key, Collection c)
|
||||
{
|
||||
backingMap.put(key, new IonSequenceWrapper(c));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put a map to the bundle.
|
||||
*
|
||||
* @param key key
|
||||
* @param m value (Map)
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void putMap(String key, Map m)
|
||||
{
|
||||
backingMap.put(key, new IonMapWrapper(m));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void load(IonInput in) throws IOException
|
||||
{
|
||||
in.readMap(backingMap);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void save(IonOutput out) throws IOException
|
||||
{
|
||||
out.writeMap(backingMap);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get number of elements in the bundle
|
||||
*
|
||||
* @return size
|
||||
*/
|
||||
public int size()
|
||||
{
|
||||
return backingMap.size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the bundle is empty
|
||||
*
|
||||
* @return true if empty
|
||||
*/
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return backingMap.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove all elements
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
backingMap.clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a value by key
|
||||
*
|
||||
* @param key key to remove
|
||||
* @return the removed object
|
||||
*/
|
||||
public Object remove(Object key)
|
||||
{
|
||||
return backingMap.remove(key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put all from another bundle
|
||||
*
|
||||
* @param anotherBundle another bundle
|
||||
*/
|
||||
public void putAll(IonBundle anotherBundle)
|
||||
{
|
||||
backingMap.putAll(anotherBundle.backingMap);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return backingMap.toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((backingMap == null) ? 0 : backingMap.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (!(obj instanceof IonBundle)) return false;
|
||||
final IonBundle other = (IonBundle) obj;
|
||||
if (backingMap == null) {
|
||||
if (other.backingMap != null) return false;
|
||||
} else if (!backingMap.equals(other.backingMap)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
package mightypork.utils.ion;
|
||||
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import mightypork.utils.exceptions.CorruptDataException;
|
||||
|
||||
|
||||
/**
|
||||
* Ion input stream
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class IonInput implements Closeable {
|
||||
|
||||
private final DataInput in;
|
||||
private final InputStream stream;
|
||||
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
public IonInput(File inFile) throws FileNotFoundException
|
||||
{
|
||||
this(new FileInputStream(inFile));
|
||||
}
|
||||
|
||||
|
||||
public IonInput(InputStream in)
|
||||
{
|
||||
this.stream = in;
|
||||
this.in = new DataInputStream(in);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read int 0-255. Suitable when the int was written using
|
||||
* <code>writeIntByte()</code> method.
|
||||
*
|
||||
* @return int
|
||||
* @throws IOException
|
||||
*/
|
||||
public int readIntByte() throws IOException
|
||||
{
|
||||
return in.readUnsignedByte();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read an int 0-65535. Suitable when the int was written using
|
||||
* <code>writeIntShort()</code> method.
|
||||
*
|
||||
* @return int
|
||||
* @throws IOException
|
||||
*/
|
||||
public int readIntShort() throws IOException
|
||||
{
|
||||
return in.readUnsignedShort();
|
||||
}
|
||||
|
||||
|
||||
public boolean readBoolean() throws IOException
|
||||
{
|
||||
return in.readBoolean();
|
||||
}
|
||||
|
||||
|
||||
public byte readByte() throws IOException
|
||||
{
|
||||
return in.readByte();
|
||||
}
|
||||
|
||||
|
||||
public short readShort() throws IOException
|
||||
{
|
||||
return in.readShort();
|
||||
}
|
||||
|
||||
|
||||
public char readChar() throws IOException
|
||||
{
|
||||
return in.readChar();
|
||||
}
|
||||
|
||||
|
||||
public int readInt() throws IOException
|
||||
{
|
||||
return in.readInt();
|
||||
}
|
||||
|
||||
|
||||
public long readLong() throws IOException
|
||||
{
|
||||
return in.readLong();
|
||||
}
|
||||
|
||||
|
||||
public float readFloat() throws IOException
|
||||
{
|
||||
return in.readFloat();
|
||||
}
|
||||
|
||||
|
||||
public double readDouble() throws IOException
|
||||
{
|
||||
return in.readDouble();
|
||||
}
|
||||
|
||||
|
||||
public String readString() throws IOException
|
||||
{
|
||||
return in.readUTF();
|
||||
}
|
||||
|
||||
|
||||
public boolean[] readBooleans() throws IOException
|
||||
{
|
||||
final int length = readLength();
|
||||
final boolean[] arr = new boolean[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
arr[i] = in.readBoolean();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
public byte[] readBytes() throws IOException
|
||||
{
|
||||
final int length = readLength();
|
||||
final byte[] arr = new byte[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
arr[i] = in.readByte();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
public char[] readChars() throws IOException
|
||||
{
|
||||
final int length = readLength();
|
||||
final char[] arr = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
arr[i] = in.readChar();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
public short[] readShorts() throws IOException
|
||||
{
|
||||
final int length = readLength();
|
||||
final short[] arr = new short[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
arr[i] = in.readShort();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
public int[] readInts() throws IOException
|
||||
{
|
||||
final int length = readLength();
|
||||
final int[] arr = new int[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
arr[i] = in.readInt();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
public long[] readLongs() throws IOException
|
||||
{
|
||||
final int length = readLength();
|
||||
final long[] arr = new long[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
arr[i] = in.readLong();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
public float[] readFloats() throws IOException
|
||||
{
|
||||
final int length = readLength();
|
||||
final float[] arr = new float[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
arr[i] = in.readFloat();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
public double[] readDoubles() throws IOException
|
||||
{
|
||||
final int length = readLength();
|
||||
final double[] arr = new double[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
arr[i] = in.readDouble();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
public String[] readStrings() throws IOException
|
||||
{
|
||||
final int length = readLength();
|
||||
final String[] arr = new String[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
arr[i] = in.readUTF();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
public Object[] readObjects() throws IOException
|
||||
{
|
||||
final int length = readLength();
|
||||
final Object[] arr = new Object[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
arr[i] = readObject();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read bundle without a mark
|
||||
*/
|
||||
public IonBundle readBundle() throws IOException
|
||||
{
|
||||
final IonBundle ib = new IonBundle();
|
||||
ib.load(this);
|
||||
return ib;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read bundle without a mark, load into a provided one
|
||||
*/
|
||||
public void readBundle(IonBundle filled) throws IOException
|
||||
{
|
||||
filled.clear();
|
||||
filled.load(this);
|
||||
}
|
||||
|
||||
|
||||
private int readMark() throws IOException
|
||||
{
|
||||
return readIntByte();
|
||||
}
|
||||
|
||||
|
||||
private int readLength() throws IOException
|
||||
{
|
||||
return readInt();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Read object based on mark; if null mark is found, returns default value.
|
||||
* </p>
|
||||
* <p>
|
||||
* If, however, an object of invalid or different type is found, an
|
||||
* exception will be thrown.
|
||||
* </p>
|
||||
*
|
||||
* @param def default value.
|
||||
* @return the loaded object
|
||||
* @throws IOException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T readObject(T def) throws IOException
|
||||
{
|
||||
try {
|
||||
final Object o = readObject();
|
||||
return (T) (o == null ? def : o);
|
||||
} catch (final Exception e) {
|
||||
throw new IOException("Could not load object.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read single object, preceded by a mark.
|
||||
*
|
||||
* @return the loaded object
|
||||
* @throws IOException
|
||||
*/
|
||||
public Object readObject() throws IOException
|
||||
{
|
||||
final int mark = readMark();
|
||||
if (Ion.isMarkForBinary(mark)) {
|
||||
IonObjBinary loaded;
|
||||
|
||||
try {
|
||||
loaded = (IonObjBinary) Ion.getClassForMark(mark).newInstance();
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException("Could not load binary object with mark: " + mark, e);
|
||||
}
|
||||
|
||||
loaded.load(this);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
if (Ion.isMarkForBundled(mark)) {
|
||||
IonObjBundled loaded;
|
||||
|
||||
try {
|
||||
loaded = (IonObjBundled) Ion.getClassForMark(mark).newInstance();
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException("Could not load bundled object with mark: " + mark, e);
|
||||
}
|
||||
|
||||
final IonBundle ib = readBundle();
|
||||
loaded.load(ib);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
switch (mark) {
|
||||
case Ion.NULL:
|
||||
return null;
|
||||
|
||||
case Ion.BOOLEAN:
|
||||
return readBoolean();
|
||||
|
||||
case Ion.BYTE:
|
||||
return readByte();
|
||||
|
||||
case Ion.CHAR:
|
||||
return readChar();
|
||||
|
||||
case Ion.SHORT:
|
||||
return readShort();
|
||||
|
||||
case Ion.INT:
|
||||
return readInt();
|
||||
|
||||
case Ion.LONG:
|
||||
return readLong();
|
||||
|
||||
case Ion.FLOAT:
|
||||
return readFloat();
|
||||
|
||||
case Ion.DOUBLE:
|
||||
return readDouble();
|
||||
|
||||
case Ion.STRING:
|
||||
return readString();
|
||||
|
||||
case Ion.BOOLEAN_ARRAY:
|
||||
return readBooleans();
|
||||
|
||||
case Ion.BYTE_ARRAY:
|
||||
return readBytes();
|
||||
|
||||
case Ion.CHAR_ARRAY:
|
||||
return readChars();
|
||||
|
||||
case Ion.SHORT_ARRAY:
|
||||
return readShorts();
|
||||
|
||||
case Ion.INT_ARRAY:
|
||||
return readInts();
|
||||
|
||||
case Ion.LONG_ARRAY:
|
||||
return readLongs();
|
||||
|
||||
case Ion.FLOAT_ARRAY:
|
||||
return readFloats();
|
||||
|
||||
case Ion.DOUBLE_ARRAY:
|
||||
return readDoubles();
|
||||
|
||||
case Ion.STRING_ARRAY:
|
||||
return readStrings();
|
||||
|
||||
case Ion.OBJECT_ARRAY:
|
||||
return readObjects();
|
||||
|
||||
case Ion.MAP:
|
||||
return readMap();
|
||||
|
||||
case Ion.SEQUENCE:
|
||||
return readSequence();
|
||||
|
||||
default:
|
||||
throw new CorruptDataException("Invalid mark: " + mark);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads mark and returns true if the mark is ENTRY, false if the mark is
|
||||
* END. Throws an exception otherwise.
|
||||
*
|
||||
* @return mark was ENTRY
|
||||
* @throws IOException when the mark is neither ENTRY or END.
|
||||
*/
|
||||
public boolean hasNextEntry() throws IOException
|
||||
{
|
||||
final int mark = readMark();
|
||||
if (mark == Ion.ENTRY) return true;
|
||||
if (mark == Ion.END) return false;
|
||||
|
||||
throw new CorruptDataException("Unexpected mark in sequence: " + mark);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a sequence of elements into an ArrayList
|
||||
*
|
||||
* @return the collection
|
||||
* @throws IOException
|
||||
*/
|
||||
public <T> Collection<T> readSequence() throws IOException
|
||||
{
|
||||
return readSequence(new ArrayList<T>());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load entries into a collection. The collection is cleaned first.
|
||||
*
|
||||
* @param filled collection to populate
|
||||
* @return the collection
|
||||
* @throws IOException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Collection<T> readSequence(Collection<T> filled) throws IOException
|
||||
{
|
||||
try {
|
||||
filled.clear();
|
||||
while (hasNextEntry()) {
|
||||
filled.add((T) readObject());
|
||||
}
|
||||
return filled;
|
||||
} catch (final ClassCastException e) {
|
||||
throw new CorruptDataException("Unexpected element type in sequence.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read element pairs into a HashMap
|
||||
*
|
||||
* @return the map
|
||||
* @throws IOException
|
||||
*/
|
||||
public <K, V> Map<K, V> readMap() throws IOException
|
||||
{
|
||||
return readMap(new HashMap<K, V>());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load data into a map. The map is cleaned first.
|
||||
*
|
||||
* @param filled filled map
|
||||
* @return the map
|
||||
* @throws IOException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <K, V> Map<K, V> readMap(Map<K, V> filled) throws IOException
|
||||
{
|
||||
try {
|
||||
filled.clear();
|
||||
while (hasNextEntry()) {
|
||||
final K key = (K) readObject();
|
||||
final V value = (V) readObject();
|
||||
|
||||
filled.put(key, value);
|
||||
}
|
||||
return filled;
|
||||
} catch (final ClassCastException e) {
|
||||
throw new CorruptDataException("Unexpected element type in map.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package mightypork.utils.ion;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
class IonMapWrapper implements IonObjBinary {
|
||||
|
||||
private final Map map;
|
||||
|
||||
|
||||
public IonMapWrapper()
|
||||
{
|
||||
map = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
|
||||
public IonMapWrapper(Map saved)
|
||||
{
|
||||
map = saved;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void load(IonInput in) throws IOException
|
||||
{
|
||||
map.clear();
|
||||
in.readMap(map);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void save(IonOutput out) throws IOException
|
||||
{
|
||||
out.writeMap(map);
|
||||
}
|
||||
|
||||
|
||||
public void fill(Map o)
|
||||
{
|
||||
o.clear();
|
||||
o.putAll(map);
|
||||
}
|
||||
|
||||
|
||||
public Map getMap()
|
||||
{
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package mightypork.utils.ion;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Binary ion object. If a class implements both binary and bundled, then binary
|
||||
* will be preferred by both IonInput and IonOutput.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface IonObjBinary {
|
||||
|
||||
/**
|
||||
* Load data from the input stream.
|
||||
*
|
||||
* @param in input stream
|
||||
* @throws IOException
|
||||
*/
|
||||
void load(IonInput in) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Store data to output stream (in such way that the load method will later
|
||||
* be able to read it).
|
||||
*
|
||||
* @param out Output stream
|
||||
* @throws IOException
|
||||
*/
|
||||
void save(IonOutput out) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.utils.ion;
|
||||
|
||||
|
||||
/**
|
||||
* Bundled ion object. If a class implements both binary and bundled, then
|
||||
* binary will be preferred by both IonInput and IonOutput.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface IonObjBundled {
|
||||
|
||||
void load(IonBundle in);
|
||||
|
||||
|
||||
void save(IonBundle out);
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
package mightypork.utils.ion;
|
||||
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
|
||||
/**
|
||||
* Ion output stream
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class IonOutput implements Closeable {
|
||||
|
||||
private final DataOutput out;
|
||||
private final OutputStream stream;
|
||||
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
public IonOutput(File outFile) throws FileNotFoundException
|
||||
{
|
||||
this(new FileOutputStream(outFile));
|
||||
}
|
||||
|
||||
|
||||
public IonOutput(OutputStream out)
|
||||
{
|
||||
this.stream = out;
|
||||
this.out = new DataOutputStream(out);
|
||||
}
|
||||
|
||||
|
||||
public void writeBoolean(boolean a) throws IOException
|
||||
{
|
||||
out.writeBoolean(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeByte(int a) throws IOException
|
||||
{
|
||||
out.writeByte(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeShort(int a) throws IOException
|
||||
{
|
||||
out.writeShort(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeChar(int a) throws IOException
|
||||
{
|
||||
out.writeChar(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeInt(int a) throws IOException
|
||||
{
|
||||
out.writeInt(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeIntShort(int a) throws IOException
|
||||
{
|
||||
out.writeShort(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeIntByte(int a) throws IOException
|
||||
{
|
||||
out.writeByte(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeLong(long a) throws IOException
|
||||
{
|
||||
out.writeLong(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeFloat(float a) throws IOException
|
||||
{
|
||||
out.writeFloat(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeDouble(double a) throws IOException
|
||||
{
|
||||
out.writeDouble(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeBytes(String a) throws IOException
|
||||
{
|
||||
out.writeBytes(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeString(String a) throws IOException
|
||||
{
|
||||
out.writeUTF(a);
|
||||
}
|
||||
|
||||
|
||||
public void writeBooleans(boolean[] arr) throws IOException
|
||||
{
|
||||
writeLength(arr.length);
|
||||
for (final boolean a : arr) {
|
||||
out.writeBoolean(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void writeBytes(byte[] arr) throws IOException
|
||||
{
|
||||
writeLength(arr.length);
|
||||
for (final byte a : arr) {
|
||||
out.writeByte(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void writeChars(char[] arr) throws IOException
|
||||
{
|
||||
writeLength(arr.length);
|
||||
for (final char a : arr) {
|
||||
out.writeChar(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void writeShorts(short[] arr) throws IOException
|
||||
{
|
||||
writeLength(arr.length);
|
||||
for (final short a : arr) {
|
||||
out.writeShort(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void writeInts(int[] arr) throws IOException
|
||||
{
|
||||
writeLength(arr.length);
|
||||
for (final int a : arr) {
|
||||
out.writeInt(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void writeLongs(long[] arr) throws IOException
|
||||
{
|
||||
writeLength(arr.length);
|
||||
for (final long a : arr) {
|
||||
out.writeLong(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void writeFloats(float[] arr) throws IOException
|
||||
{
|
||||
writeLength(arr.length);
|
||||
for (final float a : arr) {
|
||||
out.writeFloat(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void writeDoubles(double[] arr) throws IOException
|
||||
{
|
||||
writeLength(arr.length);
|
||||
for (final double a : arr) {
|
||||
out.writeDouble(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void writeStrings(String[] arr) throws IOException
|
||||
{
|
||||
writeLength(arr.length);
|
||||
for (final String a : arr) {
|
||||
out.writeUTF(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write a bundle without a mark
|
||||
*/
|
||||
public void writeBundle(IonBundle bundle) throws IOException
|
||||
{
|
||||
bundle.save(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write array of objects. Works with all that is supported by writeObject()
|
||||
*
|
||||
* @param arr array to write
|
||||
* @throws IOException on IO error or on invalid object type.
|
||||
*/
|
||||
public void writeObjects(Object[] arr) throws IOException
|
||||
{
|
||||
writeLength(arr.length);
|
||||
for (final Object a : arr) {
|
||||
writeObject(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public <T> void writeSequence(Collection<T> sequence) throws IOException
|
||||
{
|
||||
for (final T element : sequence) {
|
||||
startEntry();
|
||||
writeObject(element);
|
||||
}
|
||||
endSequence();
|
||||
}
|
||||
|
||||
|
||||
public <K, V> void writeMap(Map<K, V> map) throws IOException
|
||||
{
|
||||
for (final Entry<K, V> e : map.entrySet()) {
|
||||
if (e.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
startEntry();
|
||||
writeObject(e.getKey());
|
||||
writeObject(e.getValue());
|
||||
}
|
||||
endSequence();
|
||||
}
|
||||
|
||||
|
||||
public void endSequence() throws IOException
|
||||
{
|
||||
writeMark(Ion.END);
|
||||
}
|
||||
|
||||
|
||||
public void startEntry() throws IOException
|
||||
{
|
||||
writeMark(Ion.ENTRY);
|
||||
}
|
||||
|
||||
|
||||
private void writeMark(int mark) throws IOException
|
||||
{
|
||||
writeIntByte(mark);
|
||||
}
|
||||
|
||||
|
||||
private void writeLength(int length) throws IOException
|
||||
{
|
||||
writeInt(length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write an object. Supported are built-in types and types registered to
|
||||
* Ion.
|
||||
*
|
||||
* @param obj obj to write
|
||||
* @throws IOException on IO error or invalid object type.
|
||||
*/
|
||||
public void writeObject(Object obj) throws IOException
|
||||
{
|
||||
if (obj == null) {
|
||||
writeMark(Ion.NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof IonObjBinary) {
|
||||
final IonObjBinary iObj = (IonObjBinary) obj;
|
||||
|
||||
writeMark(Ion.getMark(iObj));
|
||||
iObj.save(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof IonObjBundled) {
|
||||
final IonObjBundled iObj = (IonObjBundled) obj;
|
||||
|
||||
writeMark(Ion.getMark(iObj));
|
||||
|
||||
final IonBundle bundle = new IonBundle();
|
||||
iObj.save(bundle);
|
||||
writeBundle(bundle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Map) {
|
||||
writeMark(Ion.MAP);
|
||||
writeMap((Map<?, ?>) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Collection) {
|
||||
writeMark(Ion.SEQUENCE);
|
||||
writeSequence((Collection<?>) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Boolean) {
|
||||
writeMark(Ion.BOOLEAN);
|
||||
writeBoolean((Boolean) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Byte) {
|
||||
writeMark(Ion.BYTE);
|
||||
writeByte((Byte) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Character) {
|
||||
writeMark(Ion.CHAR);
|
||||
writeChar((Character) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Short) {
|
||||
writeMark(Ion.SHORT);
|
||||
writeShort((Short) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Integer) {
|
||||
writeMark(Ion.INT);
|
||||
writeInt((Integer) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Long) {
|
||||
writeMark(Ion.LONG);
|
||||
writeLong((Long) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Float) {
|
||||
writeMark(Ion.FLOAT);
|
||||
writeFloat((Float) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Double) {
|
||||
writeMark(Ion.DOUBLE);
|
||||
writeDouble((Double) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof String) {
|
||||
writeMark(Ion.STRING);
|
||||
writeString((String) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof boolean[]) {
|
||||
writeMark(Ion.BOOLEAN_ARRAY);
|
||||
writeBooleans((boolean[]) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof byte[]) {
|
||||
writeMark(Ion.BYTE_ARRAY);
|
||||
writeBytes((byte[]) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof char[]) {
|
||||
writeMark(Ion.CHAR_ARRAY);
|
||||
writeChars((char[]) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof short[]) {
|
||||
writeMark(Ion.SHORT_ARRAY);
|
||||
writeShorts((short[]) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof int[]) {
|
||||
writeMark(Ion.INT_ARRAY);
|
||||
writeInts((int[]) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof long[]) {
|
||||
writeMark(Ion.LONG_ARRAY);
|
||||
writeLongs((long[]) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof float[]) {
|
||||
writeMark(Ion.FLOAT_ARRAY);
|
||||
writeFloats((float[]) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof double[]) {
|
||||
writeMark(Ion.DOUBLE_ARRAY);
|
||||
writeDoubles((double[]) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof String[]) {
|
||||
writeMark(Ion.STRING_ARRAY);
|
||||
writeStrings((String[]) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Object[]) {
|
||||
writeMark(Ion.OBJECT_ARRAY);
|
||||
writeObjects((Object[]) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new IOException("Object " + obj + " could not be be written to stream.");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package mightypork.utils.ion;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
class IonSequenceWrapper implements IonObjBinary {
|
||||
|
||||
private Collection collection = new ArrayList();
|
||||
|
||||
|
||||
public IonSequenceWrapper()
|
||||
{
|
||||
collection = new ArrayList();
|
||||
}
|
||||
|
||||
|
||||
public IonSequenceWrapper(Collection saved)
|
||||
{
|
||||
collection = saved;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void load(IonInput in) throws IOException
|
||||
{
|
||||
collection.clear();
|
||||
in.readSequence(collection);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void save(IonOutput out) throws IOException
|
||||
{
|
||||
out.writeSequence(collection);
|
||||
}
|
||||
|
||||
|
||||
public void fill(Collection o)
|
||||
{
|
||||
o.clear();
|
||||
o.addAll(collection);
|
||||
}
|
||||
|
||||
|
||||
public Collection getSequence()
|
||||
{
|
||||
return collection;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
package mightypork.utils.logging;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import mightypork.utils.annotations.FactoryMethod;
|
||||
import mightypork.utils.logging.monitors.LogMonitor;
|
||||
import mightypork.utils.logging.monitors.LogMonitorStdout;
|
||||
import mightypork.utils.logging.writers.ArchivingLog;
|
||||
import mightypork.utils.logging.writers.LogWriter;
|
||||
import mightypork.utils.logging.writers.SimpleLog;
|
||||
import mightypork.utils.string.StringUtil;
|
||||
|
||||
|
||||
/**
|
||||
* A log.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class Log {
|
||||
|
||||
private static LogWriter main = null;
|
||||
private static boolean enabled = true;
|
||||
private static final LogMonitorStdout sysoMonitor = new LogMonitorStdout();
|
||||
private static final long start_ms = System.currentTimeMillis();
|
||||
|
||||
private static HashMap<String, SimpleLog> logs = new HashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Create a logger. If another with the name already exists, it'll be
|
||||
* retrieved instead of creating a new one.
|
||||
*
|
||||
* @param logName log name (used for filename, should be application-unique)
|
||||
* @param logFile log file; old logs will be kept here too.
|
||||
* @param oldLogsCount number of old logs to keep, -1 infinite, 0 none.
|
||||
* @return the created Log instance
|
||||
*/
|
||||
@FactoryMethod
|
||||
public static synchronized LogWriter create(String logName, File logFile, int oldLogsCount)
|
||||
{
|
||||
if (logs.containsKey(logName)) return logs.get(logName);
|
||||
|
||||
final ArchivingLog log = new ArchivingLog(logName, logFile, oldLogsCount);
|
||||
log.init();
|
||||
|
||||
logs.put(logName, log);
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a logger. If another with the name already exists, it'll be
|
||||
* retrieved instead of creating a new one.
|
||||
*
|
||||
* @param logName log name (used for filename, must be application-unique)
|
||||
* @param logFile log file; old logs will be kept here too.
|
||||
* @return the created Log instance
|
||||
*/
|
||||
@FactoryMethod
|
||||
public static synchronized LogWriter create(String logName, File logFile)
|
||||
{
|
||||
if (logs.containsKey(logName)) return logs.get(logName);
|
||||
|
||||
final SimpleLog log = new SimpleLog(logName, logFile);
|
||||
log.init();
|
||||
|
||||
logs.put(logName, log);
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
|
||||
public static void setMainLogger(LogWriter log)
|
||||
{
|
||||
main = log;
|
||||
}
|
||||
|
||||
|
||||
public static void addMonitor(LogMonitor mon)
|
||||
{
|
||||
assertInited();
|
||||
|
||||
main.addMonitor(mon);
|
||||
}
|
||||
|
||||
|
||||
public static void removeMonitor(LogMonitor mon)
|
||||
{
|
||||
assertInited();
|
||||
|
||||
main.removeMonitor(mon);
|
||||
}
|
||||
|
||||
|
||||
private static void assertInited()
|
||||
{
|
||||
if (main == null) throw new IllegalStateException("Main logger not initialized.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log a message
|
||||
*
|
||||
* @param level message level
|
||||
* @param msg message text
|
||||
*/
|
||||
public static void log(Level level, String msg)
|
||||
{
|
||||
if (enabled) {
|
||||
sysoMonitor.onMessageLogged(level, formatMessage(level, msg, null, start_ms));
|
||||
|
||||
if (main != null) {
|
||||
main.log(level, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log a message
|
||||
*
|
||||
* @param level message level
|
||||
* @param msg message text
|
||||
* @param t thrown exception
|
||||
*/
|
||||
public static void log(Level level, String msg, Throwable t)
|
||||
{
|
||||
if (enabled) {
|
||||
sysoMonitor.onMessageLogged(level, formatMessage(level, msg, t, start_ms));
|
||||
|
||||
if (main != null) {
|
||||
main.log(level, msg, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log FINE message
|
||||
*
|
||||
* @param msg message
|
||||
*/
|
||||
public static void f1(String msg)
|
||||
{
|
||||
log(Level.FINE, msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log FINER message
|
||||
*
|
||||
* @param msg message
|
||||
*/
|
||||
public static void f2(String msg)
|
||||
{
|
||||
log(Level.FINER, msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log FINEST message
|
||||
*
|
||||
* @param msg message
|
||||
*/
|
||||
public static void f3(String msg)
|
||||
{
|
||||
log(Level.FINEST, msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log INFO message
|
||||
*
|
||||
* @param msg message
|
||||
*/
|
||||
public static void i(String msg)
|
||||
{
|
||||
log(Level.INFO, msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log WARNING message (less severe than ERROR)
|
||||
*
|
||||
* @param msg message
|
||||
*/
|
||||
public static void w(String msg)
|
||||
{
|
||||
log(Level.WARNING, msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log ERROR message
|
||||
*
|
||||
* @param msg message
|
||||
*/
|
||||
public static void e(String msg)
|
||||
{
|
||||
log(Level.SEVERE, msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log warning message with exception
|
||||
*
|
||||
* @param msg message
|
||||
* @param thrown thrown exception
|
||||
*/
|
||||
public static void w(String msg, Throwable thrown)
|
||||
{
|
||||
log(Level.WARNING, msg, thrown);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log exception thrown as warning
|
||||
*
|
||||
* @param thrown thrown exception
|
||||
*/
|
||||
public static void w(Throwable thrown)
|
||||
{
|
||||
log(Level.WARNING, null, thrown);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log error message
|
||||
*
|
||||
* @param msg message
|
||||
* @param thrown thrown exception
|
||||
*/
|
||||
public static void e(String msg, Throwable thrown)
|
||||
{
|
||||
log(Level.SEVERE, msg, thrown);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log exception thrown as error
|
||||
*
|
||||
* @param thrown thrown exception
|
||||
*/
|
||||
public static void e(Throwable thrown)
|
||||
{
|
||||
log(Level.SEVERE, null, thrown);
|
||||
}
|
||||
|
||||
|
||||
public static void enable(boolean flag)
|
||||
{
|
||||
enabled = flag;
|
||||
}
|
||||
|
||||
|
||||
public static void setSysoutLevel(Level level)
|
||||
{
|
||||
sysoMonitor.setLevel(level);
|
||||
}
|
||||
|
||||
|
||||
public static void setLevel(Level level)
|
||||
{
|
||||
assertInited();
|
||||
|
||||
main.setLevel(level);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get stack trace from throwable
|
||||
*
|
||||
* @param t
|
||||
* @return trace
|
||||
*/
|
||||
public static String getStackTrace(Throwable t)
|
||||
{
|
||||
final StringWriter sw = new StringWriter();
|
||||
final PrintWriter pw = new PrintWriter(sw, true);
|
||||
t.printStackTrace(pw);
|
||||
pw.flush();
|
||||
sw.flush();
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
|
||||
public static String formatMessage(Level level, String message, Throwable throwable, long start_ms)
|
||||
{
|
||||
if (message == null) message = "";
|
||||
|
||||
final String nl = System.getProperty("line.separator");
|
||||
|
||||
if (message.length() > 0) {
|
||||
if (message.equals("\n")) {
|
||||
return nl;
|
||||
}
|
||||
|
||||
if (message.charAt(0) == '\n') {
|
||||
message = nl + message.substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
final long time_ms = (System.currentTimeMillis() - start_ms);
|
||||
final double time_s = time_ms / 1000D;
|
||||
final String time = String.format("%6.2f ", time_s);
|
||||
final String time_blank = StringUtil.repeat(" ", time.length());
|
||||
|
||||
String prefix = "[ ? ]";
|
||||
|
||||
if (level == Level.FINE) {
|
||||
prefix = "[ # ] ";
|
||||
} else if (level == Level.FINER) {
|
||||
prefix = "[ - ] ";
|
||||
} else if (level == Level.FINEST) {
|
||||
prefix = "[ ] ";
|
||||
} else if (level == Level.INFO) {
|
||||
prefix = "[ i ] ";
|
||||
} else if (level == Level.SEVERE) {
|
||||
prefix = "[!E!] ";
|
||||
} else if (level == Level.WARNING) {
|
||||
prefix = "[!W!] ";
|
||||
}
|
||||
|
||||
message = time + prefix + message.replaceAll("\n", nl + time_blank + prefix) + nl;
|
||||
|
||||
if (throwable != null) {
|
||||
message += getStackTrace(throwable);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package mightypork.utils.logging.monitors;
|
||||
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
|
||||
public abstract class LogMonitor {
|
||||
|
||||
private boolean enabled = true;
|
||||
private Level accepted = Level.ALL;
|
||||
|
||||
|
||||
public void onMessageLogged(Level level, String message)
|
||||
{
|
||||
if (!enabled) return;
|
||||
if (accepted.intValue() > level.intValue()) return;
|
||||
|
||||
logMessage(level, message);
|
||||
}
|
||||
|
||||
|
||||
protected abstract void logMessage(Level level, String message);
|
||||
|
||||
|
||||
public void setLevel(Level level)
|
||||
{
|
||||
this.accepted = level;
|
||||
}
|
||||
|
||||
|
||||
public void enable(boolean flag)
|
||||
{
|
||||
this.enabled = flag;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package mightypork.utils.logging.monitors;
|
||||
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
|
||||
public class LogMonitorStdout extends LogMonitor {
|
||||
|
||||
@Override
|
||||
protected void logMessage(Level level, String message)
|
||||
{
|
||||
if (level == Level.SEVERE || level == Level.WARNING) {
|
||||
System.err.print(message);
|
||||
} else {
|
||||
System.out.print(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package mightypork.utils.logging.writers;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import mightypork.utils.files.FileUtils;
|
||||
import mightypork.utils.string.StringUtil;
|
||||
|
||||
|
||||
/**
|
||||
* Logger that cleans directory & archives old logs
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
* @copy (c) 2014
|
||||
*/
|
||||
public class ArchivingLog extends SimpleLog {
|
||||
|
||||
/** Number of old logs to keep */
|
||||
private final int logs_to_keep;
|
||||
|
||||
|
||||
/**
|
||||
* Log
|
||||
*
|
||||
* @param name log name
|
||||
* @param file log file (in log directory)
|
||||
* @param oldLogCount number of old log files to keep: -1 all, 0 none.
|
||||
*/
|
||||
public ArchivingLog(String name, File file, int oldLogCount)
|
||||
{
|
||||
super(name, file);
|
||||
this.logs_to_keep = oldLogCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log, not keeping 5 last log files (default);
|
||||
*
|
||||
* @param name log name
|
||||
* @param file log file (in log directory)
|
||||
*/
|
||||
public ArchivingLog(String name, File file)
|
||||
{
|
||||
super(name, file);
|
||||
this.logs_to_keep = 5;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
cleanLoggingDirectory();
|
||||
|
||||
super.init();
|
||||
}
|
||||
|
||||
|
||||
private void cleanLoggingDirectory()
|
||||
{
|
||||
if (logs_to_keep == 0) return; // overwrite
|
||||
|
||||
final File log_file = getFile();
|
||||
final File log_dir = log_file.getParentFile();
|
||||
final String fname = FileUtils.getBasename(log_file.toString());
|
||||
|
||||
// move old file
|
||||
for (final File f : FileUtils.listDirectory(log_dir)) {
|
||||
if (!f.isFile()) continue;
|
||||
if (f.equals(getFile())) {
|
||||
|
||||
final Date d = new Date(f.lastModified());
|
||||
final String fbase = fname + '_' + (new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss")).format(d);
|
||||
final String suff = "." + getSuffix();
|
||||
String cntStr = "";
|
||||
File f2;
|
||||
|
||||
for (int cnt = 0; (f2 = new File(log_dir, fbase + cntStr + suff)).exists(); cntStr = "_" + (++cnt)) {}
|
||||
|
||||
if (!f.renameTo(f2)) throw new RuntimeException("Could not move log file.");
|
||||
}
|
||||
}
|
||||
|
||||
if (logs_to_keep == -1) return; // keep all
|
||||
|
||||
final List<File> oldLogs = FileUtils.listDirectory(log_dir, new FileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File f)
|
||||
{
|
||||
if (f.isDirectory()) return false;
|
||||
if (!f.getName().endsWith(getSuffix())) return false;
|
||||
if (!f.getName().startsWith(fname)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Collections.sort(oldLogs, new Comparator<File>() {
|
||||
|
||||
@Override
|
||||
public int compare(File o1, File o2)
|
||||
{
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
|
||||
// playing with fireee
|
||||
for (int i = 0; i < oldLogs.size() - logs_to_keep; i++) {
|
||||
if (!oldLogs.get(i).delete()) {
|
||||
throw new RuntimeException("Could not delete old log file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return log filename suffix
|
||||
*/
|
||||
private String getSuffix()
|
||||
{
|
||||
return StringUtil.fromLastChar(getFile().toString(), '.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package mightypork.utils.logging.writers;
|
||||
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import mightypork.utils.logging.monitors.LogMonitor;
|
||||
|
||||
|
||||
/**
|
||||
* Log interface
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface LogWriter {
|
||||
|
||||
/**
|
||||
* Prepare logs for logging
|
||||
*/
|
||||
void init();
|
||||
|
||||
|
||||
/**
|
||||
* Add log monitor
|
||||
*
|
||||
* @param mon monitor
|
||||
*/
|
||||
void addMonitor(LogMonitor mon);
|
||||
|
||||
|
||||
/**
|
||||
* Remove a monitor
|
||||
*
|
||||
* @param removed monitor to remove
|
||||
*/
|
||||
void removeMonitor(LogMonitor removed);
|
||||
|
||||
|
||||
/**
|
||||
* Set logging level
|
||||
*
|
||||
* @param level
|
||||
*/
|
||||
void setLevel(Level level);
|
||||
|
||||
|
||||
/**
|
||||
* Enable logging.
|
||||
*
|
||||
* @param flag do enable logging
|
||||
*/
|
||||
void enable(boolean flag);
|
||||
|
||||
|
||||
/**
|
||||
* Log a message
|
||||
*
|
||||
* @param level message level
|
||||
* @param msg message text
|
||||
*/
|
||||
void log(Level level, String msg);
|
||||
|
||||
|
||||
/**
|
||||
* Log a message
|
||||
*
|
||||
* @param level message level
|
||||
* @param msg message text
|
||||
* @param t thrown exception
|
||||
*/
|
||||
void log(Level level, String msg, Throwable t);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package mightypork.utils.logging.writers;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import mightypork.utils.logging.Log;
|
||||
import mightypork.utils.logging.monitors.LogMonitor;
|
||||
|
||||
|
||||
/**
|
||||
* Basic logger
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class SimpleLog implements LogWriter {
|
||||
|
||||
/**
|
||||
* Log file formatter.
|
||||
*/
|
||||
class LogFormatter extends Formatter {
|
||||
|
||||
@Override
|
||||
public String format(LogRecord record)
|
||||
{
|
||||
return Log.formatMessage(record.getLevel(), record.getMessage(), record.getThrown(), started_ms);
|
||||
}
|
||||
}
|
||||
|
||||
/** Log file */
|
||||
private final File file;
|
||||
|
||||
/** Log name */
|
||||
private final String name;
|
||||
|
||||
/** Logger instance. */
|
||||
private Logger logger;
|
||||
|
||||
private boolean enabled = true;
|
||||
private final HashSet<LogMonitor> monitors = new HashSet<>();
|
||||
private final long started_ms;
|
||||
|
||||
|
||||
public SimpleLog(String name, File file)
|
||||
{
|
||||
this.name = name;
|
||||
this.file = file;
|
||||
this.started_ms = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
logger = Logger.getLogger(getName());
|
||||
|
||||
FileHandler handler = null;
|
||||
try {
|
||||
handler = new FileHandler(getFile().getPath());
|
||||
} catch (final Throwable t) {
|
||||
throw new RuntimeException("Failed to init log.", t);
|
||||
}
|
||||
|
||||
handler.setFormatter(new LogFormatter());
|
||||
logger.addHandler(handler);
|
||||
logger.setUseParentHandlers(false);
|
||||
logger.setLevel(Level.ALL);
|
||||
|
||||
printHeader();
|
||||
}
|
||||
|
||||
|
||||
protected void printHeader()
|
||||
{
|
||||
final String stamp = (new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")).format(new Date());
|
||||
log(Level.INFO, "Logger \"" + getName() + "\" initialized.\n" + stamp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add log monitor
|
||||
*
|
||||
* @param mon monitor
|
||||
*/
|
||||
@Override
|
||||
public synchronized void addMonitor(LogMonitor mon)
|
||||
{
|
||||
monitors.add(mon);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a monitor
|
||||
*
|
||||
* @param removed monitor to remove
|
||||
*/
|
||||
@Override
|
||||
public synchronized void removeMonitor(LogMonitor removed)
|
||||
{
|
||||
monitors.remove(removed);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setLevel(Level level)
|
||||
{
|
||||
logger.setLevel(level);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void enable(boolean flag)
|
||||
{
|
||||
enabled = flag;
|
||||
}
|
||||
|
||||
|
||||
public File getFile()
|
||||
{
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void log(Level level, String msg)
|
||||
{
|
||||
if (enabled) {
|
||||
logger.log(level, msg);
|
||||
|
||||
final String fmt = Log.formatMessage(level, msg, null, started_ms);
|
||||
|
||||
for (final LogMonitor mon : monitors) {
|
||||
mon.onMessageLogged(level, fmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void log(Level level, String msg, Throwable t)
|
||||
{
|
||||
if (enabled) {
|
||||
logger.log(level, msg, t);
|
||||
|
||||
final String fmt = Log.formatMessage(level, msg, t, started_ms);
|
||||
|
||||
for (final LogMonitor mon : monitors) {
|
||||
mon.onMessageLogged(level, fmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
package mightypork.utils.math;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import mightypork.utils.math.angles.Deg;
|
||||
import mightypork.utils.math.angles.Rad;
|
||||
import mightypork.utils.math.animation.Easing;
|
||||
import mightypork.utils.math.constraints.vect.Vect;
|
||||
|
||||
|
||||
/**
|
||||
* Math utils
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public final class Calc {
|
||||
|
||||
private Calc()
|
||||
{
|
||||
// not instantiable
|
||||
}
|
||||
|
||||
/** Square root of two */
|
||||
public static final double SQ2 = 1.41421356237;
|
||||
|
||||
|
||||
/**
|
||||
* Get distance from 2D line to 2D point [X,Y]
|
||||
*
|
||||
* @param lineDirVec line directional vector
|
||||
* @param linePoint point of line
|
||||
* @param point point coordinate
|
||||
* @return distance
|
||||
*/
|
||||
public static double linePointDist(Vect lineDirVec, Vect linePoint, Vect point)
|
||||
{
|
||||
// line point L[lx,ly]
|
||||
final double lx = linePoint.x();
|
||||
final double ly = linePoint.y();
|
||||
|
||||
// line equation ax+by+c=0
|
||||
final double a = -lineDirVec.y();
|
||||
final double b = lineDirVec.x();
|
||||
final double c = -a * lx - b * ly;
|
||||
|
||||
// checked point P[x,y]
|
||||
final double x = point.x();
|
||||
final double y = point.y();
|
||||
|
||||
// distance
|
||||
return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b);
|
||||
}
|
||||
|
||||
public static final Random rand = new Random();
|
||||
|
||||
|
||||
public static double sphereSurface(double radius)
|
||||
{
|
||||
return 4D * Math.PI * square(radius);
|
||||
}
|
||||
|
||||
|
||||
public static double sphereVolume(double radius)
|
||||
{
|
||||
return (4D / 3D) * Math.PI * cube(radius);
|
||||
}
|
||||
|
||||
|
||||
public static double sphereRadius(double volume)
|
||||
{
|
||||
return Math.cbrt((3D * volume) / (4 * Math.PI));
|
||||
}
|
||||
|
||||
|
||||
public static double circleSurface(double radius)
|
||||
{
|
||||
return Math.PI * square(radius);
|
||||
}
|
||||
|
||||
|
||||
public static double circleRadius(double surface)
|
||||
{
|
||||
return Math.sqrt(surface / Math.PI);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Safe equals that works with nulls
|
||||
*
|
||||
* @param a
|
||||
* @param b
|
||||
* @return are equal
|
||||
*/
|
||||
public static boolean areEqual(Object a, Object b)
|
||||
{
|
||||
return a == null ? b == null : a.equals(b);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamp integer
|
||||
*
|
||||
* @param number
|
||||
* @param min
|
||||
* @param max
|
||||
* @return result
|
||||
*/
|
||||
public static int clamp(int number, int min, int max)
|
||||
{
|
||||
return number < min ? min : number > max ? max : number;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamp double
|
||||
*
|
||||
* @param number
|
||||
* @param min
|
||||
* @param max
|
||||
* @return result
|
||||
*/
|
||||
public static double clamp(double number, double min, double max)
|
||||
{
|
||||
return number < min ? min : number > max ? max : number;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isInRange(double number, double left, double right)
|
||||
{
|
||||
return number >= left && number <= right;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get number from A to B at delta time (A -> B)
|
||||
*
|
||||
* @param from
|
||||
* @param to
|
||||
* @param elapsed progress ratio 0..1
|
||||
* @param easing
|
||||
* @return result
|
||||
*/
|
||||
public static double interpolate(double from, double to, double elapsed, Easing easing)
|
||||
{
|
||||
return from + (to - from) * easing.get(elapsed);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get angle [degrees] from A to B at delta time (tween A to B)
|
||||
*
|
||||
* @param from
|
||||
* @param to
|
||||
* @param elapsed progress ratio 0..1
|
||||
* @param easing
|
||||
* @return result
|
||||
*/
|
||||
public static double interpolateDeg(double from, double to, double elapsed, Easing easing)
|
||||
{
|
||||
return Deg.norm(from - Deg.delta(to, from) * easing.get(elapsed));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get angle [radians] from A to B at delta time (tween A to B)
|
||||
*
|
||||
* @param from
|
||||
* @param to
|
||||
* @param elapsed progress ratio 0..1
|
||||
* @param easing
|
||||
* @return result
|
||||
*/
|
||||
public static double interpolateRad(double from, double to, double elapsed, Easing easing)
|
||||
{
|
||||
return Rad.norm(from - Rad.delta(to, from) * easing.get(elapsed));
|
||||
}
|
||||
|
||||
|
||||
public static double max(double... numbers)
|
||||
{
|
||||
double highest = numbers[0];
|
||||
for (final double num : numbers) {
|
||||
if (num > highest) highest = num;
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
|
||||
|
||||
public static int max(int... numbers)
|
||||
{
|
||||
int highest = numbers[0];
|
||||
for (final int num : numbers) {
|
||||
if (num > highest) highest = num;
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
|
||||
|
||||
public static double min(double... numbers)
|
||||
{
|
||||
double lowest = numbers[0];
|
||||
for (final double num : numbers) {
|
||||
if (num < lowest) lowest = num;
|
||||
}
|
||||
return lowest;
|
||||
}
|
||||
|
||||
|
||||
public static int min(int... numbers)
|
||||
{
|
||||
int lowest = numbers[0];
|
||||
for (final int num : numbers) {
|
||||
if (num < lowest) lowest = num;
|
||||
}
|
||||
return lowest;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Split comma separated list of integers.
|
||||
*
|
||||
* @param list String containing the list.
|
||||
* @param delimiter delimiter character
|
||||
* @return array of integers or null.
|
||||
*/
|
||||
public static List<Integer> parseIntList(String list, char delimiter)
|
||||
{
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String[] parts = list.split(Character.toString(delimiter));
|
||||
|
||||
final ArrayList<Integer> intList = new ArrayList<>();
|
||||
|
||||
for (final String part : parts) {
|
||||
try {
|
||||
intList.add(Integer.parseInt(part.trim()));
|
||||
} catch (final NumberFormatException e) {}
|
||||
}
|
||||
|
||||
return intList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick random element from a given list.
|
||||
*
|
||||
* @param list list of choices
|
||||
* @return picked element
|
||||
*/
|
||||
public static <T> T pick(List<T> list)
|
||||
{
|
||||
return pick(rand, list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick random element from a given list.
|
||||
*
|
||||
* @param rand RNG
|
||||
* @param list list of choices
|
||||
* @return picked element
|
||||
*/
|
||||
public static <T> T pick(Random rand, List<T> list)
|
||||
{
|
||||
if (list.size() == 0) return null;
|
||||
return list.get(rand.nextInt(list.size()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Take a square
|
||||
*
|
||||
* @param a value
|
||||
* @return value squared
|
||||
*/
|
||||
public static double square(double a)
|
||||
{
|
||||
return a * a;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Take a cube
|
||||
*
|
||||
* @param a value
|
||||
* @return value cubed
|
||||
*/
|
||||
public static double cube(double a)
|
||||
{
|
||||
return a * a * a;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param d number
|
||||
* @return fractional part
|
||||
*/
|
||||
public static double frag(double d)
|
||||
{
|
||||
return d - Math.floor(d);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make sure value is within array length.
|
||||
*
|
||||
* @param index tested index
|
||||
* @param length array length
|
||||
* @throws IndexOutOfBoundsException if the index is not in range.
|
||||
*/
|
||||
public static void assertValidIndex(int index, int length)
|
||||
{
|
||||
if (!isInRange(index, 0, length - 1)) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get distance of two coordinates in 2D plane
|
||||
*
|
||||
* @param x1 first coordinate X
|
||||
* @param y1 first coordinate y
|
||||
* @param x2 second coordinate X
|
||||
* @param y2 second coordinate Y
|
||||
* @return distance
|
||||
*/
|
||||
public static double dist(double x1, double y1, double x2, double y2)
|
||||
{
|
||||
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
|
||||
}
|
||||
|
||||
|
||||
public static int randInt(Random rand, int low, int high)
|
||||
{
|
||||
final int range = Math.abs(high - low) + 1;
|
||||
return low + rand.nextInt(range);
|
||||
}
|
||||
|
||||
|
||||
public static int randInt(int low, int high)
|
||||
{
|
||||
return randInt(rand, low, high);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get ordinal version of numbers (1 = 1st, 5 = 5th etc.)
|
||||
*
|
||||
* @param number number
|
||||
* @return ordinal, string
|
||||
*/
|
||||
public static String ordinal(int number)
|
||||
{
|
||||
if (number % 100 < 4 || number % 100 > 13) {
|
||||
if (number % 10 == 1) return number + "st";
|
||||
if (number % 10 == 2) return number + "nd";
|
||||
if (number % 10 == 3) return number + "rd";
|
||||
}
|
||||
return number + "th";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format number with thousands separated.
|
||||
*
|
||||
* @param number number
|
||||
* @param thousandSep
|
||||
* @return string
|
||||
*/
|
||||
public static String separateThousands(long number, char thousandSep)
|
||||
{
|
||||
final String num = String.valueOf(number);
|
||||
final String dot = String.valueOf(thousandSep);
|
||||
String out = "";
|
||||
|
||||
int cnt = 1;
|
||||
for (int i = num.length() - 1; i >= 0; i--) {
|
||||
out = num.charAt(i) + out;
|
||||
if (cnt % 3 == 0 && i > 0) out = dot + out;
|
||||
cnt++;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
public static int countBits(byte b)
|
||||
{
|
||||
int c = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
c += (b >> i) & 1;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package mightypork.utils.math;
|
||||
|
||||
|
||||
import mightypork.utils.math.constraints.vect.Vect;
|
||||
|
||||
|
||||
/**
|
||||
* Polar coordinate
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class Polar {
|
||||
|
||||
/** angle in radians */
|
||||
private double angle = 0;
|
||||
|
||||
/** distance in units */
|
||||
private double radius = 0;
|
||||
|
||||
private Vect coord = null;
|
||||
|
||||
|
||||
/**
|
||||
* Create a polar
|
||||
*
|
||||
* @param angle angle in RAD
|
||||
* @param distance distance from origin
|
||||
*/
|
||||
public Polar(double angle, double distance)
|
||||
{
|
||||
this(angle, false, distance);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a polar
|
||||
*
|
||||
* @param angle angle
|
||||
* @param deg angle is in DEG
|
||||
* @param distance radius
|
||||
*/
|
||||
public Polar(double angle, boolean deg, double distance)
|
||||
{
|
||||
this.radius = distance;
|
||||
this.angle = deg ? Math.toRadians(angle) : angle;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return angle in RAD
|
||||
*/
|
||||
public double getAngle()
|
||||
{
|
||||
return angle;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return angle in DEG
|
||||
*/
|
||||
public double getAngleDeg()
|
||||
{
|
||||
return Math.toDegrees(angle);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param angle angle in RAD
|
||||
*/
|
||||
public void setAngle(double angle)
|
||||
{
|
||||
this.angle = angle;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param angle angle in DEG
|
||||
*/
|
||||
public void setAngleDeg(double angle)
|
||||
{
|
||||
this.angle = Math.toRadians(angle);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return radius
|
||||
*/
|
||||
public double getRadius()
|
||||
{
|
||||
return radius;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param r radius
|
||||
*/
|
||||
public void setRadius(double r)
|
||||
{
|
||||
this.radius = r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make polar from coord
|
||||
*
|
||||
* @param coord coord
|
||||
* @return polar
|
||||
*/
|
||||
public static Polar fromCoord(Vect coord)
|
||||
{
|
||||
return Polar.fromCoord(coord.x(), coord.y());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make polar from coords
|
||||
*
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @return polar
|
||||
*/
|
||||
public static Polar fromCoord(double x, double y)
|
||||
{
|
||||
final double a = Math.atan2(y, x);
|
||||
final double r = Math.sqrt(x * x + y * y);
|
||||
|
||||
return new Polar(a, r);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get coord from polar
|
||||
*
|
||||
* @return coord
|
||||
*/
|
||||
public Vect toCoord()
|
||||
{
|
||||
// lazy init
|
||||
if (coord == null) coord = new Vect() {
|
||||
|
||||
@Override
|
||||
public double x()
|
||||
{
|
||||
return radius * Math.cos(angle);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double y()
|
||||
{
|
||||
return radius * Math.sin(angle);
|
||||
}
|
||||
};
|
||||
|
||||
return coord;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Polar(" + angle + "rad, " + radius + ")";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(angle);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(radius);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (!(obj instanceof Polar)) return false;
|
||||
final Polar other = (Polar) obj;
|
||||
if (Double.doubleToLongBits(angle) != Double.doubleToLongBits(other.angle)) return false;
|
||||
if (Double.doubleToLongBits(radius) != Double.doubleToLongBits(other.radius)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package mightypork.utils.math;
|
||||
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
/**
|
||||
* Numeric range, able to generate random numbers and give min/max values.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class Range {
|
||||
|
||||
public static Range make(double low, double high)
|
||||
{
|
||||
return new Range(low, high);
|
||||
}
|
||||
|
||||
private double min = 0;
|
||||
private double max = 1;
|
||||
|
||||
|
||||
/**
|
||||
* Implicit range constructor 0-1
|
||||
*/
|
||||
public Range()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create new range
|
||||
*
|
||||
* @param min min number
|
||||
* @param max max number
|
||||
*/
|
||||
public Range(double min, double max)
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
norm();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create new range
|
||||
*
|
||||
* @param minmax min = max number
|
||||
*/
|
||||
public Range(double minmax)
|
||||
{
|
||||
this.min = minmax;
|
||||
this.max = minmax;
|
||||
}
|
||||
|
||||
|
||||
public static Range fromString(String string)
|
||||
{
|
||||
try {
|
||||
String s = string.trim();
|
||||
|
||||
// drop whitespace
|
||||
s = s.replaceAll("\\s", "");
|
||||
|
||||
// drop brackets
|
||||
s = s.replaceAll("[\\(\\[\\{\\)\\]\\}]", "");
|
||||
|
||||
// norm separators
|
||||
s = s.replaceAll("[:;]", "|").replace("..", "|");
|
||||
|
||||
// norm floating point
|
||||
s = s.replaceAll("[,]", ".");
|
||||
|
||||
// dash to pipe, if not a minus sign
|
||||
s = s.replaceAll("([0-9])\\s?[\\-]", "$1|");
|
||||
|
||||
final String[] parts = s.split("[|]");
|
||||
|
||||
if (parts.length >= 1) {
|
||||
|
||||
final double low = Double.parseDouble(parts[0].trim());
|
||||
|
||||
if (parts.length == 2) {
|
||||
final double high = Double.parseDouble(parts[1].trim());
|
||||
return Range.make(low, high);
|
||||
}
|
||||
|
||||
return Range.make(low, low);
|
||||
}
|
||||
} catch (final RuntimeException e) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("(%f : %f)", getMin(), getMax());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make sure min is <= max
|
||||
*/
|
||||
private void norm()
|
||||
{
|
||||
if (min > max) {
|
||||
final double t = min;
|
||||
min = max;
|
||||
max = t;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get random integer from range
|
||||
*
|
||||
* @return random int
|
||||
*/
|
||||
public int randInt()
|
||||
{
|
||||
return randInt(Calc.rand);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get random double from this range
|
||||
*
|
||||
* @return random double
|
||||
*/
|
||||
public double randDouble()
|
||||
{
|
||||
return randDouble(Calc.rand);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get random integer from range
|
||||
*
|
||||
* @param rand RNG
|
||||
* @return random int
|
||||
*/
|
||||
public int randInt(Random rand)
|
||||
{
|
||||
return Calc.randInt(rand, (int) Math.round(min), (int) Math.round(min));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get random double from this range
|
||||
*
|
||||
* @param rand RNG
|
||||
* @return random double
|
||||
*/
|
||||
public double randDouble(Random rand)
|
||||
{
|
||||
return min + rand.nextDouble() * (max - min);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get min
|
||||
*
|
||||
* @return min number
|
||||
*/
|
||||
public double getMin()
|
||||
{
|
||||
return min;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get max
|
||||
*
|
||||
* @return max number
|
||||
*/
|
||||
public double getMax()
|
||||
{
|
||||
return max;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set min
|
||||
*
|
||||
* @param min min value
|
||||
*/
|
||||
public void setMin(double min)
|
||||
{
|
||||
this.min = min;
|
||||
norm();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set max
|
||||
*
|
||||
* @param max max value
|
||||
*/
|
||||
public void setMax(double max)
|
||||
{
|
||||
this.max = max;
|
||||
norm();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get identical copy
|
||||
*
|
||||
* @return copy
|
||||
*/
|
||||
public Range copy()
|
||||
{
|
||||
return new Range(min, max);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set to value of other range
|
||||
*
|
||||
* @param other copied range
|
||||
*/
|
||||
public void setTo(Range other)
|
||||
{
|
||||
if (other == null) return;
|
||||
min = other.min;
|
||||
max = other.max;
|
||||
norm();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set to min-max values
|
||||
*
|
||||
* @param min min value
|
||||
* @param max max value
|
||||
*/
|
||||
public void setTo(double min, double max)
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
norm();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package mightypork.utils.math.algo;
|
||||
|
||||
|
||||
import mightypork.utils.annotations.FactoryMethod;
|
||||
import mightypork.utils.math.Calc;
|
||||
import mightypork.utils.math.constraints.vect.Vect;
|
||||
import mightypork.utils.math.constraints.vect.VectConst;
|
||||
|
||||
|
||||
/**
|
||||
* Very simple integer coordinate
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class Coord {
|
||||
|
||||
public int x;
|
||||
public int y;
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static Coord make(int x, int y)
|
||||
{
|
||||
return new Coord(x, y);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static Coord make(Coord other)
|
||||
{
|
||||
return new Coord(other);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static Coord zero()
|
||||
{
|
||||
return make(0, 0);
|
||||
}
|
||||
|
||||
|
||||
public Coord()
|
||||
{
|
||||
// for ion
|
||||
}
|
||||
|
||||
|
||||
public Coord(int x, int y)
|
||||
{
|
||||
super();
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
|
||||
public Coord(Coord other)
|
||||
{
|
||||
this.x = other.x;
|
||||
this.y = other.y;
|
||||
}
|
||||
|
||||
|
||||
public Coord add(int addX, int addY)
|
||||
{
|
||||
return new Coord(x + addX, y + addY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add other coord in a copy
|
||||
*
|
||||
* @param added
|
||||
* @return changed copy
|
||||
*/
|
||||
public Coord add(Coord added)
|
||||
{
|
||||
return add(added.x, added.y);
|
||||
}
|
||||
|
||||
|
||||
public Coord add(Move added)
|
||||
{
|
||||
return add(added.x(), added.y());
|
||||
}
|
||||
|
||||
|
||||
public Coord copy()
|
||||
{
|
||||
return make(this);
|
||||
}
|
||||
|
||||
|
||||
public void setTo(int x, int y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
|
||||
public void setTo(Coord pos)
|
||||
{
|
||||
setTo(pos.x, pos.y);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if coord is in a range (inclusive)
|
||||
*
|
||||
* @param x0 range min x
|
||||
* @param y0 range min y
|
||||
* @param x1 range max x
|
||||
* @param y1 range max y
|
||||
* @return is inside
|
||||
*/
|
||||
public boolean isInRange(int x0, int y0, int x1, int y1)
|
||||
{
|
||||
return !(x < x0 || x > x1 || y < y0 || y > y1);
|
||||
}
|
||||
|
||||
|
||||
public double dist(Coord coord)
|
||||
{
|
||||
return Calc.dist(x, y, coord.x, coord.y);
|
||||
}
|
||||
|
||||
|
||||
public VectConst toVect()
|
||||
{
|
||||
return Vect.make(x, y);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Coord(" + x + "," + y + ")";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + x;
|
||||
result = prime * result + y;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (!(obj instanceof Coord)) return false;
|
||||
final Coord other = (Coord) obj;
|
||||
if (x != other.x) return false;
|
||||
if (y != other.y) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static Coord fromVect(Vect vect)
|
||||
{
|
||||
return make((int) Math.floor(vect.x()), (int) Math.floor(vect.y()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package mightypork.utils.math.algo;
|
||||
|
||||
|
||||
/**
|
||||
* Path step.<br>
|
||||
* Must be binary in order to be saveable in lists.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class Move {
|
||||
|
||||
public static final Move NORTH = new Move(0, -1);
|
||||
public static final Move SOUTH = new Move(0, 1);
|
||||
public static final Move EAST = new Move(1, 0);
|
||||
public static final Move WEST = new Move(-1, 0);
|
||||
public static final Move NONE = new Move(0, 0);
|
||||
|
||||
|
||||
public static Move make(int x, int y)
|
||||
{
|
||||
x = x < 0 ? -1 : x > 0 ? 1 : 0;
|
||||
y = y < 0 ? -1 : y > 0 ? 1 : 0;
|
||||
|
||||
if (y == -1 && x == 0) return NORTH;
|
||||
if (y == 1 && x == 0) return SOUTH;
|
||||
if (x == -1 && y == 0) return WEST;
|
||||
if (x == 1 && y == 0) return EAST;
|
||||
if (x == 0 && y == 0) return NONE;
|
||||
|
||||
return new Move(x, y);
|
||||
}
|
||||
|
||||
private byte x;
|
||||
private byte y;
|
||||
|
||||
|
||||
public Move()
|
||||
{
|
||||
// for ion
|
||||
}
|
||||
|
||||
|
||||
public Move(int x, int y)
|
||||
{
|
||||
this.x = (byte) (x < 0 ? -1 : x > 0 ? 1 : 0);
|
||||
this.y = (byte) (y < 0 ? -1 : y > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
public int x()
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
public int y()
|
||||
{
|
||||
return y;
|
||||
}
|
||||
|
||||
|
||||
public Coord toCoord()
|
||||
{
|
||||
return Coord.make(x, y);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "(" + x + " ; " + y + ")";
|
||||
}
|
||||
|
||||
|
||||
protected void setTo(byte x, byte y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package mightypork.utils.math.algo;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import mightypork.utils.math.Calc;
|
||||
|
||||
|
||||
/**
|
||||
* Move lists, bit masks and other utilities
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class Moves {
|
||||
|
||||
public static final byte BIT_NW = (byte) 0b10000000;
|
||||
public static final byte BIT_N = (byte) 0b01000000;
|
||||
public static final byte BIT_NE = (byte) 0b00100000;
|
||||
public static final byte BIT_E = (byte) 0b00010000;
|
||||
public static final byte BIT_SE = (byte) 0b00001000;
|
||||
public static final byte BIT_S = (byte) 0b00000100;
|
||||
public static final byte BIT_SW = (byte) 0b00000010;
|
||||
public static final byte BIT_W = (byte) 0b00000001;
|
||||
|
||||
public static final byte BITS_CARDINAL = BIT_N | BIT_S | BIT_E | BIT_W;
|
||||
public static final byte BITS_DIAGONAL = BIT_NE | BIT_NW | BIT_SE | BIT_SW;
|
||||
|
||||
public static final byte BITS_NW_CORNER = BIT_W | BIT_NW | BIT_N;
|
||||
public static final byte BITS_NE_CORNER = BIT_E | BIT_NE | BIT_N;
|
||||
public static final byte BITS_SW_CORNER = BIT_W | BIT_SW | BIT_S;
|
||||
public static final byte BITS_SE_CORNER = BIT_E | BIT_SE | BIT_S;
|
||||
|
||||
public static final Move NW = Move.make(-1, -1);
|
||||
public static final Move N = Move.make(0, -1);
|
||||
public static final Move NE = Move.make(1, -1);
|
||||
public static final Move E = Move.make(1, 0);
|
||||
public static final Move SE = Move.make(1, 1);
|
||||
public static final Move S = Move.make(0, 1);
|
||||
public static final Move SW = Move.make(-1, 1);
|
||||
public static final Move W = Move.make(-1, 0);
|
||||
|
||||
//@formatter:off
|
||||
/** All sides, in the order of bits. */
|
||||
public final static List<Move> ALL_SIDES = Collections.unmodifiableList(Arrays.asList(
|
||||
NW,
|
||||
N,
|
||||
NE,
|
||||
E,
|
||||
SE,
|
||||
S,
|
||||
SW,
|
||||
W
|
||||
));
|
||||
|
||||
public final static List<Move> CARDINAL_SIDES = Collections.unmodifiableList(Arrays.asList(
|
||||
N,
|
||||
E,
|
||||
S,
|
||||
W
|
||||
));
|
||||
|
||||
//@formatter:on
|
||||
|
||||
/**
|
||||
* Get element from all sides
|
||||
*
|
||||
* @param i side index
|
||||
* @return the side coord
|
||||
*/
|
||||
public static Move getSide(int i)
|
||||
{
|
||||
return ALL_SIDES.get(i);
|
||||
}
|
||||
|
||||
|
||||
public static byte getBit(int i)
|
||||
{
|
||||
return (byte) (1 << (7 - i));
|
||||
}
|
||||
|
||||
|
||||
public static Move randomCardinal()
|
||||
{
|
||||
return Calc.pick(CARDINAL_SIDES);
|
||||
}
|
||||
|
||||
|
||||
public static Move randomCardinal(Random rand)
|
||||
{
|
||||
return Calc.pick(rand, CARDINAL_SIDES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package mightypork.utils.math.algo.floodfill;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import mightypork.utils.math.algo.Coord;
|
||||
import mightypork.utils.math.algo.Move;
|
||||
|
||||
|
||||
public abstract class FloodFill {
|
||||
|
||||
public abstract boolean canEnter(Coord pos);
|
||||
|
||||
|
||||
public abstract boolean canSpreadFrom(Coord pos);
|
||||
|
||||
|
||||
public abstract List<Move> getSpreadSides();
|
||||
|
||||
|
||||
/**
|
||||
* Get the max distance filled form start point. Use -1 for unlimited range.
|
||||
*
|
||||
* @return max distance
|
||||
*/
|
||||
public abstract double getMaxDistance();
|
||||
|
||||
|
||||
/**
|
||||
* @return true if start should be spread no matter what
|
||||
*/
|
||||
public abstract boolean forceSpreadStart();
|
||||
|
||||
|
||||
/**
|
||||
* Fill an area
|
||||
*
|
||||
* @param start start point
|
||||
* @param foundNodes collection to put filled coords in
|
||||
* @return true if fill was successful; false if max range was reached.
|
||||
*/
|
||||
public final boolean fill(Coord start, Collection<Coord> foundNodes)
|
||||
{
|
||||
final Queue<Coord> activeNodes = new LinkedList<>();
|
||||
|
||||
final double maxDist = getMaxDistance();
|
||||
|
||||
activeNodes.add(start);
|
||||
|
||||
boolean forceSpreadNext = forceSpreadStart();
|
||||
|
||||
boolean limitReached = false;
|
||||
|
||||
while (!activeNodes.isEmpty()) {
|
||||
final Coord current = activeNodes.poll();
|
||||
foundNodes.add(current);
|
||||
|
||||
if (!canSpreadFrom(current) && !forceSpreadNext) continue;
|
||||
|
||||
forceSpreadNext = false;
|
||||
|
||||
for (final Move spr : getSpreadSides()) {
|
||||
final Coord next = current.add(spr);
|
||||
if (activeNodes.contains(next) || foundNodes.contains(next)) continue;
|
||||
|
||||
if (next.dist(start) > maxDist) {
|
||||
limitReached = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (canEnter(next)) {
|
||||
activeNodes.add(next);
|
||||
} else {
|
||||
foundNodes.add(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !limitReached;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mightypork.utils.math.algo.pathfinding;
|
||||
|
||||
|
||||
import mightypork.utils.math.algo.Coord;
|
||||
|
||||
|
||||
public abstract class Heuristic {
|
||||
|
||||
/**
|
||||
* Get tile cost (estimate of how many tiles remain to the target)
|
||||
*
|
||||
* @param pos current pos
|
||||
* @param target target pos
|
||||
* @return estimated number of tiles
|
||||
*/
|
||||
public abstract double getCost(Coord pos, Coord target);
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package mightypork.utils.math.algo.pathfinding;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import mightypork.utils.math.algo.Coord;
|
||||
import mightypork.utils.math.algo.Move;
|
||||
import mightypork.utils.math.algo.pathfinding.heuristics.DiagonalHeuristic;
|
||||
import mightypork.utils.math.algo.pathfinding.heuristics.ManhattanHeuristic;
|
||||
|
||||
|
||||
/**
|
||||
* A* pathfinder
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public abstract class PathFinder {
|
||||
|
||||
private static final FComparator F_COMPARATOR = new FComparator();
|
||||
|
||||
public static final Heuristic CORNER_HEURISTIC = new ManhattanHeuristic();
|
||||
public static final Heuristic DIAGONAL_HEURISTIC = new DiagonalHeuristic();
|
||||
|
||||
private boolean ignoreStart;
|
||||
private boolean ignoreEnd;
|
||||
|
||||
|
||||
public List<Move> findPathRelative(Coord start, Coord end)
|
||||
{
|
||||
return findPathRelative(start, end, ignoreStart, ignoreEnd);
|
||||
}
|
||||
|
||||
|
||||
public List<Move> findPathRelative(Coord start, Coord end, boolean ignoreStart, boolean ignoreEnd)
|
||||
{
|
||||
final List<Coord> path = findPath(start, end, ignoreStart, ignoreEnd);
|
||||
|
||||
if (path == null) return null;
|
||||
|
||||
final List<Move> out = new ArrayList<>();
|
||||
|
||||
final Coord current = start.copy();
|
||||
for (final Coord c : path) {
|
||||
if (c.equals(current)) continue;
|
||||
out.add(Move.make(c.x - current.x, c.y - current.y));
|
||||
current.x = c.x;
|
||||
current.y = c.y;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
public List<Coord> findPath(Coord start, Coord end)
|
||||
{
|
||||
return findPath(start, end, ignoreStart, ignoreEnd);
|
||||
}
|
||||
|
||||
|
||||
public List<Coord> findPath(Coord start, Coord end, boolean ignoreStart, boolean ignoreEnd)
|
||||
{
|
||||
final LinkedList<Node> open = new LinkedList<>();
|
||||
final LinkedList<Node> closed = new LinkedList<>();
|
||||
|
||||
final Heuristic heuristic = getHeuristic();
|
||||
|
||||
// add first node
|
||||
{
|
||||
final Node n = new Node(start);
|
||||
n.h_cost = (int) (heuristic.getCost(start, end) * getMinCost());
|
||||
n.g_cost = 0;
|
||||
open.add(n);
|
||||
}
|
||||
|
||||
Node current = null;
|
||||
|
||||
while (true) {
|
||||
current = open.poll();
|
||||
|
||||
if (current == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
closed.add(current);
|
||||
|
||||
if (current.pos.equals(end)) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (final Move go : getWalkSides()) {
|
||||
|
||||
final Coord c = current.pos.add(go);
|
||||
if (!isAccessible(c) && !(c.equals(end) && ignoreEnd) && !(c.equals(start) && ignoreStart)) continue;
|
||||
final Node a = new Node(c);
|
||||
a.g_cost = current.g_cost + getCost(c, a.pos);
|
||||
a.h_cost = (int) (heuristic.getCost(a.pos, end) * getMinCost());
|
||||
a.parent = current;
|
||||
|
||||
|
||||
if (!closed.contains(a)) {
|
||||
|
||||
if (open.contains(a)) {
|
||||
|
||||
boolean needSort = false;
|
||||
|
||||
// find where it is
|
||||
for (final Node n : open) {
|
||||
if (n.pos.equals(a.pos)) { // found it
|
||||
if (n.g_cost > a.g_cost) {
|
||||
n.parent = current;
|
||||
n.g_cost = a.g_cost;
|
||||
needSort = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needSort) Collections.sort(open, F_COMPARATOR);
|
||||
|
||||
} else {
|
||||
open.add(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (current == null) {
|
||||
return null; // no path found
|
||||
}
|
||||
|
||||
final LinkedList<Coord> path = new LinkedList<>();
|
||||
|
||||
// extract path elements
|
||||
while (current != null) {
|
||||
path.addFirst(current.pos);
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static class Node {
|
||||
|
||||
Coord pos;
|
||||
int g_cost; // to get there
|
||||
int h_cost; // to target
|
||||
Node parent;
|
||||
|
||||
|
||||
public Node(Coord pos)
|
||||
{
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
|
||||
int fCost()
|
||||
{
|
||||
return g_cost + h_cost;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((pos == null) ? 0 : pos.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (!(obj instanceof Node)) return false;
|
||||
final Node other = (Node) obj;
|
||||
if (pos == null) {
|
||||
if (other.pos != null) return false;
|
||||
} else if (!pos.equals(other.pos)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "N " + pos + ", G =" + g_cost + ", H = " + h_cost;
|
||||
}
|
||||
}
|
||||
|
||||
private static class FComparator implements Comparator<Node> {
|
||||
|
||||
@Override
|
||||
public int compare(Node n1, Node n2)
|
||||
{
|
||||
return n1.fCost() - n2.fCost();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setIgnoreEnd(boolean ignoreEnd)
|
||||
{
|
||||
this.ignoreEnd = ignoreEnd;
|
||||
}
|
||||
|
||||
|
||||
public void setIgnoreStart(boolean ignoreStart)
|
||||
{
|
||||
this.ignoreStart = ignoreStart;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return used heuristic
|
||||
*/
|
||||
protected abstract Heuristic getHeuristic();
|
||||
|
||||
|
||||
protected abstract List<Move> getWalkSides();
|
||||
|
||||
|
||||
/**
|
||||
* @param pos tile pos
|
||||
* @return true if the tile is walkable
|
||||
*/
|
||||
public abstract boolean isAccessible(Coord pos);
|
||||
|
||||
|
||||
/**
|
||||
* Cost of walking onto a tile. It's useful to use ie. 10 for basic step.
|
||||
*
|
||||
* @param from last tile
|
||||
* @param to current tile
|
||||
* @return cost
|
||||
*/
|
||||
protected abstract int getCost(Coord from, Coord to);
|
||||
|
||||
|
||||
/**
|
||||
* @return lowest cost. Used to multiply heuristics.
|
||||
*/
|
||||
protected abstract int getMinCost();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package mightypork.utils.math.algo.pathfinding;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import mightypork.utils.math.algo.Coord;
|
||||
import mightypork.utils.math.algo.Move;
|
||||
|
||||
|
||||
/**
|
||||
* Pathfinder proxy. Can be used to override individual methods but keep the
|
||||
* rest as is.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class PathFinderProxy extends PathFinder {
|
||||
|
||||
private final PathFinder source;
|
||||
|
||||
|
||||
public PathFinderProxy(PathFinder other)
|
||||
{
|
||||
this.source = other;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isAccessible(Coord pos)
|
||||
{
|
||||
return source.isAccessible(pos);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getCost(Coord from, Coord to)
|
||||
{
|
||||
return source.getCost(from, to);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getMinCost()
|
||||
{
|
||||
return source.getMinCost();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Heuristic getHeuristic()
|
||||
{
|
||||
return source.getHeuristic();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected List<Move> getWalkSides()
|
||||
{
|
||||
return source.getWalkSides();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package mightypork.utils.math.algo.pathfinding.heuristics;
|
||||
|
||||
|
||||
import mightypork.utils.math.algo.Coord;
|
||||
import mightypork.utils.math.algo.pathfinding.Heuristic;
|
||||
|
||||
|
||||
public class DiagonalHeuristic extends Heuristic {
|
||||
|
||||
@Override
|
||||
public double getCost(Coord pos, Coord target)
|
||||
{
|
||||
return Math.sqrt(Math.pow(pos.x - target.x, 2) + Math.pow(pos.y - target.y, 2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package mightypork.utils.math.algo.pathfinding.heuristics;
|
||||
|
||||
|
||||
import mightypork.utils.math.algo.Coord;
|
||||
import mightypork.utils.math.algo.pathfinding.Heuristic;
|
||||
|
||||
|
||||
public class ManhattanHeuristic extends Heuristic {
|
||||
|
||||
@Override
|
||||
public double getCost(Coord pos, Coord target)
|
||||
{
|
||||
return Math.abs(target.x - pos.x) + Math.abs(target.y - pos.y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package mightypork.utils.math.angles;
|
||||
|
||||
|
||||
/**
|
||||
* Common angles functionality
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
class Angles {
|
||||
|
||||
/**
|
||||
* Delta of two angles (positive or negative - positive is CCW)
|
||||
*
|
||||
* @param alpha first angle
|
||||
* @param beta second angle
|
||||
* @param fullAngle value of full angle
|
||||
* @return delta of the two angles
|
||||
*/
|
||||
public static double delta(double alpha, double beta, double fullAngle)
|
||||
{
|
||||
while (Math.abs(alpha - beta) > fullAngle / 2D) {
|
||||
alpha = norm(alpha + fullAngle / 2D, fullAngle);
|
||||
beta = norm(beta + fullAngle / 2D, fullAngle);
|
||||
}
|
||||
|
||||
return beta - alpha;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Difference of two angles (same as delta, but always positive)
|
||||
*
|
||||
* @param alpha first angle
|
||||
* @param beta second angle
|
||||
* @param fullAngle value of full angle
|
||||
* @return delta of the two angles
|
||||
*/
|
||||
public static double diff(double alpha, double beta, double fullAngle)
|
||||
{
|
||||
return Math.abs(delta(alpha, beta, fullAngle));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalize angle to 0-full range
|
||||
*
|
||||
* @param angle angle
|
||||
* @param fullAngle full angle
|
||||
* @return angle normalized
|
||||
*/
|
||||
public static double norm(double angle, double fullAngle)
|
||||
{
|
||||
while (angle < 0)
|
||||
angle += fullAngle;
|
||||
while (angle > fullAngle)
|
||||
angle -= fullAngle;
|
||||
return angle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package mightypork.utils.math.angles;
|
||||
|
||||
|
||||
/**
|
||||
* Angle calculations for degrees.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class Deg {
|
||||
|
||||
/** 180° in degrees */
|
||||
public static final double a180 = 180;
|
||||
/** 270° in degrees */
|
||||
public static final double a270 = 270;
|
||||
/** 360° in degrees */
|
||||
public static final double a360 = 360;
|
||||
/** 45° in degrees */
|
||||
public static final double a45 = 45;
|
||||
/** 90° in degrees */
|
||||
public static final double a90 = 90;
|
||||
|
||||
|
||||
/**
|
||||
* Subtract two angles alpha - beta
|
||||
*
|
||||
* @param alpha first angle
|
||||
* @param beta second angle
|
||||
* @return (alpha - beta) in degrees
|
||||
*/
|
||||
public static double delta(double alpha, double beta)
|
||||
{
|
||||
return Angles.delta(alpha, beta, a360);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Difference of two angles (absolute value of delta)
|
||||
*
|
||||
* @param alpha first angle
|
||||
* @param beta second angle
|
||||
* @return difference in radians
|
||||
*/
|
||||
public static double diff(double alpha, double beta)
|
||||
{
|
||||
return Angles.diff(alpha, beta, a360);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cosinus in degrees
|
||||
*
|
||||
* @param deg angle in degrees
|
||||
* @return cosinus
|
||||
*/
|
||||
public static double cos(double deg)
|
||||
{
|
||||
return Math.cos(toRad(deg));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sinus in degrees
|
||||
*
|
||||
* @param deg angle in degrees
|
||||
* @return sinus
|
||||
*/
|
||||
public static double sin(double deg)
|
||||
{
|
||||
return Math.sin(toRad(deg));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tangents in degrees
|
||||
*
|
||||
* @param deg angle in degrees
|
||||
* @return tangents
|
||||
*/
|
||||
public static double tan(double deg)
|
||||
{
|
||||
return Math.tan(toRad(deg));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Angle normalized to 0-360 range
|
||||
*
|
||||
* @param angle angle to normalize
|
||||
* @return normalized angle
|
||||
*/
|
||||
public static double norm(double angle)
|
||||
{
|
||||
return Angles.norm(angle, a360);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert to radians
|
||||
*
|
||||
* @param deg degrees
|
||||
* @return radians
|
||||
*/
|
||||
public static double toRad(double deg)
|
||||
{
|
||||
return Math.toRadians(deg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Round angle to 0,45,90,135...
|
||||
*
|
||||
* @param deg angle in deg. to round
|
||||
* @param increment rounding increment (45 - round to 0,45,90...)
|
||||
* @return rounded
|
||||
*/
|
||||
public static int roundToIncrement(double deg, double increment)
|
||||
{
|
||||
final double half = increment / 2d;
|
||||
deg += half;
|
||||
deg = norm(deg);
|
||||
final int times = (int) Math.floor(deg / increment);
|
||||
double a = times * increment;
|
||||
if (a == 360) a = 0;
|
||||
return (int) Math.round(a);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Round angle to 0,15,30,45,60,75,90...
|
||||
*
|
||||
* @param deg angle in deg to round
|
||||
* @return rounded
|
||||
*/
|
||||
public static int roundTo15(double deg)
|
||||
{
|
||||
return roundToIncrement(deg, 15);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Round angle to 0,45,90,135...
|
||||
*
|
||||
* @param deg angle in deg. to round
|
||||
* @return rounded
|
||||
*/
|
||||
public static int roundTo45(double deg)
|
||||
{
|
||||
return roundToIncrement(deg, 45);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Round angle to 0,90,180,270
|
||||
*
|
||||
* @param deg angle in deg. to round
|
||||
* @return rounded
|
||||
*/
|
||||
public static int roundTo90(double deg)
|
||||
{
|
||||
return roundToIncrement(deg, 90);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package mightypork.utils.math.angles;
|
||||
|
||||
|
||||
/**
|
||||
* Angle calculations for radians.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class Rad {
|
||||
|
||||
/** 180° in radians */
|
||||
public static final double a180 = Math.PI;
|
||||
/** 270° in radians */
|
||||
public static final double a270 = Math.PI * 1.5D;
|
||||
/** 360° in radians */
|
||||
public static final double a360 = Math.PI * 2D;
|
||||
/** 45° in radians */
|
||||
public static final double a45 = Math.PI / 4D;
|
||||
/** 90° in radians */
|
||||
public static final double a90 = Math.PI / 2D;
|
||||
|
||||
|
||||
/**
|
||||
* Subtract two angles alpha - beta
|
||||
*
|
||||
* @param alpha first angle
|
||||
* @param beta second angle
|
||||
* @return (alpha - beta) in radians
|
||||
*/
|
||||
public static double delta(double alpha, double beta)
|
||||
{
|
||||
return Angles.delta(alpha, beta, a360);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Difference of two angles (absolute value of delta)
|
||||
*
|
||||
* @param alpha first angle
|
||||
* @param beta second angle
|
||||
* @return difference in radians
|
||||
*/
|
||||
public static double diff(double alpha, double beta)
|
||||
{
|
||||
return Angles.delta(alpha, beta, a360);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cos
|
||||
*
|
||||
* @param rad angle in rads
|
||||
* @return cos
|
||||
*/
|
||||
public static double cos(double rad)
|
||||
{
|
||||
return Math.cos(rad);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sin
|
||||
*
|
||||
* @param rad angle in rads
|
||||
* @return sin
|
||||
*/
|
||||
public static double sin(double rad)
|
||||
{
|
||||
return Math.sin(rad);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tan
|
||||
*
|
||||
* @param rad angle in rads
|
||||
* @return tan
|
||||
*/
|
||||
public static double tan(double rad)
|
||||
{
|
||||
return Math.tan(rad);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Angle normalized to 0-2*PI range
|
||||
*
|
||||
* @param angle angle to normalize
|
||||
* @return normalized angle
|
||||
*/
|
||||
public static double norm(double angle)
|
||||
{
|
||||
return Angles.norm(angle, a360);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert to degrees
|
||||
*
|
||||
* @param rad radians
|
||||
* @return degrees
|
||||
*/
|
||||
public static double toDeg(double rad)
|
||||
{
|
||||
return Math.toDegrees(rad);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package mightypork.utils.math.animation;
|
||||
|
||||
|
||||
import mightypork.utils.annotations.DefaultImpl;
|
||||
import mightypork.utils.interfaces.Pauseable;
|
||||
import mightypork.utils.interfaces.Updateable;
|
||||
import mightypork.utils.math.Calc;
|
||||
import mightypork.utils.math.constraints.num.Num;
|
||||
import mightypork.utils.math.constraints.num.NumBound;
|
||||
|
||||
|
||||
public abstract class Animator implements NumBound, Updateable, Pauseable {
|
||||
|
||||
private final NumAnimated numAnim;
|
||||
private final Num animatorValue;
|
||||
private final double highValue;
|
||||
private final double lowValue;
|
||||
|
||||
|
||||
public Animator(double period)
|
||||
{
|
||||
this(0, 1, period, Easing.LINEAR);
|
||||
}
|
||||
|
||||
|
||||
public Animator(double start, double end, double period)
|
||||
{
|
||||
this(start, end, period, Easing.LINEAR);
|
||||
}
|
||||
|
||||
|
||||
public Animator(double period, Easing easing)
|
||||
{
|
||||
this(0, 1, period, easing);
|
||||
}
|
||||
|
||||
|
||||
public Animator(double start, double end, double period, Easing easing)
|
||||
{
|
||||
numAnim = new NumAnimated(0, easing);
|
||||
numAnim.setDefaultDuration(period);
|
||||
|
||||
this.lowValue = start;
|
||||
this.highValue = end;
|
||||
|
||||
this.animatorValue = numAnim.mul(end - start).add(start);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void pause()
|
||||
{
|
||||
numAnim.pause();
|
||||
}
|
||||
|
||||
|
||||
public void start()
|
||||
{
|
||||
resume();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void resume()
|
||||
{
|
||||
numAnim.resume();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isPaused()
|
||||
{
|
||||
return numAnim.isPaused();
|
||||
}
|
||||
|
||||
|
||||
public void reset()
|
||||
{
|
||||
numAnim.reset();
|
||||
}
|
||||
|
||||
|
||||
public void restart()
|
||||
{
|
||||
reset();
|
||||
resume();
|
||||
}
|
||||
|
||||
|
||||
public void setDuration(double secs)
|
||||
{
|
||||
numAnim.setDefaultDuration(secs);
|
||||
}
|
||||
|
||||
|
||||
public double getDuration()
|
||||
{
|
||||
return numAnim.getDefaultDuration();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Num getNum()
|
||||
{
|
||||
return animatorValue;
|
||||
}
|
||||
|
||||
|
||||
public double getValue()
|
||||
{
|
||||
return animatorValue.value();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void update(double delta)
|
||||
{
|
||||
numAnim.update(delta);
|
||||
if (numAnim.isFinished()) nextCycle(numAnim);
|
||||
}
|
||||
|
||||
|
||||
@DefaultImpl
|
||||
protected abstract void nextCycle(NumAnimated anim);
|
||||
|
||||
|
||||
public void setProgress(double value)
|
||||
{
|
||||
final double target = numAnim.getEnd();
|
||||
numAnim.setTo(Calc.clamp(value, lowValue, highValue));
|
||||
numAnim.animate((target < value ? highValue : lowValue), target, numAnim.getDefaultDuration());
|
||||
}
|
||||
|
||||
|
||||
public double getProgress()
|
||||
{
|
||||
return numAnim.value();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package mightypork.utils.math.animation;
|
||||
|
||||
|
||||
/**
|
||||
* Animator that upon reaching max, animates back down and then up again
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class AnimatorBounce extends Animator {
|
||||
|
||||
private boolean wasUp = false;
|
||||
|
||||
|
||||
public AnimatorBounce(double start, double end, double period, Easing easing)
|
||||
{
|
||||
super(start, end, period, easing);
|
||||
}
|
||||
|
||||
|
||||
public AnimatorBounce(double start, double end, double period)
|
||||
{
|
||||
super(start, end, period);
|
||||
}
|
||||
|
||||
|
||||
public AnimatorBounce(double period, Easing easing)
|
||||
{
|
||||
super(period, easing);
|
||||
}
|
||||
|
||||
|
||||
public AnimatorBounce(double period)
|
||||
{
|
||||
super(period);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void nextCycle(NumAnimated anim)
|
||||
{
|
||||
if (wasUp) {
|
||||
anim.fadeOut();
|
||||
} else {
|
||||
anim.fadeIn();
|
||||
}
|
||||
|
||||
wasUp = !wasUp;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package mightypork.utils.math.animation;
|
||||
|
||||
|
||||
/**
|
||||
* Animator that upon reaching top, jumps straight to zero and continues another
|
||||
* cycle.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class AnimatorRewind extends Animator {
|
||||
|
||||
public AnimatorRewind(double start, double end, double period, Easing easing)
|
||||
{
|
||||
super(start, end, period, easing);
|
||||
}
|
||||
|
||||
|
||||
public AnimatorRewind(double start, double end, double period)
|
||||
{
|
||||
super(start, end, period);
|
||||
}
|
||||
|
||||
|
||||
public AnimatorRewind(double period, Easing easing)
|
||||
{
|
||||
super(period, easing);
|
||||
}
|
||||
|
||||
|
||||
public AnimatorRewind(double period)
|
||||
{
|
||||
super(period);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void nextCycle(NumAnimated anim)
|
||||
{
|
||||
anim.reset();
|
||||
anim.fadeIn();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
package mightypork.utils.math.animation;
|
||||
|
||||
|
||||
/**
|
||||
* EasingFunction function.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public abstract class Easing {
|
||||
|
||||
/**
|
||||
* Get value at time t.
|
||||
*
|
||||
* @param t time parameter (t = 1..1)
|
||||
* @return value at given t (0..1, can exceed if needed)
|
||||
*/
|
||||
public abstract double get(double t);
|
||||
|
||||
|
||||
/**
|
||||
* Reverse an easing (factory method)
|
||||
*
|
||||
* @param original original easing
|
||||
* @return reversed easing
|
||||
*/
|
||||
public static Easing reverse(Easing original)
|
||||
{
|
||||
return new Reverse(original);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Combine two easings (factory method)
|
||||
*
|
||||
* @param in initial easing
|
||||
* @param out terminal easing
|
||||
* @return product
|
||||
*/
|
||||
public static Easing combine(Easing in, Easing out)
|
||||
{
|
||||
return new Composite(in, out);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create "bilinear" easing - compose of straight and reverse. (factory
|
||||
* method)
|
||||
*
|
||||
* @param in initial easing
|
||||
* @return product
|
||||
*/
|
||||
public static Easing inOut(Easing in)
|
||||
{
|
||||
return combine(in, reverse(in));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse EasingFunction
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
private static class Reverse extends Easing {
|
||||
|
||||
private final Easing ea;
|
||||
|
||||
|
||||
/**
|
||||
* @param in Easing to reverse
|
||||
*/
|
||||
public Reverse(Easing in)
|
||||
{
|
||||
this.ea = in;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
return 1 - ea.get(1 - t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composite EasingFunction (0-0.5 EasingFunction A, 0.5-1 EasingFunction B)
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
private static class Composite extends Easing {
|
||||
|
||||
private final Easing in;
|
||||
private final Easing out;
|
||||
|
||||
|
||||
/**
|
||||
* Create a composite EasingFunction
|
||||
*
|
||||
* @param in initial EasingFunction
|
||||
* @param out terminal EasingFunction
|
||||
*/
|
||||
public Composite(Easing in, Easing out)
|
||||
{
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
if (t < 0.5) return in.get(2 * t) * 0.5;
|
||||
return 0.5 + out.get(2 * t - 1) * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/** No easing; At t=0.5 goes high. */
|
||||
public static final Easing NONE = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
return (t < 0.5 ? 0 : 1);
|
||||
}
|
||||
};
|
||||
|
||||
/** Linear (y=t) easing */
|
||||
public static final Easing LINEAR = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
/** Quadratic (y=t^2) easing in */
|
||||
public static final Easing QUADRATIC_IN = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
return t * t;
|
||||
}
|
||||
};
|
||||
|
||||
/** Quadratic (y=t^2) easing out */
|
||||
public static final Easing QUADRATIC_OUT = reverse(QUADRATIC_IN);
|
||||
|
||||
/** Quadratic (y=t^2) easing both */
|
||||
public static final Easing QUADRATIC_BOTH = inOut(QUADRATIC_IN);
|
||||
|
||||
/** Cubic (y=t^3) easing in */
|
||||
public static final Easing CUBIC_IN = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
return t * t * t;
|
||||
}
|
||||
};
|
||||
|
||||
/** Cubic (y=t^3) easing out */
|
||||
public static final Easing CUBIC_OUT = reverse(CUBIC_IN);
|
||||
|
||||
/** Cubic (y=t^3) easing both */
|
||||
public static final Easing CUBIC_BOTH = inOut(CUBIC_IN);
|
||||
|
||||
/** Quartic (y=t^4) easing in */
|
||||
public static final Easing QUARTIC_IN = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
return t * t * t * t;
|
||||
}
|
||||
};
|
||||
|
||||
/** Quartic (y=t^4) easing out */
|
||||
public static final Easing QUARTIC_OUT = reverse(QUADRATIC_IN);
|
||||
|
||||
/** Quartic (y=t^4) easing both */
|
||||
public static final Easing QUARTIC_BOTH = inOut(QUADRATIC_IN);
|
||||
|
||||
/** Quintic (y=t^5) easing in */
|
||||
public static final Easing QUINTIC_IN = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
return t * t * t * t * t;
|
||||
}
|
||||
};
|
||||
|
||||
/** Quintic (y=t^5) easing out */
|
||||
public static final Easing QUINTIC_OUT = reverse(QUINTIC_IN);
|
||||
|
||||
/** Quintic (y=t^5) easing both */
|
||||
public static final Easing QUINTIC_BOTH = inOut(QUINTIC_IN);
|
||||
|
||||
/** Sine easing in */
|
||||
public static final Easing SINE_IN = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
return 1 - Math.cos(t * (Math.PI / 2));
|
||||
}
|
||||
};
|
||||
|
||||
/** Sine easing out */
|
||||
public static final Easing SINE_OUT = reverse(SINE_IN);
|
||||
|
||||
/** Sine easing both */
|
||||
public static final Easing SINE_BOTH = inOut(SINE_IN);
|
||||
|
||||
/** Exponential easing in */
|
||||
public static final Easing EXPO_IN = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
return Math.pow(2, 10 * (t - 1));
|
||||
}
|
||||
};
|
||||
|
||||
/** Exponential easing out */
|
||||
public static final Easing EXPO_OUT = reverse(EXPO_IN);
|
||||
|
||||
/** Exponential easing both */
|
||||
public static final Easing EXPO_BOTH = inOut(EXPO_IN);
|
||||
|
||||
/** Circular easing in */
|
||||
public static final Easing CIRC_IN = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
return 1 - Math.sqrt(1 - t * t);
|
||||
}
|
||||
};
|
||||
|
||||
/** Circular easing out */
|
||||
public static final Easing CIRC_OUT = reverse(CIRC_IN);
|
||||
|
||||
/** Circular easing both */
|
||||
public static final Easing CIRC_BOTH = inOut(CIRC_IN);
|
||||
|
||||
/** Bounce easing in */
|
||||
public static final Easing BOUNCE_OUT = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
if (t < (1 / 2.75f)) {
|
||||
return (7.5625f * t * t);
|
||||
|
||||
} else if (t < (2 / 2.75f)) {
|
||||
t -= (1.5f / 2.75f);
|
||||
return (7.5625f * t * t + 0.75f);
|
||||
|
||||
} else if (t < (2.5 / 2.75)) {
|
||||
t -= (2.25f / 2.75f);
|
||||
return (7.5625f * t * t + 0.9375f);
|
||||
|
||||
} else {
|
||||
t -= (2.625f / 2.75f);
|
||||
return (7.5625f * t * t + 0.984375f);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Bounce easing out */
|
||||
public static final Easing BOUNCE_IN = reverse(BOUNCE_OUT);
|
||||
|
||||
/** Bounce easing both */
|
||||
public static final Easing BOUNCE_BOTH = inOut(BOUNCE_IN);
|
||||
|
||||
/** Back easing in */
|
||||
public static final Easing BACK_IN = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
final float s = 1.70158f;
|
||||
return t * t * ((s + 1) * t - s);
|
||||
}
|
||||
};
|
||||
|
||||
/** Back easing out */
|
||||
public static final Easing BACK_OUT = reverse(BACK_IN);
|
||||
|
||||
/** Back easing both */
|
||||
public static final Easing BACK_BOTH = inOut(BACK_IN);
|
||||
|
||||
/** Elastic easing in */
|
||||
public static final Easing ELASTIC_IN = new Easing() {
|
||||
|
||||
@Override
|
||||
public double get(double t)
|
||||
{
|
||||
if (t == 0) return 0;
|
||||
if (t == 1) return 1;
|
||||
|
||||
final double p = .3f;
|
||||
final double s = p / 4;
|
||||
return -(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
|
||||
}
|
||||
};
|
||||
|
||||
/** Elastic easing out */
|
||||
public static final Easing ELASTIC_OUT = reverse(ELASTIC_IN);
|
||||
|
||||
/** Elastic easing both */
|
||||
public static final Easing ELASTIC_BOTH = inOut(ELASTIC_IN);
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
package mightypork.utils.math.animation;
|
||||
|
||||
|
||||
import mightypork.utils.interfaces.Pauseable;
|
||||
import mightypork.utils.interfaces.Updateable;
|
||||
import mightypork.utils.math.Calc;
|
||||
import mightypork.utils.math.constraints.num.var.NumMutable;
|
||||
|
||||
|
||||
/**
|
||||
* Double which supports delta timing.<br>
|
||||
* When both in and out easings are set differently, then they'll be used for
|
||||
* fade-in and fade-out respectively. Otherwise both use the same.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class NumAnimated extends NumMutable implements Updateable, Pauseable {
|
||||
|
||||
/** target double */
|
||||
protected double to = 0;
|
||||
|
||||
/** last tick double */
|
||||
protected double from = 0;
|
||||
|
||||
/** how long the transition should last */
|
||||
protected double duration = 0;
|
||||
|
||||
/** current anim time */
|
||||
protected double elapsedTime = 0;
|
||||
|
||||
/** True if this animator is paused */
|
||||
protected boolean paused = false;
|
||||
|
||||
/** Easing fn */
|
||||
protected Easing easingCurrent = Easing.LINEAR;
|
||||
protected Easing easingOut = Easing.LINEAR;
|
||||
protected Easing easingIn = Easing.LINEAR;
|
||||
|
||||
/** Default duration (seconds) */
|
||||
private double defaultDuration = 1;
|
||||
|
||||
|
||||
/**
|
||||
* With linear easing
|
||||
*
|
||||
* @param value initial value
|
||||
*/
|
||||
public NumAnimated(double value)
|
||||
{
|
||||
setTo(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create animator with easing
|
||||
*
|
||||
* @param value initial value
|
||||
* @param easing easing function
|
||||
*/
|
||||
public NumAnimated(double value, Easing easing)
|
||||
{
|
||||
this(value);
|
||||
setEasing(easing);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create animator with easing
|
||||
*
|
||||
* @param value initial value
|
||||
* @param easingIn easing function (fade in)
|
||||
* @param easingOut easing function (fade out)
|
||||
*/
|
||||
public NumAnimated(double value, Easing easingIn, Easing easingOut)
|
||||
{
|
||||
this(value);
|
||||
setEasing(easingIn, easingOut);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create animator with easing
|
||||
*
|
||||
* @param value initial value
|
||||
* @param easing easing function
|
||||
* @param defaultDuration default fade duration
|
||||
*/
|
||||
public NumAnimated(double value, Easing easing, double defaultDuration)
|
||||
{
|
||||
this(value);
|
||||
setEasing(easing);
|
||||
setDefaultDuration(defaultDuration);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create animator with easing
|
||||
*
|
||||
* @param value initial value
|
||||
* @param easingIn easing function (fade in)
|
||||
* @param easingOut easing function (fade out)
|
||||
* @param defaultDuration default fade duration
|
||||
*/
|
||||
public NumAnimated(double value, Easing easingIn, Easing easingOut, double defaultDuration)
|
||||
{
|
||||
this(value);
|
||||
setEasing(easingIn, easingOut);
|
||||
setDefaultDuration(defaultDuration);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create as copy of another
|
||||
*
|
||||
* @param other other animator
|
||||
*/
|
||||
public NumAnimated(NumAnimated other)
|
||||
{
|
||||
setTo(other);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param easing easing function
|
||||
*/
|
||||
public void setEasing(Easing easing)
|
||||
{
|
||||
this.easingCurrent = this.easingIn = this.easingOut = easing;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param easingIn easing for fade in
|
||||
* @param easingOut easing for fade out
|
||||
*/
|
||||
public void setEasing(Easing easingIn, Easing easingOut)
|
||||
{
|
||||
this.easingIn = easingIn;
|
||||
this.easingOut = easingOut;
|
||||
this.easingCurrent = easingIn;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get start value
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
public double getStart()
|
||||
{
|
||||
return from;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get end value
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
public double getEnd()
|
||||
{
|
||||
return to;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return current animation duration (seconds)
|
||||
*/
|
||||
public double getDuration()
|
||||
{
|
||||
return duration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return elapsed time in current animation (seconds)
|
||||
*/
|
||||
public double getElapsed()
|
||||
{
|
||||
return elapsedTime;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return default animation duration (seconds)
|
||||
*/
|
||||
public double getDefaultDuration()
|
||||
{
|
||||
return defaultDuration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param defaultDuration default animation duration (seconds)
|
||||
*/
|
||||
public void setDefaultDuration(double defaultDuration)
|
||||
{
|
||||
this.defaultDuration = defaultDuration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get value at delta time
|
||||
*
|
||||
* @return the value
|
||||
*/
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
if (duration == 0) return to;
|
||||
return Calc.interpolate(from, to, (elapsedTime / duration), easingCurrent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get how much of the animation is already finished
|
||||
*
|
||||
* @return completion ratio (0 to 1)
|
||||
*/
|
||||
public double getProgress()
|
||||
{
|
||||
if (duration == 0) return 1;
|
||||
return elapsedTime / duration;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void update(double delta)
|
||||
{
|
||||
if (paused || isFinished()) return;
|
||||
|
||||
elapsedTime = Calc.clamp(elapsedTime + delta, 0, duration);
|
||||
if (isFinished()) {
|
||||
duration = 0;
|
||||
elapsedTime = 0;
|
||||
from = to;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get if animation is finished
|
||||
*
|
||||
* @return is finished
|
||||
*/
|
||||
public boolean isFinished()
|
||||
{
|
||||
return duration == 0 || elapsedTime >= duration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set to a value (without animation)
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
@Override
|
||||
public void setTo(double value)
|
||||
{
|
||||
from = to = value;
|
||||
elapsedTime = 0;
|
||||
duration = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy other
|
||||
*
|
||||
* @param other
|
||||
*/
|
||||
public void setTo(NumAnimated other)
|
||||
{
|
||||
this.from = other.from;
|
||||
this.to = other.to;
|
||||
this.duration = other.duration;
|
||||
this.elapsedTime = other.elapsedTime;
|
||||
this.paused = other.paused;
|
||||
this.easingCurrent = other.easingCurrent;
|
||||
this.easingIn = other.easingIn;
|
||||
this.easingOut = other.easingOut;
|
||||
this.defaultDuration = other.defaultDuration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Animate between two states, start from current value (if it's in between)
|
||||
*
|
||||
* @param from start value
|
||||
* @param to target state
|
||||
* @param time animation time (secs)
|
||||
*/
|
||||
public void animate(double from, double to, double time)
|
||||
{
|
||||
final double current = value();
|
||||
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
|
||||
final double progress = getProgressFromValue(current);
|
||||
|
||||
this.from = (progress > 0 ? current : from);
|
||||
|
||||
this.duration = time * (1 - progress);
|
||||
this.elapsedTime = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get progress already elapsed based on current value.<br>
|
||||
* Used to resume animation from current point in fading etc.
|
||||
*
|
||||
* @param value current value
|
||||
* @return progress ratio 0-1
|
||||
*/
|
||||
protected double getProgressFromValue(double value)
|
||||
{
|
||||
double p = 0;
|
||||
|
||||
if (from == to) return 0;
|
||||
|
||||
if (value >= from && value <= to) { // up
|
||||
p = ((value - from) / (to - from));
|
||||
} else if (value >= to && value <= from) { // down
|
||||
p = ((from - value) / (from - to));
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Animate to a value from current value
|
||||
*
|
||||
* @param to target state
|
||||
* @param duration animation duration (speeds)
|
||||
*/
|
||||
public void animate(double to, double duration)
|
||||
{
|
||||
this.from = value();
|
||||
this.to = to;
|
||||
this.duration = duration;
|
||||
this.elapsedTime = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Animate 0 to 1
|
||||
*
|
||||
* @param time animation time (secs)
|
||||
*/
|
||||
public void fadeIn(double time)
|
||||
{
|
||||
easingCurrent = easingIn;
|
||||
animate(0, 1, time);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Animate 1 to 0
|
||||
*
|
||||
* @param time animation time (secs)
|
||||
*/
|
||||
public void fadeOut(double time)
|
||||
{
|
||||
easingCurrent = easingOut;
|
||||
animate(1, 0, time);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Animate 0 to 1 with default duration
|
||||
*/
|
||||
public void fadeIn()
|
||||
{
|
||||
easingCurrent = easingIn;
|
||||
animate(0, 1, defaultDuration);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Animate 1 to 0 with default duration
|
||||
*/
|
||||
public void fadeOut()
|
||||
{
|
||||
easingCurrent = easingOut;
|
||||
animate(1, 0, defaultDuration);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make a copy
|
||||
*
|
||||
* @return copy
|
||||
*/
|
||||
@Override
|
||||
public NumAnimated clone()
|
||||
{
|
||||
return new NumAnimated(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Animation(" + from + " -> " + to + ", t=" + duration + "s, elapsed=" + elapsedTime + "s)";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set to zero and stop animation
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
from = to = 0;
|
||||
elapsedTime = 0;
|
||||
duration = 0;
|
||||
paused = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stop animation, keep current value
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
from = to = value();
|
||||
elapsedTime = 0;
|
||||
duration = 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void pause()
|
||||
{
|
||||
paused = true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void resume()
|
||||
{
|
||||
paused = false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isPaused()
|
||||
{
|
||||
return paused;
|
||||
}
|
||||
|
||||
|
||||
public boolean isInProgress()
|
||||
{
|
||||
return !isFinished() && !isPaused();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package mightypork.utils.math.animation;
|
||||
|
||||
|
||||
import mightypork.utils.math.Calc;
|
||||
import mightypork.utils.math.angles.Deg;
|
||||
|
||||
|
||||
/**
|
||||
* Degree animator
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class NumAnimatedDeg extends NumAnimated {
|
||||
|
||||
public NumAnimatedDeg(NumAnimated other)
|
||||
{
|
||||
super(other);
|
||||
}
|
||||
|
||||
|
||||
public NumAnimatedDeg(double value)
|
||||
{
|
||||
super(value);
|
||||
}
|
||||
|
||||
|
||||
public NumAnimatedDeg(double value, Easing easing)
|
||||
{
|
||||
super(value, easing);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
if (duration == 0) return Deg.norm(to);
|
||||
return Calc.interpolateDeg(from, to, (elapsedTime / duration), easingCurrent);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected double getProgressFromValue(double value)
|
||||
{
|
||||
final double whole = Deg.diff(from, to);
|
||||
if (Deg.diff(value, from) < whole && Deg.diff(value, to) < whole) {
|
||||
final double partial = Deg.diff(from, value);
|
||||
return partial / whole;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package mightypork.utils.math.animation;
|
||||
|
||||
|
||||
import mightypork.utils.math.Calc;
|
||||
import mightypork.utils.math.angles.Rad;
|
||||
|
||||
|
||||
/**
|
||||
* Radians animator
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class NumAnimatedRad extends NumAnimated {
|
||||
|
||||
public NumAnimatedRad(NumAnimated other)
|
||||
{
|
||||
super(other);
|
||||
}
|
||||
|
||||
|
||||
public NumAnimatedRad(double value)
|
||||
{
|
||||
super(value);
|
||||
}
|
||||
|
||||
|
||||
public NumAnimatedRad(double value, Easing easing)
|
||||
{
|
||||
super(value, easing);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
if (duration == 0) return Rad.norm(to);
|
||||
return Calc.interpolateRad(from, to, (elapsedTime / duration), easingCurrent);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected double getProgressFromValue(double value)
|
||||
{
|
||||
final double whole = Rad.diff(from, to);
|
||||
if (Rad.diff(value, from) < whole && Rad.diff(value, to) < whole) {
|
||||
final double partial = Rad.diff(from, value);
|
||||
return partial / whole;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package mightypork.utils.math.animation;
|
||||
|
||||
|
||||
import mightypork.utils.annotations.FactoryMethod;
|
||||
import mightypork.utils.interfaces.Pauseable;
|
||||
import mightypork.utils.interfaces.Updateable;
|
||||
import mightypork.utils.math.constraints.vect.Vect;
|
||||
import mightypork.utils.math.constraints.vect.var.VectMutable;
|
||||
|
||||
|
||||
/**
|
||||
* 3D coordinated with support for transitions, mutable.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class VectAnimated extends VectMutable implements Pauseable, Updateable {
|
||||
|
||||
private final NumAnimated x, y, z;
|
||||
private double defaultDuration = 0.5;
|
||||
|
||||
|
||||
/**
|
||||
* Create an animated vector; This way different easing / settings can be
|
||||
* specified for each coordinate.
|
||||
*
|
||||
* @param x x animator
|
||||
* @param y y animator
|
||||
* @param z z animator
|
||||
*/
|
||||
public VectAnimated(NumAnimated x, NumAnimated y, NumAnimated z)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an animated vector
|
||||
*
|
||||
* @param start initial positioon
|
||||
* @param easing animation easing
|
||||
*/
|
||||
public VectAnimated(Vect start, Easing easing)
|
||||
{
|
||||
x = new NumAnimated(start.x(), easing);
|
||||
y = new NumAnimated(start.y(), easing);
|
||||
z = new NumAnimated(start.z(), easing);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double x()
|
||||
{
|
||||
return x.value();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double y()
|
||||
{
|
||||
return y.value();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double z()
|
||||
{
|
||||
return z.value();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setTo(double x, double y, double z)
|
||||
{
|
||||
setX(x);
|
||||
setY(y);
|
||||
setZ(z);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setX(double x)
|
||||
{
|
||||
this.x.setTo(x);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setY(double y)
|
||||
{
|
||||
this.y.setTo(y);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setZ(double z)
|
||||
{
|
||||
this.z.setTo(z);
|
||||
}
|
||||
|
||||
|
||||
public void add(Vect offset, double duration)
|
||||
{
|
||||
animate(this.add(offset), duration);
|
||||
}
|
||||
|
||||
|
||||
public VectAnimated animate(double x, double y, double z, double duration)
|
||||
{
|
||||
this.x.animate(x, duration);
|
||||
this.y.animate(y, duration);
|
||||
this.z.animate(z, duration);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public VectAnimated animate(Vect target, double duration)
|
||||
{
|
||||
animate(target.x(), target.y(), target.z(), duration);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public VectAnimated animate(double x, double y, double z)
|
||||
{
|
||||
this.x.animate(x, defaultDuration);
|
||||
this.y.animate(y, defaultDuration);
|
||||
this.z.animate(z, defaultDuration);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public VectAnimated animate(Vect target)
|
||||
{
|
||||
animate(target.x(), target.y(), target.z());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the default duration (seconds)
|
||||
*/
|
||||
public double getDefaultDuration()
|
||||
{
|
||||
return defaultDuration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set default animation duration (when changed without using animate())
|
||||
*
|
||||
* @param defaultDuration default duration (seconds)
|
||||
*/
|
||||
public void setDefaultDuration(double defaultDuration)
|
||||
{
|
||||
this.defaultDuration = defaultDuration;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void update(double delta)
|
||||
{
|
||||
x.update(delta);
|
||||
y.update(delta);
|
||||
z.update(delta);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void pause()
|
||||
{
|
||||
x.pause();
|
||||
y.pause();
|
||||
z.pause();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void resume()
|
||||
{
|
||||
x.resume();
|
||||
y.resume();
|
||||
z.resume();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isPaused()
|
||||
{
|
||||
return x.isPaused(); // BÚNO
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the animation is finished
|
||||
*/
|
||||
public boolean isFinished()
|
||||
{
|
||||
return x.isFinished(); // BÚNO
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return current animation duration
|
||||
*/
|
||||
public double getDuration()
|
||||
{
|
||||
return x.getDuration(); // BÚNO
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return elapsed time since the start of the animation
|
||||
*/
|
||||
public double getElapsed()
|
||||
{
|
||||
return x.getElapsed(); // BÚNO
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return animation progress (elapsed / duration)
|
||||
*/
|
||||
public double getProgress()
|
||||
{
|
||||
return x.getProgress(); // BÚNO
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set easing for all three coordinates
|
||||
*
|
||||
* @param easing
|
||||
*/
|
||||
public void setEasing(Easing easing)
|
||||
{
|
||||
x.setEasing(easing);
|
||||
y.setEasing(easing);
|
||||
z.setEasing(easing);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an animated vector; This way different easing / settings can be
|
||||
* specified for each coordinate.
|
||||
*
|
||||
* @param x x animator
|
||||
* @param y y animator
|
||||
* @param z z animator
|
||||
* @return animated mutable vector
|
||||
*/
|
||||
@FactoryMethod
|
||||
public static VectAnimated makeVar(NumAnimated x, NumAnimated y, NumAnimated z)
|
||||
{
|
||||
return new VectAnimated(x, y, z);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an animated vector
|
||||
*
|
||||
* @param start initial positioon
|
||||
* @param easing animation easing
|
||||
* @return animated mutable vector
|
||||
*/
|
||||
@FactoryMethod
|
||||
public static VectAnimated makeVar(Vect start, Easing easing)
|
||||
{
|
||||
return new VectAnimated(start, easing);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an animated vector, initialized at 0,0,0
|
||||
*
|
||||
* @param easing animation easing
|
||||
* @return animated mutable vector
|
||||
*/
|
||||
@FactoryMethod
|
||||
public static VectAnimated makeVar(Easing easing)
|
||||
{
|
||||
return new VectAnimated(Vect.ZERO, easing);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package mightypork.utils.math.color;
|
||||
|
||||
|
||||
import java.util.EmptyStackException;
|
||||
import java.util.Stack;
|
||||
|
||||
import mightypork.utils.annotations.FactoryMethod;
|
||||
import mightypork.utils.math.Calc;
|
||||
import mightypork.utils.math.constraints.num.Num;
|
||||
|
||||
|
||||
/**
|
||||
* Color.<br>
|
||||
* All values are 0-1
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public abstract class Color {
|
||||
|
||||
private static final Stack<Num> alphaStack = new Stack<>();
|
||||
private static volatile boolean alphaStackEnabled = true;
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color fromHex(int rgb_hex)
|
||||
{
|
||||
final int bi = rgb_hex & 0xff;
|
||||
final int gi = (rgb_hex >> 8) & 0xff;
|
||||
final int ri = (rgb_hex >> 16) & 0xff;
|
||||
return rgb(ri / 255D, gi / 255D, bi / 255D);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color rgb(double r, double g, double b)
|
||||
{
|
||||
return rgba(Num.make(r), Num.make(g), Num.make(b), Num.ONE);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color rgba(double r, double g, double b, double a)
|
||||
{
|
||||
return rgba(Num.make(r), Num.make(g), Num.make(b), Num.make(a));
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color rgb(Num r, Num g, Num b)
|
||||
{
|
||||
return rgba(r, g, b, Num.ONE);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color rgba(Num r, Num g, Num b, Num a)
|
||||
{
|
||||
return new ColorRgb(r, g, b, a);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color hsb(double h, double s, double b)
|
||||
{
|
||||
return hsba(Num.make(h), Num.make(s), Num.make(b), Num.ONE);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color hsba(double h, double s, double b, double a)
|
||||
{
|
||||
return hsba(Num.make(h), Num.make(s), Num.make(b), Num.make(a));
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color hsb(Num h, Num s, Num b)
|
||||
{
|
||||
return hsba(h, s, b, Num.ONE);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color hsba(Num h, Num s, Num b, Num a)
|
||||
{
|
||||
return new ColorHsb(h, s, b, a);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color light(double a)
|
||||
{
|
||||
return light(Num.make(a));
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color light(Num a)
|
||||
{
|
||||
return rgba(Num.ONE, Num.ONE, Num.ONE, a);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color dark(double a)
|
||||
{
|
||||
return dark(Num.make(a));
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static final Color dark(Num a)
|
||||
{
|
||||
return rgba(Num.ZERO, Num.ZERO, Num.ZERO, a);
|
||||
}
|
||||
|
||||
|
||||
protected static final double clamp(Num n)
|
||||
{
|
||||
return Calc.clamp(n.value(), 0, 1);
|
||||
}
|
||||
|
||||
|
||||
protected static final double clamp(double n)
|
||||
{
|
||||
return Calc.clamp(n, 0, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return red 0-1
|
||||
*/
|
||||
public abstract double r();
|
||||
|
||||
|
||||
/**
|
||||
* @return green 0-1
|
||||
*/
|
||||
public abstract double g();
|
||||
|
||||
|
||||
/**
|
||||
* @return blue 0-1
|
||||
*/
|
||||
public abstract double b();
|
||||
|
||||
|
||||
/**
|
||||
* @return alpha 0-1
|
||||
*/
|
||||
public final double a()
|
||||
{
|
||||
double alpha = rawAlpha();
|
||||
|
||||
if (alphaStackEnabled) {
|
||||
|
||||
for (final Num n : alphaStack) {
|
||||
alpha *= clamp(n.value());
|
||||
}
|
||||
}
|
||||
|
||||
return clamp(alpha);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return alpha 0-1, before multiplying with the global alpha value.
|
||||
*/
|
||||
protected abstract double rawAlpha();
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Push alpha multiplier on the stack (can be animated or whatever you
|
||||
* like). Once done with rendering, the popAlpha() method should be called,
|
||||
* otherwise you may experience unexpected glitches (such as all going
|
||||
* transparent).
|
||||
* </p>
|
||||
* <p>
|
||||
* multiplier value should not exceed the range 0..1, otherwise it will be
|
||||
* clamped to it.
|
||||
* </p>
|
||||
*
|
||||
* @param alpha alpha multiplier
|
||||
*/
|
||||
public static void pushAlpha(Num alpha)
|
||||
{
|
||||
if (!alphaStackEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
alphaStack.push(alpha);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a pushed alpha multiplier from the stack. If there's no remaining
|
||||
* multiplier on the stack, an exception is raised.
|
||||
*
|
||||
* @throws EmptyStackException if the stack is empty
|
||||
*/
|
||||
public static void popAlpha()
|
||||
{
|
||||
if (!alphaStackEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (alphaStack.isEmpty()) {
|
||||
throw new EmptyStackException();
|
||||
}
|
||||
|
||||
alphaStack.pop();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enable alpha stack. When disabled, pushAlpha() and popAlpha() have no
|
||||
* effect.
|
||||
*
|
||||
* @param yes
|
||||
*/
|
||||
public static void enableAlphaStack(boolean yes)
|
||||
{
|
||||
alphaStackEnabled = yes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if alpha stack is enabled.
|
||||
*/
|
||||
public static boolean isAlphaStackEnabled()
|
||||
{
|
||||
return alphaStackEnabled;
|
||||
}
|
||||
|
||||
|
||||
public Color withAlpha(double multiplier)
|
||||
{
|
||||
return new ColorAlphaAdjuster(this, Num.make(multiplier));
|
||||
}
|
||||
|
||||
|
||||
public Color withAlpha(Num multiplier)
|
||||
{
|
||||
return new ColorAlphaAdjuster(this, multiplier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package mightypork.utils.math.color;
|
||||
|
||||
|
||||
import mightypork.utils.math.constraints.num.Num;
|
||||
|
||||
|
||||
public class ColorAlphaAdjuster extends Color {
|
||||
|
||||
private final Color source;
|
||||
private final Num alphaAdjust;
|
||||
|
||||
|
||||
public ColorAlphaAdjuster(Color source, Num alphaMul)
|
||||
{
|
||||
this.source = source;
|
||||
this.alphaAdjust = alphaMul;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double r()
|
||||
{
|
||||
return source.r();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double g()
|
||||
{
|
||||
return source.g();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double b()
|
||||
{
|
||||
return source.b();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected double rawAlpha()
|
||||
{
|
||||
return source.rawAlpha() * alphaAdjust.value();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package mightypork.utils.math.color;
|
||||
|
||||
|
||||
import mightypork.utils.math.constraints.num.Num;
|
||||
|
||||
|
||||
public class ColorHsb extends Color {
|
||||
|
||||
private final Num h;
|
||||
private final Num s;
|
||||
private final Num b;
|
||||
private final Num a;
|
||||
|
||||
|
||||
public ColorHsb(Num h, Num s, Num b, Num a)
|
||||
{
|
||||
this.h = h;
|
||||
this.s = s;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
|
||||
private double[] asRgb()
|
||||
{
|
||||
final int hex = java.awt.Color.HSBtoRGB((float) clamp(h), (float) clamp(s), (float) clamp(b));
|
||||
|
||||
final int bi = hex & 0xff;
|
||||
final int gi = (hex >> 8) & 0xff;
|
||||
final int ri = (hex >> 16) & 0xff;
|
||||
return new double[] { ri / 255D, gi / 255D, bi / 255D, clamp(a) };
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double r()
|
||||
{
|
||||
return asRgb()[0];
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double g()
|
||||
{
|
||||
return asRgb()[1];
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double b()
|
||||
{
|
||||
return asRgb()[2];
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected double rawAlpha()
|
||||
{
|
||||
return asRgb()[3];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package mightypork.utils.math.color;
|
||||
|
||||
|
||||
import mightypork.utils.math.constraints.num.Num;
|
||||
|
||||
|
||||
public class ColorRgb extends Color {
|
||||
|
||||
private final Num r;
|
||||
private final Num g;
|
||||
private final Num b;
|
||||
private final Num a;
|
||||
|
||||
|
||||
public ColorRgb(Num r, Num g, Num b, Num a)
|
||||
{
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double r()
|
||||
{
|
||||
return clamp(r);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double g()
|
||||
{
|
||||
return clamp(g);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double b()
|
||||
{
|
||||
return clamp(b);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected double rawAlpha()
|
||||
{
|
||||
return clamp(a);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package mightypork.utils.math.color.pal;
|
||||
|
||||
|
||||
import mightypork.utils.math.color.Color;
|
||||
|
||||
|
||||
/**
|
||||
* CGA palette
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface CGA {
|
||||
|
||||
Color BLACK = Color.fromHex(0x000000);
|
||||
Color GRAY_DARK = Color.fromHex(0x686868);
|
||||
Color GRAY_LIGHT = Color.fromHex(0xB8B8B8);
|
||||
Color WHITE = Color.fromHex(0xFFFFFF);
|
||||
|
||||
Color RED_DARK = Color.fromHex(0xC41F0C);
|
||||
Color RED_LIGHT = Color.fromHex(0xFF706A);
|
||||
Color MAGENTA_DARK = Color.fromHex(0xC12BB6);
|
||||
Color MAGENTA_LIGHT = Color.fromHex(0xFF76FD);
|
||||
Color BLUE_DARK = Color.fromHex(0x0019B6);
|
||||
Color BLUE_LIGHT = Color.fromHex(0x5F6EFC);
|
||||
Color CYAN_DARK = Color.fromHex(0x00B6B8);
|
||||
Color CYAN_LIGHT = Color.fromHex(0x23FCFE);
|
||||
Color GREEN_DARK = Color.fromHex(0x00B41D);
|
||||
Color GREEN_LIGHT = Color.fromHex(0x39FA6F);
|
||||
|
||||
Color YELLOW = Color.fromHex(0xFFFD72);
|
||||
Color BROWN = Color.fromHex(0xC16A14);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package mightypork.utils.math.color.pal;
|
||||
|
||||
|
||||
import mightypork.utils.math.color.Color;
|
||||
|
||||
|
||||
/**
|
||||
* COMMODORE palette
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface CMDR {
|
||||
|
||||
Color BLACK = Color.fromHex(0x040013);
|
||||
Color WHITE = Color.fromHex(0xFFFFFF);
|
||||
Color RED = Color.fromHex(0x883932);
|
||||
Color CYAN = Color.fromHex(0x67B6BD);
|
||||
Color PURPLE = Color.fromHex(0x8B3F96);
|
||||
Color GREEN = Color.fromHex(0x55A049);
|
||||
Color BLUE = Color.fromHex(0x40318D);
|
||||
Color YELLOW = Color.fromHex(0xBFCE72);
|
||||
Color ORANGE = Color.fromHex(0x8B5429);
|
||||
Color BROWN = Color.fromHex(0x574200);
|
||||
Color RED_LIGHT = Color.fromHex(0xB86962);
|
||||
Color GRAY_DARK = Color.fromHex(0x505050);
|
||||
Color GRAY = Color.fromHex(0x787878);
|
||||
Color GREEN_LIGHT = Color.fromHex(0x94E089);
|
||||
Color BLUE_LIGHT = Color.fromHex(0x7869C4);
|
||||
Color GRAY_LIGHT = Color.fromHex(0x9F9F9F);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package mightypork.utils.math.color.pal;
|
||||
|
||||
|
||||
import mightypork.utils.math.color.Color;
|
||||
|
||||
|
||||
/**
|
||||
* PAL16 palette via http://androidarts.com/palette/16pal.htm
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface PAL16 {
|
||||
|
||||
Color VOID = Color.fromHex(0x000000);
|
||||
Color ASH = Color.fromHex(0x9D9D9D);
|
||||
Color BLIND = Color.fromHex(0xFFFFFF);
|
||||
|
||||
Color BLOODRED = Color.fromHex(0xBE2633);
|
||||
Color PIGMEAT = Color.fromHex(0xE06F8B);
|
||||
|
||||
Color OLDPOOP = Color.fromHex(0x493C2B);
|
||||
Color NEWPOOP = Color.fromHex(0xA46422);
|
||||
Color BLAZE = Color.fromHex(0xEB8931);
|
||||
Color ZORNSKIN = Color.fromHex(0xF7E26B);
|
||||
|
||||
Color SHADEGREEN = Color.fromHex(0x2F484E);
|
||||
Color LEAFGREEN = Color.fromHex(0x44891A);
|
||||
Color SLIMEGREEN = Color.fromHex(0xA3CE27);
|
||||
|
||||
Color NIGHTBLUE = Color.fromHex(0x1B2632);
|
||||
Color SEABLUE = Color.fromHex(0x005784);
|
||||
Color SKYBLUE = Color.fromHex(0x31A2F2);
|
||||
Color CLOUDBLUE = Color.fromHex(0xB2DCEF);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package mightypork.utils.math.color.pal;
|
||||
|
||||
|
||||
import mightypork.utils.math.color.Color;
|
||||
|
||||
|
||||
/**
|
||||
* Basic RGB palette
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class RGB {
|
||||
|
||||
public static final Color BLACK_10 = Color.rgba(0, 0, 0, 0.1);
|
||||
public static final Color BLACK_20 = Color.rgba(0, 0, 0, 0.2);
|
||||
public static final Color BLACK_30 = Color.rgba(0, 0, 0, 0.3);
|
||||
public static final Color BLACK_40 = Color.rgba(0, 0, 0, 0.4);
|
||||
public static final Color BLACK_50 = Color.rgba(0, 0, 0, 0.5);
|
||||
public static final Color BLACK_60 = Color.rgba(0, 0, 0, 0.6);
|
||||
public static final Color BLACK_70 = Color.rgba(0, 0, 0, 0.7);
|
||||
public static final Color BLACK_80 = Color.rgba(0, 0, 0, 0.8);
|
||||
public static final Color BLACK_90 = Color.rgba(0, 0, 0, 0.9);
|
||||
|
||||
|
||||
public static final Color WHITE = Color.fromHex(0xFFFFFF);
|
||||
public static final Color BLACK = Color.fromHex(0x000000);
|
||||
public static final Color GRAY_DARK = Color.fromHex(0x808080);
|
||||
public static final Color GRAY = Color.fromHex(0xA0A0A0);
|
||||
public static final Color GRAY_LIGHT = Color.fromHex(0xC0C0C0);
|
||||
|
||||
public static final Color RED = Color.fromHex(0xFF0000);
|
||||
public static final Color GREEN = Color.fromHex(0x00FF00);
|
||||
public static final Color BLUE = Color.fromHex(0x0000FF);
|
||||
|
||||
public static final Color YELLOW = Color.fromHex(0xFFFF00);
|
||||
public static final Color CYAN = Color.fromHex(0x00FFFF);
|
||||
public static final Color MAGENTA = Color.fromHex(0xFF00FF);
|
||||
|
||||
public static final Color PINK = Color.fromHex(0xFF3FFC);
|
||||
public static final Color ORANGE = Color.fromHex(0xFC4800);
|
||||
public static final Color BROWN = Color.fromHex(0x83501B);
|
||||
|
||||
public static final Color NONE = Color.rgba(0, 0, 0, 0);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package mightypork.utils.math.color.pal;
|
||||
|
||||
|
||||
import mightypork.utils.math.color.Color;
|
||||
|
||||
|
||||
/**
|
||||
* ZX Spectrum palette
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface ZX {
|
||||
|
||||
Color BLACK = Color.fromHex(0x000000);
|
||||
Color GRAY = Color.fromHex(0xCBCBCB);
|
||||
Color WHITE = Color.fromHex(0xFFFFFF);
|
||||
|
||||
Color RED_DARK = Color.fromHex(0xD8240F);
|
||||
Color RED_LIGHT = Color.fromHex(0xFF3016);
|
||||
Color MAGENTA_DARK = Color.fromHex(0xD530C9);
|
||||
Color MAGENTA_LIGHT = Color.fromHex(0xFF3FFC);
|
||||
Color BLUE_DARK = Color.fromHex(0x001DC8);
|
||||
Color BLUE_LIGHT = Color.fromHex(0x0027FB);
|
||||
Color CYAN_DARK = Color.fromHex(0x00C9CB);
|
||||
Color CYAN_LIGHT = Color.fromHex(0xFFFD33);
|
||||
Color GREEN_DARK = Color.fromHex(0x00C721);
|
||||
Color GREEN_LIGHT = Color.fromHex(0x00F92C);
|
||||
|
||||
Color YELLOW_DARK = Color.fromHex(0xCECA26);
|
||||
Color YELLOW_LIGHT = Color.fromHex(0xFFFD33);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package mightypork.utils.math.constraints;
|
||||
|
||||
|
||||
import mightypork.utils.interfaces.Pollable;
|
||||
|
||||
|
||||
/**
|
||||
* Constraint that is cached
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
* @param <C> constraint type
|
||||
*/
|
||||
public interface CachedConstraint<C> extends Pollable {
|
||||
|
||||
/**
|
||||
* Called after the cache has changed value (and digest).
|
||||
*/
|
||||
void onConstraintChanged();
|
||||
|
||||
|
||||
/**
|
||||
* @return the cached value
|
||||
*/
|
||||
C getCacheSource();
|
||||
|
||||
|
||||
/**
|
||||
* Enable caching & digest caching
|
||||
*
|
||||
* @param yes enable caching
|
||||
*/
|
||||
void enableCaching(boolean yes);
|
||||
|
||||
|
||||
/**
|
||||
* @return true if caching is on
|
||||
*/
|
||||
boolean isCachingEnabled();
|
||||
|
||||
|
||||
/**
|
||||
* Update cached value and cached digest (if digest caching is enabled).<br>
|
||||
* source constraint is polled beforehand.
|
||||
*/
|
||||
@Override
|
||||
void poll();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package mightypork.utils.math.constraints;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Interface for constraints that support digests. Digest is a small data object
|
||||
* with final fields, typically primitive, used for processing (such as
|
||||
* rendering or other very frequent operations).
|
||||
* </p>
|
||||
* <p>
|
||||
* Taking a digest is expensive, so if it needs to be done often and the value
|
||||
* changes are deterministic (such as, triggered by timing event or screen
|
||||
* resize), it's useful to cache the last digest and reuse it until such an
|
||||
* event occurs again.
|
||||
* </p>
|
||||
* <p>
|
||||
* Implementing class typically needs a field to store the last digest, a flag
|
||||
* that digest caching is enabled, and a flag that a digest is dirty.
|
||||
* </p>
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
* @param <D> digest class
|
||||
*/
|
||||
public interface CachedDigestable<D> extends Digestable<D> {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Toggle digest caching.
|
||||
* </p>
|
||||
* <p>
|
||||
* To trigger update of the cache, call the <code>poll()</code> method.
|
||||
* </p>
|
||||
*
|
||||
* @param yes
|
||||
*/
|
||||
void enableDigestCaching(boolean yes);
|
||||
|
||||
|
||||
/**
|
||||
* @return true if digest caching is enabled.
|
||||
*/
|
||||
boolean isDigestCachingEnabled();
|
||||
|
||||
|
||||
/**
|
||||
* If digest caching is enabled, mark current cached value as "dirty". Dirty
|
||||
* digest should be re-created next time a value is requested.<br>
|
||||
*/
|
||||
void markDigestDirty();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package mightypork.utils.math.constraints;
|
||||
|
||||
|
||||
/**
|
||||
* Parametrized implementation of a {@link CachedDigestable}
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
* @param <D> digest class
|
||||
*/
|
||||
public abstract class DigestCache<D> implements CachedDigestable<D> {
|
||||
|
||||
private D last_digest;
|
||||
private boolean caching_enabled = false;
|
||||
private boolean dirty = true;
|
||||
|
||||
|
||||
@Override
|
||||
public final D digest()
|
||||
{
|
||||
if (caching_enabled) {
|
||||
if (dirty || last_digest == null) {
|
||||
last_digest = createDigest();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
return last_digest;
|
||||
}
|
||||
|
||||
return createDigest();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return fresh new digest
|
||||
*/
|
||||
protected abstract D createDigest();
|
||||
|
||||
|
||||
@Override
|
||||
public final void enableDigestCaching(boolean yes)
|
||||
{
|
||||
caching_enabled = yes;
|
||||
markDigestDirty(); // mark dirty
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean isDigestCachingEnabled()
|
||||
{
|
||||
return caching_enabled;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void markDigestDirty()
|
||||
{
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package mightypork.utils.math.constraints;
|
||||
|
||||
|
||||
/**
|
||||
* COnstraint that can be converted to a digest, representing current state
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
* @param <D>
|
||||
*/
|
||||
public interface Digestable<D> {
|
||||
|
||||
/**
|
||||
* Take a digest. If digest caching is enabled and the cached digest is
|
||||
* marked as dirty, a new one should be made.
|
||||
*
|
||||
* @return digest
|
||||
*/
|
||||
D digest();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,758 @@
|
||||
package mightypork.utils.math.constraints.num;
|
||||
|
||||
|
||||
import mightypork.utils.annotations.FactoryMethod;
|
||||
import mightypork.utils.math.constraints.CachedDigestable;
|
||||
import mightypork.utils.math.constraints.DigestCache;
|
||||
import mightypork.utils.math.constraints.num.caching.NumCache;
|
||||
import mightypork.utils.math.constraints.num.caching.NumDigest;
|
||||
import mightypork.utils.math.constraints.num.proxy.NumProxy;
|
||||
import mightypork.utils.math.constraints.num.var.NumVar;
|
||||
import mightypork.utils.math.constraints.vect.Vect;
|
||||
|
||||
|
||||
public abstract class Num implements NumBound, CachedDigestable<NumDigest> {
|
||||
|
||||
public static final NumConst ZERO = Num.make(0);
|
||||
public static final NumConst ONE = Num.make(1);
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static Num make(NumBound bound)
|
||||
{
|
||||
return new NumProxy(bound);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static NumConst make(double value)
|
||||
{
|
||||
return new NumConst(value);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static NumVar makeVar()
|
||||
{
|
||||
return makeVar(0);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static NumVar makeVar(double value)
|
||||
{
|
||||
return new NumVar(value);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static NumVar makeVar(Num copied)
|
||||
{
|
||||
return new NumVar(copied.value());
|
||||
}
|
||||
|
||||
private Num p_ceil;
|
||||
private Num p_floor;
|
||||
private Num p_sgn;
|
||||
private Num p_round;
|
||||
private Num p_atan;
|
||||
private Num p_acos;
|
||||
private Num p_asin;
|
||||
private Num p_tan;
|
||||
private Num p_cos;
|
||||
private Num p_sin;
|
||||
private Num p_cbrt;
|
||||
private Num p_sqrt;
|
||||
private Num p_cube;
|
||||
private Num p_square;
|
||||
private Num p_neg;
|
||||
private Num p_abs;
|
||||
|
||||
private final DigestCache<NumDigest> dc = new DigestCache<NumDigest>() {
|
||||
|
||||
@Override
|
||||
protected NumDigest createDigest()
|
||||
{
|
||||
return new NumDigest(Num.this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public NumConst freeze()
|
||||
{
|
||||
return new NumConst(value());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrap this constraint into a caching adapter. Value will stay fixed (ie.
|
||||
* no re-calculations) until cache receives a poll() call.
|
||||
*
|
||||
* @return the caching adapter
|
||||
*/
|
||||
public NumCache cached()
|
||||
{
|
||||
return new NumCache(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a snapshot of the current state, to be used for processing.
|
||||
*
|
||||
* @return digest
|
||||
*/
|
||||
|
||||
@Override
|
||||
public NumDigest digest()
|
||||
{
|
||||
return dc.digest();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void enableDigestCaching(boolean yes)
|
||||
{
|
||||
dc.enableDigestCaching(yes);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isDigestCachingEnabled()
|
||||
{
|
||||
return dc.isDigestCachingEnabled();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void markDigestDirty()
|
||||
{
|
||||
dc.markDigestDirty();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Num getNum()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the number
|
||||
*/
|
||||
public abstract double value();
|
||||
|
||||
|
||||
public Num add(final double addend)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
private final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return t.value() + addend;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num add(final Num addend)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return t.value() + addend.value();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num sub(final double subtrahend)
|
||||
{
|
||||
return add(-subtrahend);
|
||||
}
|
||||
|
||||
|
||||
public Num abs()
|
||||
{
|
||||
if (p_abs == null) p_abs = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.abs(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_abs;
|
||||
}
|
||||
|
||||
|
||||
public Num sub(final Num subtrahend)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return t.value() - subtrahend.value();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num div(final double factor)
|
||||
{
|
||||
return mul(1 / factor);
|
||||
}
|
||||
|
||||
|
||||
public Num div(final Num factor)
|
||||
{
|
||||
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return t.value() / factor.value();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num mul(final double factor)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
private final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return t.value() * factor;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num mul(final Num factor)
|
||||
{
|
||||
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return t.value() * factor.value();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num average(final double other)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return (t.value() + other) / 2;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num average(final Num other)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return (t.value() + other.value()) / 2;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num perc(final double percent)
|
||||
{
|
||||
return mul(percent / 100D);
|
||||
}
|
||||
|
||||
|
||||
public Num perc(final Num percent)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return t.value() * (percent.value() / 100);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num cos()
|
||||
{
|
||||
if (p_cos == null) p_cos = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.cos(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_cos;
|
||||
}
|
||||
|
||||
|
||||
public Num acos()
|
||||
{
|
||||
if (p_acos == null) p_acos = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.acos(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_acos;
|
||||
}
|
||||
|
||||
|
||||
public Num sin()
|
||||
{
|
||||
if (p_sin == null) p_sin = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.sin(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_sin;
|
||||
}
|
||||
|
||||
|
||||
public Num asin()
|
||||
{
|
||||
if (p_asin == null) p_asin = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.asin(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_asin;
|
||||
}
|
||||
|
||||
|
||||
public Num tan()
|
||||
{
|
||||
if (p_tan == null) p_tan = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.tan(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_tan;
|
||||
}
|
||||
|
||||
|
||||
public Num atan()
|
||||
{
|
||||
if (p_atan == null) p_atan = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.atan(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_atan;
|
||||
}
|
||||
|
||||
|
||||
public Num cbrt()
|
||||
{
|
||||
if (p_cbrt == null) p_cbrt = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.cbrt(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_cbrt;
|
||||
}
|
||||
|
||||
|
||||
public Num sqrt()
|
||||
{
|
||||
if (p_sqrt == null) p_sqrt = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.sqrt(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_sqrt;
|
||||
}
|
||||
|
||||
|
||||
public Num neg()
|
||||
{
|
||||
if (p_neg == null) p_neg = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return -1 * t.value();
|
||||
}
|
||||
};
|
||||
|
||||
return p_neg;
|
||||
}
|
||||
|
||||
|
||||
public Num round()
|
||||
{
|
||||
if (p_round == null) p_round = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.round(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_round;
|
||||
}
|
||||
|
||||
|
||||
public Num floor()
|
||||
{
|
||||
if (p_floor == null) p_floor = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.floor(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_floor;
|
||||
}
|
||||
|
||||
|
||||
public Num ceil()
|
||||
{
|
||||
if (p_ceil == null) p_ceil = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.round(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_ceil;
|
||||
}
|
||||
|
||||
|
||||
public Num pow(final double other)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.pow(t.value(), other);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num pow(final Num power)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.pow(t.value(), power.value());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num cube()
|
||||
{
|
||||
if (p_cube == null) p_cube = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
final double v = t.value();
|
||||
return v * v * v;
|
||||
}
|
||||
};
|
||||
|
||||
return p_cube;
|
||||
}
|
||||
|
||||
|
||||
public Num square()
|
||||
{
|
||||
if (p_square == null) p_square = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
final double v = t.value();
|
||||
return v * v;
|
||||
}
|
||||
};
|
||||
|
||||
return p_square;
|
||||
}
|
||||
|
||||
|
||||
public Num half()
|
||||
{
|
||||
return mul(0.5);
|
||||
}
|
||||
|
||||
|
||||
public Num max(final double other)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.max(t.value(), other);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num max(final Num other)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.max(t.value(), other.value());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num min(final Num other)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.min(t.value(), other.value());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num min(final double other)
|
||||
{
|
||||
return new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.min(t.value(), other);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num signum()
|
||||
{
|
||||
if (p_sgn == null) p_sgn = new Num() {
|
||||
|
||||
final Num t = Num.this;
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return Math.signum(t.value());
|
||||
}
|
||||
};
|
||||
|
||||
return p_sgn;
|
||||
}
|
||||
|
||||
|
||||
public boolean isNegative()
|
||||
{
|
||||
return value() < 0;
|
||||
}
|
||||
|
||||
|
||||
public boolean isPositive()
|
||||
{
|
||||
return value() > 0;
|
||||
}
|
||||
|
||||
|
||||
public boolean isZero()
|
||||
{
|
||||
return value() == 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(value());
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (!(obj instanceof Num)) return false;
|
||||
final Num other = (Num) obj;
|
||||
|
||||
return value() == other.value();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return Double.toString(value());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return vect with both coords of this size
|
||||
*/
|
||||
public Vect toVectXY()
|
||||
{
|
||||
return Vect.make(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.utils.math.constraints.num;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for something that has/is a Num. Num itself implements it as well.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface NumBound {
|
||||
|
||||
/**
|
||||
* @return the Num value
|
||||
*/
|
||||
Num getNum();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package mightypork.utils.math.constraints.num;
|
||||
|
||||
|
||||
import mightypork.utils.math.constraints.num.caching.NumDigest;
|
||||
|
||||
|
||||
/**
|
||||
* Constant number.<br>
|
||||
* It's arranged so that operations with constant arguments yield constant
|
||||
* results.
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class NumConst extends Num {
|
||||
|
||||
private final double value;
|
||||
private NumDigest digest;
|
||||
|
||||
|
||||
NumConst(Num copied)
|
||||
{
|
||||
this.value = copied.value();
|
||||
}
|
||||
|
||||
|
||||
NumConst(double value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated No good to copy a constant.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public NumConst freeze()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumDigest digest()
|
||||
{
|
||||
return (digest != null) ? digest : (digest = super.digest());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst add(double addend)
|
||||
{
|
||||
return Num.make(value() + addend);
|
||||
}
|
||||
|
||||
|
||||
public NumConst add(NumConst addend)
|
||||
{
|
||||
return Num.make(value + addend.value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst sub(double subtrahend)
|
||||
{
|
||||
return add(-subtrahend);
|
||||
}
|
||||
|
||||
|
||||
public NumConst sub(NumConst addend)
|
||||
{
|
||||
return Num.make(value - addend.value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst mul(double factor)
|
||||
{
|
||||
return Num.make(value() * factor);
|
||||
}
|
||||
|
||||
|
||||
public NumConst mul(NumConst addend)
|
||||
{
|
||||
return Num.make(value * addend.value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst div(double factor)
|
||||
{
|
||||
return mul(1 / factor);
|
||||
}
|
||||
|
||||
|
||||
public NumConst div(NumConst addend)
|
||||
{
|
||||
return Num.make(value / addend.value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst perc(double percents)
|
||||
{
|
||||
return mul(percents / 100);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst neg()
|
||||
{
|
||||
return mul(-1);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst abs()
|
||||
{
|
||||
return Num.make(Math.abs(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst max(double other)
|
||||
{
|
||||
return Num.make(Math.max(value(), other));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst min(double other)
|
||||
{
|
||||
return Num.make(Math.min(value(), other));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst pow(double power)
|
||||
{
|
||||
return Num.make(Math.pow(value(), power));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst square()
|
||||
{
|
||||
final double v = value();
|
||||
return Num.make(v * v);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst cube()
|
||||
{
|
||||
final double v = value();
|
||||
return Num.make(v * v * v);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst sqrt()
|
||||
{
|
||||
return Num.make(Math.sqrt(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst cbrt()
|
||||
{
|
||||
return Num.make(Math.cbrt(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst sin()
|
||||
{
|
||||
return Num.make(Math.sin(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst cos()
|
||||
{
|
||||
return Num.make(Math.cos(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst tan()
|
||||
{
|
||||
return Num.make(Math.tan(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst asin()
|
||||
{
|
||||
return Num.make(Math.asin(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst acos()
|
||||
{
|
||||
return Num.make(Math.acos(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst atan()
|
||||
{
|
||||
return Num.make(Math.atan(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst signum()
|
||||
{
|
||||
return Num.make(Math.signum(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst average(double other)
|
||||
{
|
||||
return Num.make((value() + other) / 2);
|
||||
}
|
||||
|
||||
|
||||
public NumConst average(NumConst other)
|
||||
{
|
||||
return super.average(other).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst round()
|
||||
{
|
||||
return Num.make(Math.round(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst ceil()
|
||||
{
|
||||
return Num.make(Math.ceil(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst floor()
|
||||
{
|
||||
return Num.make(Math.floor(value()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst half()
|
||||
{
|
||||
return mul(0.5);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.utils.math.constraints.num;
|
||||
|
||||
|
||||
/**
|
||||
* Pluggable numeric constraint
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public interface PluggableNumBound extends NumBound {
|
||||
|
||||
/**
|
||||
* @param num bound to set
|
||||
*/
|
||||
abstract void setNum(NumBound num);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package mightypork.utils.math.constraints.num.batch;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import mightypork.utils.math.constraints.num.Num;
|
||||
|
||||
|
||||
/**
|
||||
* Expandable multiplication of multiple numbers
|
||||
*
|
||||
* @author Ondřej Hruška (MightyPork)
|
||||
*/
|
||||
public class NumMul extends Num {
|
||||
|
||||
private final List<Num> factors = new ArrayList<>();
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
double v = 1;
|
||||
for (final Num n : factors) {
|
||||
if (n != null) v *= n.value();
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a number to the multiplication
|
||||
*
|
||||
* @param factor added number
|
||||
*/
|
||||
public void addFactor(Num factor)
|
||||
{
|
||||
factors.add(factor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a number to the multiplication
|
||||
*
|
||||
* @param factor added number
|
||||
*/
|
||||
public void addFactor(double factor)
|
||||
{
|
||||
factors.add(Num.make(factor));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user