Moved general purpose stuff into util, renamed utils->util
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package mightypork.util.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.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(value = { ElementType.METHOD })
|
||||
public @interface DefaultImpl {
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package mightypork.util.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
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
public @interface FactoryMethod {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
package mightypork.util.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.util.math.Range;
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
import mightypork.util.math.constraints.vect.VectConst;
|
||||
import mightypork.util.objects.Convert;
|
||||
|
||||
|
||||
/**
|
||||
* Property manager with advanced formatting and value checking.<br>
|
||||
* Methods starting with put are for filling. Most of the others are shortcuts
|
||||
* to getters.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class PropertyManager {
|
||||
|
||||
private abstract class Property<T> {
|
||||
|
||||
public String comment;
|
||||
public String key;
|
||||
|
||||
public T value;
|
||||
public T defaultValue;
|
||||
|
||||
|
||||
public Property(String key, T defaultValue, String comment) {
|
||||
super();
|
||||
this.comment = comment;
|
||||
this.key = key;
|
||||
this.defaultValue = defaultValue;
|
||||
this.value = defaultValue;
|
||||
}
|
||||
|
||||
|
||||
public abstract void parse(String string);
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return Convert.toString(value);
|
||||
}
|
||||
}
|
||||
|
||||
private class BooleanProperty extends Property<Boolean> {
|
||||
|
||||
public BooleanProperty(String key, Boolean defaultValue, String comment) {
|
||||
super(key, defaultValue, comment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void parse(String string)
|
||||
{
|
||||
value = Convert.toBoolean(string, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
private class IntegerProperty extends Property<Integer> {
|
||||
|
||||
public IntegerProperty(String key, Integer defaultValue, String comment) {
|
||||
super(key, defaultValue, comment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void parse(String string)
|
||||
{
|
||||
value = Convert.toInteger(string, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
private class DoubleProperty extends Property<Double> {
|
||||
|
||||
public DoubleProperty(String key, Double defaultValue, String comment) {
|
||||
super(key, defaultValue, comment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void parse(String string)
|
||||
{
|
||||
value = Convert.toDouble(string, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
private class StringProperty extends Property<String> {
|
||||
|
||||
public StringProperty(String key, String defaultValue, String comment) {
|
||||
super(key, defaultValue, comment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void parse(String string)
|
||||
{
|
||||
value = Convert.toString(string, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
private class RangeProperty extends Property<Range> {
|
||||
|
||||
public RangeProperty(String key, Range defaultValue, String comment) {
|
||||
super(key, defaultValue, comment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void parse(String string)
|
||||
{
|
||||
value = Convert.toRange(string, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
private class CoordProperty extends Property<Vect> {
|
||||
|
||||
public CoordProperty(String key, Vect defaultValue, String comment) {
|
||||
super(key, defaultValue, comment);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void parse(String string)
|
||||
{
|
||||
value = Convert.toVect(string, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/** put newline before entry comments */
|
||||
private boolean cfgNewlineBeforeComments = true;
|
||||
/** Put newline between sections. */
|
||||
private boolean cfgSeparateSections = true;
|
||||
/** Force save, even if nothing changed (used to save changed comments) */
|
||||
private boolean cfgForceSave;
|
||||
|
||||
private final File file;
|
||||
private String fileComment = "";
|
||||
|
||||
private final TreeMap<String, Property<?>> entries;
|
||||
private final TreeMap<String, String> renameTable;
|
||||
private final TreeMap<String, String> overrideValues;
|
||||
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.overrideValues = new TreeMap<>();
|
||||
this.renameTable = new TreeMap<>();
|
||||
this.fileComment = comment;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load, fix and write to file.
|
||||
*/
|
||||
public void apply()
|
||||
{
|
||||
boolean needsSave = false;
|
||||
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) {
|
||||
needsSave = true;
|
||||
props = new SortedProperties();
|
||||
}
|
||||
|
||||
props.cfgBlankRowBetweenSections = cfgSeparateSections;
|
||||
props.cfgBlankRowBeforeComment = cfgNewlineBeforeComments;
|
||||
|
||||
final ArrayList<String> keyList = new ArrayList<>();
|
||||
|
||||
// rename keys
|
||||
for (final Entry<String, String> entry : renameTable.entrySet()) {
|
||||
if (props.getProperty(entry.getKey()) == null) {
|
||||
continue;
|
||||
}
|
||||
props.setProperty(entry.getValue(), props.getProperty(entry.getKey()));
|
||||
props.remove(entry.getKey());
|
||||
needsSave = true;
|
||||
}
|
||||
|
||||
// set the override values into the freshly loaded properties file
|
||||
for (final Entry<String, String> entry : overrideValues.entrySet()) {
|
||||
props.setProperty(entry.getKey(), entry.getValue());
|
||||
needsSave = true;
|
||||
}
|
||||
|
||||
// validate entries one by one, replace with default when needed
|
||||
for (final Property<?> entry : entries.values()) {
|
||||
keyList.add(entry.key);
|
||||
|
||||
final String propOrig = props.getProperty(entry.key);
|
||||
|
||||
entry.parse(propOrig);
|
||||
|
||||
if (!entry.toString().equals(propOrig)) {
|
||||
needsSave = true;
|
||||
}
|
||||
|
||||
if (entry.comment != null) {
|
||||
props.setKeyComment(entry.key, entry.comment);
|
||||
}
|
||||
|
||||
if (propOrig == null || !entry.toString().equals(propOrig)) {
|
||||
props.setProperty(entry.key, entry.toString());
|
||||
|
||||
needsSave = true;
|
||||
}
|
||||
}
|
||||
|
||||
// removed unused props
|
||||
for (final String propname : props.keySet().toArray(new String[props.size()])) {
|
||||
if (!keyList.contains(propname)) {
|
||||
props.remove(propname);
|
||||
needsSave = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// save if needed
|
||||
if (needsSave || cfgForceSave) {
|
||||
try {
|
||||
props.store(new FileOutputStream(file), fileComment);
|
||||
} catch (final IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
overrideValues.clear();
|
||||
renameTable.clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param forceSave save even if unchanged.
|
||||
*/
|
||||
public void cfgForceSave(boolean forceSave)
|
||||
{
|
||||
this.cfgForceSave = forceSave;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a property entry (rarely used)
|
||||
*
|
||||
* @param n key
|
||||
* @return the entry
|
||||
*/
|
||||
private Property<?> get(String n)
|
||||
{
|
||||
try {
|
||||
return entries.get(n);
|
||||
} catch (final Throwable t) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get boolean property
|
||||
*
|
||||
* @param n key
|
||||
* @return the boolean found, or false
|
||||
*/
|
||||
public Boolean getBoolean(String n)
|
||||
{
|
||||
return Convert.toBoolean(get(n).value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get numeric property
|
||||
*
|
||||
* @param n key
|
||||
* @return the int found, or null
|
||||
*/
|
||||
public Integer getInteger(String n)
|
||||
{
|
||||
return Convert.toInteger(get(n).value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get numeric property as double
|
||||
*
|
||||
* @param n key
|
||||
* @return the double found, or null
|
||||
*/
|
||||
public Double getDouble(String n)
|
||||
{
|
||||
return Convert.toDouble(get(n).value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get string property
|
||||
*
|
||||
* @param n key
|
||||
* @return the string found, or null
|
||||
*/
|
||||
public String getString(String n)
|
||||
{
|
||||
return Convert.toString(get(n).value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get range property
|
||||
*
|
||||
* @param n key
|
||||
* @return the range found, or null
|
||||
*/
|
||||
public Range getRange(String n)
|
||||
{
|
||||
return Convert.toRange(get(n).value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get coord property
|
||||
*
|
||||
* @param n key
|
||||
* @return the coord found, or null
|
||||
*/
|
||||
public VectConst getCoord(String n)
|
||||
{
|
||||
return Convert.toVect(get(n).value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a boolean property
|
||||
*
|
||||
* @param n key
|
||||
* @param d default value
|
||||
* @param comment the in-file comment
|
||||
*/
|
||||
public void putBoolean(String n, boolean d, String comment)
|
||||
{
|
||||
entries.put(n, new BooleanProperty(n, d, comment));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a numeric property (double)
|
||||
*
|
||||
* @param n key
|
||||
* @param d default value
|
||||
* @param comment the in-file comment
|
||||
*/
|
||||
public void putDouble(String n, double d, String comment)
|
||||
{
|
||||
entries.put(n, new DoubleProperty(n, d, comment));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a numeric property
|
||||
*
|
||||
* @param n key
|
||||
* @param d default value
|
||||
* @param comment the in-file comment
|
||||
*/
|
||||
public void putInteger(String n, int d, String comment)
|
||||
{
|
||||
entries.put(n, new IntegerProperty(n, d, comment));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a string property
|
||||
*
|
||||
* @param n key
|
||||
* @param d default value
|
||||
* @param comment the in-file comment
|
||||
*/
|
||||
public void putString(String n, String d, String comment)
|
||||
{
|
||||
entries.put(n, new StringProperty(n, d, comment));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a coord property
|
||||
*
|
||||
* @param n key
|
||||
* @param d default value
|
||||
* @param comment the in-file comment
|
||||
*/
|
||||
public void putCoord(String n, Vect d, String comment)
|
||||
{
|
||||
entries.put(n, new CoordProperty(n, d, comment));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a range property
|
||||
*
|
||||
* @param n key
|
||||
* @param d default value
|
||||
* @param comment the in-file comment
|
||||
*/
|
||||
public void putRange(String n, Range d, String comment)
|
||||
{
|
||||
entries.put(n, new RangeProperty(n, d, comment));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rename key before doing "apply"; 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; use to save runtime-changed configuration
|
||||
* values.
|
||||
*
|
||||
* @param key key
|
||||
* @param value the saved value
|
||||
*/
|
||||
public void setValue(String key, Object value)
|
||||
{
|
||||
overrideValues.put(key, value.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package mightypork.util.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.util.files.FileUtils;
|
||||
import mightypork.util.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 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,303 @@
|
||||
package mightypork.util.config;
|
||||
|
||||
|
||||
import java.io.*;
|
||||
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 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();
|
||||
|
||||
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,51 @@
|
||||
package mightypork.util.control;
|
||||
|
||||
|
||||
/**
|
||||
* Triggered action
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class Action implements Runnable, Enableable {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
|
||||
/**
|
||||
* Enable the action
|
||||
*
|
||||
* @param enable true to enable
|
||||
*/
|
||||
@Override
|
||||
public final void enable(boolean enable)
|
||||
{
|
||||
this.enabled = enable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if this action is enabled.
|
||||
*/
|
||||
@Override
|
||||
public final boolean isEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run the action, if it's enabled.
|
||||
*/
|
||||
@Override
|
||||
public final void run()
|
||||
{
|
||||
if (enabled) execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Do the work.
|
||||
*/
|
||||
protected abstract void execute();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mightypork.util.control;
|
||||
|
||||
|
||||
/**
|
||||
* Element that can be assigned an action (ie. button);
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface ActionTrigger {
|
||||
|
||||
/**
|
||||
* Assign an action
|
||||
*
|
||||
* @param action action
|
||||
*/
|
||||
void setAction(Action action);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package mightypork.util.control;
|
||||
|
||||
|
||||
/**
|
||||
* Object that can be destroyed (free resources etc)
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface Destroyable {
|
||||
|
||||
/**
|
||||
* Destroy this object
|
||||
*/
|
||||
public void destroy();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package mightypork.util.control;
|
||||
|
||||
|
||||
/**
|
||||
* Can be enabled or disabled.<br>
|
||||
* Implementations should take appropriate action (ie. stop listening to events,
|
||||
* updating etc.)
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface Enableable {
|
||||
|
||||
/**
|
||||
* Change enabled state
|
||||
*
|
||||
* @param yes enabled
|
||||
*/
|
||||
public void enable(boolean yes);
|
||||
|
||||
|
||||
/**
|
||||
* @return true if enabled
|
||||
*/
|
||||
public boolean isEnabled();
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package mightypork.util.control.eventbus;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* HashSet that buffers add and remove calls and performs them all at once when
|
||||
* a flush() method is called.
|
||||
*
|
||||
* @author MightyPork
|
||||
* @param <E> element type
|
||||
*/
|
||||
public class BufferedHashSet<E> extends HashSet<E> {
|
||||
|
||||
private final List<E> toAdd = new LinkedList<>();
|
||||
private final List<Object> toRemove = new LinkedList<>();
|
||||
private boolean buffering = false;
|
||||
|
||||
|
||||
/**
|
||||
* make empty
|
||||
*/
|
||||
public BufferedHashSet() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* make from elements of a collection
|
||||
*
|
||||
* @param c
|
||||
*/
|
||||
public BufferedHashSet(Collection<? extends E> c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* make new
|
||||
*
|
||||
* @param initialCapacity
|
||||
* @param loadFactor
|
||||
*/
|
||||
public BufferedHashSet(int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* make new
|
||||
*
|
||||
* @param initialCapacity
|
||||
*/
|
||||
public BufferedHashSet(int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean add(E e)
|
||||
{
|
||||
if (buffering) {
|
||||
toAdd.add(e);
|
||||
} else {
|
||||
super.add(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean remove(Object e)
|
||||
{
|
||||
if (buffering) {
|
||||
toRemove.add(e);
|
||||
} else {
|
||||
super.remove(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flush all
|
||||
*/
|
||||
private void flush()
|
||||
{
|
||||
for (final E e : toAdd) {
|
||||
super.add(e);
|
||||
}
|
||||
|
||||
for (final Object e : toRemove) {
|
||||
super.remove(e);
|
||||
}
|
||||
|
||||
toAdd.clear();
|
||||
toRemove.clear();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggle buffering
|
||||
*
|
||||
* @param enable enable buffering
|
||||
*/
|
||||
public void setBuffering(boolean enable)
|
||||
{
|
||||
if (this.buffering && !enable) {
|
||||
flush();
|
||||
}
|
||||
|
||||
this.buffering = enable;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.util.control.eventbus;
|
||||
|
||||
|
||||
/**
|
||||
* Access to an {@link EventBus} instance
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface BusAccess {
|
||||
|
||||
/**
|
||||
* @return event bus
|
||||
*/
|
||||
EventBus getEventBus();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
package mightypork.util.control.eventbus;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.DelayQueue;
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import mightypork.util.annotations.FactoryMethod;
|
||||
import mightypork.util.control.Destroyable;
|
||||
import mightypork.util.control.eventbus.clients.DelegatingClient;
|
||||
import mightypork.util.control.eventbus.events.Event;
|
||||
import mightypork.util.control.eventbus.events.flags.DelayedEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.ImmediateEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.SingleReceiverEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.UnloggedEvent;
|
||||
import mightypork.util.logging.Log;
|
||||
|
||||
|
||||
/**
|
||||
* An event bus, accommodating multiple {@link EventChannel}s.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
final public class EventBus implements Destroyable {
|
||||
|
||||
/** Message channels */
|
||||
private final BufferedHashSet<EventChannel<?, ?>> channels = new BufferedHashSet<>();
|
||||
|
||||
/** Registered clients */
|
||||
private final BufferedHashSet<Object> clients = new BufferedHashSet<>();
|
||||
|
||||
/** Messages queued for delivery */
|
||||
private final DelayQueue<DelayQueueEntry> sendQueue = new DelayQueue<>();
|
||||
|
||||
/** Queue polling thread */
|
||||
private final QueuePollingThread busThread;
|
||||
|
||||
/** Whether the bus was destroyed */
|
||||
private boolean dead = false;
|
||||
|
||||
/** Log detailed messages (debug) */
|
||||
public boolean detailedLogging = false;
|
||||
|
||||
|
||||
/**
|
||||
* Make a new bus and start it's queue thread.
|
||||
*/
|
||||
public EventBus() {
|
||||
busThread = new QueuePollingThread();
|
||||
busThread.setDaemon(true);
|
||||
busThread.start();
|
||||
}
|
||||
|
||||
|
||||
private boolean shallLog(Event<?> event)
|
||||
{
|
||||
if (!detailedLogging) return false;
|
||||
if (event.getClass().isAnnotationPresent(UnloggedEvent.class)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a {@link EventChannel} to this bus.<br>
|
||||
* If a channel of matching types is already added, it is returned instead.
|
||||
*
|
||||
* @param channel channel to be added
|
||||
* @return the channel that's now in the bus
|
||||
*/
|
||||
public EventChannel<?, ?> addChannel(EventChannel<?, ?> channel)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
// if the channel already exists, return this instance instead.
|
||||
for (final EventChannel<?, ?> ch : channels) {
|
||||
if (ch.equals(channel)) {
|
||||
Log.w("<bus> Channel of type " + Log.str(channel) + " already registered.");
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
|
||||
channels.add(channel);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make & connect a channel for given event and client type.
|
||||
*
|
||||
* @param eventClass event type
|
||||
* @param clientClass client type
|
||||
* @return the created channel instance
|
||||
*/
|
||||
@FactoryMethod
|
||||
public <F_EVENT extends Event<F_CLIENT>, F_CLIENT> EventChannel<?, ?> addChannel(Class<F_EVENT> eventClass, Class<F_CLIENT> clientClass)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
final EventChannel<F_EVENT, F_CLIENT> channel = EventChannel.create(eventClass, clientClass);
|
||||
return addChannel(channel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a {@link EventChannel} from this bus
|
||||
*
|
||||
* @param channel true if channel was removed
|
||||
*/
|
||||
public void removeChannel(EventChannel<?, ?> channel)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
channels.remove(channel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send based on annotation.clients
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
public void send(Event<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
final DelayedEvent adelay = event.getClass().getAnnotation(DelayedEvent.class);
|
||||
if (adelay != null) {
|
||||
sendDelayed(event, adelay.delay());
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getClass().isAnnotationPresent(ImmediateEvent.class)) {
|
||||
sendDirect(event);
|
||||
return;
|
||||
}
|
||||
|
||||
sendQueued(event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add event to a queue
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
public void sendQueued(Event<?> 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(Event<?> event, double delay)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
final DelayQueueEntry dm = new DelayQueueEntry(delay, event);
|
||||
|
||||
if (shallLog(event)) Log.f3("<bus> Qu " + Log.str(event) + ", t = +" + 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(Event<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
if (shallLog(event)) Log.f3("<bus> Di " + Log.str(event));
|
||||
|
||||
dispatch(event);
|
||||
}
|
||||
|
||||
|
||||
public void sendDirectToChildren(DelegatingClient delegatingClient, Event<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
if (shallLog(event)) Log.f3("<bus> Di sub " + Log.str(event));
|
||||
|
||||
doDispatch(delegatingClient.getChildClients(), event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send immediately.<br>
|
||||
* Should be used for real-time events that require immediate response, such
|
||||
* as timing events.
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
private void dispatch(Event<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
channels.setBuffering(true);
|
||||
clients.setBuffering(true);
|
||||
|
||||
doDispatch(clients, event);
|
||||
|
||||
channels.setBuffering(false);
|
||||
clients.setBuffering(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send to a set of clients
|
||||
*
|
||||
* @param clients clients
|
||||
* @param event event
|
||||
*/
|
||||
private void doDispatch(Collection<Object> clients, Event<?> event)
|
||||
{
|
||||
boolean sent = false;
|
||||
boolean accepted = false;
|
||||
|
||||
final boolean singular = event.getClass().isAnnotationPresent(SingleReceiverEvent.class);
|
||||
|
||||
for (final EventChannel<?, ?> b : channels) {
|
||||
if (b.canBroadcast(event)) {
|
||||
accepted = true;
|
||||
sent |= b.broadcast(event, clients);
|
||||
}
|
||||
|
||||
if (sent && singular) break;
|
||||
}
|
||||
|
||||
if (!accepted) Log.e("<bus> Not accepted by any channel: " + Log.str(event));
|
||||
if (!sent && shallLog(event)) Log.w("<bus> Not delivered: " + Log.str(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("<bus> Client joined: " + Log.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("<bus> Client left: " + Log.str(client));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if client can be accepted by any channel
|
||||
*
|
||||
* @param client tested client
|
||||
* @return would be accepted
|
||||
*/
|
||||
public boolean isClientValid(Object client)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
if (client == null) return false;
|
||||
|
||||
for (final EventChannel<?, ?> ch : channels) {
|
||||
if (ch.isClientValid(client)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class DelayQueueEntry implements Delayed {
|
||||
|
||||
private final long due;
|
||||
private Event<?> evt = null;
|
||||
|
||||
|
||||
public DelayQueueEntry(double seconds, Event<?> 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 Event<?> getEvent()
|
||||
{
|
||||
return evt;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class QueuePollingThread extends Thread {
|
||||
|
||||
public 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) {
|
||||
dispatch(evt.getEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Halt bus thread and reject any future events.
|
||||
*/
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
assertLive();
|
||||
|
||||
busThread.stopped = true;
|
||||
|
||||
dead = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package mightypork.util.control.eventbus;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import mightypork.util.control.eventbus.clients.DelegatingClient;
|
||||
import mightypork.util.control.eventbus.clients.ToggleableClient;
|
||||
import mightypork.util.control.eventbus.events.Event;
|
||||
import mightypork.util.control.eventbus.events.flags.SingleReceiverEvent;
|
||||
import mightypork.util.logging.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Event delivery channel, module of {@link EventBus}
|
||||
*
|
||||
* @author MightyPork
|
||||
* @param <EVENT> event type
|
||||
* @param <CLIENT> client (subscriber) type
|
||||
*/
|
||||
final public class EventChannel<EVENT extends Event<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
|
||||
* @return true if event was sent
|
||||
*/
|
||||
public boolean broadcast(Event<?> event, Collection<Object> clients)
|
||||
{
|
||||
if (!canBroadcast(event)) return false;
|
||||
|
||||
return doBroadcast(eventClass.cast(event), clients, new HashSet<>());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send the event
|
||||
*
|
||||
* @param event sent event
|
||||
* @param clients subscribing clients
|
||||
* @param processed clients already processed
|
||||
* @return success
|
||||
*/
|
||||
private boolean doBroadcast(final EVENT event, final Collection<Object> clients, final Collection<Object> processed)
|
||||
{
|
||||
boolean sent = false;
|
||||
final boolean singular = event.getClass().isAnnotationPresent(SingleReceiverEvent.class);
|
||||
|
||||
for (final Object client : clients) {
|
||||
|
||||
// exclude obvious non-clients
|
||||
if (!isClientValid(client)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// avoid executing more times
|
||||
if (processed.contains(client)) {
|
||||
Log.w("<bus> Client already served: " + Log.str(client));
|
||||
continue;
|
||||
}
|
||||
processed.add(client);
|
||||
|
||||
// opt-out
|
||||
if (client instanceof ToggleableClient) {
|
||||
if (!((ToggleableClient) client).isListening()) continue;
|
||||
}
|
||||
|
||||
sent |= sendTo(client, event);
|
||||
|
||||
// singular event ain't no whore, handled once only.
|
||||
if (sent && singular) return true;
|
||||
|
||||
// pass on to delegated clients
|
||||
if (client instanceof DelegatingClient) {
|
||||
if (((DelegatingClient) client).doesDelegate()) {
|
||||
|
||||
final Collection<Object> children = ((DelegatingClient) client).getChildClients();
|
||||
|
||||
if (children != null && children.size() > 0) {
|
||||
sent |= doBroadcast(event, children, processed);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send an event to a client.
|
||||
*
|
||||
* @param client target client
|
||||
* @param event event to send
|
||||
* @return success
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean sendTo(Object client, EVENT event)
|
||||
{
|
||||
if (isClientOfType(client)) {
|
||||
((Event<CLIENT>) event).handleBy((CLIENT) client);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the given event can be broadcasted by this {@link EventChannel}
|
||||
*
|
||||
* @param event event object
|
||||
* @return can be broadcasted
|
||||
*/
|
||||
public boolean canBroadcast(Event<?> 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 Event<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 isClientOfType(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 isClientOfType(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 "{ " + Log.str(eventClass) + " => " + Log.str(clientClass) + " }";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package mightypork.util.control.eventbus.clients;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import mightypork.util.control.eventbus.BusAccess;
|
||||
import mightypork.util.control.eventbus.EventBus;
|
||||
|
||||
|
||||
/**
|
||||
* Client that can be attached to the {@link EventBus}, or added as a child
|
||||
* client to another {@link DelegatingClient}
|
||||
*
|
||||
* @author 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 final Collection<Object> getChildClients()
|
||||
{
|
||||
return clients;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean doesDelegate()
|
||||
{
|
||||
return delegating;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean isListening()
|
||||
{
|
||||
return listening;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a child subscriber to the {@link EventBus}.<br>
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
@Override
|
||||
public final void addChildClient(Object client)
|
||||
{
|
||||
if (client instanceof RootBusNode) {
|
||||
throw new IllegalArgumentException("Cannot nest RootBusNode.");
|
||||
}
|
||||
|
||||
if (getEventBus().isClientValid(client)) {
|
||||
clients.add(client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a child subscriber
|
||||
*
|
||||
* @param client subscriber to remove
|
||||
*/
|
||||
@Override
|
||||
public final void removeChildClient(Object client)
|
||||
{
|
||||
if (client != null) {
|
||||
clients.remove(client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether events should be received.
|
||||
*
|
||||
* @param listening receive events
|
||||
*/
|
||||
public final void setListening(boolean listening)
|
||||
{
|
||||
this.listening = listening;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether events should be passed on to child nodes
|
||||
*
|
||||
* @param delegating
|
||||
*/
|
||||
public final void setDelegating(boolean delegating)
|
||||
{
|
||||
this.delegating = delegating;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final EventBus getEventBus()
|
||||
{
|
||||
return busAccess.getEventBus();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package mightypork.util.control.eventbus.clients;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import mightypork.util.control.eventbus.EventBus;
|
||||
|
||||
|
||||
/**
|
||||
* Common methods for client hubs (ie delegating vlient implementations)
|
||||
*
|
||||
* @author 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,27 @@
|
||||
package mightypork.util.control.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 MightyPork
|
||||
*/
|
||||
public interface DelegatingClient {
|
||||
|
||||
/**
|
||||
* @return collection of child clients. Can not be null.
|
||||
*/
|
||||
public Collection<Object> getChildClients();
|
||||
|
||||
|
||||
/**
|
||||
* @return true if delegating is active
|
||||
*/
|
||||
public boolean doesDelegate();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package mightypork.util.control.eventbus.clients;
|
||||
|
||||
|
||||
import mightypork.util.control.Destroyable;
|
||||
import mightypork.util.control.eventbus.BusAccess;
|
||||
|
||||
|
||||
/**
|
||||
* Bus node that should be directly attached to the bus.
|
||||
*
|
||||
* @author 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)
|
||||
*/
|
||||
protected abstract void deinit();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.util.control.eventbus.clients;
|
||||
|
||||
|
||||
/**
|
||||
* Client that can toggle receiving messages.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface ToggleableClient {
|
||||
|
||||
/**
|
||||
* @return true if the client wants to receive messages
|
||||
*/
|
||||
public boolean isListening();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package mightypork.util.control.eventbus.events;
|
||||
|
||||
|
||||
import mightypork.util.control.eventbus.events.flags.DelayedEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.ImmediateEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.SingleReceiverEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.UnloggedEvent;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Something that can be handled by HANDLER.
|
||||
* </p>
|
||||
* <p>
|
||||
* Can be annotated as {@link SingleReceiverEvent} to be delivered once only,
|
||||
* and {@link DelayedEvent} or {@link ImmediateEvent} to specify default sending
|
||||
* mode. When marked as {@link UnloggedEvent}, it will not appear in detailed
|
||||
* bus logging (useful for very frequent events, such as UpdateEvent).
|
||||
* </p>
|
||||
* <p>
|
||||
* Default sending mode (if not changed by annotations) is <i>queued</i> with
|
||||
* zero delay.
|
||||
* </p>
|
||||
*
|
||||
* @author MightyPork
|
||||
* @param <HANDLER> handler type
|
||||
*/
|
||||
public interface Event<HANDLER> {
|
||||
|
||||
/**
|
||||
* Ask handler to handle this message.
|
||||
*
|
||||
* @param handler handler instance
|
||||
*/
|
||||
public void handleBy(HANDLER handler);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package mightypork.util.control.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* Event that should be queued with given delay (default: 0);
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface DelayedEvent {
|
||||
|
||||
/**
|
||||
* @return event dispatch delay [seconds]
|
||||
*/
|
||||
double delay() default 0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.util.control.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* Event that should not be queued.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface ImmediateEvent {}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.util.control.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* Handled only by the first client, then discarded.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface SingleReceiverEvent {}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mightypork.util.control.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* 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 MightyPork
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface UnloggedEvent {}
|
||||
@@ -0,0 +1,39 @@
|
||||
package mightypork.util.control.timing;
|
||||
|
||||
|
||||
/**
|
||||
* Class for counting FPS in games.<br>
|
||||
* This class can be used also as a simple frequency meter - output is in Hz.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class FpsMeter {
|
||||
|
||||
private long frames = 0;
|
||||
private long lastTimeMillis = System.currentTimeMillis();
|
||||
private long lastSecFPS = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @return current second's FPS
|
||||
*/
|
||||
public long getFPS()
|
||||
{
|
||||
return lastSecFPS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notification that frame was rendered
|
||||
*/
|
||||
public void frame()
|
||||
{
|
||||
if (System.currentTimeMillis() - lastTimeMillis > 1000) {
|
||||
lastSecFPS = frames;
|
||||
frames = 0;
|
||||
final long over = System.currentTimeMillis() - lastTimeMillis - 1000;
|
||||
lastTimeMillis = System.currentTimeMillis() - over;
|
||||
}
|
||||
frames++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package mightypork.util.control.timing;
|
||||
|
||||
|
||||
/**
|
||||
* Can be paused & resumed
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface Pauseable {
|
||||
|
||||
/**
|
||||
* Pause operation
|
||||
*/
|
||||
public void pause();
|
||||
|
||||
|
||||
/**
|
||||
* Resume operation
|
||||
*/
|
||||
public void resume();
|
||||
|
||||
|
||||
/**
|
||||
* @return paused state
|
||||
*/
|
||||
public boolean isPaused();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package mightypork.util.control.timing;
|
||||
|
||||
|
||||
/**
|
||||
* Timer for delta timing
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class TimerDelta {
|
||||
|
||||
private long lastFrame;
|
||||
|
||||
private static final long SECOND = 1000000000; // a million nanoseconds
|
||||
|
||||
|
||||
/**
|
||||
* New delta timer
|
||||
*/
|
||||
public TimerDelta() {
|
||||
lastFrame = System.nanoTime();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get current time in NS
|
||||
*
|
||||
* @return current time NS
|
||||
*/
|
||||
public long getTime()
|
||||
{
|
||||
return System.nanoTime();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get time since the last "getDelta()" call.
|
||||
*
|
||||
* @return delta time (seconds)
|
||||
*/
|
||||
public double getDelta()
|
||||
{
|
||||
final long time = getTime();
|
||||
final double delta = (time - lastFrame) / (double) SECOND;
|
||||
lastFrame = time;
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package mightypork.util.control.timing;
|
||||
|
||||
|
||||
/**
|
||||
* Timer for interpolated timing
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class TimerFps {
|
||||
|
||||
private long lastFrame = 0;
|
||||
private long nextFrame = 0;
|
||||
private long skipped = 0;
|
||||
private long lastSkipped = 0;
|
||||
|
||||
private static final long SECOND = 1000000000; // a million nanoseconds
|
||||
private final long FRAME; // a time of one frame in nanoseconds
|
||||
|
||||
|
||||
/**
|
||||
* New interpolated timer
|
||||
*
|
||||
* @param fps target FPS
|
||||
*/
|
||||
public TimerFps(long fps) {
|
||||
FRAME = Math.round(SECOND / (double) fps);
|
||||
|
||||
lastFrame = System.nanoTime();
|
||||
nextFrame = System.nanoTime() + FRAME;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sync and calculate dropped frames etc.
|
||||
*/
|
||||
public void sync()
|
||||
{
|
||||
final long time = getTime();
|
||||
if (time >= nextFrame) {
|
||||
final long skippedNow = (long) Math.floor((time - nextFrame) / (double) FRAME) + 1;
|
||||
skipped += skippedNow;
|
||||
lastFrame = nextFrame + (1 - skippedNow) * FRAME;
|
||||
nextFrame += skippedNow * FRAME;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get nanotime
|
||||
*
|
||||
* @return nanotime
|
||||
*/
|
||||
public long getTime()
|
||||
{
|
||||
return System.nanoTime();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get fraction of next frame
|
||||
*
|
||||
* @return fraction
|
||||
*/
|
||||
public double getFraction()
|
||||
{
|
||||
if (getSkipped() >= 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
final long time = getTime();
|
||||
|
||||
if (time <= nextFrame) {
|
||||
return (double) (time - lastFrame) / (double) FRAME;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get number of elapsed ticks
|
||||
*
|
||||
* @return ticks
|
||||
*/
|
||||
public int getSkipped()
|
||||
{
|
||||
final long change = skipped - lastSkipped;
|
||||
lastSkipped = skipped;
|
||||
return (int) change;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear timer and start counting new tick.
|
||||
*/
|
||||
public void startNewFrame()
|
||||
{
|
||||
final long time = getTime();
|
||||
lastFrame = time;
|
||||
nextFrame = time + FRAME;
|
||||
lastSkipped = skipped;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mightypork.util.control.timing;
|
||||
|
||||
|
||||
/**
|
||||
* Uses delta timing
|
||||
*
|
||||
* @author 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,149 @@
|
||||
package mightypork.util.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.util.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,505 @@
|
||||
package mightypork.util.files;
|
||||
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import mightypork.util.logging.Log;
|
||||
import mightypork.util.string.StringUtils;
|
||||
import mightypork.util.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.accept(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)
|
||||
{
|
||||
try {
|
||||
dir.mkdir();
|
||||
} catch (final RuntimeException e) {
|
||||
Log.e("Error creating folder " + dir, e);
|
||||
}
|
||||
|
||||
final List<File> list = new ArrayList<>();
|
||||
|
||||
try {
|
||||
for (final File f : dir.listFiles(filter)) {
|
||||
list.add(f);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Log.e("Error listing folder " + dir, e);
|
||||
}
|
||||
|
||||
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 StringUtils.fromLastChar(file, '.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove extension.
|
||||
*
|
||||
* @param filename
|
||||
* @return filename and extension
|
||||
*/
|
||||
public static String[] getFilenameParts(String filename)
|
||||
{
|
||||
String ext, name;
|
||||
|
||||
try {
|
||||
ext = StringUtils.fromLastDot(filename);
|
||||
} catch (final StringIndexOutOfBoundsException e) {
|
||||
ext = "";
|
||||
}
|
||||
|
||||
try {
|
||||
name = StringUtils.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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace special characters with place holders in a filename.
|
||||
*
|
||||
* @param filestring filename string
|
||||
* @return escaped
|
||||
*/
|
||||
public static String escapeFileString(String filestring)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (final char c : filestring.toCharArray()) {
|
||||
switch (c) {
|
||||
case '%':
|
||||
sb.append("%%");
|
||||
break;
|
||||
|
||||
case '.':
|
||||
sb.append("%d");
|
||||
break;
|
||||
|
||||
default:
|
||||
sb.append(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unescape filename string obtained by escapeFileString().
|
||||
*
|
||||
* @param filestring escaped string
|
||||
* @return clean string
|
||||
*/
|
||||
public static String unescapeFileString(String filestring)
|
||||
{
|
||||
filestring = filestring.replace("%d", ".");
|
||||
filestring = filestring.replace("%%", "%");
|
||||
|
||||
return filestring;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Escape filename, keeping the same extension
|
||||
*
|
||||
* @param filename filename
|
||||
* @return escaped
|
||||
*/
|
||||
public static String escapeFilename(String filename)
|
||||
{
|
||||
final String[] parts = getFilenameParts(filename);
|
||||
|
||||
return escapeFileString(parts[0]) + "." + parts[1];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unescape filename, keeping the same extension
|
||||
*
|
||||
* @param filename escaped filename
|
||||
* @return unesaped
|
||||
*/
|
||||
public static String unescapeFilename(String filename)
|
||||
{
|
||||
final String[] parts = getFilenameParts(filename);
|
||||
|
||||
return unescapeFileString(parts[0]) + "." + parts[1];
|
||||
}
|
||||
|
||||
|
||||
public static String getBasename(String name)
|
||||
{
|
||||
return StringUtils.toLastChar(StringUtils.fromLastChar(name, '/'), '.');
|
||||
}
|
||||
|
||||
|
||||
public static String getFilename(String name)
|
||||
{
|
||||
return StringUtils.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,51 @@
|
||||
package mightypork.util.files;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileLock;
|
||||
|
||||
|
||||
/**
|
||||
* Instance lock (avoid running twice)
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class InstanceLock {
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
public static boolean onFile(final File lockFile)
|
||||
{
|
||||
try {
|
||||
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 Exception e) {
|
||||
System.err.println("Unable to remove lock file.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (final IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package mightypork.util.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;
|
||||
|
||||
|
||||
/**
|
||||
* Get App dir, ensure it exists
|
||||
*
|
||||
* @param dirname
|
||||
* @return app dir
|
||||
*/
|
||||
public static File getWorkDir(String dirname)
|
||||
{
|
||||
return getWorkDir(dirname, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get App sub-folder
|
||||
*
|
||||
* @param dirname
|
||||
* @param subfolderName
|
||||
* @param create
|
||||
* @return the folder
|
||||
*/
|
||||
public static File getWorkDir(String dirname, String subfolderName, boolean create)
|
||||
{
|
||||
final File f = new File(getWorkDir(dirname), subfolderName);
|
||||
|
||||
if (!f.exists() && create) {
|
||||
if (!f.mkdirs()) {
|
||||
throw new RuntimeException("Could not create.");
|
||||
}
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get App sub-folder, create
|
||||
*
|
||||
* @param dirname
|
||||
* @param subfolderName
|
||||
* @return the folder
|
||||
*/
|
||||
public static File getWorkDir(String dirname, String subfolderName)
|
||||
{
|
||||
return getWorkDir(dirname, subfolderName, true);
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
private static File getWorkDir(String dirname, boolean create)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (!file.exists() || !file.isDirectory()) {
|
||||
if (create) {
|
||||
if (!file.mkdirs()) {
|
||||
throw new RuntimeException("Could not create working directory.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package mightypork.util.files.ion;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
/**
|
||||
* Ionizable Arraylist
|
||||
*
|
||||
* @author MightyPork
|
||||
* @param <T>
|
||||
*/
|
||||
public abstract class AbstractIonList<T> extends ArrayList<T> implements Ionizable {
|
||||
|
||||
@Override
|
||||
public void ionRead(InputStream in) throws IonException
|
||||
{
|
||||
try {
|
||||
while (true) {
|
||||
final byte b = BinaryUtils.readByte(in);
|
||||
|
||||
if (b == IonMarks.ENTRY) {
|
||||
final T value = (T) Ion.readObject(in);
|
||||
add(value);
|
||||
} else if (b == IonMarks.END) {
|
||||
break;
|
||||
} else {
|
||||
throw new IonException("Unexpected mark in IonList: " + Integer.toHexString(b));
|
||||
}
|
||||
}
|
||||
ionReadCustomData(in);
|
||||
} catch (final IOException e) {
|
||||
throw new IonException("Error reading ion list", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void ionWrite(OutputStream out) throws IonException
|
||||
{
|
||||
try {
|
||||
for (final T entry : this) {
|
||||
if (entry instanceof IonizableOptional && !((IonizableOptional) entry).ionShouldSave()) continue;
|
||||
BinaryUtils.writeByte(out, IonMarks.ENTRY);
|
||||
Ion.writeObject(out, entry);
|
||||
}
|
||||
BinaryUtils.writeByte(out, IonMarks.END);
|
||||
ionWriteCustomData(out);
|
||||
} catch (final IOException e) {
|
||||
throw new IonException("Error reading ion map", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read custom data of this AbstractIonList implementation
|
||||
*
|
||||
* @param in input stream
|
||||
*/
|
||||
public void ionReadCustomData(InputStream in)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write custom data of this AbstractIonList implementation
|
||||
*
|
||||
* @param out output stream
|
||||
*/
|
||||
public void ionWriteCustomData(OutputStream out)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public abstract byte ionMark();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package mightypork.util.files.ion;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
|
||||
/**
|
||||
* Ionizable HashMap
|
||||
*
|
||||
* @author MightyPork
|
||||
* @param <V>
|
||||
*/
|
||||
public abstract class AbstractIonMap<V> extends LinkedHashMap<String, V> implements Ionizable {
|
||||
|
||||
@Override
|
||||
public V get(Object key)
|
||||
{
|
||||
return super.get(key);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public V put(String key, V value)
|
||||
{
|
||||
return super.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void ionRead(InputStream in) throws IonException
|
||||
{
|
||||
try {
|
||||
while (true) {
|
||||
final byte b = BinaryUtils.readByte(in);
|
||||
if (b == IonMarks.ENTRY) {
|
||||
final String key = BinaryUtils.readString(in);
|
||||
|
||||
final V value = (V) Ion.readObject(in);
|
||||
put(key, value);
|
||||
|
||||
} else if (b == IonMarks.END) {
|
||||
break;
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected mark in IonMap: " + Integer.toHexString(b));
|
||||
}
|
||||
}
|
||||
ionReadCustomData(in);
|
||||
} catch (final IOException e) {
|
||||
throw new IonException("Error reading ion map", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void ionWrite(OutputStream out) throws IonException
|
||||
{
|
||||
try {
|
||||
for (final java.util.Map.Entry<String, V> entry : entrySet()) {
|
||||
BinaryUtils.writeByte(out, IonMarks.ENTRY);
|
||||
BinaryUtils.writeString(out, entry.getKey());
|
||||
Ion.writeObject(out, entry.getValue());
|
||||
}
|
||||
BinaryUtils.writeByte(out, IonMarks.END);
|
||||
ionWriteCustomData(out);
|
||||
} catch (final IOException e) {
|
||||
throw new IonException("Error reading ion map", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read custom data of this AbstractIonMap implementation
|
||||
*
|
||||
* @param in input stream
|
||||
*/
|
||||
public void ionReadCustomData(InputStream in)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write custom data of this AbstractIonMap implementation
|
||||
*
|
||||
* @param out output stream
|
||||
*/
|
||||
public void ionWriteCustomData(OutputStream out)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte ionMark()
|
||||
{
|
||||
return IonMarks.MAP;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package mightypork.util.files.ion;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
|
||||
/**
|
||||
* Utilities to store and load objects to streams.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class BinaryUtils {
|
||||
|
||||
private static ByteBuffer bi = ByteBuffer.allocate(Integer.SIZE / 8);
|
||||
private static ByteBuffer bd = ByteBuffer.allocate(Double.SIZE / 8);
|
||||
private static ByteBuffer bf = ByteBuffer.allocate(Float.SIZE / 8);
|
||||
private static ByteBuffer bc = ByteBuffer.allocate(Character.SIZE / 8);
|
||||
private static ByteBuffer bl = ByteBuffer.allocate(Long.SIZE / 8);
|
||||
private static ByteBuffer bs = ByteBuffer.allocate(Short.SIZE / 8);
|
||||
|
||||
private static byte[] ai = new byte[Integer.SIZE / 8];
|
||||
private static byte[] ad = new byte[Double.SIZE / 8];
|
||||
private static byte[] af = new byte[Float.SIZE / 8];
|
||||
private static byte[] ac = new byte[Character.SIZE / 8];
|
||||
private static byte[] al = new byte[Long.SIZE / 8];
|
||||
private static byte[] as = new byte[Short.SIZE / 8];
|
||||
|
||||
|
||||
// CONVERSIONS
|
||||
|
||||
public static byte[] getBytesBool(boolean bool)
|
||||
{
|
||||
return new byte[] { (byte) (bool ? 1 : 0) };
|
||||
}
|
||||
|
||||
|
||||
public static byte[] getBytesByte(byte num)
|
||||
{
|
||||
return new byte[] { num };
|
||||
}
|
||||
|
||||
|
||||
public static byte[] getBytesChar(char num)
|
||||
{
|
||||
bc.clear();
|
||||
bc.putChar(num);
|
||||
return bc.array();
|
||||
}
|
||||
|
||||
|
||||
public static byte[] getBytesShort(short num)
|
||||
{
|
||||
bs.clear();
|
||||
bs.putShort(num);
|
||||
return bs.array();
|
||||
}
|
||||
|
||||
|
||||
public static byte[] getBytesInt(int num)
|
||||
{
|
||||
bi.clear();
|
||||
bi.putInt(num);
|
||||
return bi.array();
|
||||
}
|
||||
|
||||
|
||||
public static byte[] getBytesLong(long num)
|
||||
{
|
||||
bl.clear();
|
||||
bl.putLong(num);
|
||||
return bl.array();
|
||||
}
|
||||
|
||||
|
||||
public static byte[] getBytesFloat(float num)
|
||||
{
|
||||
bf.clear();
|
||||
bf.putFloat(num);
|
||||
return bf.array();
|
||||
}
|
||||
|
||||
|
||||
public static byte[] getBytesDouble(double num)
|
||||
{
|
||||
bd.clear();
|
||||
bd.putDouble(num);
|
||||
return bd.array();
|
||||
}
|
||||
|
||||
|
||||
public static byte[] getBytesString(String str)
|
||||
{
|
||||
final char[] chars = str.toCharArray();
|
||||
|
||||
final ByteBuffer bstr = ByteBuffer.allocate((Character.SIZE / 8) * chars.length + (Character.SIZE / 8));
|
||||
for (final char c : chars) {
|
||||
bstr.putChar(c);
|
||||
}
|
||||
|
||||
bstr.putChar((char) 0);
|
||||
|
||||
return bstr.array();
|
||||
}
|
||||
|
||||
|
||||
public static void writeBoolean(OutputStream out, boolean num) throws IOException
|
||||
{
|
||||
out.write(getBytesBool(num));
|
||||
}
|
||||
|
||||
|
||||
public static void writeByte(OutputStream out, byte num) throws IOException
|
||||
{
|
||||
out.write(getBytesByte(num));
|
||||
}
|
||||
|
||||
|
||||
public static void writeChar(OutputStream out, char num) throws IOException
|
||||
{
|
||||
out.write(getBytesChar(num));
|
||||
}
|
||||
|
||||
|
||||
public static void writeShort(OutputStream out, short num) throws IOException
|
||||
{
|
||||
out.write(getBytesShort(num));
|
||||
}
|
||||
|
||||
|
||||
public static void writeInt(OutputStream out, int num) throws IOException
|
||||
{
|
||||
out.write(getBytesInt(num));
|
||||
}
|
||||
|
||||
|
||||
public static void writeLong(OutputStream out, long num) throws IOException
|
||||
{
|
||||
out.write(getBytesLong(num));
|
||||
}
|
||||
|
||||
|
||||
public static void writeFloat(OutputStream out, float num) throws IOException
|
||||
{
|
||||
out.write(getBytesFloat(num));
|
||||
}
|
||||
|
||||
|
||||
public static void writeDouble(OutputStream out, double num) throws IOException
|
||||
{
|
||||
out.write(getBytesDouble(num));
|
||||
}
|
||||
|
||||
|
||||
public static void writeString(OutputStream out, String str) throws IOException
|
||||
{
|
||||
out.write(getBytesString(str));
|
||||
}
|
||||
|
||||
|
||||
// READING
|
||||
|
||||
public static boolean readBoolean(InputStream in) throws IOException
|
||||
{
|
||||
return in.read() > 0;
|
||||
}
|
||||
|
||||
|
||||
public static byte readByte(InputStream in) throws IOException
|
||||
{
|
||||
return (byte) in.read();
|
||||
}
|
||||
|
||||
|
||||
public static char readChar(InputStream in) throws IOException
|
||||
{
|
||||
if (-1 == in.read(ac, 0, ac.length)) throw new IOException("End of stream.");
|
||||
final ByteBuffer buf = ByteBuffer.wrap(ac);
|
||||
return buf.getChar();
|
||||
}
|
||||
|
||||
|
||||
public static short readShort(InputStream in) throws IOException
|
||||
{
|
||||
if (-1 == in.read(as, 0, as.length)) throw new IOException("End of stream.");
|
||||
final ByteBuffer buf = ByteBuffer.wrap(as);
|
||||
return buf.getShort();
|
||||
}
|
||||
|
||||
|
||||
public static long readLong(InputStream in) throws IOException
|
||||
{
|
||||
if (-1 == in.read(al, 0, al.length)) throw new IOException("End of stream.");
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.wrap(al);
|
||||
return buf.getLong();
|
||||
}
|
||||
|
||||
|
||||
public static int readInt(InputStream in) throws IOException
|
||||
{
|
||||
if (-1 == in.read(ai, 0, ai.length)) throw new IOException("End of stream.");
|
||||
final ByteBuffer buf = ByteBuffer.wrap(ai);
|
||||
return buf.getInt();
|
||||
}
|
||||
|
||||
|
||||
public static float readFloat(InputStream in) throws IOException
|
||||
{
|
||||
if (-1 == in.read(af, 0, af.length)) throw new IOException("End of stream.");
|
||||
final ByteBuffer buf = ByteBuffer.wrap(af);
|
||||
return buf.getFloat();
|
||||
}
|
||||
|
||||
|
||||
public static double readDouble(InputStream in) throws IOException
|
||||
{
|
||||
if (-1 == in.read(ad, 0, ad.length)) throw new IOException("End of stream.");
|
||||
final ByteBuffer buf = ByteBuffer.wrap(ad);
|
||||
return buf.getDouble();
|
||||
}
|
||||
|
||||
|
||||
public static String readString(InputStream in) throws IOException
|
||||
{
|
||||
String s = "";
|
||||
char c;
|
||||
while ((c = readChar(in)) > 0) {
|
||||
s += c;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package mightypork.util.files.ion;
|
||||
|
||||
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import mightypork.util.math.Calc;
|
||||
|
||||
|
||||
/**
|
||||
* Universal data storage system
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class Ion {
|
||||
|
||||
/** Ionizables<Mark, Class> */
|
||||
private static Map<Byte, Class<?>> customIonizables = new HashMap<>();
|
||||
|
||||
// register default ionizables
|
||||
static {
|
||||
try {
|
||||
registerIonizable(IonMarks.MAP, IonMap.class);
|
||||
registerIonizable(IonMarks.LIST, IonList.class);
|
||||
} catch (final IonException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register new Ionizable for direct reconstructing.
|
||||
*
|
||||
* @param mark byte mark to be used, see {@link IonMarks} for reference.
|
||||
* @param objClass class of the registered Ionizable
|
||||
* @throws IonException
|
||||
*/
|
||||
public static void registerIonizable(byte mark, Class<?> objClass) throws IonException
|
||||
{
|
||||
if (customIonizables.containsKey(mark)) {
|
||||
throw new IonException("IonMark " + mark + " is already used.");
|
||||
}
|
||||
customIonizables.put(mark, objClass);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load Ion object from file.
|
||||
*
|
||||
* @param file file path
|
||||
* @return the loaded object
|
||||
* @throws IonException
|
||||
*/
|
||||
public static Object fromFile(String file) throws IonException
|
||||
{
|
||||
return fromFile(new File(file));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load Ion object from file.
|
||||
*
|
||||
* @param file file
|
||||
* @return the loaded object
|
||||
* @throws IonException on failure
|
||||
*/
|
||||
public static Object fromFile(File file) throws IonException
|
||||
{
|
||||
try (InputStream in = new FileInputStream(file)) {
|
||||
|
||||
final Object obj = fromStream(in);
|
||||
return obj;
|
||||
|
||||
} catch (final IOException e) {
|
||||
throw new IonException("Error loading ION file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load Ion object from stream.
|
||||
*
|
||||
* @param in input stream
|
||||
* @return the loaded object
|
||||
* @throws IonException
|
||||
*/
|
||||
public static Object fromStream(InputStream in) throws IonException
|
||||
{
|
||||
return readObject(in);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store Ion object to file.
|
||||
*
|
||||
* @param path file path
|
||||
* @param obj object to store
|
||||
* @throws IonException
|
||||
*/
|
||||
public static void toFile(String path, Object obj) throws IonException
|
||||
{
|
||||
toFile(new File(path), obj);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store Ion object to file.
|
||||
*
|
||||
* @param path file path
|
||||
* @param obj object to store
|
||||
* @throws IonException
|
||||
*/
|
||||
public static void toFile(File path, Object obj) throws IonException
|
||||
{
|
||||
try (OutputStream out = new FileOutputStream(path)) {
|
||||
final String f = path.toString();
|
||||
final File dir = new File(f.substring(0, f.lastIndexOf(File.separator)));
|
||||
|
||||
if (!dir.mkdirs()) throw new IOException("Could not create file.");
|
||||
|
||||
toStream(out, obj);
|
||||
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (final Exception e) {
|
||||
throw new IonException("Error writing to ION file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store Ion object to output stream.
|
||||
*
|
||||
* @param out output stream *
|
||||
* @param obj object to store
|
||||
* @throws IonException
|
||||
*/
|
||||
public static void toStream(OutputStream out, Object obj) throws IonException
|
||||
{
|
||||
writeObject(out, obj);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read single ionizable or primitive object from input stream
|
||||
*
|
||||
* @param in input stream
|
||||
* @return the loaded object
|
||||
* @throws IonException
|
||||
*/
|
||||
public static Object readObject(InputStream in) throws IonException
|
||||
{
|
||||
try {
|
||||
final int bi = in.read();
|
||||
if (bi == -1) throw new IonException("Unexpected end of stream.");
|
||||
final byte b = (byte) bi;
|
||||
if (customIonizables.containsKey(b)) {
|
||||
Ionizable ion;
|
||||
try {
|
||||
ion = ((Ionizable) customIonizables.get(b).newInstance());
|
||||
} catch (final InstantiationException e) {
|
||||
throw new IonException("Cound not instantiate " + customIonizables.get(b).getSimpleName(), e);
|
||||
} catch (final IllegalAccessException e) {
|
||||
throw new IonException("Cound not instantiate " + customIonizables.get(b).getSimpleName(), e);
|
||||
}
|
||||
ion.ionRead(in);
|
||||
return ion;
|
||||
}
|
||||
|
||||
switch (b) {
|
||||
case IonMarks.BOOLEAN:
|
||||
return BinaryUtils.readBoolean(in);
|
||||
case IonMarks.BYTE:
|
||||
return BinaryUtils.readByte(in);
|
||||
case IonMarks.CHAR:
|
||||
return BinaryUtils.readChar(in);
|
||||
case IonMarks.SHORT:
|
||||
return BinaryUtils.readShort(in);
|
||||
case IonMarks.INT:
|
||||
return BinaryUtils.readInt(in);
|
||||
case IonMarks.LONG:
|
||||
return BinaryUtils.readLong(in);
|
||||
case IonMarks.FLOAT:
|
||||
return BinaryUtils.readFloat(in);
|
||||
case IonMarks.DOUBLE:
|
||||
return BinaryUtils.readDouble(in);
|
||||
case IonMarks.STRING:
|
||||
final String s = BinaryUtils.readString(in);
|
||||
return s;
|
||||
default:
|
||||
throw new IonException("Invalid Ion mark " + Integer.toHexString(bi));
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
throw new IonException("Error loading ION file: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write single ionizable or primitive object to output stream
|
||||
*
|
||||
* @param out output stream
|
||||
* @param obj stored object
|
||||
* @throws IonException
|
||||
*/
|
||||
public static void writeObject(OutputStream out, Object obj) throws IonException
|
||||
{
|
||||
try {
|
||||
if (obj instanceof Ionizable) {
|
||||
out.write(((Ionizable) obj).ionMark());
|
||||
((Ionizable) obj).ionWrite(out);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Boolean) {
|
||||
out.write(IonMarks.BOOLEAN);
|
||||
BinaryUtils.writeBoolean(out, (Boolean) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Byte) {
|
||||
out.write(IonMarks.BYTE);
|
||||
BinaryUtils.writeByte(out, (Byte) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Character) {
|
||||
out.write(IonMarks.CHAR);
|
||||
BinaryUtils.writeChar(out, (Character) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Short) {
|
||||
out.write(IonMarks.SHORT);
|
||||
BinaryUtils.writeShort(out, (Short) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Integer) {
|
||||
out.write(IonMarks.INT);
|
||||
BinaryUtils.writeInt(out, (Integer) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Long) {
|
||||
out.write(IonMarks.LONG);
|
||||
BinaryUtils.writeLong(out, (Long) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Float) {
|
||||
out.write(IonMarks.FLOAT);
|
||||
BinaryUtils.writeFloat(out, (Float) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof Double) {
|
||||
out.write(IonMarks.DOUBLE);
|
||||
BinaryUtils.writeDouble(out, (Double) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj instanceof String) {
|
||||
out.write(IonMarks.STRING);
|
||||
BinaryUtils.writeString(out, (String) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new IonException(Calc.cname(obj) + " can't be stored in Ion storage.");
|
||||
|
||||
} catch (final IOException e) {
|
||||
throw new IonException("Could not store " + obj, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package mightypork.util.files.ion;
|
||||
|
||||
|
||||
public class IonException extends Exception {
|
||||
|
||||
public IonException() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
public IonException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
||||
public IonException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
public IonException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package mightypork.util.files.ion;
|
||||
|
||||
|
||||
/**
|
||||
* Ionizable Arraylist
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class IonList extends AbstractIonList<Object> {
|
||||
|
||||
public Ionizable getIonizable(int index) throws IonException
|
||||
{
|
||||
return (Ionizable) getCheckType(index, Ionizable.class);
|
||||
}
|
||||
|
||||
|
||||
public boolean getBoolean(int index) throws IonException
|
||||
{
|
||||
return (Boolean) getCheckType(index, Boolean.class);
|
||||
}
|
||||
|
||||
|
||||
public byte getByte(int index) throws IonException
|
||||
{
|
||||
return (Byte) getCheckType(index, Byte.class);
|
||||
}
|
||||
|
||||
|
||||
public char getChar(int index) throws IonException
|
||||
{
|
||||
return (Character) getCheckType(index, Character.class);
|
||||
}
|
||||
|
||||
|
||||
public short getShort(int index) throws IonException
|
||||
{
|
||||
return (Short) getCheckType(index, Short.class);
|
||||
}
|
||||
|
||||
|
||||
public int getInt(int index) throws IonException
|
||||
{
|
||||
return (Integer) getCheckType(index, Integer.class);
|
||||
}
|
||||
|
||||
|
||||
public long getLong(int index) throws IonException
|
||||
{
|
||||
return (Long) getCheckType(index, Long.class);
|
||||
}
|
||||
|
||||
|
||||
public float getFloat(int index) throws IonException
|
||||
{
|
||||
return (Float) getCheckType(index, Float.class);
|
||||
}
|
||||
|
||||
|
||||
public double getDouble(int index) throws IonException
|
||||
{
|
||||
return (Double) getCheckType(index, Double.class);
|
||||
}
|
||||
|
||||
|
||||
public String getString(int index) throws IonException
|
||||
{
|
||||
return (String) getCheckType(index, String.class);
|
||||
}
|
||||
|
||||
|
||||
public void addIonizable(Ionizable o) throws IonException
|
||||
{
|
||||
assertNotNull(o);
|
||||
}
|
||||
|
||||
|
||||
public void addBoolean(boolean o) throws IonException
|
||||
{
|
||||
assertNotNull(o);
|
||||
}
|
||||
|
||||
|
||||
public void addByte(byte o) throws IonException
|
||||
{
|
||||
assertNotNull(o);
|
||||
}
|
||||
|
||||
|
||||
public void addChar(char o) throws IonException
|
||||
{
|
||||
assertNotNull(o);
|
||||
}
|
||||
|
||||
|
||||
public void addShort(short o) throws IonException
|
||||
{
|
||||
assertNotNull(o);
|
||||
}
|
||||
|
||||
|
||||
public void addInt(int o) throws IonException
|
||||
{
|
||||
assertNotNull(o);
|
||||
}
|
||||
|
||||
|
||||
public void addLong(long o) throws IonException
|
||||
{
|
||||
assertNotNull(o);
|
||||
}
|
||||
|
||||
|
||||
public void addFloat(float o) throws IonException
|
||||
{
|
||||
assertNotNull(o);
|
||||
}
|
||||
|
||||
|
||||
public void addDouble(double o) throws IonException
|
||||
{
|
||||
assertNotNull(o);
|
||||
}
|
||||
|
||||
|
||||
public void addString(String o) throws IonException
|
||||
{
|
||||
assertNotNull(o);
|
||||
}
|
||||
|
||||
|
||||
public Object getCheckType(int index, Class<?> type) throws IonException
|
||||
{
|
||||
try {
|
||||
final Object o = super.get(index);
|
||||
if (o == null || !o.getClass().isAssignableFrom(type)) {
|
||||
throw new IonException("Incorrect object type");
|
||||
}
|
||||
return o;
|
||||
} catch (final IndexOutOfBoundsException e) {
|
||||
throw new IonException("Out of bounds");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void assertNotNull(Object o) throws IonException
|
||||
{
|
||||
if (o == null) throw new IonException("Cannot store null");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte ionMark()
|
||||
{
|
||||
return IonMarks.LIST;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package mightypork.util.files.ion;
|
||||
|
||||
|
||||
/**
|
||||
* Ionizable HashMap
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class IonMap extends AbstractIonMap<Object> {
|
||||
|
||||
public boolean getBoolean(String key)
|
||||
{
|
||||
return (Boolean) get(key);
|
||||
}
|
||||
|
||||
|
||||
public boolean getBool(String key)
|
||||
{
|
||||
return (Boolean) get(key);
|
||||
}
|
||||
|
||||
|
||||
public byte getByte(String key)
|
||||
{
|
||||
return (Byte) get(key);
|
||||
}
|
||||
|
||||
|
||||
public char getChar(String key)
|
||||
{
|
||||
return (Character) get(key);
|
||||
}
|
||||
|
||||
|
||||
public short getShort(String key)
|
||||
{
|
||||
return (Short) get(key);
|
||||
}
|
||||
|
||||
|
||||
public int getInt(String key)
|
||||
{
|
||||
return (Integer) get(key);
|
||||
}
|
||||
|
||||
|
||||
public long getLong(String key)
|
||||
{
|
||||
return (Long) get(key);
|
||||
}
|
||||
|
||||
|
||||
public float getFloat(String key)
|
||||
{
|
||||
return (Float) get(key);
|
||||
}
|
||||
|
||||
|
||||
public double getDouble(String key)
|
||||
{
|
||||
return (Double) get(key);
|
||||
}
|
||||
|
||||
|
||||
public String getString(String key)
|
||||
{
|
||||
return (String) get(key);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object get(Object arg0)
|
||||
{
|
||||
return super.get(arg0);
|
||||
}
|
||||
|
||||
|
||||
public void putBoolean(String key, boolean num)
|
||||
{
|
||||
put(key, num);
|
||||
}
|
||||
|
||||
|
||||
public void putBool(String key, boolean num)
|
||||
{
|
||||
put(key, num);
|
||||
}
|
||||
|
||||
|
||||
public void putByte(String key, int num)
|
||||
{
|
||||
put(key, (byte) num);
|
||||
}
|
||||
|
||||
|
||||
public void putChar(String key, char num)
|
||||
{
|
||||
put(key, num);
|
||||
}
|
||||
|
||||
|
||||
public void putCharacter(String key, char num)
|
||||
{
|
||||
put(key, num);
|
||||
}
|
||||
|
||||
|
||||
public void putShort(String key, int num)
|
||||
{
|
||||
put(key, num);
|
||||
}
|
||||
|
||||
|
||||
public void putInt(String key, int num)
|
||||
{
|
||||
put(key, num);
|
||||
}
|
||||
|
||||
|
||||
public void putInteger(String key, int num)
|
||||
{
|
||||
put(key, num);
|
||||
}
|
||||
|
||||
|
||||
public void putLong(String key, long num)
|
||||
{
|
||||
put(key, num);
|
||||
}
|
||||
|
||||
|
||||
public void putFloat(String key, double num)
|
||||
{
|
||||
put(key, (float) num);
|
||||
}
|
||||
|
||||
|
||||
public void putDouble(String key, double num)
|
||||
{
|
||||
put(key, num);
|
||||
}
|
||||
|
||||
|
||||
public void putString(String key, String num)
|
||||
{
|
||||
put(key, num);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte ionMark()
|
||||
{
|
||||
return IonMarks.MAP;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package mightypork.util.files.ion;
|
||||
|
||||
|
||||
/**
|
||||
* Byte marks used to structure data in Ion files.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class IonMarks {
|
||||
|
||||
/** Null value */
|
||||
public static final byte NULL = 0;
|
||||
|
||||
/** Boolean value */
|
||||
public static final byte BOOLEAN = 1;
|
||||
|
||||
/** Byte value */
|
||||
public static final byte BYTE = 2;
|
||||
|
||||
/** Character value */
|
||||
public static final byte CHAR = 3;
|
||||
|
||||
/** Short value */
|
||||
public static final byte SHORT = 4;
|
||||
|
||||
/** Integer value */
|
||||
public static final byte INT = 5;
|
||||
|
||||
/** Long value */
|
||||
public static final byte LONG = 6;
|
||||
|
||||
/** Float value */
|
||||
public static final byte FLOAT = 7;
|
||||
|
||||
/** Double value */
|
||||
public static final byte DOUBLE = 8;
|
||||
|
||||
/** String value */
|
||||
public static final byte STRING = 9;
|
||||
|
||||
/** List value (begin) - contains entries, ends with END */
|
||||
public static final byte LIST = 10;
|
||||
|
||||
/** Map value (begin) - contains entries, ends with END */
|
||||
public static final byte MAP = 11;
|
||||
|
||||
/**
|
||||
* List / Map entry<br>
|
||||
* In list directly followed by entry value. In map followed by (string) key
|
||||
* and the entry value.
|
||||
*/
|
||||
public static final byte ENTRY = 12;
|
||||
|
||||
/** End of List / Map */
|
||||
public static final byte END = 13;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package mightypork.util.files.ion;
|
||||
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
||||
/**
|
||||
* Object that can be saved to and loaded from Ion file.<br>
|
||||
* All classes implementing Ionizable must be registered to {@link Ion} using
|
||||
* Ion.registerIonizable(obj.class).
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface Ionizable {
|
||||
|
||||
/**
|
||||
* Load data from the input stream. Mark has already been read, begin
|
||||
* reading right after it.
|
||||
*
|
||||
* @param in input stream
|
||||
* @throws IonException
|
||||
*/
|
||||
public void ionRead(InputStream in) throws IonException;
|
||||
|
||||
|
||||
/**
|
||||
* Store data to output stream. mark has already been written, begin right
|
||||
* after it.
|
||||
*
|
||||
* @param out Output stream
|
||||
* @throws IonException
|
||||
*/
|
||||
public void ionWrite(OutputStream out) throws IonException;
|
||||
|
||||
|
||||
/**
|
||||
* Get Ion mark byte.
|
||||
*
|
||||
* @return Ion mark byte.
|
||||
*/
|
||||
public byte ionMark();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mightypork.util.files.ion;
|
||||
|
||||
|
||||
/**
|
||||
* Optional ionizable
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface IonizableOptional extends Ionizable {
|
||||
|
||||
/**
|
||||
* Get if this ionizable should be saved to a list
|
||||
*
|
||||
* @return should save
|
||||
*/
|
||||
public boolean ionShouldSave();
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package mightypork.util.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.util.files.FileUtils;
|
||||
import mightypork.util.logging.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Class for building a zip file
|
||||
*
|
||||
* @author 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,179 @@
|
||||
package mightypork.util.files.zip;
|
||||
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import mightypork.util.files.FileUtils;
|
||||
import mightypork.util.logging.Log;
|
||||
import mightypork.util.string.validation.StringFilter;
|
||||
|
||||
|
||||
/**
|
||||
* Utilities for manipulating zip files
|
||||
*
|
||||
* @author 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.accept(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,380 @@
|
||||
package mightypork.util.logging;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import mightypork.util.annotations.FactoryMethod;
|
||||
import mightypork.util.logging.monitors.LogMonitor;
|
||||
import mightypork.util.logging.monitors.LogToSysoutMonitor;
|
||||
import mightypork.util.logging.writers.ArchivingLog;
|
||||
import mightypork.util.logging.writers.LogWriter;
|
||||
import mightypork.util.logging.writers.SimpleLog;
|
||||
import mightypork.util.string.StringUtils;
|
||||
|
||||
|
||||
/**
|
||||
* A log.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class Log {
|
||||
|
||||
private static LogWriter main = null;
|
||||
private static boolean enabled = true;
|
||||
private static final LogToSysoutMonitor sysoMonitor = new LogToSysoutMonitor();
|
||||
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)
|
||||
{
|
||||
|
||||
final String nl = System.getProperty("line.separator");
|
||||
|
||||
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 = StringUtils.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;
|
||||
}
|
||||
|
||||
|
||||
public static String str(Object o)
|
||||
{
|
||||
|
||||
boolean hasToString = false;
|
||||
|
||||
try {
|
||||
hasToString = (o.getClass().getMethod("toString").getDeclaringClass() != Object.class);
|
||||
} catch (final Exception e) {
|
||||
// oh well..
|
||||
}
|
||||
|
||||
if (hasToString) {
|
||||
return o.toString();
|
||||
} else {
|
||||
|
||||
return str(o.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String str(Class<?> cls)
|
||||
{
|
||||
final LogAlias ln = cls.getAnnotation(LogAlias.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 ? "" : str(enclosing) + sep) + name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package mightypork.util.logging;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
|
||||
/**
|
||||
* Specify pretty name to be used when logging (eg. <code>Log.str()</code>)
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface LogAlias {
|
||||
|
||||
String name();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package mightypork.util.logging.monitors;
|
||||
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
|
||||
public abstract class BaseLogMonitor implements LogMonitor {
|
||||
|
||||
private boolean enabled = true;
|
||||
private Level accepted = Level.ALL;
|
||||
|
||||
|
||||
@Override
|
||||
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);
|
||||
|
||||
|
||||
@Override
|
||||
public void setLevel(Level level)
|
||||
{
|
||||
this.accepted = level;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void enable(boolean flag)
|
||||
{
|
||||
this.enabled = flag;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mightypork.util.logging.monitors;
|
||||
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
|
||||
public interface LogMonitor {
|
||||
|
||||
void onMessageLogged(Level level, String message);
|
||||
|
||||
|
||||
void setLevel(Level level);
|
||||
|
||||
|
||||
void enable(boolean flag);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package mightypork.util.logging.monitors;
|
||||
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
|
||||
public class LogToSysoutMonitor extends BaseLogMonitor {
|
||||
|
||||
@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,127 @@
|
||||
package mightypork.util.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.util.files.FileUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Logger that cleans directory & archives old logs
|
||||
*
|
||||
* @author 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 FileUtils.getExtension(getFile());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package mightypork.util.logging.writers;
|
||||
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import mightypork.util.logging.monitors.LogMonitor;
|
||||
|
||||
|
||||
/**
|
||||
* Log interface
|
||||
*
|
||||
* @author 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,165 @@
|
||||
package mightypork.util.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.util.logging.Log;
|
||||
import mightypork.util.logging.monitors.LogMonitor;
|
||||
|
||||
|
||||
/**
|
||||
* Basic logger
|
||||
*
|
||||
* @author 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 Exception e) {
|
||||
throw new RuntimeException("Failed to init log.", e);
|
||||
}
|
||||
|
||||
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,934 @@
|
||||
package mightypork.util.math;
|
||||
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Math helper
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class Calc {
|
||||
|
||||
/** 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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get longest side of a right-angled triangle
|
||||
*
|
||||
* @param a side a (opposite)
|
||||
* @param b side b (adjacent)
|
||||
* @return longest side (hypotenuse)
|
||||
*/
|
||||
public static double pythC(double a, double b)
|
||||
{
|
||||
return Math.sqrt(square(a) + square(b));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get adjacent side of a right-angled triangle
|
||||
*
|
||||
* @param a side a (opposite)
|
||||
* @param c side c (hypotenuse)
|
||||
* @return side b (adjacent)
|
||||
*/
|
||||
public static double pythB(double a, double c)
|
||||
{
|
||||
return Math.sqrt(square(c) - square(a));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get opposite side of a right-angled triangle
|
||||
*
|
||||
* @param b side b (adjacent)
|
||||
* @param c side c (hypotenuse)
|
||||
* @return side a (opposite)
|
||||
*/
|
||||
public static double pythA(double b, double c)
|
||||
{
|
||||
return Math.sqrt(square(c) - square(b));
|
||||
}
|
||||
|
||||
private static class Angles {
|
||||
|
||||
public static double delta(double alpha, double beta, double a360)
|
||||
{
|
||||
while (Math.abs(alpha - beta) > a360 / 2D) {
|
||||
alpha = norm(alpha + a360 / 2D, a360);
|
||||
beta = norm(beta + a360 / 2D, a360);
|
||||
}
|
||||
|
||||
return beta - alpha;
|
||||
}
|
||||
|
||||
|
||||
public static double norm(double angle, double a360)
|
||||
{
|
||||
while (angle < 0)
|
||||
angle += a360;
|
||||
while (angle > a360)
|
||||
angle -= a360;
|
||||
return angle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calc subclass with buffer utils.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public static class Buffers {
|
||||
|
||||
/**
|
||||
* Create java.nio.FloatBuffer of given floats, and flip it.
|
||||
*
|
||||
* @param obj floats or float array
|
||||
* @return float buffer
|
||||
*/
|
||||
public static FloatBuffer mkFillBuff(float... obj)
|
||||
{
|
||||
return (FloatBuffer) BufferUtils.createFloatBuffer(obj.length).put(obj).flip();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fill java.nio.FloatBuffer with floats or float array
|
||||
*
|
||||
* @param buff
|
||||
* @param obj
|
||||
*/
|
||||
public static void fill(FloatBuffer buff, float... obj)
|
||||
{
|
||||
buff.put(obj);
|
||||
buff.flip();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create new java.nio.FloatBuffer of given length
|
||||
*
|
||||
* @param count elements
|
||||
* @return the new java.nio.FloatBuffer
|
||||
*/
|
||||
public static FloatBuffer alloc(int count)
|
||||
{
|
||||
return BufferUtils.createFloatBuffer(count);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Angle calculations for degrees.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public static 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 Math.abs(Angles.delta(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 x rounding increment (45 - round to 0,45,90...)
|
||||
* @return rounded
|
||||
*/
|
||||
public static int roundX(double deg, double x)
|
||||
{
|
||||
final double half = x / 2d;
|
||||
deg += half;
|
||||
deg = norm(deg);
|
||||
final int times = (int) Math.floor(deg / x);
|
||||
double a = times * x;
|
||||
if (a == 360) a = 0;
|
||||
return (int) Math.round(a);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Round angle to 0,45,90,135...
|
||||
*
|
||||
* @param deg angle in deg. to round
|
||||
* @return rounded
|
||||
*/
|
||||
public static int round45(double deg)
|
||||
{
|
||||
return roundX(deg, 45);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Round angle to 0,90,180,270
|
||||
*
|
||||
* @param deg angle in deg. to round
|
||||
* @return rounded
|
||||
*/
|
||||
public static int round90(double deg)
|
||||
{
|
||||
return roundX(deg, 90);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Round angle to 0,15,30,45,60,75,90...
|
||||
*
|
||||
* @param deg angle in deg to round
|
||||
* @return rounded
|
||||
*/
|
||||
public static int round15(double deg)
|
||||
{
|
||||
return roundX(deg, 15);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Angle calculations for radians.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public static 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 Math.abs(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);
|
||||
}
|
||||
}
|
||||
|
||||
private static Random rand = new Random();
|
||||
|
||||
|
||||
/**
|
||||
* Get volume of a sphere
|
||||
*
|
||||
* @param radius sphere radius
|
||||
* @return volume in cubic units
|
||||
*/
|
||||
public static double sphereGetVolume(double radius)
|
||||
{
|
||||
return (4D / 3D) * Math.PI * cube(radius);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get radius of a sphere
|
||||
*
|
||||
* @param volume sphere volume
|
||||
* @return radius in units
|
||||
*/
|
||||
public static double sphereGetRadius(double volume)
|
||||
{
|
||||
return Math.cbrt((3D * volume) / (4 * Math.PI));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get surface of a circle
|
||||
*
|
||||
* @param radius circle radius
|
||||
* @return volume in square units
|
||||
*/
|
||||
public static double circleGetSurface(double radius)
|
||||
{
|
||||
return Math.PI * square(radius);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get radius of a circle
|
||||
*
|
||||
* @param surface circle volume
|
||||
* @return radius in units
|
||||
*/
|
||||
public static double circleGetRadius(double surface)
|
||||
{
|
||||
return Math.sqrt(surface / Math.PI);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if objects are equal (for equals function)
|
||||
*
|
||||
* @param a
|
||||
* @param b
|
||||
* @return are equal
|
||||
*/
|
||||
public static boolean areObjectsEqual(Object a, Object b)
|
||||
{
|
||||
return a == null ? b == null : a.equals(b);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Private clamping helper.
|
||||
*
|
||||
* @param number number to be clamped
|
||||
* @param min min value
|
||||
* @param max max value
|
||||
* @return clamped double
|
||||
*/
|
||||
private static double clamp_double(Number number, Number min, Number max)
|
||||
{
|
||||
double n = number.doubleValue();
|
||||
final double mind = min.doubleValue();
|
||||
final double maxd = max.doubleValue();
|
||||
if (n > maxd) n = maxd;
|
||||
if (n < mind) n = mind;
|
||||
if (Double.isNaN(n)) return mind;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Private clamping helper.
|
||||
*
|
||||
* @param number number to be clamped
|
||||
* @param min min value
|
||||
* @return clamped double
|
||||
*/
|
||||
private static double clamp_double(Number number, Number min)
|
||||
{
|
||||
double n = number.doubleValue();
|
||||
final double mind = min.doubleValue();
|
||||
if (n < mind) n = mind;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamp number to min and max bounds, inclusive.<br>
|
||||
* DOUBLE version
|
||||
*
|
||||
* @param number clamped number
|
||||
* @param min minimal allowed value
|
||||
* @param max maximal allowed value
|
||||
* @return result
|
||||
*/
|
||||
public static double clampd(Number number, Number min, Number max)
|
||||
{
|
||||
return clamp_double(number, min, max);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamp number to min and max bounds, inclusive.<br>
|
||||
* FLOAT version
|
||||
*
|
||||
* @param number clamped number
|
||||
* @param min minimal allowed value
|
||||
* @param max maximal allowed value
|
||||
* @return result
|
||||
*/
|
||||
public static float clampf(Number number, Number min, Number max)
|
||||
{
|
||||
return (float) clamp_double(number, min, max);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamp number to min and max bounds, inclusive.<br>
|
||||
* INTEGER version
|
||||
*
|
||||
* @param number clamped number
|
||||
* @param min minimal allowed value
|
||||
* @param max maximal allowed value
|
||||
* @return result
|
||||
*/
|
||||
public static int clampi(Number number, Number min, Number max)
|
||||
{
|
||||
return (int) Math.round(clamp_double(number, min, max));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamp number to min and max bounds, inclusive.<br>
|
||||
* INTEGER version
|
||||
*
|
||||
* @param number clamped number
|
||||
* @param range range
|
||||
* @return result
|
||||
*/
|
||||
public static int clampi(Number number, Range range)
|
||||
{
|
||||
return (int) Math.round(clamp_double(number, range.getMin(), range.getMax()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamp number to min and max bounds, inclusive.<br>
|
||||
* DOUBLE version
|
||||
*
|
||||
* @param number clamped number
|
||||
* @param range range
|
||||
* @return result
|
||||
*/
|
||||
public static double clampd(Number number, Range range)
|
||||
{
|
||||
return clamp_double(number, range.getMin(), range.getMax());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamp number to min and max bounds, inclusive.<br>
|
||||
* FLOAT version
|
||||
*
|
||||
* @param number clamped number
|
||||
* @param range range
|
||||
* @return result
|
||||
*/
|
||||
public static float clampf(Number number, Range range)
|
||||
{
|
||||
return (float) clamp_double(number, range.getMin(), range.getMax());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamp number to min and infinite bounds, inclusive.<br>
|
||||
* DOUBLE version
|
||||
*
|
||||
* @param number clamped number
|
||||
* @param min minimal allowed value
|
||||
* @return result
|
||||
*/
|
||||
public static double clampd(Number number, Number min)
|
||||
{
|
||||
return clamp_double(number, min);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamp number to min and infinite bounds, inclusive.<br>
|
||||
* FLOAT version
|
||||
*
|
||||
* @param number clamped number
|
||||
* @param min minimal allowed value
|
||||
* @return result
|
||||
*/
|
||||
public static float clampf(Number number, Number min)
|
||||
{
|
||||
return (float) clamp_double(number, min);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clamp number to min and infinite bounds, inclusive.<br>
|
||||
* INTEGER version
|
||||
*
|
||||
* @param number clamped number
|
||||
* @param min minimal allowed value
|
||||
* @return result
|
||||
*/
|
||||
public static int clampi(Number number, Number min)
|
||||
{
|
||||
return (int) Math.round(clamp_double(number, min));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get class simple name
|
||||
*
|
||||
* @param obj object
|
||||
* @return simple name
|
||||
*/
|
||||
public static String cname(Object obj)
|
||||
{
|
||||
if (obj == null) return "NULL";
|
||||
return obj.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cube a double
|
||||
*
|
||||
* @param a squared double
|
||||
* @return square
|
||||
*/
|
||||
public static double cube(double a)
|
||||
{
|
||||
return a * a * a;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert double to string, remove the mess at the end.
|
||||
*
|
||||
* @param d double
|
||||
* @return string
|
||||
*/
|
||||
public static String toString(double d)
|
||||
{
|
||||
String s = Double.toString(d);
|
||||
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 toString(float f)
|
||||
{
|
||||
String s = Float.toString(f);
|
||||
s = s.replaceAll("([0-9]+\\.[0-9]+)00+[0-9]+", "$1");
|
||||
s = s.replaceAll("0+$", "");
|
||||
s = s.replaceAll("\\.$", "");
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if number is in range
|
||||
*
|
||||
* @param number checked
|
||||
* @param left lower end
|
||||
* @param right upper end
|
||||
* @return is in range
|
||||
*/
|
||||
public static boolean inRange(double number, double left, double right)
|
||||
{
|
||||
return number >= left && number <= right;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get number from A to B at delta time (tween A to B)
|
||||
*
|
||||
* @param from last number
|
||||
* @param to new number
|
||||
* @param time time 0..1
|
||||
* @param easing easing function
|
||||
* @return current number to render
|
||||
*/
|
||||
public static double interpolate(double from, double to, double time, Easing easing)
|
||||
{
|
||||
return from + (to - from) * easing.get(time);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get angle [degrees] from A to B at delta time (tween A to B)
|
||||
*
|
||||
* @param from last angle
|
||||
* @param to new angle
|
||||
* @param time time 0..1
|
||||
* @param easing easing function
|
||||
* @return current angle to render
|
||||
*/
|
||||
public static double interpolateDeg(double from, double to, double time, Easing easing)
|
||||
{
|
||||
return Deg.norm(from - Deg.delta(to, from) * easing.get(time));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get angle [radians] from A to B at delta time (tween A to B)
|
||||
*
|
||||
* @param from last angle
|
||||
* @param to new angle
|
||||
* @param time time 0..1
|
||||
* @param easing easing function
|
||||
* @return current angle to render
|
||||
*/
|
||||
public static double interpolateRad(double from, double to, double time, Easing easing)
|
||||
{
|
||||
return Rad.norm(from - Rad.delta(to, from) * easing.get(time));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get highest number of a list
|
||||
*
|
||||
* @param numbers numbers
|
||||
* @return lowest
|
||||
*/
|
||||
public static double max(double... numbers)
|
||||
{
|
||||
double highest = numbers[0];
|
||||
for (final double num : numbers) {
|
||||
if (num > highest) highest = num;
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get highest number of a list
|
||||
*
|
||||
* @param numbers numbers
|
||||
* @return lowest
|
||||
*/
|
||||
public static float max(float... numbers)
|
||||
{
|
||||
float highest = numbers[0];
|
||||
for (final float num : numbers) {
|
||||
if (num > highest) highest = num;
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get highest number of a list
|
||||
*
|
||||
* @param numbers numbers
|
||||
* @return lowest
|
||||
*/
|
||||
public static int max(int... numbers)
|
||||
{
|
||||
int highest = numbers[0];
|
||||
for (final int num : numbers) {
|
||||
if (num > highest) highest = num;
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get lowest number of a list
|
||||
*
|
||||
* @param numbers numbers
|
||||
* @return lowest
|
||||
*/
|
||||
public static double min(double... numbers)
|
||||
{
|
||||
double lowest = numbers[0];
|
||||
for (final double num : numbers) {
|
||||
if (num < lowest) lowest = num;
|
||||
}
|
||||
return lowest;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get lowest number of a list
|
||||
*
|
||||
* @param numbers numbers
|
||||
* @return lowest
|
||||
*/
|
||||
public static float min(float... numbers)
|
||||
{
|
||||
float lowest = numbers[0];
|
||||
for (final float num : numbers) {
|
||||
if (num < lowest) lowest = num;
|
||||
}
|
||||
return lowest;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get lowest number of a list
|
||||
*
|
||||
* @param numbers numbers
|
||||
* @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.
|
||||
* @return array of integers or null.
|
||||
*/
|
||||
public static List<Integer> parseIntList(String list)
|
||||
{
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
final String[] parts = list.split(",");
|
||||
|
||||
final ArrayList<Integer> intList = new ArrayList<>();
|
||||
|
||||
for (final String part : parts) {
|
||||
try {
|
||||
intList.add(Integer.parseInt(part));
|
||||
} catch (final NumberFormatException e) {}
|
||||
}
|
||||
|
||||
return intList;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick random element from a given list.
|
||||
*
|
||||
* @param list list of choices
|
||||
* @return picked element
|
||||
*/
|
||||
public static Object pick(List<?> list)
|
||||
{
|
||||
if (list.size() == 0) return null;
|
||||
return list.get(rand.nextInt(list.size()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Square a double
|
||||
*
|
||||
* @param a squared double
|
||||
* @return square
|
||||
*/
|
||||
public static double square(double a)
|
||||
{
|
||||
return a * a;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Signum.
|
||||
*
|
||||
* @param number
|
||||
* @return sign, -1,0,1
|
||||
*/
|
||||
public static int sgn(double number)
|
||||
{
|
||||
return number > 0 ? 1 : number < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
|
||||
public static double frag(double d)
|
||||
{
|
||||
return d - Math.floor(d);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
package mightypork.util.math;
|
||||
|
||||
|
||||
import mightypork.util.annotations.FactoryMethod;
|
||||
|
||||
|
||||
/**
|
||||
* EasingFunction function.
|
||||
*
|
||||
* @author 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
|
||||
*
|
||||
* @param original original easing
|
||||
* @return reversed easing
|
||||
*/
|
||||
@FactoryMethod
|
||||
public static Easing reverse(Easing original)
|
||||
{
|
||||
return new Reverse(original);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Combine two easings
|
||||
*
|
||||
* @param in initial easing
|
||||
* @param out terminal easing
|
||||
* @return product
|
||||
*/
|
||||
@FactoryMethod
|
||||
public static Easing combine(Easing in, Easing out)
|
||||
{
|
||||
return new Composite(in, out);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create "bilinear" easing - compose of straight and reverse.
|
||||
*
|
||||
* @param in initial easing
|
||||
* @return product
|
||||
*/
|
||||
@FactoryMethod
|
||||
public static Easing inOut(Easing in)
|
||||
{
|
||||
return combine(in, reverse(in));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse EasingFunction
|
||||
*
|
||||
* @author 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 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,190 @@
|
||||
package mightypork.util.math;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
|
||||
|
||||
/**
|
||||
* Polar coordinate
|
||||
*
|
||||
* @author 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,182 @@
|
||||
package mightypork.util.math;
|
||||
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
/**
|
||||
* Numeric range, able to generate random numbers and give min/max values.
|
||||
*
|
||||
* @author 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;
|
||||
|
||||
private static Random rand = new Random();
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 (int) (Math.round(min) + rand.nextInt((int) (Math.round(max) - Math.round(min)) + 1));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get random double from this range
|
||||
*
|
||||
* @return random double
|
||||
*/
|
||||
public double randDouble()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("{%.1f|%.1f}", min, max);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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,212 @@
|
||||
package mightypork.util.math.color;
|
||||
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import mightypork.util.annotations.FactoryMethod;
|
||||
import mightypork.util.math.Calc;
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
|
||||
|
||||
/**
|
||||
* Color.<br>
|
||||
* All values are 0-1
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class Color {
|
||||
|
||||
public static final Color NONE = rgba(0, 0, 0, 0);
|
||||
public static final Color SHADOW = rgba(0, 0, 0, 0.5);
|
||||
|
||||
public static final Color WHITE = rgb(1, 1, 1);
|
||||
public static final Color BLACK = rgb(0, 0, 0);
|
||||
public static final Color DARK_GRAY = rgb(0.25, 0.25, 0.25);
|
||||
public static final Color GRAY = rgb(0.5, 0.5, 0.5);
|
||||
public static final Color LIGHT_GRAY = rgb(0.75, 0.75, 0.75);
|
||||
|
||||
public static final Color RED = rgb(1, 0, 0);
|
||||
public static final Color GREEN = rgb(0, 1, 0);
|
||||
public static final Color BLUE = rgb(0, 0, 1);
|
||||
|
||||
public static final Color YELLOW = rgb(1, 1, 0);
|
||||
public static final Color MAGENTA = rgb(1, 0, 1);
|
||||
public static final Color CYAN = rgb(0, 1, 1);
|
||||
|
||||
public static final Color ORANGE = rgb(1, 0.78, 0);
|
||||
public static final Color PINK = rgb(1, 0.68, 0.68);
|
||||
|
||||
private static final Stack<Num> globalAlphaStack = new Stack<>();
|
||||
|
||||
|
||||
@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 rgba(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.clampd(n.value(), 0, 1);
|
||||
}
|
||||
|
||||
|
||||
protected static final double clamp(double n)
|
||||
{
|
||||
return Calc.clampd(n, 0, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return red 0-1
|
||||
*/
|
||||
public abstract double red();
|
||||
|
||||
|
||||
/**
|
||||
* @return green 0-1
|
||||
*/
|
||||
public abstract double green();
|
||||
|
||||
|
||||
/**
|
||||
* @return blue 0-1
|
||||
*/
|
||||
public abstract double blue();
|
||||
|
||||
|
||||
/**
|
||||
* @return alpha 0-1
|
||||
*/
|
||||
public final double alpha()
|
||||
{
|
||||
double alpha = rawAlpha();
|
||||
|
||||
for (Num n : globalAlphaStack) {
|
||||
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)
|
||||
{
|
||||
globalAlphaStack.push(alpha);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a pushed alpha multiplier from the stack. If there's no remaining
|
||||
* multiplier on the stack, an exception is raised.
|
||||
*
|
||||
* @return the popped multiplier
|
||||
* @throws IllegalStateException if the stack is empty
|
||||
*/
|
||||
public static Num popAlpha()
|
||||
{
|
||||
if (globalAlphaStack.isEmpty()) {
|
||||
throw new IllegalStateException("Global alpha stack underflow.");
|
||||
}
|
||||
|
||||
return globalAlphaStack.pop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package mightypork.util.math.color;
|
||||
|
||||
|
||||
import mightypork.util.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 red()
|
||||
{
|
||||
return asRgb()[0];
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double green()
|
||||
{
|
||||
return asRgb()[1];
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double blue()
|
||||
{
|
||||
return asRgb()[2];
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double rawAlpha()
|
||||
{
|
||||
return asRgb()[3];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package mightypork.util.math.color;
|
||||
|
||||
|
||||
import mightypork.util.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 red()
|
||||
{
|
||||
return clamp(r);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double green()
|
||||
{
|
||||
return clamp(g);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double blue()
|
||||
{
|
||||
return clamp(b);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double rawAlpha()
|
||||
{
|
||||
return clamp(a);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package mightypork.util.math.constraints;
|
||||
|
||||
|
||||
/**
|
||||
* Constraint cache
|
||||
*
|
||||
* @author MightyPork
|
||||
* @param <C> constraint type
|
||||
*/
|
||||
public interface ConstraintCache<C> extends Pollable {
|
||||
|
||||
/**
|
||||
* Called after the cache has changed value (and digest).
|
||||
*/
|
||||
void onChange();
|
||||
|
||||
|
||||
/**
|
||||
* @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,60 @@
|
||||
package mightypork.util.math.constraints;
|
||||
|
||||
|
||||
/**
|
||||
* Parametrized implementation of a {@link Digestable}
|
||||
*
|
||||
* @author MightyPork
|
||||
* @param <D> digest class
|
||||
*/
|
||||
public abstract class DigestCache<D> implements Digestable<D> {
|
||||
|
||||
private D last_digest;
|
||||
private boolean caching_enabled;
|
||||
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,60 @@
|
||||
package mightypork.util.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 MightyPork
|
||||
* @param <D> digest class
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public D digest();
|
||||
|
||||
|
||||
/**
|
||||
* <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,15 @@
|
||||
package mightypork.util.math.constraints;
|
||||
|
||||
|
||||
/**
|
||||
* Can be asked to update it's state
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface Pollable {
|
||||
|
||||
/**
|
||||
* Update internal state
|
||||
*/
|
||||
void poll();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package mightypork.util.math.constraints;
|
||||
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* Used to poll a number of {@link Pollable}s
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class Poller implements Pollable {
|
||||
|
||||
private final Set<Pollable> pollables = new LinkedHashSet<>();
|
||||
|
||||
|
||||
/**
|
||||
* Add a pollable
|
||||
*
|
||||
* @param p pollable
|
||||
*/
|
||||
public void add(Pollable p)
|
||||
{
|
||||
pollables.add(p);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a pollalbe
|
||||
*
|
||||
* @param p pollable
|
||||
*/
|
||||
public void remove(Pollable p)
|
||||
{
|
||||
pollables.remove(p);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void poll()
|
||||
{
|
||||
for (final Pollable p : pollables) {
|
||||
p.poll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,750 @@
|
||||
package mightypork.util.math.constraints.num;
|
||||
|
||||
|
||||
import mightypork.util.annotations.FactoryMethod;
|
||||
import mightypork.util.math.Calc;
|
||||
import mightypork.util.math.constraints.DigestCache;
|
||||
import mightypork.util.math.constraints.Digestable;
|
||||
import mightypork.util.math.constraints.num.caching.NumCache;
|
||||
import mightypork.util.math.constraints.num.caching.NumDigest;
|
||||
import mightypork.util.math.constraints.num.mutable.NumVar;
|
||||
import mightypork.util.math.constraints.num.proxy.NumBound;
|
||||
import mightypork.util.math.constraints.num.proxy.NumBoundAdapter;
|
||||
|
||||
|
||||
public abstract class Num implements NumBound, Digestable<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 NumBoundAdapter(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 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 / 100);
|
||||
}
|
||||
|
||||
|
||||
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 Calc.toString(value());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package mightypork.util.math.constraints.num;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.num.caching.NumDigest;
|
||||
|
||||
|
||||
/**
|
||||
* Constant number.<br>
|
||||
* It's arranged so that operations with constant arguments yield constant
|
||||
* results.
|
||||
*
|
||||
* @author 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,83 @@
|
||||
package mightypork.util.math.constraints.num.caching;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.ConstraintCache;
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
import mightypork.util.math.constraints.num.mutable.NumVar;
|
||||
import mightypork.util.math.constraints.num.proxy.NumAdapter;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A Num cache.
|
||||
* </p>
|
||||
* <p>
|
||||
* Values are held in a caching VectVar, and digest caching is enabled by
|
||||
* default.
|
||||
* </p>
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class AbstractNumCache extends NumAdapter implements ConstraintCache<Num> {
|
||||
|
||||
private final NumVar cache = Num.makeVar();
|
||||
private boolean inited = false;
|
||||
private boolean cachingEnabled = true;
|
||||
|
||||
|
||||
public AbstractNumCache() {
|
||||
enableDigestCaching(true); // it changes only on poll
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final Num getSource()
|
||||
{
|
||||
if (!inited) markDigestDirty();
|
||||
|
||||
return (cachingEnabled ? cache : getCacheSource());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void poll()
|
||||
{
|
||||
inited = true;
|
||||
|
||||
// poll source
|
||||
final Num source = getCacheSource();
|
||||
source.markDigestDirty(); // poll cached
|
||||
|
||||
// store source value
|
||||
cache.setTo(source);
|
||||
|
||||
// mark my digest dirty
|
||||
markDigestDirty();
|
||||
|
||||
onChange();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public abstract void onChange();
|
||||
|
||||
|
||||
@Override
|
||||
public abstract Num getCacheSource();
|
||||
|
||||
|
||||
@Override
|
||||
public final void enableCaching(boolean yes)
|
||||
{
|
||||
cachingEnabled = yes;
|
||||
enableDigestCaching(yes);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean isCachingEnabled()
|
||||
{
|
||||
return cachingEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package mightypork.util.math.constraints.num.caching;
|
||||
|
||||
|
||||
import mightypork.util.annotations.DefaultImpl;
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
|
||||
|
||||
/**
|
||||
* Num cache implementation
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class NumCache extends AbstractNumCache {
|
||||
|
||||
private final Num source;
|
||||
|
||||
|
||||
public NumCache(Num source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final Num getCacheSource()
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@DefaultImpl
|
||||
public void onChange()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package mightypork.util.math.constraints.num.caching;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
|
||||
|
||||
public class NumDigest {
|
||||
|
||||
public final double value;
|
||||
|
||||
|
||||
public NumDigest(Num num) {
|
||||
this.value = num.value();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("Num(%.1f)", value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
package mightypork.util.math.constraints.num.mutable;
|
||||
|
||||
|
||||
import mightypork.util.control.timing.Pauseable;
|
||||
import mightypork.util.control.timing.Updateable;
|
||||
import mightypork.util.math.Calc;
|
||||
import mightypork.util.math.Easing;
|
||||
|
||||
|
||||
/**
|
||||
* Double which supports delta timing
|
||||
*
|
||||
* @author 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 easing = Easing.LINEAR;
|
||||
|
||||
/** Default duration (seconds) */
|
||||
private double defaultDuration = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Create linear animator
|
||||
*
|
||||
* @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 as copy of another
|
||||
*
|
||||
* @param other other animator
|
||||
*/
|
||||
public NumAnimated(NumAnimated other) {
|
||||
setTo(other);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return easing function
|
||||
*/
|
||||
public Easing getEasing()
|
||||
{
|
||||
return easing;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param easing easing function
|
||||
*/
|
||||
public void setEasing(Easing easing)
|
||||
{
|
||||
this.easing = easing;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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), easing);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.clampd(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 = defaultDuration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.easing = other.easing;
|
||||
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)
|
||||
{
|
||||
animate(0, 1, time);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Animate 1 to 0
|
||||
*
|
||||
* @param time animation time (secs)
|
||||
*/
|
||||
public void fadeOut(double time)
|
||||
{
|
||||
animate(1, 0, time);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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,50 @@
|
||||
package mightypork.util.math.constraints.num.mutable;
|
||||
|
||||
|
||||
import mightypork.util.math.Calc;
|
||||
import mightypork.util.math.Calc.Deg;
|
||||
import mightypork.util.math.Easing;
|
||||
|
||||
|
||||
/**
|
||||
* Degree animator
|
||||
*
|
||||
* @author 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), easing);
|
||||
}
|
||||
|
||||
|
||||
@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,50 @@
|
||||
package mightypork.util.math.constraints.num.mutable;
|
||||
|
||||
|
||||
import mightypork.util.math.Calc;
|
||||
import mightypork.util.math.Calc.Rad;
|
||||
import mightypork.util.math.Easing;
|
||||
|
||||
|
||||
/**
|
||||
* Radians animator
|
||||
*
|
||||
* @author 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), easing);
|
||||
}
|
||||
|
||||
|
||||
@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,41 @@
|
||||
package mightypork.util.math.constraints.num.mutable;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
|
||||
|
||||
/**
|
||||
* Mutable numeric variable
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class NumMutable extends Num {
|
||||
|
||||
/**
|
||||
* Assign a value
|
||||
*
|
||||
* @param value new value
|
||||
*/
|
||||
public abstract void setTo(double value);
|
||||
|
||||
|
||||
/**
|
||||
* Assign a value
|
||||
*
|
||||
* @param value new value
|
||||
*/
|
||||
public void setTo(Num value)
|
||||
{
|
||||
setTo(value.value());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set to zero
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
setTo(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package mightypork.util.math.constraints.num.mutable;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
|
||||
|
||||
/**
|
||||
* Mutable numeric variable.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class NumVar extends NumMutable {
|
||||
|
||||
private double value;
|
||||
|
||||
|
||||
public NumVar(Num value) {
|
||||
this(value.value());
|
||||
}
|
||||
|
||||
|
||||
public NumVar(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setTo(double value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package mightypork.util.math.constraints.num.proxy;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
|
||||
|
||||
public abstract class NumAdapter extends Num {
|
||||
|
||||
protected abstract Num getSource();
|
||||
|
||||
|
||||
@Override
|
||||
public double value()
|
||||
{
|
||||
return getSource().value();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package mightypork.util.math.constraints.num.proxy;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
|
||||
|
||||
/**
|
||||
* Numeric constraint
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface NumBound {
|
||||
|
||||
/**
|
||||
* @return current value
|
||||
*/
|
||||
Num getNum();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package mightypork.util.math.constraints.num.proxy;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
|
||||
|
||||
public class NumBoundAdapter extends NumAdapter implements PluggableNumBound {
|
||||
|
||||
private NumBound backing = null;
|
||||
|
||||
|
||||
public NumBoundAdapter() {
|
||||
}
|
||||
|
||||
|
||||
public NumBoundAdapter(NumBound bound) {
|
||||
backing = bound;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setNum(NumBound rect)
|
||||
{
|
||||
this.backing = rect;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Num getSource()
|
||||
{
|
||||
return backing.getNum();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package mightypork.util.math.constraints.num.proxy;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
|
||||
|
||||
public class NumProxy extends NumAdapter {
|
||||
|
||||
private final Num source;
|
||||
|
||||
|
||||
public NumProxy(Num source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Num getSource()
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.util.math.constraints.num.proxy;
|
||||
|
||||
|
||||
/**
|
||||
* Pluggable numeric constraint
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface PluggableNumBound extends NumBound {
|
||||
|
||||
/**
|
||||
* @param num bound to set
|
||||
*/
|
||||
abstract void setNum(NumBound num);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,943 @@
|
||||
package mightypork.util.math.constraints.rect;
|
||||
|
||||
|
||||
import mightypork.util.annotations.FactoryMethod;
|
||||
import mightypork.util.math.constraints.DigestCache;
|
||||
import mightypork.util.math.constraints.Digestable;
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
import mightypork.util.math.constraints.num.NumConst;
|
||||
import mightypork.util.math.constraints.rect.builders.TiledRect;
|
||||
import mightypork.util.math.constraints.rect.caching.RectCache;
|
||||
import mightypork.util.math.constraints.rect.caching.RectDigest;
|
||||
import mightypork.util.math.constraints.rect.mutable.RectVar;
|
||||
import mightypork.util.math.constraints.rect.proxy.RectBound;
|
||||
import mightypork.util.math.constraints.rect.proxy.RectBoundAdapter;
|
||||
import mightypork.util.math.constraints.rect.proxy.RectVectAdapter;
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
import mightypork.util.math.constraints.vect.VectConst;
|
||||
|
||||
|
||||
/**
|
||||
* Common methods for all kinds of Rects
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class Rect implements RectBound, Digestable<RectDigest> {
|
||||
|
||||
public static final RectConst ZERO = new RectConst(0, 0, 0, 0);
|
||||
public static final RectConst ONE = new RectConst(0, 0, 1, 1);
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static Rect make(Num width, Num height)
|
||||
{
|
||||
final Vect origin = Vect.ZERO;
|
||||
final Vect size = Vect.make(width, height);
|
||||
|
||||
return Rect.make(origin, size);
|
||||
}
|
||||
|
||||
|
||||
public static Rect make(Vect size)
|
||||
{
|
||||
return Rect.make(size.xn(), size.yn());
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static Rect make(RectBound bound)
|
||||
{
|
||||
return new RectBoundAdapter(bound);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static Rect make(Num x, Num y, Num width, Num height)
|
||||
{
|
||||
final Vect origin = Vect.make(x, y);
|
||||
final Vect size = Vect.make(width, height);
|
||||
|
||||
return Rect.make(origin, size);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static Rect make(Vect origin, Num width, Num height)
|
||||
{
|
||||
return make(origin, Vect.make(width, height));
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static Rect make(final Vect origin, final Vect size)
|
||||
{
|
||||
return new RectVectAdapter(origin, size);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static RectConst make(NumConst width, NumConst height)
|
||||
{
|
||||
final VectConst origin = Vect.ZERO;
|
||||
final VectConst size = Vect.make(width, height);
|
||||
|
||||
return Rect.make(origin, size);
|
||||
}
|
||||
|
||||
|
||||
public static Rect make(Num side)
|
||||
{
|
||||
return make(side, side);
|
||||
}
|
||||
|
||||
|
||||
public static RectConst make(NumConst side)
|
||||
{
|
||||
return make(side, side);
|
||||
}
|
||||
|
||||
|
||||
public static RectConst make(double side)
|
||||
{
|
||||
return make(side, side);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static RectConst make(NumConst x, NumConst y, NumConst width, NumConst height)
|
||||
{
|
||||
final VectConst origin = Vect.make(x, y);
|
||||
final VectConst size = Vect.make(width, height);
|
||||
|
||||
return Rect.make(origin, size);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static RectConst make(final VectConst origin, final VectConst size)
|
||||
{
|
||||
return new RectConst(origin, size);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static RectConst make(double width, double height)
|
||||
{
|
||||
return Rect.make(0, 0, width, height);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static RectConst make(double x, double y, double width, double height)
|
||||
{
|
||||
return new RectConst(x, y, width, height);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static RectVar makeVar(double x, double y)
|
||||
{
|
||||
return Rect.makeVar(0, 0, x, y);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static RectVar makeVar(Rect copied)
|
||||
{
|
||||
return Rect.makeVar(copied.origin(), copied.size());
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static RectVar makeVar(Vect origin, Vect size)
|
||||
{
|
||||
return Rect.makeVar(origin.x(), origin.y(), size.x(), size.y());
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static RectVar makeVar(double x, double y, double width, double height)
|
||||
{
|
||||
return new RectVar(x, y, width, height);
|
||||
}
|
||||
|
||||
|
||||
@FactoryMethod
|
||||
public static RectVar makeVar()
|
||||
{
|
||||
return Rect.makeVar(Rect.ZERO);
|
||||
}
|
||||
|
||||
private Vect p_bl;
|
||||
private Vect p_bc;
|
||||
private Vect p_br;
|
||||
// p_t == origin
|
||||
private Vect p_tc;
|
||||
private Vect p_tr;
|
||||
|
||||
private Vect p_cl;
|
||||
private Vect p_cc;
|
||||
private Vect p_cr;
|
||||
|
||||
private Num p_x;
|
||||
private Num p_y;
|
||||
private Num p_w;
|
||||
private Num p_h;
|
||||
private Num p_l;
|
||||
private Num p_r;
|
||||
private Num p_t;
|
||||
private Num p_b;
|
||||
private Rect p_edge_l;
|
||||
private Rect p_edge_r;
|
||||
private Rect p_edge_t;
|
||||
private Rect p_edge_b;
|
||||
|
||||
private DigestCache<RectDigest> dc = new DigestCache<RectDigest>() {
|
||||
|
||||
@Override
|
||||
protected RectDigest createDigest()
|
||||
{
|
||||
return new RectDigest(Rect.this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a copy of current value
|
||||
*
|
||||
* @return copy
|
||||
*/
|
||||
public RectConst freeze()
|
||||
{
|
||||
// must NOT call RectVal.make, it'd cause infinite recursion.
|
||||
return new RectConst(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 RectCache cached()
|
||||
{
|
||||
return new RectCache(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectDigest 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 Rect getRect()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("Rect { at %s , size %s }", origin(), size());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Origin (top left).
|
||||
*
|
||||
* @return origin (top left)
|
||||
*/
|
||||
public abstract Vect origin();
|
||||
|
||||
|
||||
/**
|
||||
* Size (spanning right down from Origin).
|
||||
*
|
||||
* @return size vector
|
||||
*/
|
||||
public abstract Vect size();
|
||||
|
||||
|
||||
/**
|
||||
* Add vector to origin
|
||||
*
|
||||
* @param move offset vector
|
||||
* @return result
|
||||
*/
|
||||
public Rect move(final Vect move)
|
||||
{
|
||||
return new Rect() {
|
||||
|
||||
private final Rect t = Rect.this;
|
||||
|
||||
|
||||
@Override
|
||||
public Vect size()
|
||||
{
|
||||
return t.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect origin()
|
||||
{
|
||||
return t.origin().add(move);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Rect moveX(Num x)
|
||||
{
|
||||
return move(x, Num.ZERO);
|
||||
}
|
||||
|
||||
|
||||
public Rect moveY(Num y)
|
||||
{
|
||||
return move(Num.ZERO, y);
|
||||
}
|
||||
|
||||
|
||||
public Rect moveX(double x)
|
||||
{
|
||||
return move(x, 0);
|
||||
}
|
||||
|
||||
|
||||
public Rect moveY(double y)
|
||||
{
|
||||
return move(0, y);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add X and Y to origin
|
||||
*
|
||||
* @param x x to add
|
||||
* @param y y to add
|
||||
* @return result
|
||||
*/
|
||||
public Rect move(final double x, final double y)
|
||||
{
|
||||
return new Rect() {
|
||||
|
||||
private final Rect t = Rect.this;
|
||||
|
||||
|
||||
@Override
|
||||
public Vect size()
|
||||
{
|
||||
return t.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect origin()
|
||||
{
|
||||
return t.origin().add(x, y);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Rect move(final Num x, final Num y)
|
||||
{
|
||||
return new Rect() {
|
||||
|
||||
private final Rect t = Rect.this;
|
||||
|
||||
|
||||
@Override
|
||||
public Vect size()
|
||||
{
|
||||
return t.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect origin()
|
||||
{
|
||||
return t.origin().add(x, y);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shrink to sides
|
||||
*
|
||||
* @param shrink shrink size (horizontal and vertical)
|
||||
* @return result
|
||||
*/
|
||||
|
||||
public Rect shrink(Vect shrink)
|
||||
{
|
||||
return shrink(shrink.x(), shrink.y());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shrink to all sides
|
||||
*
|
||||
* @param shrink shrink
|
||||
* @return result
|
||||
*/
|
||||
public final Rect shrink(double shrink)
|
||||
{
|
||||
return shrink(shrink, shrink, shrink, shrink);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shrink to all sides
|
||||
*
|
||||
* @param shrink shrink
|
||||
* @return result
|
||||
*/
|
||||
public final Rect shrink(Num shrink)
|
||||
{
|
||||
return shrink(shrink, shrink, shrink, shrink);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shrink to sides at sides
|
||||
*
|
||||
* @param x horizontal shrink
|
||||
* @param y vertical shrink
|
||||
* @return result
|
||||
*/
|
||||
public Rect shrink(double x, double y)
|
||||
{
|
||||
return shrink(x, x, y, y);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shrink the rect
|
||||
*
|
||||
* @param left shrink
|
||||
* @param right shrink
|
||||
* @param top shrink
|
||||
* @param bottom shrink
|
||||
* @return result
|
||||
*/
|
||||
public Rect shrink(final double left, final double right, final double top, final double bottom)
|
||||
{
|
||||
return grow(-left, -right, -top, -bottom);
|
||||
}
|
||||
|
||||
|
||||
public Rect shrinkLeft(final double shrink)
|
||||
{
|
||||
return growLeft(-shrink);
|
||||
}
|
||||
|
||||
|
||||
public Rect shrinkRight(final double shrink)
|
||||
{
|
||||
return growLeft(-shrink);
|
||||
}
|
||||
|
||||
|
||||
public Rect shrinkTop(final double shrink)
|
||||
{
|
||||
return growUp(-shrink);
|
||||
}
|
||||
|
||||
|
||||
public Rect shrinkBottom(final double shrink)
|
||||
{
|
||||
return growDown(-shrink);
|
||||
}
|
||||
|
||||
|
||||
public Rect growLeft(final double shrink)
|
||||
{
|
||||
return grow(shrink, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
public Rect growRight(final double shrink)
|
||||
{
|
||||
return grow(0, shrink, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
public Rect growUp(final double shrink)
|
||||
{
|
||||
return grow(0, 0, shrink, 0);
|
||||
}
|
||||
|
||||
|
||||
public Rect growDown(final double shrink)
|
||||
{
|
||||
return grow(0, 0, 0, shrink);
|
||||
}
|
||||
|
||||
|
||||
public Rect shrinkLeft(final Num shrink)
|
||||
{
|
||||
return shrink(shrink, Num.ZERO, Num.ZERO, Num.ZERO);
|
||||
}
|
||||
|
||||
|
||||
public Rect shrinkRight(final Num shrink)
|
||||
{
|
||||
return shrink(Num.ZERO, shrink, Num.ZERO, Num.ZERO);
|
||||
}
|
||||
|
||||
|
||||
public Rect shrinkTop(final Num shrink)
|
||||
{
|
||||
return shrink(Num.ZERO, Num.ZERO, shrink, Num.ZERO);
|
||||
}
|
||||
|
||||
|
||||
public Rect shrinkBottom(final Num shrink)
|
||||
{
|
||||
return shrink(Num.ZERO, Num.ZERO, Num.ZERO, shrink);
|
||||
}
|
||||
|
||||
|
||||
public Rect growLeft(final Num shrink)
|
||||
{
|
||||
return grow(shrink, Num.ZERO, Num.ZERO, Num.ZERO);
|
||||
}
|
||||
|
||||
|
||||
public Rect growRight(final Num shrink)
|
||||
{
|
||||
return grow(Num.ZERO, shrink, Num.ZERO, Num.ZERO);
|
||||
}
|
||||
|
||||
|
||||
public Rect growUp(final Num shrink)
|
||||
{
|
||||
return grow(Num.ZERO, Num.ZERO, shrink, Num.ZERO);
|
||||
}
|
||||
|
||||
|
||||
public Rect growDown(final Num shrink)
|
||||
{
|
||||
return grow(Num.ZERO, Num.ZERO, Num.ZERO, shrink);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Grow to sides
|
||||
*
|
||||
* @param grow grow size (added to each side)
|
||||
* @return grown copy
|
||||
*/
|
||||
public final Rect grow(Vect grow)
|
||||
{
|
||||
return grow(grow.x(), grow.y());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Grow to all sides
|
||||
*
|
||||
* @param grow grow
|
||||
* @return result
|
||||
*/
|
||||
public final Rect grow(double grow)
|
||||
{
|
||||
return grow(grow, grow, grow, grow);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Grow to all sides
|
||||
*
|
||||
* @param grow grow
|
||||
* @return result
|
||||
*/
|
||||
public final Rect grow(Num grow)
|
||||
{
|
||||
return grow(grow, grow, grow, grow);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Grow to sides
|
||||
*
|
||||
* @param x horizontal grow
|
||||
* @param y vertical grow
|
||||
* @return result
|
||||
*/
|
||||
public final Rect grow(double x, double y)
|
||||
{
|
||||
return grow(x, x, y, y);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Grow the rect
|
||||
*
|
||||
* @param left growth
|
||||
* @param right growth
|
||||
* @param top growth
|
||||
* @param bottom growth
|
||||
* @return result
|
||||
*/
|
||||
public Rect grow(final double left, final double right, final double top, final double bottom)
|
||||
{
|
||||
return new Rect() {
|
||||
|
||||
private final Rect t = Rect.this;
|
||||
|
||||
|
||||
@Override
|
||||
public Vect size()
|
||||
{
|
||||
return t.size().add(left + right, top + bottom);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect origin()
|
||||
{
|
||||
return t.origin().sub(left, top);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Rect shrink(final Num left, final Num right, final Num top, final Num bottom)
|
||||
{
|
||||
return new Rect() {
|
||||
|
||||
private final Rect t = Rect.this;
|
||||
|
||||
|
||||
@Override
|
||||
public Vect size()
|
||||
{
|
||||
return t.size().sub(left.add(right), top.add(bottom));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect origin()
|
||||
{
|
||||
return t.origin().add(left, top);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Rect grow(final Num left, final Num right, final Num top, final Num bottom)
|
||||
{
|
||||
|
||||
return new Rect() {
|
||||
|
||||
private final Rect t = Rect.this;
|
||||
|
||||
|
||||
@Override
|
||||
public Vect size()
|
||||
{
|
||||
return t.size().add(left.add(right), top.add(bottom));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect origin()
|
||||
{
|
||||
return t.origin().sub(left, top);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Round coords
|
||||
*
|
||||
* @return result
|
||||
*/
|
||||
public Rect round()
|
||||
{
|
||||
|
||||
return new Rect() {
|
||||
|
||||
private final Rect t = Rect.this;
|
||||
|
||||
|
||||
@Override
|
||||
public Vect size()
|
||||
{
|
||||
return t.size().round();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect origin()
|
||||
{
|
||||
return t.origin().round();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Num x()
|
||||
{
|
||||
return p_x != null ? p_x : (p_x = origin().xn());
|
||||
}
|
||||
|
||||
|
||||
public Num y()
|
||||
{
|
||||
return p_y != null ? p_y : (p_y = origin().yn());
|
||||
}
|
||||
|
||||
|
||||
public Num width()
|
||||
{
|
||||
return p_w != null ? p_w : (p_w = size().xn());
|
||||
}
|
||||
|
||||
|
||||
public Num height()
|
||||
{
|
||||
return p_h != null ? p_h : (p_h = size().yn());
|
||||
}
|
||||
|
||||
|
||||
public Num left()
|
||||
{
|
||||
return p_l != null ? p_l : (p_l = origin().xn());
|
||||
}
|
||||
|
||||
|
||||
public Num right()
|
||||
{
|
||||
return p_r != null ? p_r : (p_r = origin().xn().add(size().xn()));
|
||||
}
|
||||
|
||||
|
||||
public Num top()
|
||||
{
|
||||
return p_t != null ? p_t : (p_t = origin().yn());
|
||||
}
|
||||
|
||||
|
||||
public Num bottom()
|
||||
{
|
||||
return p_b != null ? p_b : (p_b = origin().yn().add(size().yn()));
|
||||
}
|
||||
|
||||
|
||||
public Vect topLeft()
|
||||
{
|
||||
return origin();
|
||||
}
|
||||
|
||||
|
||||
public Vect topCenter()
|
||||
{
|
||||
return p_tc != null ? p_tc : (p_tc = origin().add(size().xn().half(), Num.ZERO));
|
||||
}
|
||||
|
||||
|
||||
public Vect topRight()
|
||||
{
|
||||
return p_tr != null ? p_tr : (p_tr = origin().add(size().xn(), Num.ZERO));
|
||||
}
|
||||
|
||||
|
||||
public Vect centerLeft()
|
||||
{
|
||||
return p_cl != null ? p_cl : (p_cl = origin().add(Num.ZERO, size().yn().half()));
|
||||
}
|
||||
|
||||
|
||||
public Vect center()
|
||||
{
|
||||
return p_cc != null ? p_cc : (p_cc = origin().add(size().half()));
|
||||
}
|
||||
|
||||
|
||||
public Vect centerRight()
|
||||
{
|
||||
return p_cr != null ? p_cr : (p_cr = origin().add(size().xn(), size().yn().half()));
|
||||
}
|
||||
|
||||
|
||||
public Vect bottomLeft()
|
||||
{
|
||||
return p_bl != null ? p_bl : (p_bl = origin().add(Num.ZERO, size().yn()));
|
||||
}
|
||||
|
||||
|
||||
public Vect bottomCenter()
|
||||
{
|
||||
return p_bc != null ? p_bc : (p_bc = origin().add(size().xn().half(), size().yn()));
|
||||
}
|
||||
|
||||
|
||||
public Vect bottomRight()
|
||||
{
|
||||
return p_br != null ? p_br : (p_br = origin().add(size().xn(), size().yn()));
|
||||
}
|
||||
|
||||
|
||||
public Rect leftEdge()
|
||||
{
|
||||
return p_edge_l != null ? p_edge_l : (p_edge_l = topLeft().expand(Num.ZERO, Num.ZERO, Num.ZERO, height()));
|
||||
}
|
||||
|
||||
|
||||
public Rect rightEdge()
|
||||
{
|
||||
return p_edge_r != null ? p_edge_r : (p_edge_r = topRight().expand(Num.ZERO, Num.ZERO, Num.ZERO, height()));
|
||||
}
|
||||
|
||||
|
||||
public Rect topEdge()
|
||||
{
|
||||
return p_edge_t != null ? p_edge_t : (p_edge_t = topLeft().expand(Num.ZERO, width(), Num.ZERO, Num.ZERO));
|
||||
}
|
||||
|
||||
|
||||
public Rect bottomEdge()
|
||||
{
|
||||
return p_edge_b != null ? p_edge_b : (p_edge_b = bottomLeft().expand(Num.ZERO, width(), Num.ZERO, Num.ZERO));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Center to given point
|
||||
*
|
||||
* @param point new center
|
||||
* @return centered
|
||||
*/
|
||||
public Rect centerTo(final Vect point)
|
||||
{
|
||||
return new Rect() {
|
||||
|
||||
Rect t = Rect.this;
|
||||
|
||||
|
||||
@Override
|
||||
public Vect size()
|
||||
{
|
||||
return t.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect origin()
|
||||
{
|
||||
return point.sub(t.size().half());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if point is inside this rectangle
|
||||
*
|
||||
* @param point point to test
|
||||
* @return is inside
|
||||
*/
|
||||
public boolean contains(Vect point)
|
||||
{
|
||||
final double x = point.x();
|
||||
final double y = point.y();
|
||||
|
||||
final double x1 = origin().x();
|
||||
final double y1 = origin().y();
|
||||
final double x2 = x1 + size().x();
|
||||
final double y2 = y1 + size().y();
|
||||
|
||||
return x >= x1 && y >= y1 && x <= x2 && y <= y2;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Center to given rect's center
|
||||
*
|
||||
* @param parent rect to center to
|
||||
* @return centered
|
||||
*/
|
||||
public Rect centerTo(Rect parent)
|
||||
{
|
||||
return centerTo(parent.center());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get TiledRect with given number of evenly spaced tiles. Tile indexes are
|
||||
* one-based by default.
|
||||
*
|
||||
* @param horizontal horizontal tile count
|
||||
* @param vertical vertical tile count
|
||||
* @return tiled rect
|
||||
*/
|
||||
public TiledRect tiles(int horizontal, int vertical)
|
||||
{
|
||||
return new TiledRect(this, horizontal, vertical);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get TiledRect with N columns and 1 row. Column indexes are one-based by
|
||||
* default.
|
||||
*
|
||||
* @param columns number of columns
|
||||
* @return tiled rect
|
||||
*/
|
||||
public TiledRect columns(int columns)
|
||||
{
|
||||
return new TiledRect(this, columns, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get TiledRect with N rows and 1 column. Row indexes are one-based by
|
||||
* default.
|
||||
*
|
||||
* @param rows number of columns
|
||||
* @return tiled rect
|
||||
*/
|
||||
public TiledRect rows(int rows)
|
||||
{
|
||||
return new TiledRect(this, 1, rows);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
package mightypork.util.math.constraints.rect;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.num.NumConst;
|
||||
import mightypork.util.math.constraints.rect.caching.RectDigest;
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
import mightypork.util.math.constraints.vect.VectConst;
|
||||
|
||||
|
||||
/**
|
||||
* Rectangle with constant bounds, that can never change.<br>
|
||||
* It's arranged so that operations with constant arguments yield constant
|
||||
* results.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class RectConst extends Rect {
|
||||
|
||||
private final VectConst pos;
|
||||
private final VectConst size;
|
||||
|
||||
// cached with lazy loading
|
||||
private NumConst v_b;
|
||||
private NumConst v_r;
|
||||
private VectConst v_br;
|
||||
private VectConst v_tc;
|
||||
private VectConst v_tr;
|
||||
private VectConst v_cl;
|
||||
private VectConst v_c;
|
||||
private VectConst v_cr;
|
||||
private VectConst v_bl;
|
||||
private VectConst v_bc;
|
||||
private RectConst v_round;
|
||||
private RectConst v_edge_l;
|
||||
private RectConst v_edge_r;
|
||||
private RectConst v_edge_t;
|
||||
private RectConst v_edge_b;
|
||||
private RectDigest digest;
|
||||
|
||||
|
||||
/**
|
||||
* Create at given origin, with given size.
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
RectConst(double x, double y, double width, double height) {
|
||||
this.pos = Vect.make(x, y);
|
||||
this.size = Vect.make(width, height);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create at given origin, with given size.
|
||||
*
|
||||
* @param origin
|
||||
* @param size
|
||||
*/
|
||||
RectConst(Vect origin, Vect size) {
|
||||
this.pos = origin.freeze();
|
||||
this.size = size.freeze();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create at given origin, with given size.
|
||||
*
|
||||
* @param another other coord
|
||||
*/
|
||||
RectConst(Rect another) {
|
||||
this.pos = another.origin().freeze();
|
||||
this.size = another.size().freeze();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated it's useless to copy a constant
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public RectConst freeze()
|
||||
{
|
||||
return this; // already constant
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectDigest digest()
|
||||
{
|
||||
return (digest != null) ? digest : (digest = super.digest());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst origin()
|
||||
{
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst size()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst move(Vect move)
|
||||
{
|
||||
return move(move.x(), move.y());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst move(double x, double y)
|
||||
{
|
||||
return Rect.make(pos.add(x, y), size);
|
||||
}
|
||||
|
||||
|
||||
public RectConst move(NumConst x, NumConst y)
|
||||
{
|
||||
return super.move(x, y).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst shrink(double left, double right, double top, double bottom)
|
||||
{
|
||||
return super.shrink(left, right, top, bottom).freeze();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst grow(double left, double right, double top, double bottom)
|
||||
{
|
||||
return super.grow(left, right, top, bottom).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst round()
|
||||
{
|
||||
return (v_round != null) ? v_round : (v_round = Rect.make(pos.round(), size.round()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst x()
|
||||
{
|
||||
return pos.xn();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst y()
|
||||
{
|
||||
return pos.yn();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst width()
|
||||
{
|
||||
return size.xn();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst height()
|
||||
{
|
||||
return size.yn();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst left()
|
||||
{
|
||||
return pos.xn();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst right()
|
||||
{
|
||||
return (v_r != null) ? v_r : (v_r = super.right().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst top()
|
||||
{
|
||||
return pos.yn();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst bottom()
|
||||
{
|
||||
return (v_b != null) ? v_b : (v_b = super.bottom().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst topLeft()
|
||||
{
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst topCenter()
|
||||
{
|
||||
return (v_tc != null) ? v_tc : (v_tc = super.topCenter().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst topRight()
|
||||
{
|
||||
return (v_tr != null) ? v_tr : (v_tr = super.topRight().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst centerLeft()
|
||||
{
|
||||
return (v_cl != null) ? v_cl : (v_cl = super.centerLeft().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst center()
|
||||
{
|
||||
return (v_c != null) ? v_c : (v_c = super.center().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst centerRight()
|
||||
{
|
||||
return (v_cr != null) ? v_cr : (v_cr = super.centerRight().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst bottomLeft()
|
||||
{
|
||||
return (v_bl != null) ? v_bl : (v_bl = super.bottomLeft().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst bottomCenter()
|
||||
{
|
||||
return (v_bc != null) ? v_bc : (v_bc = super.bottomCenter().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst bottomRight()
|
||||
{
|
||||
return (v_br != null) ? v_br : (v_br = super.bottomRight().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst leftEdge()
|
||||
{
|
||||
return (v_edge_l != null) ? v_edge_l : (v_edge_l = super.leftEdge().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst rightEdge()
|
||||
{
|
||||
return (v_edge_r != null) ? v_edge_r : (v_edge_r = super.rightEdge().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst topEdge()
|
||||
{
|
||||
return (v_edge_t != null) ? v_edge_t : (v_edge_t = super.topEdge().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst bottomEdge()
|
||||
{
|
||||
return (v_edge_b != null) ? v_edge_b : (v_edge_b = super.bottomEdge().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Rect shrink(Vect shrink)
|
||||
{
|
||||
return super.shrink(shrink);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst shrink(double x, double y)
|
||||
{
|
||||
return super.shrink(x, y).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst shrink(NumConst left, NumConst right, NumConst top, NumConst bottom)
|
||||
{
|
||||
return super.shrink(left, right, top, bottom).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst grow(NumConst left, NumConst right, NumConst top, NumConst bottom)
|
||||
{
|
||||
return super.grow(left, right, top, bottom).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst shrinkLeft(double shrink)
|
||||
{
|
||||
return super.shrinkLeft(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst shrinkRight(double shrink)
|
||||
{
|
||||
return super.shrinkRight(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst shrinkTop(double shrink)
|
||||
{
|
||||
return super.shrinkTop(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst shrinkBottom(double shrink)
|
||||
{
|
||||
return super.shrinkBottom(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst growLeft(double shrink)
|
||||
{
|
||||
return super.growLeft(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst growRight(double shrink)
|
||||
{
|
||||
return super.growRight(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst growUp(double shrink)
|
||||
{
|
||||
return super.growUp(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst growDown(double shrink)
|
||||
{
|
||||
return super.growDown(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst shrinkLeft(NumConst shrink)
|
||||
{
|
||||
return super.shrinkLeft(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst shrinkRight(NumConst shrink)
|
||||
{
|
||||
return super.shrinkRight(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst shrinkTop(NumConst shrink)
|
||||
{
|
||||
return super.shrinkTop(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst shrinkBottom(NumConst shrink)
|
||||
{
|
||||
return super.shrinkBottom(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst growLeft(NumConst shrink)
|
||||
{
|
||||
return super.growLeft(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst growRight(NumConst shrink)
|
||||
{
|
||||
return super.growRight(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst growUp(NumConst shrink)
|
||||
{
|
||||
return super.growUp(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst growBottom(NumConst shrink)
|
||||
{
|
||||
return super.growDown(shrink).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst centerTo(VectConst point)
|
||||
{
|
||||
return super.centerTo(point).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst centerTo(RectConst parent)
|
||||
{
|
||||
return super.centerTo(parent).freeze();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package mightypork.util.math.constraints.rect.builders;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
import mightypork.util.math.constraints.rect.Rect;
|
||||
import mightypork.util.math.constraints.rect.proxy.RectProxy;
|
||||
|
||||
|
||||
/**
|
||||
* Utility for cutting rect into evenly sized cells.<br>
|
||||
* It's by default one-based, but this can be switched by calling the oneBased()
|
||||
* and zeroBased() methods.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class TiledRect extends RectProxy {
|
||||
|
||||
final private int tilesY;
|
||||
final private int tilesX;
|
||||
final private Num perRow;
|
||||
final private Num perCol;
|
||||
|
||||
private int based = 1;
|
||||
|
||||
|
||||
public TiledRect(Rect source, int horizontal, int vertical) {
|
||||
super(source);
|
||||
this.tilesX = horizontal;
|
||||
this.tilesY = vertical;
|
||||
|
||||
this.perRow = height().div(vertical);
|
||||
this.perCol = width().div(horizontal);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set to one-based mode, and return itself (for chaining).
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public TiledRect oneBased()
|
||||
{
|
||||
based = 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set to zero-based mode, and return itself (for chaining).
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public TiledRect zeroBased()
|
||||
{
|
||||
based = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a tile.
|
||||
*
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @return tile
|
||||
* @throws IndexOutOfBoundsException when invalid index is specified.
|
||||
*/
|
||||
public Rect tile(int x, int y)
|
||||
{
|
||||
x -= based;
|
||||
y -= based;
|
||||
|
||||
if (x >= tilesX || x < 0) {
|
||||
throw new IndexOutOfBoundsException("X coordinate out fo range.");
|
||||
}
|
||||
|
||||
if (y >= tilesY || y < 0) {
|
||||
throw new IndexOutOfBoundsException("Y coordinate out of range.");
|
||||
}
|
||||
|
||||
final Num leftMove = left().add(perCol.mul(x));
|
||||
final Num topMove = top().add(perRow.mul(y));
|
||||
|
||||
return Rect.make(perCol, perRow).move(leftMove, topMove);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a span (tile spanning across multiple cells)
|
||||
*
|
||||
* @param x_from x start position
|
||||
* @param y_from y start position
|
||||
* @param size_x horizontal size (columns)
|
||||
* @param size_y vertical size (rows)
|
||||
* @return tile the tile
|
||||
* @throws IndexOutOfBoundsException when invalid index is specified.
|
||||
*/
|
||||
public Rect span(int x_from, int y_from, int size_x, int size_y)
|
||||
{
|
||||
x_from -= based;
|
||||
y_from -= based;
|
||||
|
||||
final int x_to = x_from + size_x;
|
||||
final int y_to = y_from + size_y;
|
||||
|
||||
if (size_x <= 0 || size_y <= 0) {
|
||||
throw new IndexOutOfBoundsException("Size must be > 0.");
|
||||
}
|
||||
|
||||
if (x_from >= tilesX || x_from < 0 || x_to >= tilesX || x_to < 0) {
|
||||
throw new IndexOutOfBoundsException("X coordinate(s) out of range.");
|
||||
}
|
||||
|
||||
if (y_from >= tilesY || y_from < 0 || y_to >= tilesY || y_to < 0) {
|
||||
throw new IndexOutOfBoundsException("Y coordinate(s) out of range.");
|
||||
}
|
||||
|
||||
final Num leftMove = left().add(perCol.mul(x_from));
|
||||
final Num topMove = top().add(perRow.mul(y_from));
|
||||
|
||||
return Rect.make(perCol.mul(size_x), perRow.mul(size_y)).move(leftMove, topMove);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get n-th column
|
||||
*
|
||||
* @param n column index
|
||||
* @return the column tile
|
||||
* @throws IndexOutOfBoundsException when invalid index is specified.
|
||||
*/
|
||||
public Rect column(int n)
|
||||
{
|
||||
return tile(n, based);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get n-th row
|
||||
*
|
||||
* @param n row index
|
||||
* @return the row rect
|
||||
* @throws IndexOutOfBoundsException when invalid index is specified.
|
||||
*/
|
||||
public Rect row(int n)
|
||||
{
|
||||
return tile(based, n);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package mightypork.util.math.constraints.rect.caching;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.ConstraintCache;
|
||||
import mightypork.util.math.constraints.rect.Rect;
|
||||
import mightypork.util.math.constraints.rect.mutable.RectVar;
|
||||
import mightypork.util.math.constraints.rect.proxy.RectAdapter;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A rect cache.
|
||||
* </p>
|
||||
* <p>
|
||||
* Values are held in a caching VectVar, and digest caching is enabled by
|
||||
* default.
|
||||
* </p>
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class AbstractRectCache extends RectAdapter implements ConstraintCache<Rect> {
|
||||
|
||||
private final RectVar cache = Rect.makeVar();
|
||||
private boolean inited = false;
|
||||
private boolean cachingEnabled = true;
|
||||
|
||||
|
||||
public AbstractRectCache() {
|
||||
enableDigestCaching(true); // it changes only on poll
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final Rect getSource()
|
||||
{
|
||||
if (!inited) poll();
|
||||
|
||||
return (cachingEnabled ? cache : getCacheSource());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void poll()
|
||||
{
|
||||
inited = true;
|
||||
|
||||
// poll source
|
||||
final Rect source = getCacheSource();
|
||||
source.markDigestDirty(); // poll cached
|
||||
|
||||
// store source value
|
||||
cache.setTo(source);
|
||||
|
||||
markDigestDirty();
|
||||
|
||||
onChange();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void enableCaching(boolean yes)
|
||||
{
|
||||
cachingEnabled = yes;
|
||||
enableDigestCaching(yes);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean isCachingEnabled()
|
||||
{
|
||||
return cachingEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package mightypork.util.math.constraints.rect.caching;
|
||||
|
||||
|
||||
import mightypork.util.annotations.DefaultImpl;
|
||||
import mightypork.util.math.constraints.rect.Rect;
|
||||
|
||||
|
||||
/**
|
||||
* Rect cache implementation
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class RectCache extends AbstractRectCache {
|
||||
|
||||
private final Rect source;
|
||||
|
||||
|
||||
public RectCache(Rect source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final Rect getCacheSource()
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@DefaultImpl
|
||||
public void onChange()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package mightypork.util.math.constraints.rect.caching;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.rect.Rect;
|
||||
import mightypork.util.math.constraints.rect.RectConst;
|
||||
|
||||
|
||||
public class RectDigest {
|
||||
|
||||
public final double x;
|
||||
public final double y;
|
||||
public final double width;
|
||||
public final double height;
|
||||
|
||||
public final double left;
|
||||
public final double right;
|
||||
public final double top;
|
||||
public final double bottom;
|
||||
|
||||
|
||||
public RectDigest(Rect rect) {
|
||||
|
||||
final RectConst frozen = rect.freeze();
|
||||
|
||||
this.x = frozen.x().value();
|
||||
this.y = frozen.y().value();
|
||||
|
||||
this.width = frozen.width().value();
|
||||
this.height = frozen.height().value();
|
||||
|
||||
this.left = frozen.left().value();
|
||||
this.right = frozen.right().value();
|
||||
this.top = frozen.top().value();
|
||||
this.bottom = frozen.bottom().value();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("Rect{ at: (%.1f, %.1f), size: (%.1f, %.1f), bounds: L %.1f R %.1f T %.1f B %.1f }", x, y, width, height, left, right, top, bottom);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package mightypork.util.math.constraints.rect.mutable;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.rect.Rect;
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
|
||||
|
||||
/**
|
||||
* Mutable rectangle; operations change it's state.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class RectMutable extends Rect {
|
||||
|
||||
/**
|
||||
* Set to other rect's coordinates
|
||||
*
|
||||
* @param rect other rect
|
||||
*/
|
||||
public void setTo(Rect rect)
|
||||
{
|
||||
setTo(rect.origin(), rect.size());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set to given size and position
|
||||
*
|
||||
* @param origin new origin
|
||||
* @param width new width
|
||||
* @param height new height
|
||||
*/
|
||||
public void setTo(Vect origin, double width, double height)
|
||||
{
|
||||
setTo(origin, Vect.make(width, height));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set to given size and position
|
||||
*
|
||||
* @param x origin.x
|
||||
* @param y origin.y
|
||||
* @param width new width
|
||||
* @param height new height
|
||||
*/
|
||||
public void setTo(double x, double y, double width, double height)
|
||||
{
|
||||
setTo(Vect.make(x, y), Vect.make(width, height));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set to given size and position
|
||||
*
|
||||
* @param origin new origin
|
||||
* @param size new size
|
||||
*/
|
||||
public void setTo(Vect origin, Vect size)
|
||||
{
|
||||
setOrigin(origin);
|
||||
setSize(size);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set to zero
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
setTo(Vect.ZERO, Vect.ZERO);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set new origin
|
||||
*
|
||||
* @param origin new origin
|
||||
*/
|
||||
public abstract void setOrigin(Vect origin);
|
||||
|
||||
|
||||
/**
|
||||
* Set new size
|
||||
*
|
||||
* @param size new size
|
||||
*/
|
||||
public abstract void setSize(Vect size);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package mightypork.util.math.constraints.rect.mutable;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
import mightypork.util.math.constraints.vect.mutable.VectVar;
|
||||
|
||||
|
||||
public class RectVar extends RectMutable {
|
||||
|
||||
final VectVar pos = Vect.makeVar();
|
||||
final VectVar size = Vect.makeVar();
|
||||
|
||||
|
||||
/**
|
||||
* Create at given origin, with given size.
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public RectVar(double x, double y, double width, double height) {
|
||||
this.pos.setTo(x, y);
|
||||
this.size.setTo(width, height);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect origin()
|
||||
{
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect size()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setOrigin(Vect origin)
|
||||
{
|
||||
this.pos.setTo(origin);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setSize(Vect size)
|
||||
{
|
||||
this.size.setTo(size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.util.math.constraints.rect.proxy;
|
||||
|
||||
|
||||
/**
|
||||
* Pluggable rect bound
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface PluggableRectBound extends RectBound {
|
||||
|
||||
/**
|
||||
* @param rect context to set
|
||||
*/
|
||||
abstract void setRect(RectBound rect);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package mightypork.util.math.constraints.rect.proxy;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.rect.Rect;
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
import mightypork.util.math.constraints.vect.proxy.VectAdapter;
|
||||
|
||||
|
||||
/**
|
||||
* Rect proxy with abstract method for plugging in / generating rect
|
||||
* dynamically.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class RectAdapter extends Rect {
|
||||
|
||||
// adapters are needed in case the vect returned from source changes
|
||||
// (is replaced). This way, references to origin and rect will stay intact.
|
||||
|
||||
private final VectAdapter originAdapter = new VectAdapter() {
|
||||
|
||||
@Override
|
||||
protected Vect getSource()
|
||||
{
|
||||
return RectAdapter.this.getSource().origin();
|
||||
}
|
||||
};
|
||||
|
||||
private final VectAdapter sizeAdapter = new VectAdapter() {
|
||||
|
||||
@Override
|
||||
protected Vect getSource()
|
||||
{
|
||||
return RectAdapter.this.getSource().size();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return the proxied coord
|
||||
*/
|
||||
protected abstract Rect getSource();
|
||||
|
||||
|
||||
@Override
|
||||
public Vect origin()
|
||||
{
|
||||
return originAdapter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect size()
|
||||
{
|
||||
return sizeAdapter;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package mightypork.util.math.constraints.rect.proxy;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.rect.Rect;
|
||||
|
||||
|
||||
/**
|
||||
* Rect constraint (ie. region)
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface RectBound {
|
||||
|
||||
/**
|
||||
* @return rect region
|
||||
*/
|
||||
Rect getRect();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package mightypork.util.math.constraints.rect.proxy;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.rect.Rect;
|
||||
|
||||
|
||||
/**
|
||||
* Pluggable rect bound adapter
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class RectBoundAdapter extends RectAdapter implements PluggableRectBound {
|
||||
|
||||
private RectBound backing = null;
|
||||
|
||||
|
||||
public RectBoundAdapter() {
|
||||
}
|
||||
|
||||
|
||||
public RectBoundAdapter(RectBound bound) {
|
||||
backing = bound;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setRect(RectBound rect)
|
||||
{
|
||||
this.backing = rect;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Rect getSource()
|
||||
{
|
||||
return backing.getRect();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package mightypork.util.math.constraints.rect.proxy;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.rect.Rect;
|
||||
|
||||
|
||||
public class RectProxy extends RectAdapter {
|
||||
|
||||
private final Rect source;
|
||||
|
||||
|
||||
public RectProxy(Rect source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Rect getSource()
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package mightypork.util.math.constraints.rect.proxy;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.rect.Rect;
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
|
||||
|
||||
/**
|
||||
* Rect made of two {@link Vect}s
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class RectVectAdapter extends Rect {
|
||||
|
||||
private final Vect origin;
|
||||
private final Vect size;
|
||||
|
||||
|
||||
public RectVectAdapter(Vect origin, Vect size) {
|
||||
this.origin = origin;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect origin()
|
||||
{
|
||||
return origin;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect size()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,373 @@
|
||||
package mightypork.util.math.constraints.vect;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.num.Num;
|
||||
import mightypork.util.math.constraints.num.NumConst;
|
||||
import mightypork.util.math.constraints.rect.RectConst;
|
||||
import mightypork.util.math.constraints.vect.caching.VectDigest;
|
||||
|
||||
|
||||
/**
|
||||
* Coordinate with immutable numeric values.<br>
|
||||
* This coordinate is guaranteed to never change, as opposed to view.<br>
|
||||
* It's arranged so that operations with constant arguments yield constant
|
||||
* results.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public final class VectConst extends Vect {
|
||||
|
||||
private final double x, y, z;
|
||||
// non-parametric operations are cached using lazy load.
|
||||
private NumConst v_size;
|
||||
private VectConst v_neg;
|
||||
private VectConst v_ceil;
|
||||
private VectConst v_floor;
|
||||
private VectConst v_round;
|
||||
private VectConst v_half;
|
||||
private VectConst v_abs;
|
||||
private NumConst v_xc;
|
||||
private NumConst v_yc;
|
||||
private NumConst v_zc;
|
||||
private VectDigest digest;
|
||||
|
||||
|
||||
VectConst(Vect other) {
|
||||
this(other.x(), other.y(), other.z());
|
||||
}
|
||||
|
||||
|
||||
VectConst(double x, double y, double z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double x()
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double y()
|
||||
{
|
||||
return y;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double z()
|
||||
{
|
||||
return z;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return X constraint
|
||||
*/
|
||||
|
||||
@Override
|
||||
public final NumConst xn()
|
||||
{
|
||||
return (v_xc != null) ? v_xc : (v_xc = Num.make(this.x));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Y constraint
|
||||
*/
|
||||
|
||||
@Override
|
||||
public final NumConst yn()
|
||||
{
|
||||
return (v_yc != null) ? v_yc : (v_yc = Num.make(this.y));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Z constraint
|
||||
*/
|
||||
|
||||
@Override
|
||||
public final NumConst zn()
|
||||
{
|
||||
return (v_zc != null) ? v_zc : (v_zc = Num.make(this.z));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated it's useless to copy a constant
|
||||
*/
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public VectConst freeze()
|
||||
{
|
||||
return this; // it's constant already
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectDigest digest()
|
||||
{
|
||||
return (digest != null) ? digest : (digest = super.digest());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst abs()
|
||||
{
|
||||
return (v_abs != null) ? v_abs : (v_abs = super.abs().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst add(double x, double y)
|
||||
{
|
||||
return super.add(x, y).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst add(double x, double y, double z)
|
||||
{
|
||||
return super.add(x, y, z).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst half()
|
||||
{
|
||||
return (v_half != null) ? v_half : (v_half = super.half().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst mul(double d)
|
||||
{
|
||||
return super.mul(d).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst mul(double x, double y)
|
||||
{
|
||||
return super.mul(x, y).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst mul(double x, double y, double z)
|
||||
{
|
||||
return super.mul(x, y, z).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst round()
|
||||
{
|
||||
return (v_round != null) ? v_round : (v_round = super.round().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst floor()
|
||||
{
|
||||
return (v_floor != null) ? v_floor : (v_floor = super.floor().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst ceil()
|
||||
{
|
||||
if (v_ceil != null) return v_ceil;
|
||||
return v_ceil = super.ceil().freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst sub(double x, double y)
|
||||
{
|
||||
return super.sub(x, y).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst sub(double x, double y, double z)
|
||||
{
|
||||
return super.sub(x, y, z).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst neg()
|
||||
{
|
||||
return (v_neg != null) ? v_neg : (v_neg = super.neg().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst norm(double size)
|
||||
{
|
||||
return super.norm(size).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NumConst size()
|
||||
{
|
||||
return (v_size != null) ? v_size : (v_size = super.size().freeze());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst withX(double x)
|
||||
{
|
||||
return super.withX(x).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst withY(double y)
|
||||
{
|
||||
return super.withY(y).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VectConst withZ(double z)
|
||||
{
|
||||
return super.withZ(z).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst withX(NumConst x)
|
||||
{
|
||||
return super.withX(x).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst withY(NumConst y)
|
||||
{
|
||||
return super.withY(y).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst withZ(NumConst z)
|
||||
{
|
||||
return super.withZ(z).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst add(VectConst vec)
|
||||
{
|
||||
return super.add(vec).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst add(NumConst x, NumConst y)
|
||||
{
|
||||
return super.add(x, y).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst add(NumConst x, NumConst y, NumConst z)
|
||||
{
|
||||
return super.add(x, y, z).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst mul(VectConst vec)
|
||||
{
|
||||
return super.mul(vec).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst mul(NumConst d)
|
||||
{
|
||||
return super.mul(d).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst mul(NumConst x, NumConst y)
|
||||
{
|
||||
return super.mul(x, y).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst mul(NumConst x, NumConst y, NumConst z)
|
||||
{
|
||||
return super.mul(x, y, z).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst sub(VectConst vec)
|
||||
{
|
||||
return super.sub(vec).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst sub(NumConst x, NumConst y)
|
||||
{
|
||||
return super.sub(x, y).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst sub(NumConst x, NumConst y, NumConst z)
|
||||
{
|
||||
return super.sub(x, y, z).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst norm(NumConst size)
|
||||
{
|
||||
return super.norm(size).freeze();
|
||||
}
|
||||
|
||||
|
||||
public NumConst dist(VectConst point)
|
||||
{
|
||||
return super.dist(point).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst midTo(VectConst point)
|
||||
{
|
||||
return super.midTo(point).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst vectTo(VectConst point)
|
||||
{
|
||||
return super.vectTo(point).freeze();
|
||||
}
|
||||
|
||||
|
||||
public NumConst dot(VectConst vec)
|
||||
{
|
||||
return super.dot(vec).freeze();
|
||||
}
|
||||
|
||||
|
||||
public VectConst cross(VectConst vec)
|
||||
{
|
||||
return super.cross(vec).freeze();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RectConst expand(double left, double right, double top, double bottom)
|
||||
{
|
||||
return super.expand(left, right, top, bottom).freeze();
|
||||
}
|
||||
|
||||
|
||||
public RectConst expand(NumConst left, NumConst right, NumConst top, NumConst bottom)
|
||||
{
|
||||
return super.expand(left, right, top, bottom).freeze();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package mightypork.util.math.constraints.vect;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.vect.proxy.VectAdapter;
|
||||
|
||||
|
||||
public class VectProxy extends VectAdapter {
|
||||
|
||||
private final Vect source;
|
||||
|
||||
|
||||
public VectProxy(Vect source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Vect getSource()
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package mightypork.util.math.constraints.vect.caching;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.ConstraintCache;
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
import mightypork.util.math.constraints.vect.mutable.VectVar;
|
||||
import mightypork.util.math.constraints.vect.proxy.VectAdapter;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A vect cache.
|
||||
* </p>
|
||||
* <p>
|
||||
* Values are held in a caching VectVar, and digest caching is enabled by
|
||||
* default.
|
||||
* </p>
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class AbstractVectCache extends VectAdapter implements ConstraintCache<Vect> {
|
||||
|
||||
private final VectVar cache = Vect.makeVar();
|
||||
private boolean inited = false;
|
||||
private boolean cachingEnabled = true;
|
||||
|
||||
|
||||
public AbstractVectCache() {
|
||||
enableDigestCaching(true); // it changes only on poll
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final Vect getSource()
|
||||
{
|
||||
if (!inited) markDigestDirty();
|
||||
|
||||
return (cachingEnabled ? cache : getCacheSource());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void poll()
|
||||
{
|
||||
inited = true;
|
||||
|
||||
// poll source
|
||||
final Vect source = getCacheSource();
|
||||
source.markDigestDirty(); // poll cached
|
||||
|
||||
// store source value
|
||||
cache.setTo(source);
|
||||
|
||||
markDigestDirty();
|
||||
|
||||
onChange();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void enableCaching(boolean yes)
|
||||
{
|
||||
cachingEnabled = yes;
|
||||
enableDigestCaching(yes);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean isCachingEnabled()
|
||||
{
|
||||
return cachingEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package mightypork.util.math.constraints.vect.caching;
|
||||
|
||||
|
||||
import mightypork.util.annotations.DefaultImpl;
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
|
||||
|
||||
/**
|
||||
* Vect cache implementation
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class VectCache extends AbstractVectCache {
|
||||
|
||||
private final Vect source;
|
||||
|
||||
|
||||
public VectCache(Vect source) {
|
||||
this.source = source;
|
||||
enableDigestCaching(true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Vect getCacheSource()
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@DefaultImpl
|
||||
public void onChange()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package mightypork.util.math.constraints.vect.caching;
|
||||
|
||||
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
|
||||
|
||||
public class VectDigest {
|
||||
|
||||
public final double x;
|
||||
public final double y;
|
||||
public final double z;
|
||||
|
||||
|
||||
public VectDigest(Vect vect) {
|
||||
this.x = vect.x();
|
||||
this.y = vect.y();
|
||||
this.z = vect.z();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("Vect(%.1f, %.1f, %.1f)", x, y, z);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
package mightypork.util.math.constraints.vect.mutable;
|
||||
|
||||
|
||||
import mightypork.util.annotations.FactoryMethod;
|
||||
import mightypork.util.control.timing.Pauseable;
|
||||
import mightypork.util.control.timing.Updateable;
|
||||
import mightypork.util.math.Easing;
|
||||
import mightypork.util.math.constraints.num.mutable.NumAnimated;
|
||||
import mightypork.util.math.constraints.vect.Vect;
|
||||
|
||||
|
||||
/**
|
||||
* 3D coordinated with support for transitions, mutable.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class VectAnimated extends VectMutable implements Pauseable, Updateable {
|
||||
|
||||
private final NumAnimated x, y, z;
|
||||
private double defaultDuration = 0;
|
||||
|
||||
|
||||
/**
|
||||
* 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.animate(x, defaultDuration);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setY(double y)
|
||||
{
|
||||
this.y.animate(y, defaultDuration);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setZ(double z)
|
||||
{
|
||||
this.z.animate(z, defaultDuration);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add offset with animation
|
||||
*
|
||||
* @param offset added offset
|
||||
* @param duration animation time (seconds)
|
||||
*/
|
||||
public void add(Vect offset, double duration)
|
||||
{
|
||||
animate(this.add(offset), duration);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Animate to given coordinates in given amount of time
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param z
|
||||
* @param duration animation time (seconds)
|
||||
* @return this
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Animate to given vec in given amount of time.
|
||||
*
|
||||
* @param target target (only it's current value will be used)
|
||||
* @param duration animation time (seconds)
|
||||
* @return this
|
||||
*/
|
||||
public VectAnimated animate(Vect target, double duration)
|
||||
{
|
||||
animate(target.x(), target.y(), target.z(), duration);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user