commit 400ee6a079130bd1cbc58d71f0added7f81e4ad3 Author: Ondřej Hruška Date: Thu May 29 20:45:10 2014 +0200 Initial source import. diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..fb565a5 --- /dev/null +++ b/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..561482d --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + mighty_utils + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..7341ab1 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f8d6ef2 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,23 @@ +Copyright (c) 2014, Ondřej Hruška (MightyPork), +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a321a8 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +MightyPork's Utils Library +========================== + +This is a library of general-purpose utilities. + +Can be used as a library, or individual parts alone. Feel free to contribute or use it at will. diff --git a/bin/mightypork/utils/Convert.class b/bin/mightypork/utils/Convert.class new file mode 100644 index 0000000..edb6ab6 Binary files /dev/null and b/bin/mightypork/utils/Convert.class differ diff --git a/bin/mightypork/utils/MapSort$1.class b/bin/mightypork/utils/MapSort$1.class new file mode 100644 index 0000000..7639991 Binary files /dev/null and b/bin/mightypork/utils/MapSort$1.class differ diff --git a/bin/mightypork/utils/MapSort.class b/bin/mightypork/utils/MapSort.class new file mode 100644 index 0000000..94b7ea2 Binary files /dev/null and b/bin/mightypork/utils/MapSort.class differ diff --git a/bin/mightypork/utils/Reflect.class b/bin/mightypork/utils/Reflect.class new file mode 100644 index 0000000..cf684d5 Binary files /dev/null and b/bin/mightypork/utils/Reflect.class differ diff --git a/bin/mightypork/utils/Support$IterableEnumerationWrapper$1.class b/bin/mightypork/utils/Support$IterableEnumerationWrapper$1.class new file mode 100644 index 0000000..7512f3d Binary files /dev/null and b/bin/mightypork/utils/Support$IterableEnumerationWrapper$1.class differ diff --git a/bin/mightypork/utils/Support$IterableEnumerationWrapper.class b/bin/mightypork/utils/Support$IterableEnumerationWrapper.class new file mode 100644 index 0000000..b206a19 Binary files /dev/null and b/bin/mightypork/utils/Support$IterableEnumerationWrapper.class differ diff --git a/bin/mightypork/utils/Support.class b/bin/mightypork/utils/Support.class new file mode 100644 index 0000000..e168079 Binary files /dev/null and b/bin/mightypork/utils/Support.class differ diff --git a/bin/mightypork/utils/annotations/Alias.class b/bin/mightypork/utils/annotations/Alias.class new file mode 100644 index 0000000..1283d26 Binary files /dev/null and b/bin/mightypork/utils/annotations/Alias.class differ diff --git a/bin/mightypork/utils/annotations/DefaultImpl.class b/bin/mightypork/utils/annotations/DefaultImpl.class new file mode 100644 index 0000000..0f4742f Binary files /dev/null and b/bin/mightypork/utils/annotations/DefaultImpl.class differ diff --git a/bin/mightypork/utils/annotations/FactoryMethod.class b/bin/mightypork/utils/annotations/FactoryMethod.class new file mode 100644 index 0000000..1aae185 Binary files /dev/null and b/bin/mightypork/utils/annotations/FactoryMethod.class differ diff --git a/bin/mightypork/utils/eventbus/BusAccess.class b/bin/mightypork/utils/eventbus/BusAccess.class new file mode 100644 index 0000000..8c8b181 Binary files /dev/null and b/bin/mightypork/utils/eventbus/BusAccess.class differ diff --git a/bin/mightypork/utils/eventbus/BusEvent.class b/bin/mightypork/utils/eventbus/BusEvent.class new file mode 100644 index 0000000..9fbe4c8 Binary files /dev/null and b/bin/mightypork/utils/eventbus/BusEvent.class differ diff --git a/bin/mightypork/utils/eventbus/EventBus$DelayQueueEntry.class b/bin/mightypork/utils/eventbus/EventBus$DelayQueueEntry.class new file mode 100644 index 0000000..74bf225 Binary files /dev/null and b/bin/mightypork/utils/eventbus/EventBus$DelayQueueEntry.class differ diff --git a/bin/mightypork/utils/eventbus/EventBus$QueuePollingThread.class b/bin/mightypork/utils/eventbus/EventBus$QueuePollingThread.class new file mode 100644 index 0000000..92e58b5 Binary files /dev/null and b/bin/mightypork/utils/eventbus/EventBus$QueuePollingThread.class differ diff --git a/bin/mightypork/utils/eventbus/EventBus.class b/bin/mightypork/utils/eventbus/EventBus.class new file mode 100644 index 0000000..5d75420 Binary files /dev/null and b/bin/mightypork/utils/eventbus/EventBus.class differ diff --git a/bin/mightypork/utils/eventbus/EventChannel.class b/bin/mightypork/utils/eventbus/EventChannel.class new file mode 100644 index 0000000..7053447 Binary files /dev/null and b/bin/mightypork/utils/eventbus/EventChannel.class differ diff --git a/bin/mightypork/utils/eventbus/clients/BusNode.class b/bin/mightypork/utils/eventbus/clients/BusNode.class new file mode 100644 index 0000000..bde14c6 Binary files /dev/null and b/bin/mightypork/utils/eventbus/clients/BusNode.class differ diff --git a/bin/mightypork/utils/eventbus/clients/ClientHub.class b/bin/mightypork/utils/eventbus/clients/ClientHub.class new file mode 100644 index 0000000..4fb13a5 Binary files /dev/null and b/bin/mightypork/utils/eventbus/clients/ClientHub.class differ diff --git a/bin/mightypork/utils/eventbus/clients/ClientList.class b/bin/mightypork/utils/eventbus/clients/ClientList.class new file mode 100644 index 0000000..760edc1 Binary files /dev/null and b/bin/mightypork/utils/eventbus/clients/ClientList.class differ diff --git a/bin/mightypork/utils/eventbus/clients/DelegatingClient.class b/bin/mightypork/utils/eventbus/clients/DelegatingClient.class new file mode 100644 index 0000000..d87d0e3 Binary files /dev/null and b/bin/mightypork/utils/eventbus/clients/DelegatingClient.class differ diff --git a/bin/mightypork/utils/eventbus/clients/DelegatingList.class b/bin/mightypork/utils/eventbus/clients/DelegatingList.class new file mode 100644 index 0000000..532bbac Binary files /dev/null and b/bin/mightypork/utils/eventbus/clients/DelegatingList.class differ diff --git a/bin/mightypork/utils/eventbus/clients/RootBusNode.class b/bin/mightypork/utils/eventbus/clients/RootBusNode.class new file mode 100644 index 0000000..cdda791 Binary files /dev/null and b/bin/mightypork/utils/eventbus/clients/RootBusNode.class differ diff --git a/bin/mightypork/utils/eventbus/clients/ToggleableClient.class b/bin/mightypork/utils/eventbus/clients/ToggleableClient.class new file mode 100644 index 0000000..31898d9 Binary files /dev/null and b/bin/mightypork/utils/eventbus/clients/ToggleableClient.class differ diff --git a/bin/mightypork/utils/eventbus/events/DestroyEvent.class b/bin/mightypork/utils/eventbus/events/DestroyEvent.class new file mode 100644 index 0000000..8be28d7 Binary files /dev/null and b/bin/mightypork/utils/eventbus/events/DestroyEvent.class differ diff --git a/bin/mightypork/utils/eventbus/events/UpdateEvent.class b/bin/mightypork/utils/eventbus/events/UpdateEvent.class new file mode 100644 index 0000000..e7c7949 Binary files /dev/null and b/bin/mightypork/utils/eventbus/events/UpdateEvent.class differ diff --git a/bin/mightypork/utils/eventbus/events/flags/DelayedEvent.class b/bin/mightypork/utils/eventbus/events/flags/DelayedEvent.class new file mode 100644 index 0000000..bdc4303 Binary files /dev/null and b/bin/mightypork/utils/eventbus/events/flags/DelayedEvent.class differ diff --git a/bin/mightypork/utils/eventbus/events/flags/DirectEvent.class b/bin/mightypork/utils/eventbus/events/flags/DirectEvent.class new file mode 100644 index 0000000..abab69e Binary files /dev/null and b/bin/mightypork/utils/eventbus/events/flags/DirectEvent.class differ diff --git a/bin/mightypork/utils/eventbus/events/flags/NonConsumableEvent.class b/bin/mightypork/utils/eventbus/events/flags/NonConsumableEvent.class new file mode 100644 index 0000000..4ec14fc Binary files /dev/null and b/bin/mightypork/utils/eventbus/events/flags/NonConsumableEvent.class differ diff --git a/bin/mightypork/utils/eventbus/events/flags/NonRejectableEvent.class b/bin/mightypork/utils/eventbus/events/flags/NonRejectableEvent.class new file mode 100644 index 0000000..920ee9a Binary files /dev/null and b/bin/mightypork/utils/eventbus/events/flags/NonRejectableEvent.class differ diff --git a/bin/mightypork/utils/eventbus/events/flags/NotLoggedEvent.class b/bin/mightypork/utils/eventbus/events/flags/NotLoggedEvent.class new file mode 100644 index 0000000..c03dfae Binary files /dev/null and b/bin/mightypork/utils/eventbus/events/flags/NotLoggedEvent.class differ diff --git a/bin/mightypork/utils/eventbus/events/flags/SingleReceiverEvent.class b/bin/mightypork/utils/eventbus/events/flags/SingleReceiverEvent.class new file mode 100644 index 0000000..a9abc52 Binary files /dev/null and b/bin/mightypork/utils/eventbus/events/flags/SingleReceiverEvent.class differ diff --git a/bin/mightypork/utils/exceptions/CorruptDataException.class b/bin/mightypork/utils/exceptions/CorruptDataException.class new file mode 100644 index 0000000..5076945 Binary files /dev/null and b/bin/mightypork/utils/exceptions/CorruptDataException.class differ diff --git a/bin/mightypork/utils/exceptions/IllegalValueException.class b/bin/mightypork/utils/exceptions/IllegalValueException.class new file mode 100644 index 0000000..f3ff899 Binary files /dev/null and b/bin/mightypork/utils/exceptions/IllegalValueException.class differ diff --git a/bin/mightypork/utils/exceptions/KeyAlreadyExistsException.class b/bin/mightypork/utils/exceptions/KeyAlreadyExistsException.class new file mode 100644 index 0000000..57fb7c9 Binary files /dev/null and b/bin/mightypork/utils/exceptions/KeyAlreadyExistsException.class differ diff --git a/bin/mightypork/utils/files/FileSuffixFilter.class b/bin/mightypork/utils/files/FileSuffixFilter.class new file mode 100644 index 0000000..b1be9a0 Binary files /dev/null and b/bin/mightypork/utils/files/FileSuffixFilter.class differ diff --git a/bin/mightypork/utils/files/FileTreeDiff$1.class b/bin/mightypork/utils/files/FileTreeDiff$1.class new file mode 100644 index 0000000..5230544 Binary files /dev/null and b/bin/mightypork/utils/files/FileTreeDiff$1.class differ diff --git a/bin/mightypork/utils/files/FileTreeDiff$NotEqualException.class b/bin/mightypork/utils/files/FileTreeDiff$NotEqualException.class new file mode 100644 index 0000000..a7b5580 Binary files /dev/null and b/bin/mightypork/utils/files/FileTreeDiff$NotEqualException.class differ diff --git a/bin/mightypork/utils/files/FileTreeDiff$Tuple.class b/bin/mightypork/utils/files/FileTreeDiff$Tuple.class new file mode 100644 index 0000000..a47717d Binary files /dev/null and b/bin/mightypork/utils/files/FileTreeDiff$Tuple.class differ diff --git a/bin/mightypork/utils/files/FileTreeDiff.class b/bin/mightypork/utils/files/FileTreeDiff.class new file mode 100644 index 0000000..51033f5 Binary files /dev/null and b/bin/mightypork/utils/files/FileTreeDiff.class differ diff --git a/bin/mightypork/utils/files/FileUtils.class b/bin/mightypork/utils/files/FileUtils.class new file mode 100644 index 0000000..9cedc5d Binary files /dev/null and b/bin/mightypork/utils/files/FileUtils.class differ diff --git a/bin/mightypork/utils/files/InstanceLock$1.class b/bin/mightypork/utils/files/InstanceLock$1.class new file mode 100644 index 0000000..b14a003 Binary files /dev/null and b/bin/mightypork/utils/files/InstanceLock$1.class differ diff --git a/bin/mightypork/utils/files/InstanceLock.class b/bin/mightypork/utils/files/InstanceLock.class new file mode 100644 index 0000000..d25ab38 Binary files /dev/null and b/bin/mightypork/utils/files/InstanceLock.class differ diff --git a/bin/mightypork/utils/files/OsUtils$EnumOS.class b/bin/mightypork/utils/files/OsUtils$EnumOS.class new file mode 100644 index 0000000..88fb281 Binary files /dev/null and b/bin/mightypork/utils/files/OsUtils$EnumOS.class differ diff --git a/bin/mightypork/utils/files/OsUtils.class b/bin/mightypork/utils/files/OsUtils.class new file mode 100644 index 0000000..8b119f7 Binary files /dev/null and b/bin/mightypork/utils/files/OsUtils.class differ diff --git a/bin/mightypork/utils/files/config/Property.class b/bin/mightypork/utils/files/config/Property.class new file mode 100644 index 0000000..3bbc00d Binary files /dev/null and b/bin/mightypork/utils/files/config/Property.class differ diff --git a/bin/mightypork/utils/files/config/PropertyManager$BooleanProperty.class b/bin/mightypork/utils/files/config/PropertyManager$BooleanProperty.class new file mode 100644 index 0000000..932f7a0 Binary files /dev/null and b/bin/mightypork/utils/files/config/PropertyManager$BooleanProperty.class differ diff --git a/bin/mightypork/utils/files/config/PropertyManager$DoubleProperty.class b/bin/mightypork/utils/files/config/PropertyManager$DoubleProperty.class new file mode 100644 index 0000000..a9f57d9 Binary files /dev/null and b/bin/mightypork/utils/files/config/PropertyManager$DoubleProperty.class differ diff --git a/bin/mightypork/utils/files/config/PropertyManager$IntegerProperty.class b/bin/mightypork/utils/files/config/PropertyManager$IntegerProperty.class new file mode 100644 index 0000000..c933f0a Binary files /dev/null and b/bin/mightypork/utils/files/config/PropertyManager$IntegerProperty.class differ diff --git a/bin/mightypork/utils/files/config/PropertyManager$StringProperty.class b/bin/mightypork/utils/files/config/PropertyManager$StringProperty.class new file mode 100644 index 0000000..65797c6 Binary files /dev/null and b/bin/mightypork/utils/files/config/PropertyManager$StringProperty.class differ diff --git a/bin/mightypork/utils/files/config/PropertyManager.class b/bin/mightypork/utils/files/config/PropertyManager.class new file mode 100644 index 0000000..2f7fd89 Binary files /dev/null and b/bin/mightypork/utils/files/config/PropertyManager.class differ diff --git a/bin/mightypork/utils/files/config/SimpleConfig.class b/bin/mightypork/utils/files/config/SimpleConfig.class new file mode 100644 index 0000000..51e54d0 Binary files /dev/null and b/bin/mightypork/utils/files/config/SimpleConfig.class differ diff --git a/bin/mightypork/utils/files/config/SortedProperties.class b/bin/mightypork/utils/files/config/SortedProperties.class new file mode 100644 index 0000000..273b0b1 Binary files /dev/null and b/bin/mightypork/utils/files/config/SortedProperties.class differ diff --git a/bin/mightypork/utils/files/zip/ZipBuilder.class b/bin/mightypork/utils/files/zip/ZipBuilder.class new file mode 100644 index 0000000..a0198bf Binary files /dev/null and b/bin/mightypork/utils/files/zip/ZipBuilder.class differ diff --git a/bin/mightypork/utils/files/zip/ZipUtils.class b/bin/mightypork/utils/files/zip/ZipUtils.class new file mode 100644 index 0000000..5f2dbec Binary files /dev/null and b/bin/mightypork/utils/files/zip/ZipUtils.class differ diff --git a/bin/mightypork/utils/interfaces/Destroyable.class b/bin/mightypork/utils/interfaces/Destroyable.class new file mode 100644 index 0000000..eaf82aa Binary files /dev/null and b/bin/mightypork/utils/interfaces/Destroyable.class differ diff --git a/bin/mightypork/utils/interfaces/Enableable.class b/bin/mightypork/utils/interfaces/Enableable.class new file mode 100644 index 0000000..c9cfd37 Binary files /dev/null and b/bin/mightypork/utils/interfaces/Enableable.class differ diff --git a/bin/mightypork/utils/interfaces/Pauseable.class b/bin/mightypork/utils/interfaces/Pauseable.class new file mode 100644 index 0000000..24e62ea Binary files /dev/null and b/bin/mightypork/utils/interfaces/Pauseable.class differ diff --git a/bin/mightypork/utils/interfaces/Pollable.class b/bin/mightypork/utils/interfaces/Pollable.class new file mode 100644 index 0000000..8768c50 Binary files /dev/null and b/bin/mightypork/utils/interfaces/Pollable.class differ diff --git a/bin/mightypork/utils/interfaces/Updateable.class b/bin/mightypork/utils/interfaces/Updateable.class new file mode 100644 index 0000000..e8ea5bd Binary files /dev/null and b/bin/mightypork/utils/interfaces/Updateable.class differ diff --git a/bin/mightypork/utils/ion/Ion.class b/bin/mightypork/utils/ion/Ion.class new file mode 100644 index 0000000..20ad86c Binary files /dev/null and b/bin/mightypork/utils/ion/Ion.class differ diff --git a/bin/mightypork/utils/ion/IonBundle.class b/bin/mightypork/utils/ion/IonBundle.class new file mode 100644 index 0000000..b57af51 Binary files /dev/null and b/bin/mightypork/utils/ion/IonBundle.class differ diff --git a/bin/mightypork/utils/ion/IonInput.class b/bin/mightypork/utils/ion/IonInput.class new file mode 100644 index 0000000..b10800c Binary files /dev/null and b/bin/mightypork/utils/ion/IonInput.class differ diff --git a/bin/mightypork/utils/ion/IonMapWrapper.class b/bin/mightypork/utils/ion/IonMapWrapper.class new file mode 100644 index 0000000..8aa1c05 Binary files /dev/null and b/bin/mightypork/utils/ion/IonMapWrapper.class differ diff --git a/bin/mightypork/utils/ion/IonObjBinary.class b/bin/mightypork/utils/ion/IonObjBinary.class new file mode 100644 index 0000000..cb3a08e Binary files /dev/null and b/bin/mightypork/utils/ion/IonObjBinary.class differ diff --git a/bin/mightypork/utils/ion/IonObjBundled.class b/bin/mightypork/utils/ion/IonObjBundled.class new file mode 100644 index 0000000..2908523 Binary files /dev/null and b/bin/mightypork/utils/ion/IonObjBundled.class differ diff --git a/bin/mightypork/utils/ion/IonOutput.class b/bin/mightypork/utils/ion/IonOutput.class new file mode 100644 index 0000000..17c450d Binary files /dev/null and b/bin/mightypork/utils/ion/IonOutput.class differ diff --git a/bin/mightypork/utils/ion/IonSequenceWrapper.class b/bin/mightypork/utils/ion/IonSequenceWrapper.class new file mode 100644 index 0000000..d0dd651 Binary files /dev/null and b/bin/mightypork/utils/ion/IonSequenceWrapper.class differ diff --git a/bin/mightypork/utils/logging/Log.class b/bin/mightypork/utils/logging/Log.class new file mode 100644 index 0000000..ec6a80e Binary files /dev/null and b/bin/mightypork/utils/logging/Log.class differ diff --git a/bin/mightypork/utils/logging/monitors/LogMonitor.class b/bin/mightypork/utils/logging/monitors/LogMonitor.class new file mode 100644 index 0000000..9914a45 Binary files /dev/null and b/bin/mightypork/utils/logging/monitors/LogMonitor.class differ diff --git a/bin/mightypork/utils/logging/monitors/LogMonitorStdout.class b/bin/mightypork/utils/logging/monitors/LogMonitorStdout.class new file mode 100644 index 0000000..eeb8e17 Binary files /dev/null and b/bin/mightypork/utils/logging/monitors/LogMonitorStdout.class differ diff --git a/bin/mightypork/utils/logging/writers/ArchivingLog$1.class b/bin/mightypork/utils/logging/writers/ArchivingLog$1.class new file mode 100644 index 0000000..5ef14af Binary files /dev/null and b/bin/mightypork/utils/logging/writers/ArchivingLog$1.class differ diff --git a/bin/mightypork/utils/logging/writers/ArchivingLog$2.class b/bin/mightypork/utils/logging/writers/ArchivingLog$2.class new file mode 100644 index 0000000..0749b0e Binary files /dev/null and b/bin/mightypork/utils/logging/writers/ArchivingLog$2.class differ diff --git a/bin/mightypork/utils/logging/writers/ArchivingLog.class b/bin/mightypork/utils/logging/writers/ArchivingLog.class new file mode 100644 index 0000000..8d6d8be Binary files /dev/null and b/bin/mightypork/utils/logging/writers/ArchivingLog.class differ diff --git a/bin/mightypork/utils/logging/writers/LogWriter.class b/bin/mightypork/utils/logging/writers/LogWriter.class new file mode 100644 index 0000000..c29dcc0 Binary files /dev/null and b/bin/mightypork/utils/logging/writers/LogWriter.class differ diff --git a/bin/mightypork/utils/logging/writers/SimpleLog$LogFormatter.class b/bin/mightypork/utils/logging/writers/SimpleLog$LogFormatter.class new file mode 100644 index 0000000..d1b99fa Binary files /dev/null and b/bin/mightypork/utils/logging/writers/SimpleLog$LogFormatter.class differ diff --git a/bin/mightypork/utils/logging/writers/SimpleLog.class b/bin/mightypork/utils/logging/writers/SimpleLog.class new file mode 100644 index 0000000..7d25427 Binary files /dev/null and b/bin/mightypork/utils/logging/writers/SimpleLog.class differ diff --git a/bin/mightypork/utils/math/Calc.class b/bin/mightypork/utils/math/Calc.class new file mode 100644 index 0000000..981b7a1 Binary files /dev/null and b/bin/mightypork/utils/math/Calc.class differ diff --git a/bin/mightypork/utils/math/Polar$1.class b/bin/mightypork/utils/math/Polar$1.class new file mode 100644 index 0000000..5748665 Binary files /dev/null and b/bin/mightypork/utils/math/Polar$1.class differ diff --git a/bin/mightypork/utils/math/Polar.class b/bin/mightypork/utils/math/Polar.class new file mode 100644 index 0000000..f9b0bb8 Binary files /dev/null and b/bin/mightypork/utils/math/Polar.class differ diff --git a/bin/mightypork/utils/math/Range.class b/bin/mightypork/utils/math/Range.class new file mode 100644 index 0000000..16b90c8 Binary files /dev/null and b/bin/mightypork/utils/math/Range.class differ diff --git a/bin/mightypork/utils/math/algo/Coord.class b/bin/mightypork/utils/math/algo/Coord.class new file mode 100644 index 0000000..ff5e11a Binary files /dev/null and b/bin/mightypork/utils/math/algo/Coord.class differ diff --git a/bin/mightypork/utils/math/algo/Move.class b/bin/mightypork/utils/math/algo/Move.class new file mode 100644 index 0000000..06138ce Binary files /dev/null and b/bin/mightypork/utils/math/algo/Move.class differ diff --git a/bin/mightypork/utils/math/algo/Moves.class b/bin/mightypork/utils/math/algo/Moves.class new file mode 100644 index 0000000..1b23734 Binary files /dev/null and b/bin/mightypork/utils/math/algo/Moves.class differ diff --git a/bin/mightypork/utils/math/algo/floodfill/FloodFill.class b/bin/mightypork/utils/math/algo/floodfill/FloodFill.class new file mode 100644 index 0000000..420729b Binary files /dev/null and b/bin/mightypork/utils/math/algo/floodfill/FloodFill.class differ diff --git a/bin/mightypork/utils/math/algo/pathfinding/Heuristic.class b/bin/mightypork/utils/math/algo/pathfinding/Heuristic.class new file mode 100644 index 0000000..1c2490a Binary files /dev/null and b/bin/mightypork/utils/math/algo/pathfinding/Heuristic.class differ diff --git a/bin/mightypork/utils/math/algo/pathfinding/PathFinder$FComparator.class b/bin/mightypork/utils/math/algo/pathfinding/PathFinder$FComparator.class new file mode 100644 index 0000000..5be3a4a Binary files /dev/null and b/bin/mightypork/utils/math/algo/pathfinding/PathFinder$FComparator.class differ diff --git a/bin/mightypork/utils/math/algo/pathfinding/PathFinder$Node.class b/bin/mightypork/utils/math/algo/pathfinding/PathFinder$Node.class new file mode 100644 index 0000000..b762fe3 Binary files /dev/null and b/bin/mightypork/utils/math/algo/pathfinding/PathFinder$Node.class differ diff --git a/bin/mightypork/utils/math/algo/pathfinding/PathFinder.class b/bin/mightypork/utils/math/algo/pathfinding/PathFinder.class new file mode 100644 index 0000000..dbcc1a8 Binary files /dev/null and b/bin/mightypork/utils/math/algo/pathfinding/PathFinder.class differ diff --git a/bin/mightypork/utils/math/algo/pathfinding/PathFinderProxy.class b/bin/mightypork/utils/math/algo/pathfinding/PathFinderProxy.class new file mode 100644 index 0000000..7bd2f0a Binary files /dev/null and b/bin/mightypork/utils/math/algo/pathfinding/PathFinderProxy.class differ diff --git a/bin/mightypork/utils/math/algo/pathfinding/heuristics/DiagonalHeuristic.class b/bin/mightypork/utils/math/algo/pathfinding/heuristics/DiagonalHeuristic.class new file mode 100644 index 0000000..c91ad81 Binary files /dev/null and b/bin/mightypork/utils/math/algo/pathfinding/heuristics/DiagonalHeuristic.class differ diff --git a/bin/mightypork/utils/math/algo/pathfinding/heuristics/ManhattanHeuristic.class b/bin/mightypork/utils/math/algo/pathfinding/heuristics/ManhattanHeuristic.class new file mode 100644 index 0000000..fc7a687 Binary files /dev/null and b/bin/mightypork/utils/math/algo/pathfinding/heuristics/ManhattanHeuristic.class differ diff --git a/bin/mightypork/utils/math/angles/Angles.class b/bin/mightypork/utils/math/angles/Angles.class new file mode 100644 index 0000000..2d49495 Binary files /dev/null and b/bin/mightypork/utils/math/angles/Angles.class differ diff --git a/bin/mightypork/utils/math/angles/Deg.class b/bin/mightypork/utils/math/angles/Deg.class new file mode 100644 index 0000000..d938b89 Binary files /dev/null and b/bin/mightypork/utils/math/angles/Deg.class differ diff --git a/bin/mightypork/utils/math/angles/Rad.class b/bin/mightypork/utils/math/angles/Rad.class new file mode 100644 index 0000000..fe15b3d Binary files /dev/null and b/bin/mightypork/utils/math/angles/Rad.class differ diff --git a/bin/mightypork/utils/math/animation/Animator.class b/bin/mightypork/utils/math/animation/Animator.class new file mode 100644 index 0000000..f3d08ee Binary files /dev/null and b/bin/mightypork/utils/math/animation/Animator.class differ diff --git a/bin/mightypork/utils/math/animation/AnimatorBounce.class b/bin/mightypork/utils/math/animation/AnimatorBounce.class new file mode 100644 index 0000000..9817f2e Binary files /dev/null and b/bin/mightypork/utils/math/animation/AnimatorBounce.class differ diff --git a/bin/mightypork/utils/math/animation/AnimatorRewind.class b/bin/mightypork/utils/math/animation/AnimatorRewind.class new file mode 100644 index 0000000..58b2b0c Binary files /dev/null and b/bin/mightypork/utils/math/animation/AnimatorRewind.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$1.class b/bin/mightypork/utils/math/animation/Easing$1.class new file mode 100644 index 0000000..0c6170d Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$1.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$10.class b/bin/mightypork/utils/math/animation/Easing$10.class new file mode 100644 index 0000000..784f785 Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$10.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$11.class b/bin/mightypork/utils/math/animation/Easing$11.class new file mode 100644 index 0000000..112f826 Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$11.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$12.class b/bin/mightypork/utils/math/animation/Easing$12.class new file mode 100644 index 0000000..68efa22 Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$12.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$2.class b/bin/mightypork/utils/math/animation/Easing$2.class new file mode 100644 index 0000000..89e5551 Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$2.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$3.class b/bin/mightypork/utils/math/animation/Easing$3.class new file mode 100644 index 0000000..ab9bbc1 Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$3.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$4.class b/bin/mightypork/utils/math/animation/Easing$4.class new file mode 100644 index 0000000..c50709a Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$4.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$5.class b/bin/mightypork/utils/math/animation/Easing$5.class new file mode 100644 index 0000000..415b4cf Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$5.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$6.class b/bin/mightypork/utils/math/animation/Easing$6.class new file mode 100644 index 0000000..4963231 Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$6.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$7.class b/bin/mightypork/utils/math/animation/Easing$7.class new file mode 100644 index 0000000..0d59cc2 Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$7.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$8.class b/bin/mightypork/utils/math/animation/Easing$8.class new file mode 100644 index 0000000..9633ce1 Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$8.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$9.class b/bin/mightypork/utils/math/animation/Easing$9.class new file mode 100644 index 0000000..3ee27fb Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$9.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$Composite.class b/bin/mightypork/utils/math/animation/Easing$Composite.class new file mode 100644 index 0000000..a24a8d1 Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$Composite.class differ diff --git a/bin/mightypork/utils/math/animation/Easing$Reverse.class b/bin/mightypork/utils/math/animation/Easing$Reverse.class new file mode 100644 index 0000000..f50c099 Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing$Reverse.class differ diff --git a/bin/mightypork/utils/math/animation/Easing.class b/bin/mightypork/utils/math/animation/Easing.class new file mode 100644 index 0000000..7138cb0 Binary files /dev/null and b/bin/mightypork/utils/math/animation/Easing.class differ diff --git a/bin/mightypork/utils/math/animation/NumAnimated.class b/bin/mightypork/utils/math/animation/NumAnimated.class new file mode 100644 index 0000000..14bbc44 Binary files /dev/null and b/bin/mightypork/utils/math/animation/NumAnimated.class differ diff --git a/bin/mightypork/utils/math/animation/NumAnimatedDeg.class b/bin/mightypork/utils/math/animation/NumAnimatedDeg.class new file mode 100644 index 0000000..4daacda Binary files /dev/null and b/bin/mightypork/utils/math/animation/NumAnimatedDeg.class differ diff --git a/bin/mightypork/utils/math/animation/NumAnimatedRad.class b/bin/mightypork/utils/math/animation/NumAnimatedRad.class new file mode 100644 index 0000000..0dfd856 Binary files /dev/null and b/bin/mightypork/utils/math/animation/NumAnimatedRad.class differ diff --git a/bin/mightypork/utils/math/animation/VectAnimated.class b/bin/mightypork/utils/math/animation/VectAnimated.class new file mode 100644 index 0000000..fd986ef Binary files /dev/null and b/bin/mightypork/utils/math/animation/VectAnimated.class differ diff --git a/bin/mightypork/utils/math/color/Color.class b/bin/mightypork/utils/math/color/Color.class new file mode 100644 index 0000000..541998d Binary files /dev/null and b/bin/mightypork/utils/math/color/Color.class differ diff --git a/bin/mightypork/utils/math/color/ColorAlphaAdjuster.class b/bin/mightypork/utils/math/color/ColorAlphaAdjuster.class new file mode 100644 index 0000000..cd64962 Binary files /dev/null and b/bin/mightypork/utils/math/color/ColorAlphaAdjuster.class differ diff --git a/bin/mightypork/utils/math/color/ColorHsb.class b/bin/mightypork/utils/math/color/ColorHsb.class new file mode 100644 index 0000000..138635f Binary files /dev/null and b/bin/mightypork/utils/math/color/ColorHsb.class differ diff --git a/bin/mightypork/utils/math/color/ColorRgb.class b/bin/mightypork/utils/math/color/ColorRgb.class new file mode 100644 index 0000000..e9c9afb Binary files /dev/null and b/bin/mightypork/utils/math/color/ColorRgb.class differ diff --git a/bin/mightypork/utils/math/color/pal/CGA.class b/bin/mightypork/utils/math/color/pal/CGA.class new file mode 100644 index 0000000..3d4cb27 Binary files /dev/null and b/bin/mightypork/utils/math/color/pal/CGA.class differ diff --git a/bin/mightypork/utils/math/color/pal/CMDR.class b/bin/mightypork/utils/math/color/pal/CMDR.class new file mode 100644 index 0000000..14396f4 Binary files /dev/null and b/bin/mightypork/utils/math/color/pal/CMDR.class differ diff --git a/bin/mightypork/utils/math/color/pal/PAL16.class b/bin/mightypork/utils/math/color/pal/PAL16.class new file mode 100644 index 0000000..f86ab1d Binary files /dev/null and b/bin/mightypork/utils/math/color/pal/PAL16.class differ diff --git a/bin/mightypork/utils/math/color/pal/RGB.class b/bin/mightypork/utils/math/color/pal/RGB.class new file mode 100644 index 0000000..64cb8bc Binary files /dev/null and b/bin/mightypork/utils/math/color/pal/RGB.class differ diff --git a/bin/mightypork/utils/math/color/pal/ZX.class b/bin/mightypork/utils/math/color/pal/ZX.class new file mode 100644 index 0000000..d4b278b Binary files /dev/null and b/bin/mightypork/utils/math/color/pal/ZX.class differ diff --git a/bin/mightypork/utils/math/constraints/CachedConstraint.class b/bin/mightypork/utils/math/constraints/CachedConstraint.class new file mode 100644 index 0000000..447c373 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/CachedConstraint.class differ diff --git a/bin/mightypork/utils/math/constraints/CachedDigestable.class b/bin/mightypork/utils/math/constraints/CachedDigestable.class new file mode 100644 index 0000000..fe612ea Binary files /dev/null and b/bin/mightypork/utils/math/constraints/CachedDigestable.class differ diff --git a/bin/mightypork/utils/math/constraints/DigestCache.class b/bin/mightypork/utils/math/constraints/DigestCache.class new file mode 100644 index 0000000..7c15c5f Binary files /dev/null and b/bin/mightypork/utils/math/constraints/DigestCache.class differ diff --git a/bin/mightypork/utils/math/constraints/Digestable.class b/bin/mightypork/utils/math/constraints/Digestable.class new file mode 100644 index 0000000..8ea0e6f Binary files /dev/null and b/bin/mightypork/utils/math/constraints/Digestable.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$1.class b/bin/mightypork/utils/math/constraints/num/Num$1.class new file mode 100644 index 0000000..b532aec Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$1.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$10.class b/bin/mightypork/utils/math/constraints/num/Num$10.class new file mode 100644 index 0000000..cbb9b46 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$10.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$11.class b/bin/mightypork/utils/math/constraints/num/Num$11.class new file mode 100644 index 0000000..73f4eab Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$11.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$12.class b/bin/mightypork/utils/math/constraints/num/Num$12.class new file mode 100644 index 0000000..8f4b69a Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$12.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$13.class b/bin/mightypork/utils/math/constraints/num/Num$13.class new file mode 100644 index 0000000..a55ef86 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$13.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$14.class b/bin/mightypork/utils/math/constraints/num/Num$14.class new file mode 100644 index 0000000..73dcdcc Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$14.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$15.class b/bin/mightypork/utils/math/constraints/num/Num$15.class new file mode 100644 index 0000000..66cf942 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$15.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$16.class b/bin/mightypork/utils/math/constraints/num/Num$16.class new file mode 100644 index 0000000..65203dd Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$16.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$17.class b/bin/mightypork/utils/math/constraints/num/Num$17.class new file mode 100644 index 0000000..8a3fc53 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$17.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$18.class b/bin/mightypork/utils/math/constraints/num/Num$18.class new file mode 100644 index 0000000..c82d9fb Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$18.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$19.class b/bin/mightypork/utils/math/constraints/num/Num$19.class new file mode 100644 index 0000000..cc0a4c1 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$19.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$2.class b/bin/mightypork/utils/math/constraints/num/Num$2.class new file mode 100644 index 0000000..ed56203 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$2.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$20.class b/bin/mightypork/utils/math/constraints/num/Num$20.class new file mode 100644 index 0000000..a93793a Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$20.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$21.class b/bin/mightypork/utils/math/constraints/num/Num$21.class new file mode 100644 index 0000000..792c558 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$21.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$22.class b/bin/mightypork/utils/math/constraints/num/Num$22.class new file mode 100644 index 0000000..8f97006 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$22.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$23.class b/bin/mightypork/utils/math/constraints/num/Num$23.class new file mode 100644 index 0000000..42c6372 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$23.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$24.class b/bin/mightypork/utils/math/constraints/num/Num$24.class new file mode 100644 index 0000000..1544ff6 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$24.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$25.class b/bin/mightypork/utils/math/constraints/num/Num$25.class new file mode 100644 index 0000000..f04e949 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$25.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$26.class b/bin/mightypork/utils/math/constraints/num/Num$26.class new file mode 100644 index 0000000..9f9bb49 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$26.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$27.class b/bin/mightypork/utils/math/constraints/num/Num$27.class new file mode 100644 index 0000000..7034df9 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$27.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$28.class b/bin/mightypork/utils/math/constraints/num/Num$28.class new file mode 100644 index 0000000..10c132d Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$28.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$29.class b/bin/mightypork/utils/math/constraints/num/Num$29.class new file mode 100644 index 0000000..5d9b74a Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$29.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$3.class b/bin/mightypork/utils/math/constraints/num/Num$3.class new file mode 100644 index 0000000..6774b1f Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$3.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$30.class b/bin/mightypork/utils/math/constraints/num/Num$30.class new file mode 100644 index 0000000..2575c7a Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$30.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$31.class b/bin/mightypork/utils/math/constraints/num/Num$31.class new file mode 100644 index 0000000..7786d4c Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$31.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$32.class b/bin/mightypork/utils/math/constraints/num/Num$32.class new file mode 100644 index 0000000..e0584c5 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$32.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$4.class b/bin/mightypork/utils/math/constraints/num/Num$4.class new file mode 100644 index 0000000..ac2043c Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$4.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$5.class b/bin/mightypork/utils/math/constraints/num/Num$5.class new file mode 100644 index 0000000..bb7ad75 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$5.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$6.class b/bin/mightypork/utils/math/constraints/num/Num$6.class new file mode 100644 index 0000000..fca8589 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$6.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$7.class b/bin/mightypork/utils/math/constraints/num/Num$7.class new file mode 100644 index 0000000..9872d1f Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$7.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$8.class b/bin/mightypork/utils/math/constraints/num/Num$8.class new file mode 100644 index 0000000..7517a1e Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$8.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num$9.class b/bin/mightypork/utils/math/constraints/num/Num$9.class new file mode 100644 index 0000000..25d23c4 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num$9.class differ diff --git a/bin/mightypork/utils/math/constraints/num/Num.class b/bin/mightypork/utils/math/constraints/num/Num.class new file mode 100644 index 0000000..a18bf6a Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/Num.class differ diff --git a/bin/mightypork/utils/math/constraints/num/NumBound.class b/bin/mightypork/utils/math/constraints/num/NumBound.class new file mode 100644 index 0000000..54fee81 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/NumBound.class differ diff --git a/bin/mightypork/utils/math/constraints/num/NumConst.class b/bin/mightypork/utils/math/constraints/num/NumConst.class new file mode 100644 index 0000000..1a2f593 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/NumConst.class differ diff --git a/bin/mightypork/utils/math/constraints/num/PluggableNumBound.class b/bin/mightypork/utils/math/constraints/num/PluggableNumBound.class new file mode 100644 index 0000000..a4ae510 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/PluggableNumBound.class differ diff --git a/bin/mightypork/utils/math/constraints/num/batch/NumMul.class b/bin/mightypork/utils/math/constraints/num/batch/NumMul.class new file mode 100644 index 0000000..3b3e951 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/batch/NumMul.class differ diff --git a/bin/mightypork/utils/math/constraints/num/batch/NumSum.class b/bin/mightypork/utils/math/constraints/num/batch/NumSum.class new file mode 100644 index 0000000..e047d31 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/batch/NumSum.class differ diff --git a/bin/mightypork/utils/math/constraints/num/caching/AbstractNumCache.class b/bin/mightypork/utils/math/constraints/num/caching/AbstractNumCache.class new file mode 100644 index 0000000..11cf800 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/caching/AbstractNumCache.class differ diff --git a/bin/mightypork/utils/math/constraints/num/caching/NumCache.class b/bin/mightypork/utils/math/constraints/num/caching/NumCache.class new file mode 100644 index 0000000..8414b89 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/caching/NumCache.class differ diff --git a/bin/mightypork/utils/math/constraints/num/caching/NumDigest.class b/bin/mightypork/utils/math/constraints/num/caching/NumDigest.class new file mode 100644 index 0000000..c8733ac Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/caching/NumDigest.class differ diff --git a/bin/mightypork/utils/math/constraints/num/proxy/NumAdapter.class b/bin/mightypork/utils/math/constraints/num/proxy/NumAdapter.class new file mode 100644 index 0000000..764d6f9 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/proxy/NumAdapter.class differ diff --git a/bin/mightypork/utils/math/constraints/num/proxy/NumProxy.class b/bin/mightypork/utils/math/constraints/num/proxy/NumProxy.class new file mode 100644 index 0000000..df8ea6b Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/proxy/NumProxy.class differ diff --git a/bin/mightypork/utils/math/constraints/num/var/NumMutable.class b/bin/mightypork/utils/math/constraints/num/var/NumMutable.class new file mode 100644 index 0000000..0412c67 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/var/NumMutable.class differ diff --git a/bin/mightypork/utils/math/constraints/num/var/NumVar.class b/bin/mightypork/utils/math/constraints/num/var/NumVar.class new file mode 100644 index 0000000..f09d88a Binary files /dev/null and b/bin/mightypork/utils/math/constraints/num/var/NumVar.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/PluggableRectBound.class b/bin/mightypork/utils/math/constraints/rect/PluggableRectBound.class new file mode 100644 index 0000000..924389e Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/PluggableRectBound.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect$1.class b/bin/mightypork/utils/math/constraints/rect/Rect$1.class new file mode 100644 index 0000000..7cc1e1e Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect$1.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect$10.class b/bin/mightypork/utils/math/constraints/rect/Rect$10.class new file mode 100644 index 0000000..5f1ea4e Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect$10.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect$11.class b/bin/mightypork/utils/math/constraints/rect/Rect$11.class new file mode 100644 index 0000000..ca448cd Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect$11.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect$2.class b/bin/mightypork/utils/math/constraints/rect/Rect$2.class new file mode 100644 index 0000000..fd187ea Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect$2.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect$3.class b/bin/mightypork/utils/math/constraints/rect/Rect$3.class new file mode 100644 index 0000000..a6fc3f0 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect$3.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect$4.class b/bin/mightypork/utils/math/constraints/rect/Rect$4.class new file mode 100644 index 0000000..7068267 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect$4.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect$5.class b/bin/mightypork/utils/math/constraints/rect/Rect$5.class new file mode 100644 index 0000000..3e9b405 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect$5.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect$6.class b/bin/mightypork/utils/math/constraints/rect/Rect$6.class new file mode 100644 index 0000000..2108682 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect$6.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect$7.class b/bin/mightypork/utils/math/constraints/rect/Rect$7.class new file mode 100644 index 0000000..c4ffa16 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect$7.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect$8.class b/bin/mightypork/utils/math/constraints/rect/Rect$8.class new file mode 100644 index 0000000..6607afb Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect$8.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect$9.class b/bin/mightypork/utils/math/constraints/rect/Rect$9.class new file mode 100644 index 0000000..b931249 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect$9.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/Rect.class b/bin/mightypork/utils/math/constraints/rect/Rect.class new file mode 100644 index 0000000..187737e Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/Rect.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/RectBound.class b/bin/mightypork/utils/math/constraints/rect/RectBound.class new file mode 100644 index 0000000..c1775d9 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/RectBound.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/RectConst.class b/bin/mightypork/utils/math/constraints/rect/RectConst.class new file mode 100644 index 0000000..31f3dd3 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/RectConst.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/builders/TiledRect.class b/bin/mightypork/utils/math/constraints/rect/builders/TiledRect.class new file mode 100644 index 0000000..2e39c33 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/builders/TiledRect.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/caching/AbstractRectCache.class b/bin/mightypork/utils/math/constraints/rect/caching/AbstractRectCache.class new file mode 100644 index 0000000..6f02ae5 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/caching/AbstractRectCache.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/caching/RectCache.class b/bin/mightypork/utils/math/constraints/rect/caching/RectCache.class new file mode 100644 index 0000000..3dcbbfb Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/caching/RectCache.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/caching/RectDigest.class b/bin/mightypork/utils/math/constraints/rect/caching/RectDigest.class new file mode 100644 index 0000000..8fe3c41 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/caching/RectDigest.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/proxy/RectAdapter$1.class b/bin/mightypork/utils/math/constraints/rect/proxy/RectAdapter$1.class new file mode 100644 index 0000000..0de61c0 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/proxy/RectAdapter$1.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/proxy/RectAdapter$2.class b/bin/mightypork/utils/math/constraints/rect/proxy/RectAdapter$2.class new file mode 100644 index 0000000..5eda74f Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/proxy/RectAdapter$2.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/proxy/RectAdapter.class b/bin/mightypork/utils/math/constraints/rect/proxy/RectAdapter.class new file mode 100644 index 0000000..369ce00 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/proxy/RectAdapter.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/proxy/RectProxy.class b/bin/mightypork/utils/math/constraints/rect/proxy/RectProxy.class new file mode 100644 index 0000000..e82fa28 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/proxy/RectProxy.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/proxy/RectVectAdapter.class b/bin/mightypork/utils/math/constraints/rect/proxy/RectVectAdapter.class new file mode 100644 index 0000000..87bb7ac Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/proxy/RectVectAdapter.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/var/RectMutable.class b/bin/mightypork/utils/math/constraints/rect/var/RectMutable.class new file mode 100644 index 0000000..9394bff Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/var/RectMutable.class differ diff --git a/bin/mightypork/utils/math/constraints/rect/var/RectVar.class b/bin/mightypork/utils/math/constraints/rect/var/RectVar.class new file mode 100644 index 0000000..53143ba Binary files /dev/null and b/bin/mightypork/utils/math/constraints/rect/var/RectVar.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/PluggableVectBound.class b/bin/mightypork/utils/math/constraints/vect/PluggableVectBound.class new file mode 100644 index 0000000..b63e21f Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/PluggableVectBound.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$1.class b/bin/mightypork/utils/math/constraints/vect/Vect$1.class new file mode 100644 index 0000000..5a59d7b Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$1.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$10.class b/bin/mightypork/utils/math/constraints/vect/Vect$10.class new file mode 100644 index 0000000..a70a836 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$10.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$11.class b/bin/mightypork/utils/math/constraints/vect/Vect$11.class new file mode 100644 index 0000000..960cada Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$11.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$12.class b/bin/mightypork/utils/math/constraints/vect/Vect$12.class new file mode 100644 index 0000000..1bfaa47 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$12.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$13.class b/bin/mightypork/utils/math/constraints/vect/Vect$13.class new file mode 100644 index 0000000..4b8da81 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$13.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$14.class b/bin/mightypork/utils/math/constraints/vect/Vect$14.class new file mode 100644 index 0000000..fd495f1 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$14.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$15.class b/bin/mightypork/utils/math/constraints/vect/Vect$15.class new file mode 100644 index 0000000..29a7051 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$15.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$16.class b/bin/mightypork/utils/math/constraints/vect/Vect$16.class new file mode 100644 index 0000000..c51c14f Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$16.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$17.class b/bin/mightypork/utils/math/constraints/vect/Vect$17.class new file mode 100644 index 0000000..7166f51 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$17.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$18.class b/bin/mightypork/utils/math/constraints/vect/Vect$18.class new file mode 100644 index 0000000..285668a Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$18.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$19.class b/bin/mightypork/utils/math/constraints/vect/Vect$19.class new file mode 100644 index 0000000..67d0e13 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$19.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$2.class b/bin/mightypork/utils/math/constraints/vect/Vect$2.class new file mode 100644 index 0000000..f31d405 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$2.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$20.class b/bin/mightypork/utils/math/constraints/vect/Vect$20.class new file mode 100644 index 0000000..4b63a50 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$20.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$21.class b/bin/mightypork/utils/math/constraints/vect/Vect$21.class new file mode 100644 index 0000000..b3737ea Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$21.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$22.class b/bin/mightypork/utils/math/constraints/vect/Vect$22.class new file mode 100644 index 0000000..284496e Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$22.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$23.class b/bin/mightypork/utils/math/constraints/vect/Vect$23.class new file mode 100644 index 0000000..50567c5 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$23.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$3.class b/bin/mightypork/utils/math/constraints/vect/Vect$3.class new file mode 100644 index 0000000..210bc1e Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$3.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$4.class b/bin/mightypork/utils/math/constraints/vect/Vect$4.class new file mode 100644 index 0000000..02e9a74 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$4.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$5.class b/bin/mightypork/utils/math/constraints/vect/Vect$5.class new file mode 100644 index 0000000..0173879 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$5.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$6.class b/bin/mightypork/utils/math/constraints/vect/Vect$6.class new file mode 100644 index 0000000..0cc4f82 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$6.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$7.class b/bin/mightypork/utils/math/constraints/vect/Vect$7.class new file mode 100644 index 0000000..b02ec38 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$7.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$8.class b/bin/mightypork/utils/math/constraints/vect/Vect$8.class new file mode 100644 index 0000000..874719b Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$8.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect$9.class b/bin/mightypork/utils/math/constraints/vect/Vect$9.class new file mode 100644 index 0000000..582f2d8 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect$9.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/Vect.class b/bin/mightypork/utils/math/constraints/vect/Vect.class new file mode 100644 index 0000000..492ab88 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/Vect.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/VectBound.class b/bin/mightypork/utils/math/constraints/vect/VectBound.class new file mode 100644 index 0000000..9a586e7 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/VectBound.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/VectConst.class b/bin/mightypork/utils/math/constraints/vect/VectConst.class new file mode 100644 index 0000000..9cf93c0 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/VectConst.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/caching/AbstractVectCache.class b/bin/mightypork/utils/math/constraints/vect/caching/AbstractVectCache.class new file mode 100644 index 0000000..8470061 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/caching/AbstractVectCache.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/caching/VectCache.class b/bin/mightypork/utils/math/constraints/vect/caching/VectCache.class new file mode 100644 index 0000000..8cd1a64 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/caching/VectCache.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/caching/VectDigest.class b/bin/mightypork/utils/math/constraints/vect/caching/VectDigest.class new file mode 100644 index 0000000..555dc79 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/caching/VectDigest.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/proxy/VectAdapter.class b/bin/mightypork/utils/math/constraints/vect/proxy/VectAdapter.class new file mode 100644 index 0000000..a1cf579 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/proxy/VectAdapter.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/proxy/VectNumAdapter.class b/bin/mightypork/utils/math/constraints/vect/proxy/VectNumAdapter.class new file mode 100644 index 0000000..6bb92b2 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/proxy/VectNumAdapter.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/proxy/VectProxy.class b/bin/mightypork/utils/math/constraints/vect/proxy/VectProxy.class new file mode 100644 index 0000000..db164df Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/proxy/VectProxy.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/var/VectMutable.class b/bin/mightypork/utils/math/constraints/vect/var/VectMutable.class new file mode 100644 index 0000000..8f38486 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/var/VectMutable.class differ diff --git a/bin/mightypork/utils/math/constraints/vect/var/VectVar.class b/bin/mightypork/utils/math/constraints/vect/var/VectVar.class new file mode 100644 index 0000000..291ac14 Binary files /dev/null and b/bin/mightypork/utils/math/constraints/vect/var/VectVar.class differ diff --git a/bin/mightypork/utils/math/noise/NoiseGen.class b/bin/mightypork/utils/math/noise/NoiseGen.class new file mode 100644 index 0000000..6a6df70 Binary files /dev/null and b/bin/mightypork/utils/math/noise/NoiseGen.class differ diff --git a/bin/mightypork/utils/math/noise/PerlinNoiseGenerator.class b/bin/mightypork/utils/math/noise/PerlinNoiseGenerator.class new file mode 100644 index 0000000..9bf0dcc Binary files /dev/null and b/bin/mightypork/utils/math/noise/PerlinNoiseGenerator.class differ diff --git a/bin/mightypork/utils/math/timing/FpsMeter.class b/bin/mightypork/utils/math/timing/FpsMeter.class new file mode 100644 index 0000000..bd2701f Binary files /dev/null and b/bin/mightypork/utils/math/timing/FpsMeter.class differ diff --git a/bin/mightypork/utils/math/timing/TaskRepeater.class b/bin/mightypork/utils/math/timing/TaskRepeater.class new file mode 100644 index 0000000..9f156d8 Binary files /dev/null and b/bin/mightypork/utils/math/timing/TaskRepeater.class differ diff --git a/bin/mightypork/utils/math/timing/TimedTask.class b/bin/mightypork/utils/math/timing/TimedTask.class new file mode 100644 index 0000000..9d1fa47 Binary files /dev/null and b/bin/mightypork/utils/math/timing/TimedTask.class differ diff --git a/bin/mightypork/utils/math/timing/TimerDelta.class b/bin/mightypork/utils/math/timing/TimerDelta.class new file mode 100644 index 0000000..0ab1306 Binary files /dev/null and b/bin/mightypork/utils/math/timing/TimerDelta.class differ diff --git a/bin/mightypork/utils/math/timing/TimerFps.class b/bin/mightypork/utils/math/timing/TimerFps.class new file mode 100644 index 0000000..ae0d10a Binary files /dev/null and b/bin/mightypork/utils/math/timing/TimerFps.class differ diff --git a/bin/mightypork/utils/string/AlphanumComparator.class b/bin/mightypork/utils/string/AlphanumComparator.class new file mode 100644 index 0000000..170cc22 Binary files /dev/null and b/bin/mightypork/utils/string/AlphanumComparator.class differ diff --git a/bin/mightypork/utils/string/StringProvider.class b/bin/mightypork/utils/string/StringProvider.class new file mode 100644 index 0000000..4e20774 Binary files /dev/null and b/bin/mightypork/utils/string/StringProvider.class differ diff --git a/bin/mightypork/utils/string/StringUtil.class b/bin/mightypork/utils/string/StringUtil.class new file mode 100644 index 0000000..cab8a62 Binary files /dev/null and b/bin/mightypork/utils/string/StringUtil.class differ diff --git a/bin/mightypork/utils/string/StringWrapper.class b/bin/mightypork/utils/string/StringWrapper.class new file mode 100644 index 0000000..d2474fd Binary files /dev/null and b/bin/mightypork/utils/string/StringWrapper.class differ diff --git a/bin/mightypork/utils/string/validation/CharFilter.class b/bin/mightypork/utils/string/validation/CharFilter.class new file mode 100644 index 0000000..cfbdb5e Binary files /dev/null and b/bin/mightypork/utils/string/validation/CharFilter.class differ diff --git a/bin/mightypork/utils/string/validation/CharFilterRegex.class b/bin/mightypork/utils/string/validation/CharFilterRegex.class new file mode 100644 index 0000000..d9dd128 Binary files /dev/null and b/bin/mightypork/utils/string/validation/CharFilterRegex.class differ diff --git a/bin/mightypork/utils/string/validation/CharFilterWhitelist.class b/bin/mightypork/utils/string/validation/CharFilterWhitelist.class new file mode 100644 index 0000000..b61caf7 Binary files /dev/null and b/bin/mightypork/utils/string/validation/CharFilterWhitelist.class differ diff --git a/bin/mightypork/utils/string/validation/StringFilter.class b/bin/mightypork/utils/string/validation/StringFilter.class new file mode 100644 index 0000000..295264b Binary files /dev/null and b/bin/mightypork/utils/string/validation/StringFilter.class differ diff --git a/bin/mightypork/utils/struct/Mutable.class b/bin/mightypork/utils/struct/Mutable.class new file mode 100644 index 0000000..dd7d220 Binary files /dev/null and b/bin/mightypork/utils/struct/Mutable.class differ diff --git a/bin/mightypork/utils/struct/Pair.class b/bin/mightypork/utils/struct/Pair.class new file mode 100644 index 0000000..a5ed425 Binary files /dev/null and b/bin/mightypork/utils/struct/Pair.class differ diff --git a/bin/mightypork/utils/struct/Triad.class b/bin/mightypork/utils/struct/Triad.class new file mode 100644 index 0000000..c7be2ef Binary files /dev/null and b/bin/mightypork/utils/struct/Triad.class differ diff --git a/doc/ion.md b/doc/ion.md new file mode 100644 index 0000000..3987a24 --- /dev/null +++ b/doc/ion.md @@ -0,0 +1,157 @@ +# ION + +ION is a data storage library, mainly intended for saving games. + +It uses a raw stream for writing/reading bytes, so you could use it both for files and ie. sockets. + +Ion can write and load primitive types, their arrays, String, Collections, +Maps, and any other types which are properly registered to the system. + + +## Why not just use serialization? + +- YOU have complete control of what's going on, no magic +- Smaller file size +- Safe to change the data types while keeping old data files +- ION is cool + + +## Basic API + +The main API class is called `Ion`, and provides a bunch of useful static methods, such as: + +- `obj = Ion.fromFile(file)` - load an object from file +- `obj = Ion.fromStream(stream)` - load an object from stream +- `Ion.toFile(file, obj)` - save an object from file +- `Ion.toStream(stream, obj)` - save an object from stream +- `ionInput = Ion.getInput(file)` - get ion input (reading from a file) +- `ionOutput = Ion.getOutput(file)` - get ion output (writing to a file) + +And there is more, check out the sources. + +The actual writing and reading of objects is realized using `IonOutput` and `IonInput` which you can use as well - the methods on `Ion` are really just for convenience. + + +## Supported data types + +Out-of-the-box, ION supports the following types: + +### Built-ins & their arrays + +- `boolean` +- `byte` +- `char` +- `short` +- `int` +- `long` +- `float` +- `double` +- `String` + +### Map, Collection, arrays + +- `Map` - if both key and value are of supported type +- `Collection` - if elements are of a supported type +- `IonBundle` - data holder, based on `Map` +- Array of any supported objects + +## Adding custom data types + +### Marks + +Ion uses byte marks to identify all the supported objects. Possible values for +a mark are 0..255, where values 0..49 are reserved for internal use. + +Therefore, you can use the remaining values (50..255) for custom data objects. + +### Creating a data type + +First, let your data class implement either `IonObjBinary` or `IonObjBundled`, and make sure it has a *implicit constructor* - that is, with no arguments. You can have other constructors as well, that's okay. + +**Binary vs Bundled** + +- **IonObjBinary** + - uses directly `IonOutput` and `IonInput` for saving and loading + - the data fields can be written without marks, since the implementing class knows what is where + - that means smaller file size, but it's less portable (if you add a new data field, the old saves will be corrupted) + +- **IonObjBundled** + - uses `IonBundle` which is automatically saved and loaded by `IonOutput` and `IonInput` + - that means you can safely add new data fields to such an object, and migrating from old saves will work without problems + - the drawback is a considerably bigger file size, since the string keys have to be stored as well as the values (of course, using short keys is beneficial here). + + +### Registering new data type + +Once you have the data type implemented, you can register it. + +You have two options: + +- Use `Ion.register(mark, objClass)`, where + - _mark_ is a byte mark to be used + - _objClass_ is a class of the registered object + +- Use `Ion.register(objClass)` and define the mark via a constant in the registered type:
+ `public static final int ION_MARK = ;` + + + +## Behind the scenes - how it works + +### How ion saves/loads the data + +This is very simplified, but you should et the general idea. For better info, read the source, it's pretty well commented via JavaDoc. + +**Saving:** + +1. `IonOutput` looks into a registry what mark the object has +2. It writes this mark into the stream +3. It writes the object to the stream + +**Loading:** + +1. `IonInput` reads a mark, and finds a class associated with that mark. +2. It creates an instance using the implicit constructor (if it's not a primitive type) +3. It loads the object from the stream + +### Data structures + +**Arrays** are typically done by writing array mark, then length of the array, and then the elements one-by-one without marks (unless they are of different type). + +``` +ARRAY (specific for each type) +...length (int)... +...all the elements, without marks... +``` + +An exception is an Object array, where each entry also contains the type mark (to distinguish them while loading). + +**Collection** is written like this - reading is simply a loop until the next mark is END instead of ENTRY. + +``` +SEQUENCE +ENTRY + // element +...entry data... +ENTRY + // element +...entry data... +END +``` + +**Map** is done the same way as collection, except each entry contains both key and the value. + +``` +MAP +ENTRY + // key +...entry data... + // value +...entry data... +ENTRY + // key +...entry data... + // value +...entry data... +END +``` diff --git a/doc/math-constraints.md b/doc/math-constraints.md new file mode 100644 index 0000000..8cdc7c5 --- /dev/null +++ b/doc/math-constraints.md @@ -0,0 +1,81 @@ +# Dynamic constraints - DynMath library + +DynMath is a library aimed at making GUI positioning and sizing easier. + +It allows you to compose numeric, vector and rectangle constraints for the GUI elements, and when some of the input values changes (such as window size), all the constraints will reflect this change. + +For example: + +```java +// a Rect representing the whole window +Rect window = new Rect() { + + @Override + public Vect size() + { + return /* actual window size */; + } + + @Override + public Vect origin() + { + return Vect.ZERO; + } +}; + +// navbar in the bottom 20% of the window +Rect navbar = window.bottomEdge().growUp(window.height().perc(20)); + +// now whenever the window is resized, navbar will have the right size +``` + +The library is, however, very general-purpose, and has many other uses besides the GUI positioning. + +## What is available + +Short answer: A LOT! + +Long answer: + +### Constraint Types + +There are three kinds of "constraints": + +- `Num` - a number (or a 1D vector) +- `Vect` - 2D or 3D vector, can be used as absolute or relative coordinate +- `Rect` - a 2D rectangle composed of _origin_ and _size_ `Vect` + +Each of them has a bunch of different variants, and can also be further extended to suit your needs. For example, you could make Gui components extend `AbstractRectCache` or something like that. + +Generally, all the constraints are immutable (except the mutable variants, such as `VectVar`. + +### Making Constraints + +Each `Vect`, `Rect` and `Num` have a lot of factory methods (`.make()`, `.makeVar()` etc, with various argument types) to cover all the most common scenarios. If none of those is what you want, you can just make a new instance of the respective constraint, and make it fit your needs. + +### Variants of Constraints +Just `Num`, `Vect` and `Rect` would be boring. + +Each of them has (at least) the following variants *(it'll be shown on `Vect`)*: + +- `Vect` - the basic abstract type. Can be extended to make dynamic variables (override the `.x()`, `.y()` (and optionally `.z()`) methods with your own calculations). `Vect` is generally dynamic, thus doing math on `Vect` will give you "view" of the result, which changes as the original `Vect` changes. + + Note, that `Vect` uses `.x()`, `.y()` and `.z()`, `Rect` uses `.origin()` and `.size()`, and `Num` uses `.value()`. Each variant of the constraints overrides those methods in a different way. +- `VectConst` - concrete implementation of `Vect`, backed by actual numbers. As the name suggests, it is immutable and keeps the same value. You can obtain a const from ie. `Vect` by calling it's `.freeze()` method. +- `VectVar` - like const, it is backed by actual numbers, but they are not final and it has setters to change their values. This could be used as a input for computation. +- `VectAdapter` - kinda like a proxy with abstract method to provide the proxied `Vect`. +- `VectProxy` - implementation of `VectAdapter`, that keeps the proxied constraint in a variable and this can be changed using the `.setVect()` method. +- `VectCache` (and `AbstractVectCache`) - useful if you have a very complex calculation and don't want it all to be evaluated each time you want the result. Simply call the `.cached()` result on your formula, and store it into a `VectCache` variable. Each time you call it's `.poll()` method, it will copy current value into an internal `VectVar` (see, that is what they're good for), and this is then used to get the result. +- `VectBound` - okay, this is not really a variant. It is an interface for **anything** that can has a `Vect` value. You can use `VectBoundAdapter` to make an actual `Vect` out of it. + +### Digests + +If you use some constraint often, like VERY often, ie. for OpenGL rendering each frame, it's a good idea to use digests. The important part is that the digest is cached until the value changes, so it's very efficient if you need the digest often. + +All of the constraints support taking digests. + +Steps to enable and use digest caching: + +1. Call `.enableDigestCaching(true)` on your constraint to turn on the feature (cache does this by default) +2. Each time you need a digest, call the `.digest()` method on your constraint. +3. Each time the value is expected to have changed, call `.markDigestDirty()` on it, and the digest will be rebuilt next time it is needed. Cache does this on each `.poll()` automatically for you. diff --git a/src/mightypork/utils/Convert.java b/src/mightypork/utils/Convert.java new file mode 100644 index 0000000..32a5787 --- /dev/null +++ b/src/mightypork/utils/Convert.java @@ -0,0 +1,196 @@ +package mightypork.utils; + + +/** + * Utility for converting Object to data types; Can also convert strings to data + * types. + * + * @author Ondřej Hruška (MightyPork) + */ +public class Convert { + + /** + * Get INTEGER + * + * @param o object + * @param def default value + * @return integer + */ + public static int toInteger(Object o, Integer def) + { + try { + if (o == null) return def; + if (o instanceof Number) return ((Number) o).intValue(); + if (o instanceof String) return (int) Math.round(Double.parseDouble((String) o)); + if (o instanceof Boolean) return ((Boolean) o) ? 1 : 0; + } catch (final NumberFormatException e) {} + return def; + } + + + /** + * Get DOUBLE + * + * @param o object + * @param def default value + * @return double + */ + public static double toDouble(Object o, Double def) + { + try { + if (o == null) return def; + if (o instanceof Number) return ((Number) o).doubleValue(); + if (o instanceof String) return Double.parseDouble((String) o); + if (o instanceof Boolean) return ((Boolean) o) ? 1 : 0; + } catch (final NumberFormatException e) {} + return def; + } + + + /** + * Get FLOAT + * + * @param o object + * @param def default value + * @return float + */ + public static double toFloat(Object o, Float def) + { + try { + if (o == null) return def; + if (o instanceof Number) return ((Number) o).floatValue(); + } catch (final NumberFormatException e) {} + return def; + } + + + /** + * Get BOOLEAN + * + * @param o object + * @param def default value + * @return boolean + */ + public static boolean toBoolean(Object o, Boolean def) + { + if (o == null) return def; + if (o instanceof Boolean) return ((Boolean) o).booleanValue(); + if (o instanceof Number) return ((Number) o).intValue() != 0; + + if (o instanceof String) { + final String s = ((String) o).trim().toLowerCase(); + if (s.equals("0")) return false; + if (s.equals("1")) return true; + try { + final double n = Double.parseDouble(s); + return n != 0; + } catch (final NumberFormatException e) {} + + if (s.equals("true")) return true; + if (s.equals("yes")) return true; + if (s.equals("y")) return true; + if (s.equals("a")) return true; + if (s.equals("enabled")) return true; + + if (s.equals("false")) return false; + if (s.equals("no")) return false; + if (s.equals("n")) return false; + if (s.equals("disabled")) return true; + } + + return def; + } + + + /** + * Get STRING + * + * @param o object + * @param def default value + * @return String + */ + public static String toString(Object o, String def) + { + if (o == null) return def; + if (o instanceof String) return ((String) o); + + if (o instanceof Float) return Support.str((float) o); + + if (o instanceof Double) return Support.str((double) o); + + if (o instanceof Class) { + return Support.str(o); + } + + return o.toString(); + } + + + /** + * Get RANGE + * + * @param o object + * @param def default value + * @return AiCoord + */ + + + /** + * Get INTEGER + * + * @param o object + * @return integer + */ + public static int toInteger(Object o) + { + return toInteger(o, 0); + } + + + /** + * Get DOUBLE + * + * @param o object + * @return double + */ + public static double toDouble(Object o) + { + return toDouble(o, 0d); + } + + + /** + * Get FLOAT + * + * @param o object + * @return float + */ + public static double toFloat(Object o) + { + return toFloat(o, 0f); + } + + + /** + * Get BOOLEAN + * + * @param o object + * @return boolean + */ + public static boolean toBoolean(Object o) + { + return toBoolean(o, false); + } + + + /** + * Get STRING + * + * @param o object + * @return String + */ + public static String toString(Object o) + { + return toString(o, ""); + } +} diff --git a/src/mightypork/utils/MapSort.java b/src/mightypork/utils/MapSort.java new file mode 100644 index 0000000..26d9a0d --- /dev/null +++ b/src/mightypork/utils/MapSort.java @@ -0,0 +1,81 @@ +package mightypork.utils; + + +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + + +/** + * Map sorting utils + * + * @author Ondřej Hruška (MightyPork) + */ +public class MapSort { + + /** + * Sort a map by keys, maintaining key-value pairs. + * + * @param map map to be sorted + * @param comparator a comparator, or null for natural ordering + * @return linked hash map with sorted entries + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Map sortByKeys(Map map, final Comparator comparator) + { + final List keys = new LinkedList<>(map.keySet()); + + if (comparator == null) { + Collections.sort(keys); + } else { + Collections.sort(keys, comparator); + } + + // LinkedHashMap will keep the keys in the order they are inserted + // which is currently sorted on natural ordering + final Map sortedMap = new LinkedHashMap<>(); + for (final K key : keys) { + sortedMap.put(key, map.get(key)); + } + + return sortedMap; + } + + + /** + * Sort a map by values, maintaining key-value pairs. + * + * @param map map to be sorted + * @param comparator a comparator, or null for natural ordering + * @return linked hash map with sorted entries + */ + @SuppressWarnings("rawtypes") + public static Map sortByValues(Map map, final Comparator comparator) + { + final List> entries = new LinkedList<>(map.entrySet()); + + Collections.sort(entries, new Comparator>() { + + @Override + public int compare(Entry o1, Entry o2) + { + if (comparator == null) return o1.getValue().compareTo(o2.getValue()); + return comparator.compare(o1.getValue(), o2.getValue()); + } + }); + + // LinkedHashMap will keep the keys in the order they are inserted + // which is currently sorted on natural ordering + final Map sortedMap = new LinkedHashMap<>(); + + for (final Map.Entry entry : entries) { + sortedMap.put(entry.getKey(), entry.getValue()); + } + + return sortedMap; + } +} diff --git a/src/mightypork/utils/Reflect.java b/src/mightypork/utils/Reflect.java new file mode 100644 index 0000000..d5f9362 --- /dev/null +++ b/src/mightypork/utils/Reflect.java @@ -0,0 +1,39 @@ +package mightypork.utils; + + +import java.lang.annotation.Annotation; + + +/** + * Miscelanous reflection-related utilities + * + * @author Ondřej Hruška (MightyPork) + */ +public class Reflect { + + /** + * Get annotation of given type from an object + * + * @param tested the examined object + * @param annotation annotation we want + * @return the anotation on that object, or null + */ + public static T getAnnotation(Object tested, Class annotation) + { + return tested.getClass().getAnnotation(annotation); + } + + + /** + * Check if an object has an annotation of given trype + * + * @param tested the examined object + * @param annotation annotation we want + * @return true if the annotation is present on the object + */ + public static boolean hasAnnotation(Object tested, Class annotation) + { + return tested.getClass().isAnnotationPresent(annotation); + } + +} diff --git a/src/mightypork/utils/Support.java b/src/mightypork/utils/Support.java new file mode 100644 index 0000000..20d5904 --- /dev/null +++ b/src/mightypork/utils/Support.java @@ -0,0 +1,298 @@ +package mightypork.utils; + + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import mightypork.utils.annotations.Alias; + + +/** + * Miscelanous utilities + * + * @author Ondřej Hruška (MightyPork) + */ +public final class Support { + + /** + * Create a new thread of the runnable, and start it. + * + * @param r runnable + * @return the thread started + */ + public static Thread runAsThread(Runnable r) + { + final Thread t = new Thread(r); + t.start(); + return t; + } + + + /** + * Pick first non-null option + * + * @param options options + * @return the selected option + */ + public static Object fallback(Object... options) + { + for (final Object o : options) { + if (o != null) return o; + } + + return null; // all null + } + + + /** + * Get current time/date for given format. + * + * @param format format, according to {@link DateFormat}. + * @return the formatted time/date + */ + public static String getTime(String format) + { + return (new SimpleDateFormat(format)).format(new Date()); + } + + + /** + * Parse array of vararg key, value pairs to a LinkedHashMap.
+ * Example: + * + *
+	 * Object[] array = {
+	 *     "one", 1,
+	 *     "two", 4,
+	 *     "three", 9,
+	 *     "four", 16
+	 * };
+	 * 
+	 * Map<String, Integer> args = parseVarArgs(array);
+	 * 
+ * + * @param args varargs + * @return LinkedHashMap + * @throws ClassCastException in case of incompatible type in the array + * @throws IllegalArgumentException in case of invalid array length (odd) + */ + @SuppressWarnings("unchecked") + public static Map parseVarArgs(Object... args) throws ClassCastException, IllegalArgumentException + { + final LinkedHashMap attrs = new LinkedHashMap<>(); + + if (args.length % 2 != 0) { + throw new IllegalArgumentException("Odd number of elements in varargs map!"); + } + + K key = null; + for (final Object o : args) { + if (key == null) { + if (o == null) throw new RuntimeException("Key cannot be NULL in varargs map."); + key = (K) o; + } else { + attrs.put(key, (V) o); + key = null; + } + } + + return attrs; + } + + + /** + * Get if an Object is in array (using equals) + * + * @param needle checked Object + * @param haystack array of Objects + * @return is in array + */ + public static boolean isInArray(Object needle, Object... haystack) + { + for (final Object s : haystack) { + if (needle.equals(s)) return true; + } + return false; + } + + + /** + * Get if string is in array + * + * @param needle checked string + * @param case_sensitive case sensitive comparision + * @param haystack array of possible values + * @return is in array + */ + public static boolean isInArray(String needle, boolean case_sensitive, String... haystack) + { + if (case_sensitive) { + return isInArray(needle, (Object[]) haystack); + } else { + for (final String s : haystack) { + if (needle.equalsIgnoreCase(s)) return true; + } + return false; + } + } + + + /** + * Make enumeration iterable + * + * @param enumeration enumeration + * @return iterable wrapper + */ + public static Iterable enumIter(Enumeration enumeration) + { + return new IterableEnumerationWrapper<>(enumeration); + } + + + /** + * Helper class for iterationg over an {@link Enumeration} + * + * @author Ondřej Hruška (MightyPork) + * @param target element type (will be cast) + */ + private static class IterableEnumerationWrapper implements Iterable { + + private final Enumeration enumeration; + + + /** + * @param enumeration the iterated enumeration + */ + public IterableEnumerationWrapper(Enumeration enumeration) + { + this.enumeration = enumeration; + } + + + @Override + public Iterator iterator() + { + return new Iterator() { + + @Override + public boolean hasNext() + { + return enumeration.hasMoreElements(); + } + + + @Override + public T next() + { + return enumeration.nextElement(); + } + + + @Override + public void remove() + { + throw new UnsupportedOperationException("Operation not supported."); + } + }; + } + + } + + + /** + * Convert a class to string, preserving name and outer class, but excluding + * path. + * + * @param cls + * @return + */ + public static String str(Class cls) + { + final Alias ln = cls.getAnnotation(Alias.class); + if (ln != null) { + return ln.name(); + } + + String name = cls.getName(); + + String sep = ""; + + if (name.contains("$")) { + name = name.substring(name.lastIndexOf("$") + 1); + sep = "$"; + } else { + name = name.substring(name.lastIndexOf(".") + 1); + sep = "."; + } + + final Class enclosing = cls.getEnclosingClass(); + + return (enclosing == null ? "" : Support.str(enclosing) + sep) + name; + } + + + /** + * Convert double to string, remove the mess at the end. + * + * @param d double + * @return string + */ + public static String str(Double d) + { + String s = d.toString(); + s = s.replace(',', '.'); + s = s.replaceAll("([0-9]+\\.[0-9]+)00+[0-9]+", "$1"); + s = s.replaceAll("0+$", ""); + s = s.replaceAll("\\.$", ""); + return s; + } + + + /** + * Convert float to string, remove the mess at the end. + * + * @param f float + * @return string + */ + public static String str(Float f) + { + String s = f.toString(); + s = s.replaceAll("([0-9]+\\.[0-9]+)00+[0-9]+", "$1"); + s = s.replaceAll("0+$", ""); + s = s.replaceAll("\\.$", ""); + return s; + } + + + /** + * Convert object to string. If the object overrides toString(), it is + * caled. Otherwise it's class name is converted to string. + * + * @param o object + * @return string representation + */ + public static String str(Object o) + { + if (o == null) return ""; + + boolean hasToString = false; + + try { + hasToString = (o.getClass().getMethod("toString").getDeclaringClass() != Object.class); + } catch (final Throwable t) { + // oh well.. + } + + if (hasToString) { + return o.toString(); + } else { + + return str(o.getClass()); + } + } +} diff --git a/src/mightypork/utils/annotations/Alias.java b/src/mightypork/utils/annotations/Alias.java new file mode 100644 index 0000000..48a7750 --- /dev/null +++ b/src/mightypork/utils/annotations/Alias.java @@ -0,0 +1,25 @@ +package mightypork.utils.annotations; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Specify pretty name to be used when logging / converting class name to + * string. + * + * @author Ondřej Hruška (MightyPork) + */ +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Target(ElementType.TYPE) +public @interface Alias { + + String name(); +} diff --git a/src/mightypork/utils/annotations/DefaultImpl.java b/src/mightypork/utils/annotations/DefaultImpl.java new file mode 100644 index 0000000..c5e574c --- /dev/null +++ b/src/mightypork/utils/annotations/DefaultImpl.java @@ -0,0 +1,23 @@ +package mightypork.utils.annotations; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Marked method can be safely overriden; it's left blank (or with default + * implementation) as a convenience.
+ * This is a description annotation and has no other function. + * + * @author Ondřej Hruška (MightyPork) + */ +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target(value = { ElementType.METHOD }) +public @interface DefaultImpl { + // +} diff --git a/src/mightypork/utils/annotations/FactoryMethod.java b/src/mightypork/utils/annotations/FactoryMethod.java new file mode 100644 index 0000000..a502e90 --- /dev/null +++ b/src/mightypork/utils/annotations/FactoryMethod.java @@ -0,0 +1,22 @@ +package mightypork.utils.annotations; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Marks a static factory method. This is a description annotation and has no + * other function. + * + * @author Ondřej Hruška (MightyPork) + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +@Documented +public @interface FactoryMethod { + +} diff --git a/src/mightypork/utils/eventbus/BusAccess.java b/src/mightypork/utils/eventbus/BusAccess.java new file mode 100644 index 0000000..8085fa7 --- /dev/null +++ b/src/mightypork/utils/eventbus/BusAccess.java @@ -0,0 +1,16 @@ +package mightypork.utils.eventbus; + + +/** + * Access to an {@link EventBus} instance + * + * @author Ondřej Hruška (MightyPork) + */ +public interface BusAccess { + + /** + * @return event bus + */ + EventBus getEventBus(); + +} diff --git a/src/mightypork/utils/eventbus/BusEvent.java b/src/mightypork/utils/eventbus/BusEvent.java new file mode 100644 index 0000000..63f4196 --- /dev/null +++ b/src/mightypork/utils/eventbus/BusEvent.java @@ -0,0 +1,123 @@ +package mightypork.utils.eventbus; + + +import mightypork.utils.eventbus.events.flags.DelayedEvent; +import mightypork.utils.eventbus.events.flags.DirectEvent; +import mightypork.utils.eventbus.events.flags.NonConsumableEvent; +import mightypork.utils.eventbus.events.flags.NotLoggedEvent; +import mightypork.utils.eventbus.events.flags.SingleReceiverEvent; + + +/** + *

+ * Event that can be handled by HANDLER, subscribing to the event bus. + *

+ *

+ * Can be annotated as {@link SingleReceiverEvent} to be delivered once only, + * and {@link DelayedEvent} or {@link DirectEvent} to specify default sending + * mode. When marked as {@link NotLoggedEvent}, it will not appear in detailed + * bus logging (useful for very frequent events, such as UpdateEvent). + *

+ *

+ * Events annotated as {@link NonConsumableEvent} will throw an exception upon + * an attempt to consume them. + *

+ *

+ * Default sending mode (if not changed by annotations) is queued with + * zero delay. + *

+ * + * @author Ondřej Hruška (MightyPork) + * @param handler type + */ +public abstract class BusEvent { + + private boolean consumed; + private boolean served; + + + /** + * Ask handler to handle this message. + * + * @param handler handler instance + */ + protected abstract void handleBy(HANDLER handler); + + + /** + * Consume the event, so no other clients will receive it. + * + * @throws UnsupportedOperationException if the {@link NonConsumableEvent} + * annotation is present. + */ + public final void consume() + { + if (consumed) throw new IllegalStateException("Already consumed."); + + if (getClass().isAnnotationPresent(NonConsumableEvent.class)) { + throw new UnsupportedOperationException("Not consumable."); + } + + consumed = true; + } + + + /** + * Deliver to a handler using the handleBy method. + * + * @param handler handler instance + */ + final void deliverTo(HANDLER handler) + { + handleBy(handler); + + if (!served) { + if (getClass().isAnnotationPresent(SingleReceiverEvent.class)) { + consumed = true; + } + + served = true; + } + } + + + /** + * Check if the event is consumed. Consumed event is not served to other + * clients. + * + * @return true if consumed + */ + public final boolean isConsumed() + { + return consumed; + } + + + /** + * @return true if the event was served to at least 1 client + */ + final boolean wasServed() + { + return served; + } + + + /** + * Clear "served" and "consumed" flags before dispatching. + */ + final void clearFlags() + { + served = false; + consumed = false; + } + + + /** + * Called after all clients have received the event. + * + * @param bus event bus instance + */ + public void onDispatchComplete(EventBus bus) + { + } +} diff --git a/src/mightypork/utils/eventbus/EventBus.java b/src/mightypork/utils/eventbus/EventBus.java new file mode 100644 index 0000000..b3330f0 --- /dev/null +++ b/src/mightypork/utils/eventbus/EventBus.java @@ -0,0 +1,400 @@ +package mightypork.utils.eventbus; + + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +import mightypork.utils.Reflect; +import mightypork.utils.Support; +import mightypork.utils.eventbus.clients.DelegatingClient; +import mightypork.utils.eventbus.events.flags.DelayedEvent; +import mightypork.utils.eventbus.events.flags.DirectEvent; +import mightypork.utils.eventbus.events.flags.NotLoggedEvent; +import mightypork.utils.interfaces.Destroyable; +import mightypork.utils.logging.Log; + + +/** + * An event bus, accommodating multiple EventChannels.
+ * Channel will be created when an event of type is first encountered. + * + * @author Ondřej Hruška (MightyPork) + */ +final public class EventBus implements Destroyable, BusAccess { + + /** + * Queued event holder + */ + private class DelayQueueEntry implements Delayed { + + private final long due; + private final BusEvent evt; + + + public DelayQueueEntry(double seconds, BusEvent event) + { + super(); + this.due = System.currentTimeMillis() + (long) (seconds * 1000); + this.evt = event; + } + + + @Override + public int compareTo(Delayed o) + { + return Long.valueOf(getDelay(TimeUnit.MILLISECONDS)).compareTo(o.getDelay(TimeUnit.MILLISECONDS)); + } + + + @Override + public long getDelay(TimeUnit unit) + { + return unit.convert(due - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + } + + + public BusEvent getEvent() + { + return evt; + } + + } + + /** + * Thread handling queued events + */ + private class QueuePollingThread extends Thread { + + public volatile boolean stopped = false; + + + public QueuePollingThread() + { + super("Queue Polling Thread"); + } + + + @Override + public void run() + { + DelayQueueEntry evt; + + while (!stopped) { + evt = null; + + try { + evt = sendQueue.take(); + } catch (final InterruptedException ignored) { + // + } + + if (evt != null) { + try { + dispatch(evt.getEvent()); + } catch (final Throwable t) { + Log.w(logMark + "Error while dispatching event: ", t); + } + } + } + } + + } + + static final String logMark = "(bus) "; + + + private static Class getEventListenerClass(BusEvent event) + { + // BEHOLD, MAGIC! + + final Type evtc = event.getClass().getGenericSuperclass(); + + if (evtc instanceof ParameterizedType) { + if (((ParameterizedType) evtc).getRawType() == BusEvent.class) { + final Type[] types = ((ParameterizedType) evtc).getActualTypeArguments(); + for (final Type genericType : types) { + return (Class) genericType; + } + } + } + + throw new RuntimeException("Could not detect event listener type."); + } + + /** Log detailed messages (debug) */ + public boolean detailedLogging = false; + + /** Queue polling thread */ + private final QueuePollingThread busThread; + + /** Registered clients */ + private final Set clients = Collections.newSetFromMap(new ConcurrentHashMap()); + + /** Whether the bus was destroyed */ + private boolean dead = false; + + /** Message channels */ + private final Set> channels = Collections.newSetFromMap(new ConcurrentHashMap, Boolean>()); + + /** Messages queued for delivery */ + private final DelayQueue sendQueue = new DelayQueue<>(); + + + /** + * Make a new bus and start it's queue thread. + */ + public EventBus() + { + busThread = new QueuePollingThread(); + busThread.setDaemon(true); + busThread.start(); + } + + + /** + * Halt bus thread and reject any future events. + */ + @Override + public void destroy() + { + assertLive(); + + busThread.stopped = true; + dead = true; + } + + + /** + * Send based on annotation + * + * @param event event + */ + public void send(BusEvent event) + { + assertLive(); + + final DelayedEvent adelay = Reflect.getAnnotation(event, DelayedEvent.class); + if (adelay != null) { + sendDelayed(event, adelay.delay()); + return; + } + + if (Reflect.hasAnnotation(event, DirectEvent.class)) { + sendDirect(event); + return; + } + + sendQueued(event); + } + + + /** + * Add event to a queue + * + * @param event event + */ + public void sendQueued(BusEvent event) + { + assertLive(); + + sendDelayed(event, 0); + } + + + /** + * Add event to a queue, scheduled for given time. + * + * @param event event + * @param delay delay before event is dispatched + */ + public void sendDelayed(BusEvent event, double delay) + { + assertLive(); + + final DelayQueueEntry dm = new DelayQueueEntry(delay, event); + + if (shallLog(event)) { + Log.f3(logMark + "Qu [" + Support.str(event) + "]" + (delay == 0 ? "" : (", delay: " + delay + "s"))); + } + + sendQueue.add(dm); + } + + + /** + * Send immediately.
+ * Should be used for real-time events that require immediate response, such + * as timing events. + * + * @param event event + */ + public void sendDirect(BusEvent event) + { + assertLive(); + + if (shallLog(event)) Log.f3(logMark + "Di [" + Support.str(event) + "]"); + + dispatch(event); + } + + + public void sendDirectToChildren(DelegatingClient delegatingClient, BusEvent event) + { + assertLive(); + + if (shallLog(event)) Log.f3(logMark + "Di->sub [" + Support.str(event) + "]"); + + doDispatch(delegatingClient.getChildClients(), event); + } + + + /** + * Connect a client to the bus. The client will be connected to all current + * and future channels, until removed from the bus. + * + * @param client the client + */ + public void subscribe(Object client) + { + assertLive(); + + if (client == null) return; + + clients.add(client); + + if (detailedLogging) Log.f3(logMark + "Client joined: " + Support.str(client)); + } + + + /** + * Disconnect a client from the bus. + * + * @param client the client + */ + public void unsubscribe(Object client) + { + assertLive(); + + clients.remove(client); + + if (detailedLogging) Log.f3(logMark + "Client left: " + Support.str(client)); + } + + + @SuppressWarnings("unchecked") + private boolean addChannelForEvent(BusEvent event) + { + try { + if (detailedLogging) { + Log.f3(logMark + "Setting up channel for new event type: " + Support.str(event.getClass())); + } + + final Class listener = getEventListenerClass(event); + final EventChannel ch = EventChannel.create(event.getClass(), listener); + + if (ch.canBroadcast(event)) { + + channels.add(ch); + //channels.flush(); + + if (detailedLogging) { + Log.f3(logMark + "Created new channel: " + Support.str(event.getClass()) + " -> " + Support.str(listener)); + } + + return true; + + } else { + Log.w(logMark + "Could not create channel for event " + Support.str(event.getClass())); + } + + } catch (final Throwable t) { + Log.w(logMark + "Error while trying to add channel for event.", t); + } + + return false; + } + + + /** + * Make sure the bus is not destroyed. + * + * @throws IllegalStateException if the bus is dead. + */ + private void assertLive() throws IllegalStateException + { + if (dead) throw new IllegalStateException("EventBus is dead."); + } + + + /** + * Send immediately.
+ * Should be used for real-time events that require immediate response, such + * as timing events. + * + * @param event event + */ + private synchronized void dispatch(BusEvent event) + { + assertLive(); + + doDispatch(clients, event); + event.onDispatchComplete(this); + } + + + /** + * Send to a set of clients + * + * @param clients clients + * @param event event + */ + private synchronized void doDispatch(Collection clients, BusEvent event) + { + boolean accepted = false; + + event.clearFlags(); + + for (int i = 0; i < 2; i++) { // two tries. + + for (final EventChannel b : channels) { + if (b.canBroadcast(event)) { + accepted = true; + b.broadcast(event, clients); + } + + if (event.isConsumed()) break; + } + + if (!accepted) if (addChannelForEvent(event)) continue; + + break; + } + + if (!accepted) Log.e(logMark + "Not accepted by any channel: " + Support.str(event)); + if (!event.wasServed() && shallLog(event)) Log.w(logMark + "Not delivered: " + Support.str(event)); + } + + + private boolean shallLog(BusEvent event) + { + if (!detailedLogging) return false; + if (Reflect.hasAnnotation(event, NotLoggedEvent.class)) return false; + + return true; + } + + + @Override + public EventBus getEventBus() + { + return this; // just for compatibility use-case + } + +} diff --git a/src/mightypork/utils/eventbus/EventChannel.java b/src/mightypork/utils/eventbus/EventChannel.java new file mode 100644 index 0000000..784ab35 --- /dev/null +++ b/src/mightypork/utils/eventbus/EventChannel.java @@ -0,0 +1,208 @@ +package mightypork.utils.eventbus; + + +import java.util.Collection; +import java.util.HashSet; + +import mightypork.utils.Reflect; +import mightypork.utils.Support; +import mightypork.utils.eventbus.clients.DelegatingClient; +import mightypork.utils.eventbus.clients.ToggleableClient; +import mightypork.utils.eventbus.events.flags.NonRejectableEvent; +import mightypork.utils.logging.Log; + + +/** + * Event delivery channel, module of {@link EventBus} + * + * @author Ondřej Hruška (MightyPork) + * @param event type + * @param client (subscriber) type + */ +class EventChannel, CLIENT> { + + private final Class clientClass; + private final Class eventClass; + + + /** + * Create a channel + * + * @param eventClass event class + * @param clientClass client class + */ + public EventChannel(Class eventClass, Class 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.
+ * If event is of wrong type, false is returned. + * + * @param event a event to be sent + * @param clients collection of clients + */ + public void broadcast(BusEvent event, Collection clients) + { + if (!canBroadcast(event)) return; + + doBroadcast(eventClass.cast(event), clients, new HashSet<>()); + } + + + /** + * Send the event + * + * @param event sent event + * @param clients subscribing clients + * @param processed clients already processed + */ + private void doBroadcast(final EVENT event, final Collection clients, final Collection processed) + { + for (final Object client : clients) { + + // exclude obvious non-clients + if (!isClientValid(client)) { + continue; + } + + // avoid executing more times + if (processed.contains(client)) { + Log.w(EventBus.logMark + "Client already served: " + Support.str(client)); + continue; + } + processed.add(client); + + final boolean must_deliver = Reflect.hasAnnotation(event, NonRejectableEvent.class); + + // opt-out + if (client instanceof ToggleableClient) { + if (!must_deliver && !((ToggleableClient) client).isListening()) continue; + } + + sendTo(client, event); + + if (event.isConsumed()) return; + + // pass on to delegated clients + if (client instanceof DelegatingClient) { + if (must_deliver || ((DelegatingClient) client).doesDelegate()) { + + final Collection children = ((DelegatingClient) client).getChildClients(); + + if (children != null && children.size() > 0) { + doBroadcast(event, children, processed); + } + + } + } + } + } + + + /** + * Send an event to a client. + * + * @param client target client + * @param event event to send + */ + @SuppressWarnings("unchecked") + private void sendTo(Object client, EVENT event) + { + if (isClientOfChannelType(client)) { + ((BusEvent) event).deliverTo((CLIENT) client); + } + } + + + /** + * Check if the given event can be broadcasted by this channel + * + * @param event event object + * @return can be broadcasted + */ + public boolean canBroadcast(BusEvent event) + { + return event != null && eventClass.isInstance(event); + } + + + /** + * Create an instance for given types + * + * @param eventClass event class + * @param clientClass client class + * @return the broadcaster + */ + public static , F_CLIENT> EventChannel create(Class eventClass, Class clientClass) + { + return new EventChannel<>(eventClass, clientClass); + } + + + /** + * Check if client is of channel type + * + * @param client client + * @return is of type + */ + private boolean isClientOfChannelType(Object client) + { + return clientClass.isInstance(client); + } + + + /** + * Check if the channel is compatible with given + * + * @param client client + * @return is supported + */ + public boolean isClientValid(Object client) + { + return isClientOfChannelType(client) || (client instanceof DelegatingClient); + } + + + @Override + public int hashCode() + { + final int prime = 13; + int result = 1; + result = prime * result + ((clientClass == null) ? 0 : clientClass.hashCode()); + result = prime * result + ((eventClass == null) ? 0 : eventClass.hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof EventChannel)) return false; + final EventChannel other = (EventChannel) obj; + if (clientClass == null) { + if (other.clientClass != null) return false; + } else if (!clientClass.equals(other.clientClass)) return false; + if (eventClass == null) { + if (other.eventClass != null) return false; + } else if (!eventClass.equals(other.eventClass)) return false; + return true; + } + + + @Override + public String toString() + { + return "{ " + Support.str(eventClass) + " => " + Support.str(clientClass) + " }"; + } +} diff --git a/src/mightypork/utils/eventbus/clients/BusNode.java b/src/mightypork/utils/eventbus/clients/BusNode.java new file mode 100644 index 0000000..02884a7 --- /dev/null +++ b/src/mightypork/utils/eventbus/clients/BusNode.java @@ -0,0 +1,115 @@ +package mightypork.utils.eventbus.clients; + + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +import mightypork.utils.eventbus.BusAccess; +import mightypork.utils.eventbus.EventBus; + + +/** + * Client that can be attached to the {@link EventBus}, or added as a child + * client to another {@link DelegatingClient} + * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class BusNode implements BusAccess, ClientHub { + + private final BusAccess busAccess; + + private final Set clients = new LinkedHashSet<>(); + private boolean listening = true; + private boolean delegating = true; + + + /** + * @param busAccess access to bus + */ + public BusNode(BusAccess busAccess) + { + this.busAccess = busAccess; + } + + + @Override + public Collection getChildClients() + { + return clients; + } + + + @Override + public boolean doesDelegate() + { + return delegating; + } + + + @Override + public boolean isListening() + { + return listening; + } + + + /** + * Add a child subscriber to the {@link EventBus}.
+ * + * @param client + */ + @Override + public void addChildClient(Object client) + { + if (client instanceof RootBusNode) { + throw new IllegalArgumentException("Cannot nest RootBusNode."); + } + + clients.add(client); + } + + + /** + * Remove a child subscriber + * + * @param client subscriber to remove + */ + @Override + public void removeChildClient(Object client) + { + if (client != null) { + clients.remove(client); + } + } + + + /** + * Set whether events should be received. + * + * @param listening receive events + */ + public void setListening(boolean listening) + { + this.listening = listening; + } + + + /** + * Set whether events should be passed on to child nodes + * + * @param delegating + */ + public void setDelegating(boolean delegating) + { + this.delegating = delegating; + } + + + @Override + public EventBus getEventBus() + { + return busAccess.getEventBus(); + } + +} diff --git a/src/mightypork/utils/eventbus/clients/ClientHub.java b/src/mightypork/utils/eventbus/clients/ClientHub.java new file mode 100644 index 0000000..81011c3 --- /dev/null +++ b/src/mightypork/utils/eventbus/clients/ClientHub.java @@ -0,0 +1,42 @@ +package mightypork.utils.eventbus.clients; + + +import java.util.Collection; + +import mightypork.utils.eventbus.EventBus; + + +/** + * Common methods for client hubs (ie delegating vlient implementations) + * + * @author Ondřej Hruška (MightyPork) + */ +public interface ClientHub extends DelegatingClient, ToggleableClient { + + @Override + public boolean doesDelegate(); + + + @Override + public Collection getChildClients(); + + + @Override + public boolean isListening(); + + + /** + * Add a child subscriber to the {@link EventBus}.
+ * + * @param client + */ + public void addChildClient(Object client); + + + /** + * Remove a child subscriber + * + * @param client subscriber to remove + */ + void removeChildClient(Object client); +} diff --git a/src/mightypork/utils/eventbus/clients/ClientList.java b/src/mightypork/utils/eventbus/clients/ClientList.java new file mode 100644 index 0000000..bfaf3d5 --- /dev/null +++ b/src/mightypork/utils/eventbus/clients/ClientList.java @@ -0,0 +1,22 @@ +package mightypork.utils.eventbus.clients; + + +import java.util.ArrayList; + + +/** + * Array-list with varargs constructor, intended to wrap fre clients for + * delegating client. + * + * @author Ondřej Hruška (MightyPork) + */ +public class ClientList extends ArrayList { + + public ClientList(Object... clients) + { + for (final Object c : clients) { + super.add(c); + } + } + +} diff --git a/src/mightypork/utils/eventbus/clients/DelegatingClient.java b/src/mightypork/utils/eventbus/clients/DelegatingClient.java new file mode 100644 index 0000000..5d202d3 --- /dev/null +++ b/src/mightypork/utils/eventbus/clients/DelegatingClient.java @@ -0,0 +1,27 @@ +package mightypork.utils.eventbus.clients; + + +import java.util.Collection; + + +/** + * Client containing child clients. According to the contract, if the collection + * of clients is ordered, the clients will be served in that order. In any case, + * the {@link DelegatingClient} itself will be served beforehand. + * + * @author Ondřej Hruška (MightyPork) + */ +public interface DelegatingClient { + + /** + * @return collection of child clients. Can not be null. + */ + public Collection getChildClients(); + + + /** + * @return true if delegating is active + */ + public boolean doesDelegate(); + +} diff --git a/src/mightypork/utils/eventbus/clients/DelegatingList.java b/src/mightypork/utils/eventbus/clients/DelegatingList.java new file mode 100644 index 0000000..ebb5336 --- /dev/null +++ b/src/mightypork/utils/eventbus/clients/DelegatingList.java @@ -0,0 +1,51 @@ +package mightypork.utils.eventbus.clients; + + +import java.util.Collection; + +import mightypork.utils.interfaces.Enableable; + + +/** + * List of clients, that can be used as a delegating client. + * + * @author Ondřej Hruška (MightyPork) + */ +public class DelegatingList extends ClientList implements DelegatingClient, Enableable { + + private boolean enabled = true; + + + public DelegatingList(Object... clients) + { + super(clients); + } + + + @Override + public Collection getChildClients() + { + return this; + } + + + @Override + public boolean doesDelegate() + { + return isEnabled(); + } + + + @Override + public void setEnabled(boolean yes) + { + enabled = yes; + } + + + @Override + public boolean isEnabled() + { + return enabled; + } +} diff --git a/src/mightypork/utils/eventbus/clients/RootBusNode.java b/src/mightypork/utils/eventbus/clients/RootBusNode.java new file mode 100644 index 0000000..39849e2 --- /dev/null +++ b/src/mightypork/utils/eventbus/clients/RootBusNode.java @@ -0,0 +1,45 @@ +package mightypork.utils.eventbus.clients; + + +import mightypork.utils.annotations.DefaultImpl; +import mightypork.utils.eventbus.BusAccess; +import mightypork.utils.interfaces.Destroyable; + + +/** + * Bus node that should be directly attached to the bus. + * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class RootBusNode extends BusNode implements Destroyable { + + /** + * @param busAccess access to bus + */ + public RootBusNode(BusAccess busAccess) + { + super(busAccess); + + getEventBus().subscribe(this); + } + + + @Override + public final void destroy() + { + deinit(); + + getEventBus().unsubscribe(this); + } + + + /** + * Deinitialize the subsystem
+ * (called during destruction) + */ + @DefaultImpl + protected void deinit() + { + } + +} diff --git a/src/mightypork/utils/eventbus/clients/ToggleableClient.java b/src/mightypork/utils/eventbus/clients/ToggleableClient.java new file mode 100644 index 0000000..52b0d41 --- /dev/null +++ b/src/mightypork/utils/eventbus/clients/ToggleableClient.java @@ -0,0 +1,16 @@ +package mightypork.utils.eventbus.clients; + + +/** + * Client that can toggle receiving messages. + * + * @author Ondřej Hruška (MightyPork) + */ +public interface ToggleableClient { + + /** + * @return true if the client wants to receive messages + */ + public boolean isListening(); + +} diff --git a/src/mightypork/utils/eventbus/events/DestroyEvent.java b/src/mightypork/utils/eventbus/events/DestroyEvent.java new file mode 100644 index 0000000..d07fe96 --- /dev/null +++ b/src/mightypork/utils/eventbus/events/DestroyEvent.java @@ -0,0 +1,25 @@ +package mightypork.utils.eventbus.events; + + +import mightypork.utils.eventbus.BusEvent; +import mightypork.utils.eventbus.events.flags.DirectEvent; +import mightypork.utils.eventbus.events.flags.NonConsumableEvent; +import mightypork.utils.interfaces.Destroyable; + + +/** + * Invoke destroy() method of all subscribers. Used to deinit a system. + * + * @author Ondřej Hruška (MightyPork) + */ +@DirectEvent +@NonConsumableEvent +public class DestroyEvent extends BusEvent { + + @Override + public void handleBy(Destroyable handler) + { + handler.destroy(); + } + +} diff --git a/src/mightypork/utils/eventbus/events/UpdateEvent.java b/src/mightypork/utils/eventbus/events/UpdateEvent.java new file mode 100644 index 0000000..7e8c8a3 --- /dev/null +++ b/src/mightypork/utils/eventbus/events/UpdateEvent.java @@ -0,0 +1,38 @@ +package mightypork.utils.eventbus.events; + + +import mightypork.utils.eventbus.BusEvent; +import mightypork.utils.eventbus.events.flags.DirectEvent; +import mightypork.utils.eventbus.events.flags.NonConsumableEvent; +import mightypork.utils.eventbus.events.flags.NotLoggedEvent; +import mightypork.utils.interfaces.Updateable; + + +/** + * Delta timing update event. Not logged. + * + * @author Ondřej Hruška (MightyPork) + */ +@NotLoggedEvent +@DirectEvent +@NonConsumableEvent +public class UpdateEvent extends BusEvent { + + private final double deltaTime; + + + /** + * @param deltaTime time since last update (sec) + */ + public UpdateEvent(double deltaTime) + { + this.deltaTime = deltaTime; + } + + + @Override + public void handleBy(Updateable handler) + { + handler.update(deltaTime); + } +} diff --git a/src/mightypork/utils/eventbus/events/flags/DelayedEvent.java b/src/mightypork/utils/eventbus/events/flags/DelayedEvent.java new file mode 100644 index 0000000..e6a3114 --- /dev/null +++ b/src/mightypork/utils/eventbus/events/flags/DelayedEvent.java @@ -0,0 +1,27 @@ +package mightypork.utils.eventbus.events.flags; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Event that should be queued with given delay (default: 0); + * + * @author Ondřej Hruška (MightyPork) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +public @interface DelayedEvent { + + /** + * @return event dispatch delay [seconds] + */ + double delay() default 0; +} diff --git a/src/mightypork/utils/eventbus/events/flags/DirectEvent.java b/src/mightypork/utils/eventbus/events/flags/DirectEvent.java new file mode 100644 index 0000000..8ad9713 --- /dev/null +++ b/src/mightypork/utils/eventbus/events/flags/DirectEvent.java @@ -0,0 +1,21 @@ +package mightypork.utils.eventbus.events.flags; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Event that should not be queued. + * + * @author Ondřej Hruška (MightyPork) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +public @interface DirectEvent {} diff --git a/src/mightypork/utils/eventbus/events/flags/NonConsumableEvent.java b/src/mightypork/utils/eventbus/events/flags/NonConsumableEvent.java new file mode 100644 index 0000000..873afdc --- /dev/null +++ b/src/mightypork/utils/eventbus/events/flags/NonConsumableEvent.java @@ -0,0 +1,21 @@ +package mightypork.utils.eventbus.events.flags; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Event that cannot be consumed + * + * @author Ondřej Hruška (MightyPork) + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target(ElementType.TYPE) +public @interface NonConsumableEvent { + +} diff --git a/src/mightypork/utils/eventbus/events/flags/NonRejectableEvent.java b/src/mightypork/utils/eventbus/events/flags/NonRejectableEvent.java new file mode 100644 index 0000000..164bf4a --- /dev/null +++ b/src/mightypork/utils/eventbus/events/flags/NonRejectableEvent.java @@ -0,0 +1,21 @@ +package mightypork.utils.eventbus.events.flags; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Event that is forcibly delivered to all clients (bypass Toggleable etc) + * + * @author Ondřej Hruška (MightyPork) + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target(ElementType.TYPE) +public @interface NonRejectableEvent { + +} diff --git a/src/mightypork/utils/eventbus/events/flags/NotLoggedEvent.java b/src/mightypork/utils/eventbus/events/flags/NotLoggedEvent.java new file mode 100644 index 0000000..291d1b8 --- /dev/null +++ b/src/mightypork/utils/eventbus/events/flags/NotLoggedEvent.java @@ -0,0 +1,22 @@ +package mightypork.utils.eventbus.events.flags; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Event that's not worth logging, unless there was an error with it.
+ * Useful for common events that would otherwise clutter the log. + * + * @author Ondřej Hruška (MightyPork) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +public @interface NotLoggedEvent {} diff --git a/src/mightypork/utils/eventbus/events/flags/SingleReceiverEvent.java b/src/mightypork/utils/eventbus/events/flags/SingleReceiverEvent.java new file mode 100644 index 0000000..1c15082 --- /dev/null +++ b/src/mightypork/utils/eventbus/events/flags/SingleReceiverEvent.java @@ -0,0 +1,21 @@ +package mightypork.utils.eventbus.events.flags; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Handled only by the first client, then discarded. + * + * @author Ondřej Hruška (MightyPork) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +public @interface SingleReceiverEvent {} diff --git a/src/mightypork/utils/exceptions/CorruptDataException.java b/src/mightypork/utils/exceptions/CorruptDataException.java new file mode 100644 index 0000000..db75243 --- /dev/null +++ b/src/mightypork/utils/exceptions/CorruptDataException.java @@ -0,0 +1,37 @@ +package mightypork.utils.exceptions; + + +import java.io.IOException; + + +/** + * Thrown when data could not be read successfully. + * + * @author Ondřej Hruška (MightyPork) + */ +public class CorruptDataException extends IOException { + + public CorruptDataException() + { + super(); + } + + + public CorruptDataException(String message, Throwable cause) + { + super(message, cause); + } + + + public CorruptDataException(String message) + { + super(message); + } + + + public CorruptDataException(Throwable cause) + { + super(cause); + } + +} diff --git a/src/mightypork/utils/exceptions/IllegalValueException.java b/src/mightypork/utils/exceptions/IllegalValueException.java new file mode 100644 index 0000000..b2b8794 --- /dev/null +++ b/src/mightypork/utils/exceptions/IllegalValueException.java @@ -0,0 +1,34 @@ +package mightypork.utils.exceptions; + + +/** + * Thrown when a invalid value is given to a method, or found in a data object / + * file etc + * + * @author Ondřej Hruška (MightyPork) + */ +public class IllegalValueException extends RuntimeException { + + public IllegalValueException() + { + } + + + public IllegalValueException(String message) + { + super(message); + } + + + public IllegalValueException(Throwable cause) + { + super(cause); + } + + + public IllegalValueException(String message, Throwable cause) + { + super(message, cause); + } + +} diff --git a/src/mightypork/utils/exceptions/KeyAlreadyExistsException.java b/src/mightypork/utils/exceptions/KeyAlreadyExistsException.java new file mode 100644 index 0000000..5e0e31d --- /dev/null +++ b/src/mightypork/utils/exceptions/KeyAlreadyExistsException.java @@ -0,0 +1,34 @@ +package mightypork.utils.exceptions; + + +/** + * Thrown by a map-like class when the key specified is already taken. + * + * @author Ondřej Hruška (MightyPork) + */ +public class KeyAlreadyExistsException extends RuntimeException { + + public KeyAlreadyExistsException() + { + super(); + } + + + public KeyAlreadyExistsException(String message, Throwable cause) + { + super(message, cause); + } + + + public KeyAlreadyExistsException(String message) + { + super(message); + } + + + public KeyAlreadyExistsException(Throwable cause) + { + super(cause); + } + +} diff --git a/src/mightypork/utils/files/FileSuffixFilter.java b/src/mightypork/utils/files/FileSuffixFilter.java new file mode 100644 index 0000000..94308e4 --- /dev/null +++ b/src/mightypork/utils/files/FileSuffixFilter.java @@ -0,0 +1,46 @@ +package mightypork.utils.files; + + +import java.io.File; +import java.io.FileFilter; + + +/** + * File filter for certain suffixes + * + * @author Ondřej Hruška (MightyPork) + */ +public class FileSuffixFilter implements FileFilter { + + /** Array of allowed suffixes */ + private String[] suffixes = null; + + + /** + * Suffix filter + * + * @param suffixes var-args allowed suffixes, case insensitive + */ + public FileSuffixFilter(String... suffixes) + { + this.suffixes = suffixes; + } + + + @Override + public boolean accept(File pathname) + { + if (!pathname.isFile()) return false; + + final String fname = pathname.getName().toLowerCase().trim(); + + for (final String suffix : suffixes) { + if (fname.endsWith(suffix.toLowerCase().trim())) { + return true; + } + } + + return false; + } + +} diff --git a/src/mightypork/utils/files/FileTreeDiff.java b/src/mightypork/utils/files/FileTreeDiff.java new file mode 100644 index 0000000..dd909a0 --- /dev/null +++ b/src/mightypork/utils/files/FileTreeDiff.java @@ -0,0 +1,153 @@ +package mightypork.utils.files; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.zip.Adler32; +import java.util.zip.CheckedInputStream; +import java.util.zip.Checksum; + +import mightypork.utils.logging.Log; + + +public class FileTreeDiff { + + private static final byte[] BUFFER = new byte[2048]; + private final Checksum ck1 = new Adler32(); + private final Checksum ck2 = new Adler32(); + + private boolean logging = true; + + private final List> compared = new ArrayList<>(); + private final Comparator fileFirstSorter = new Comparator() { + + @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 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 { + + public T a; + public T b; + + + public Tuple(T a, T b) + { + this.a = a; + this.b = b; + } + } + +} diff --git a/src/mightypork/utils/files/FileUtils.java b/src/mightypork/utils/files/FileUtils.java new file mode 100644 index 0000000..b9810eb --- /dev/null +++ b/src/mightypork/utils/files/FileUtils.java @@ -0,0 +1,438 @@ +package mightypork.utils.files; + + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import mightypork.utils.logging.Log; +import mightypork.utils.string.StringUtil; +import mightypork.utils.string.validation.StringFilter; + + +public class FileUtils { + + /** + * Copy directory recursively. + * + * @param source source file + * @param target target file + * @throws IOException on error + */ + public static void copyDirectory(File source, File target) throws IOException + { + copyDirectory(source, target, null, null); + } + + + /** + * Copy directory recursively - advanced variant. + * + * @param source source file + * @param target target file + * @param filter filter accepting only files and dirs to be copied + * @param filesCopied list into which all the target files will be added + * @throws IOException on error + */ + public static void copyDirectory(File source, File target, FileFilter filter, List 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 files) throws IOException + { + if (source.isDirectory()) { + final String[] children = source.list(); + for (final String element : children) { + listDirectoryRecursive(new File(source, element), filter, files); + } + + } else { + if (filter != null && !filter.isValid(source.getAbsolutePath())) { + return; + } + + files.add(source); + } + } + + + /** + * Copy file using streams. Make sure target directory exists! + * + * @param source source file + * @param target target file + * @throws IOException on error + */ + public static void copyFile(File source, File target) throws IOException + { + + try(InputStream in = new FileInputStream(source); + OutputStream out = new FileOutputStream(target)) { + + copyStream(in, out); + } + } + + + /** + * Copy bytes from input to output stream, leaving out stream open + * + * @param in input stream + * @param out output stream + * @throws IOException on error + */ + public static void copyStream(InputStream in, OutputStream out) throws IOException + { + if (in == null) { + throw new NullPointerException("Input stream is null"); + } + + if (out == null) { + throw new NullPointerException("Output stream is null"); + } + + final byte[] buf = new byte[2048]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } + + + /** + * Improved delete + * + * @param path deleted path + * @param recursive recursive delete + * @return success + */ + public static boolean delete(File path, boolean recursive) + { + if (!path.exists()) { + return true; + } + + if (!recursive || !path.isDirectory()) return path.delete(); + + final String[] list = path.list(); + for (int i = 0; i < list.length; i++) { + if (!delete(new File(path, list[i]), true)) return false; + } + + return path.delete(); + } + + + /** + * Read entire file to a string. + * + * @param file file + * @return file contents + * @throws IOException + */ + public static String fileToString(File file) throws IOException + { + try(FileInputStream fin = new FileInputStream(file)) { + + return streamToString(fin); + } + } + + + /** + * Get files in a folder (create folder if needed) + * + * @param dir folder + * @return list of files + */ + public static List 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 listDirectory(File dir, FileFilter filter) + { + dir.mkdir(); + + final List list = new ArrayList<>(); + + for (final File f : dir.listFiles(filter)) { + list.add(f); + } + + return list; + } + + + /** + * Remove extension. + * + * @param file file + * @return filename without extension + */ + public static String[] getFilenameParts(File file) + { + return getFilenameParts(file.getName()); + } + + + public static String getExtension(File file) + { + return getExtension(file.getName()); + } + + + public static String getExtension(String file) + { + return StringUtil.fromLastChar(file, '.'); + } + + + /** + * Remove extension. + * + * @param filename + * @return filename and extension + */ + public static String[] getFilenameParts(String filename) + { + String ext, name; + + try { + ext = StringUtil.fromLastDot(filename); + } catch (final StringIndexOutOfBoundsException e) { + ext = ""; + } + + try { + name = StringUtil.toLastDot(filename); + } catch (final StringIndexOutOfBoundsException e) { + name = ""; + Log.w("Error extracting extension from file " + filename); + } + + return new String[] { name, ext }; + } + + + /** + * Read entire input stream to a string, and close it. + * + * @param in input stream + * @return file contents + */ + public static String streamToString(InputStream in) + { + return streamToString(in, -1); + } + + + /** + * Read input stream to a string, and close it. + * + * @param in input stream + * @param lines max number of lines (-1 to disable limit) + * @return file contents + */ + public static String streamToString(InputStream in, int lines) + { + if (in == null) { + Log.e(new NullPointerException("Null stream to be converted to String.")); + return ""; // to avoid NPE's + } + + BufferedReader br = null; + final StringBuilder sb = new StringBuilder(); + + String line; + try { + int cnt = 0; + br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + while ((line = br.readLine()) != null && (cnt < lines || lines <= 0)) { + sb.append(line + "\n"); + cnt++; + } + + if (cnt == lines && lines > 0) { + sb.append("--- end of preview ---\n"); + } + + } catch (final IOException e) { + Log.e(e); + } finally { + try { + if (br != null) br.close(); + } catch (final IOException e) { + // ignore + } + } + + return sb.toString(); + } + + + public static InputStream stringToStream(String text) + { + if (text == null) return null; + + try { + return new ByteArrayInputStream(text.getBytes("UTF-8")); + } catch (final UnsupportedEncodingException e) { + Log.e(e); + return null; + } + } + + + public static InputStream getResource(String path) + { + final InputStream in = FileUtils.class.getResourceAsStream(path); + + if (in != null) return in; + + try { + return new FileInputStream(new File(".", path)); + } catch (final FileNotFoundException e) { + // error + Log.w("Could not open resource stream: " + path); + return null; + } + + } + + + public static String getResourceAsString(String path) + { + return streamToString(FileUtils.class.getResourceAsStream(path)); + } + + + /** + * Save string to file + * + * @param file file + * @param text string + * @throws IOException on error + */ + public static void stringToFile(File file, String text) throws IOException + { + try(PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8")) { + + out.print(text); + + out.flush(); + + } + } + + + public static void deleteEmptyDirs(File base) throws IOException + { + for (final File f : listDirectory(base)) { + if (!f.isDirectory()) continue; + + deleteEmptyDirs(f); + + final List children = listDirectory(f); + if (children.size() == 0) { + if (!f.delete()) throw new IOException("Could not delete a directory: " + f); + continue; + } + } + + } + + + public static String getBasename(String name) + { + return StringUtil.toLastChar(StringUtil.fromLastChar(name, '/'), '.'); + } + + + public static String getFilename(String name) + { + return StringUtil.fromLastChar(name, '/'); + } + + + /** + * Copy resource to file + * + * @param resname resource name + * @param file out file + * @throws IOException + */ + public static void resourceToFile(String resname, File file) throws IOException + { + try(InputStream in = FileUtils.getResource(resname); + OutputStream out = new FileOutputStream(file)) { + + FileUtils.copyStream(in, out); + } + + } + + + /** + * Get resource as string, safely closing streams. + * + * @param resname resource name + * @return resource as string, empty string on failure + * @throws IOException on fail + */ + public static String resourceToString(String resname) throws IOException + { + try(InputStream in = FileUtils.getResource(resname)) { + return streamToString(in); + } + } +} diff --git a/src/mightypork/utils/files/InstanceLock.java b/src/mightypork/utils/files/InstanceLock.java new file mode 100644 index 0000000..1ceff14 --- /dev/null +++ b/src/mightypork/utils/files/InstanceLock.java @@ -0,0 +1,54 @@ +package mightypork.utils.files; + + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileLock; + + +/** + * Instance lock (avoid running twice) + * + * @author Ondřej Hruška (MightyPork) + */ +public class InstanceLock { + + @SuppressWarnings("resource") + public static boolean onFile(final File lockFile) + { + try { + lockFile.getParentFile().mkdirs(); + final RandomAccessFile randomAccessFile = new RandomAccessFile(lockFile, "rw"); + + final FileLock fileLock = randomAccessFile.getChannel().tryLock(); + if (fileLock != null) { + + Runtime.getRuntime().addShutdownHook(new Thread() { + + @Override + public void run() + { + try { + fileLock.release(); + randomAccessFile.close(); + if (!lockFile.delete()) throw new IOException(); + } catch (final Throwable t) { + System.err.println("Unable to remove lock file."); + t.printStackTrace(); + } + } + }); + + return true; + } + + return false; + } catch (final IOException e) { + System.err.println("IO error while obtaining lock."); + e.printStackTrace(); + return false; + } + } + +} diff --git a/src/mightypork/utils/files/OsUtils.java b/src/mightypork/utils/files/OsUtils.java new file mode 100644 index 0000000..5caf312 --- /dev/null +++ b/src/mightypork/utils/files/OsUtils.java @@ -0,0 +1,94 @@ +package mightypork.utils.files; + + +import java.io.File; + + +public class OsUtils { + + public static enum EnumOS + { + linux, macos, solaris, unknown, windows; + + public boolean isLinux() + { + return this == linux || this == solaris; + } + + + public boolean isMac() + { + return this == macos; + } + + + public boolean isWindows() + { + return this == windows; + } + } + + private static EnumOS cachedOs; + + + public static File getHomeWorkDir(String dirname) + { + final String userhome = System.getProperty("user.home", "."); + File file; + + switch (getOs()) { + case linux: + case solaris: + file = new File(userhome, dirname + '/'); + break; + + case windows: + final String appdata = System.getenv("APPDATA"); + + if (appdata != null) { + file = new File(appdata, dirname + '/'); + } else { + file = new File(userhome, dirname + '/'); + } + + break; + + case macos: + file = new File(userhome, "Library/Application Support/" + dirname); + break; + + default: + file = new File(userhome, dirname + "/"); + break; + } + + return file; + } + + + public static EnumOS getOs() + { + if (cachedOs != null) return cachedOs; + + final String s = System.getProperty("os.name").toLowerCase(); + + if (s.contains("win")) { + cachedOs = EnumOS.windows; + + } else if (s.contains("mac")) { + cachedOs = EnumOS.macos; + + } else if (s.contains("linux") || s.contains("unix")) { + cachedOs = EnumOS.linux; + + } else if (s.contains("solaris") || s.contains("sunos")) { + cachedOs = EnumOS.solaris; + + } else { + cachedOs = EnumOS.unknown; + } + + return cachedOs; + } + +} diff --git a/src/mightypork/utils/files/config/Property.java b/src/mightypork/utils/files/config/Property.java new file mode 100644 index 0000000..d4ea4ea --- /dev/null +++ b/src/mightypork/utils/files/config/Property.java @@ -0,0 +1,71 @@ +package mightypork.utils.files.config; + + +import mightypork.utils.Convert; + + +public abstract class Property { + + private final String comment; + private final String key; + + private T value; + private final T defaultValue; + + + public Property(String key, T defaultValue, String comment) + { + super(); + this.comment = comment; + this.key = key; + this.value = defaultValue; + this.defaultValue = defaultValue; + } + + + public final void parse(String string) + { + setValue(decode(string, defaultValue)); + } + + + public abstract T decode(String string, T defval); + + + public String encode(T value) + { + return Convert.toString(value, Convert.toString(defaultValue)); + } + + + @Override + public final String toString() + { + return encode(value); + } + + + public T getValue() + { + return value; + } + + + @SuppressWarnings("unchecked") + public void setValue(Object value) + { + this.value = (T) value; + } + + + public String getComment() + { + return comment; + } + + + public String getKey() + { + return key; + } +} diff --git a/src/mightypork/utils/files/config/PropertyManager.java b/src/mightypork/utils/files/config/PropertyManager.java new file mode 100644 index 0000000..f3af075 --- /dev/null +++ b/src/mightypork/utils/files/config/PropertyManager.java @@ -0,0 +1,377 @@ +package mightypork.utils.files.config; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map.Entry; +import java.util.TreeMap; + +import mightypork.utils.Convert; +import mightypork.utils.logging.Log; + + +/** + * Property manager with advanced formatting and value checking. + * + * @author Ondřej Hruška (MightyPork) + */ +public class PropertyManager { + + private class BooleanProperty extends Property { + + public BooleanProperty(String key, Boolean defaultValue, String comment) + { + super(key, defaultValue, comment); + } + + + @Override + public Boolean decode(String string, Boolean defval) + { + return Convert.toBoolean(string, defval); + } + } + + private class IntegerProperty extends Property { + + public IntegerProperty(String key, Integer defaultValue, String comment) + { + super(key, defaultValue, comment); + } + + + @Override + public Integer decode(String string, Integer defval) + { + return Convert.toInteger(string, defval); + } + } + + private class DoubleProperty extends Property { + + public DoubleProperty(String key, Double defaultValue, String comment) + { + super(key, defaultValue, comment); + } + + + @Override + public Double decode(String string, Double defval) + { + return Convert.toDouble(string, defval); + } + } + + private class StringProperty extends Property { + + public StringProperty(String key, String defaultValue, String comment) + { + super(key, defaultValue, comment); + } + + + @Override + public String decode(String string, String defval) + { + return Convert.toString(string, defval); + } + } + + /** put newline before entry comments */ + private boolean cfgNewlineBeforeComments = true; + + /** Put newline between sections. */ + private boolean cfgSeparateSections = true; + + private final File file; + private String fileComment = ""; + + private final TreeMap> entries; + private final TreeMap renameTable; + private SortedProperties props = new SortedProperties(); + + + /** + * Create property manager from file path and an initial comment. + * + * @param file file with the props + * @param comment the initial comment. Use \n in it if you want. + */ + public PropertyManager(File file, String comment) + { + this.file = file; + this.entries = new TreeMap<>(); + this.renameTable = new TreeMap<>(); + this.fileComment = comment; + } + + + /** + * Load, fix and write to file. + */ + public void load() + { + if (!file.getParentFile().mkdirs()) { + if (!file.getParentFile().exists()) { + throw new RuntimeException("Cound not create config file."); + } + } + + try(FileInputStream fis = new FileInputStream(file)) { + props.load(fis); + } catch (final IOException e) { + props = new SortedProperties(); + } + + props.cfgBlankRowBetweenSections = cfgSeparateSections; + props.cfgBlankRowBeforeComment = cfgNewlineBeforeComments; + + // rename keys + for (final Entry entry : renameTable.entrySet()) { + + final String pr = props.getProperty(entry.getKey()); + + if (pr == null) continue; + + props.remove(entry.getKey()); + props.setProperty(entry.getValue(), pr); + } + + for (final Property entry : entries.values()) { + entry.parse(props.getProperty(entry.getKey())); + } + + renameTable.clear(); + } + + + public void save() + { + try { + final ArrayList keyList = new ArrayList<>(); + + // validate entries one by one, replace with default when needed + for (final Property entry : entries.values()) { + keyList.add(entry.getKey()); + + if (entry.getComment() != null) { + props.setKeyComment(entry.getKey(), entry.getComment()); + } + + props.setProperty(entry.getKey(), entry.toString()); + } + + // removed unused props + for (final String propname : props.keySet().toArray(new String[props.size()])) { + if (!keyList.contains(propname)) { + props.remove(propname); + } + } + + try(FileOutputStream fos = new FileOutputStream(file)) { + + props.store(fos, fileComment); + } + } catch (final IOException ioe) { + ioe.printStackTrace(); + } + } + + + /** + * @param newlineBeforeComments put newline before comments + */ + public void cfgNewlineBeforeComments(boolean newlineBeforeComments) + { + this.cfgNewlineBeforeComments = newlineBeforeComments; + } + + + /** + * @param separateSections do separate sections by newline + */ + public void cfgSeparateSections(boolean separateSections) + { + this.cfgSeparateSections = separateSections; + } + + + /** + * Get a property entry (rarely used) + * + * @param k key + * @return the entry + */ + public Property getProperty(String k) + { + try { + return entries.get(k); + } catch (final Exception e) { + Log.w(e); + return null; + } + } + + + /** + * Get boolean property + * + * @param k key + * @return the boolean found, or false + */ + public Boolean getBoolean(String k) + { + return Convert.toBoolean(getProperty(k).getValue()); + } + + + /** + * Get numeric property + * + * @param k key + * @return the int found, or null + */ + public Integer getInteger(String k) + { + return Convert.toInteger(getProperty(k).getValue()); + } + + + /** + * Get numeric property as double + * + * @param k key + * @return the double found, or null + */ + public Double getDouble(String k) + { + return Convert.toDouble(getProperty(k).getValue()); + } + + + /** + * Get string property + * + * @param k key + * @return the string found, or null + */ + public String getString(String k) + { + return Convert.toString(getProperty(k).getValue()); + } + + + /** + * Get arbitrary property. Make sure it's of the right type! + * + * @param k key + * @return the prioperty found + */ + @SuppressWarnings("unchecked") + public T getValue(String k) + { + try { + return ((Property) getProperty(k)).getValue(); + } catch (final ClassCastException e) { + return null; + } + } + + + /** + * Add a boolean property + * + * @param k key + * @param d default value + * @param comment the in-file comment + */ + public void putBoolean(String k, boolean d, String comment) + { + putProperty(new BooleanProperty(k, d, comment)); + } + + + /** + * Add a numeric property (double) + * + * @param k key + * @param d default value + * @param comment the in-file comment + */ + public void putDouble(String k, double d, String comment) + { + putProperty(new DoubleProperty(k, d, comment)); + } + + + /** + * Add a numeric property + * + * @param k key + * @param d default value + * @param comment the in-file comment + */ + public void putInteger(String k, int d, String comment) + { + putProperty(new IntegerProperty(k, d, comment)); + } + + + /** + * Add a string property + * + * @param k key + * @param d default value + * @param comment the in-file comment + */ + public void putString(String k, String d, String comment) + { + putProperty(new StringProperty(k, d, comment)); + } + + + /** + * Add a range property + * + * @param prop property to put + */ + public void putProperty(Property prop) + { + entries.put(prop.getKey(), prop); + } + + + /** + * Rename key before loading; value is preserved + * + * @param oldKey old key + * @param newKey new key + */ + public void renameKey(String oldKey, String newKey) + { + renameTable.put(oldKey, newKey); + return; + } + + + /** + * Set value saved to certain key. + * + * @param key key + * @param value the saved value + */ + public void setValue(String key, Object value) + { + getProperty(key).setValue(value); + } + + + public void setFileComment(String fileComment) + { + this.fileComment = fileComment; + } + +} diff --git a/src/mightypork/utils/files/config/SimpleConfig.java b/src/mightypork/utils/files/config/SimpleConfig.java new file mode 100644 index 0000000..531d9ac --- /dev/null +++ b/src/mightypork/utils/files/config/SimpleConfig.java @@ -0,0 +1,205 @@ +package mightypork.utils.files.config; + + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import mightypork.utils.files.FileUtils; +import mightypork.utils.logging.Log; + + +/** + * Utility for parsing simple config files
+ * # and // mark a comment
+ * empty lines and lines without "=" are ignored
+ * lines with "=" must have "key = value" format, or a warning is logged.
+ * use "NULL" to create empty value. + * + * @author Ondřej Hruška (MightyPork) + */ +public class SimpleConfig { + + /** + * Load list from file + * + * @param file file + * @return map of keys and values + * @throws IOException + */ + public static List 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 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 listFromString(String text) + { + final List 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 mapFromString(String text) + { + final LinkedHashMap 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 data, boolean allowNulls) throws IOException + { + final List lines = new ArrayList<>(); + + for (final Entry 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 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); + + } +} diff --git a/src/mightypork/utils/files/config/SortedProperties.java b/src/mightypork/utils/files/config/SortedProperties.java new file mode 100644 index 0000000..124b526 --- /dev/null +++ b/src/mightypork/utils/files/config/SortedProperties.java @@ -0,0 +1,309 @@ +package mightypork.utils.files.config; + + +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + + +/** + * Properties stored in file, alphabetically sorted.
+ * Uses UTF-8 encoding and each property can have it's own comment. + * + * @author Ondřej Hruška (MightyPork) + */ +public class SortedProperties extends java.util.Properties { + + /** Option: put empty line before each comment. */ + public boolean cfgBlankRowBeforeComment = true; + + /** + * Option: Separate sections by newline
+ * Section = string before first dot in key. + */ + public boolean cfgBlankRowBetweenSections = true; + + /** Comments for individual keys */ + private final Hashtable keyComments = new Hashtable<>(); + + + private static void writeComments(BufferedWriter bw, String comm) throws IOException + { + final String comments = comm.replace("\n\n", "\n \n"); + + final int len = comments.length(); + int current = 0; + int last = 0; + final char[] uu = new char[6]; + uu[0] = '\\'; + uu[1] = 'u'; + while (current < len) { + final char c = comments.charAt(current); + if (c > '\u00ff' || c == '\n' || c == '\r') { + if (last != current) { + bw.write("# " + comments.substring(last, current)); + } + + if (c > '\u00ff') { + uu[2] = hexDigit(c, 12); + uu[3] = hexDigit(c, 8); + uu[4] = hexDigit(c, 4); + uu[5] = hexDigit(c, 0); + bw.write(new String(uu)); + } else { + bw.newLine(); + if (c == '\r' && current != len - 1 && comments.charAt(current + 1) == '\n') { + current++; + } + } + last = current + 1; + } + current++; + } + if (last != current) { + bw.write("# " + comments.substring(last, current)); + } + + bw.newLine(); + bw.newLine(); + bw.newLine(); + } + + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public synchronized Enumeration keys() + { + final Enumeration keysEnum = super.keys(); + final Vector keyList = new Vector(); + while (keysEnum.hasMoreElements()) { + keyList.add(keysEnum.nextElement()); + } + Collections.sort(keyList); //sort! + return keyList.elements(); + } + + + private static String saveConvert(String theString, boolean escapeSpace, boolean escapeUnicode) + { + final int len = theString.length(); + int bufLen = len * 2; + if (bufLen < 0) { + bufLen = Integer.MAX_VALUE; + } + final StringBuffer result = new StringBuffer(bufLen); + + for (int x = 0; x < len; x++) { + final char ch = theString.charAt(x); + + // Handle common case first, selecting largest block that + // avoids the specials below + if ((ch > 61) && (ch < 127)) { + if (ch == '\\') { + result.append('\\'); + result.append('\\'); + continue; + } + result.append(ch); + continue; + } + + switch (ch) { + case ' ': + if (x == 0 || escapeSpace) { + result.append('\\'); + } + result.append(' '); + break; + + case '\t': + result.append('\\'); + result.append('t'); + break; + + case '\n': + result.append('\\'); + result.append('n'); + break; + + case '\r': + result.append('\\'); + result.append('r'); + break; + + case '\f': + result.append('\\'); + result.append('f'); + break; + + case '=': // Fall through + case ':': // Fall through + case '#': // Fall through + case '!': + result.append('\\'); + result.append(ch); + break; + + default: + if (((ch < 0x0020) || (ch > 0x007e)) & escapeUnicode) { + result.append('\\'); + result.append('u'); + result.append(hexDigit(ch, 12)); + result.append(hexDigit(ch, 8)); + result.append(hexDigit(ch, 4)); + result.append(hexDigit(ch, 0)); + } else { + result.append(ch); + } + } + } + + return result.toString(); + } + + + /** + * Set additional comment to a key + * + * @param key key for comment + * @param comment the comment + */ + public void setKeyComment(String key, String comment) + { + keyComments.put(key, comment); + } + + + @SuppressWarnings("rawtypes") + @Override + public void store(OutputStream out, String comments) throws IOException + { + final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out, "UTF-8")); + + final boolean escUnicode = false; + boolean firstEntry = true; + String lastSectionBeginning = ""; + + if (comments != null) { + writeComments(bw, comments); + } + + synchronized (this) { + for (final Enumeration e = keys(); e.hasMoreElements();) { + boolean wasNewLine = false; + + String key = (String) e.nextElement(); + String val = (String) get(key); + key = saveConvert(key, true, escUnicode); + val = saveConvert(val, false, escUnicode); + + if (cfgBlankRowBetweenSections && !lastSectionBeginning.equals(key.split("[.]")[0])) { + if (!firstEntry) { + bw.newLine(); + bw.newLine(); + } + + wasNewLine = true; + lastSectionBeginning = key.split("[.]")[0]; + } + + if (keyComments.containsKey(key)) { + String cm = keyComments.get(key); + cm = cm.replace("\r", "\n"); + cm = cm.replace("\r\n", "\n"); + cm = cm.replace("\n\n", "\n \n"); + + final String[] cmlines = cm.split("\n"); + + if (!wasNewLine && !firstEntry && cfgBlankRowBeforeComment) { + bw.newLine(); + } + + for (final String cmline : cmlines) { + bw.write("# " + cmline); + bw.newLine(); + } + } + + bw.write(key + " = " + val); + bw.newLine(); + + firstEntry = false; + } + } + bw.flush(); + } + + + private static String escapifyStr(String str) + { + final StringBuilder result = new StringBuilder(); + + final int len = str.length(); + for (int x = 0; x < len; x++) { + final char ch = str.charAt(x); + if (ch <= 0x007e) { + result.append(ch); + continue; + } + + result.append('\\'); + result.append('u'); + result.append(hexDigit(ch, 12)); + result.append(hexDigit(ch, 8)); + result.append(hexDigit(ch, 4)); + result.append(hexDigit(ch, 0)); + } + return result.toString(); + } + + + private static char hexDigit(char ch, int offset) + { + final int val = (ch >> offset) & 0xF; + if (val <= 9) { + return (char) ('0' + val); + } + + return (char) ('A' + val - 10); + } + + + @Override + public synchronized void load(InputStream is) throws IOException + { + load(is, "utf-8"); + } + + + public void load(InputStream is, String encoding) throws IOException + { + final StringBuilder sb = new StringBuilder(); + final InputStreamReader isr = new InputStreamReader(is, encoding); + while (true) { + final int temp = isr.read(); + if (temp < 0) { + break; + } + + final char c = (char) temp; + sb.append(c); + } + + final String read = sb.toString().replaceAll("(#|;|//|--)[^\n]*\n", "\n"); + + final String inputString = escapifyStr(read); + final byte[] bs = inputString.getBytes("ISO-8859-1"); + final ByteArrayInputStream bais = new ByteArrayInputStream(bs); + + super.load(bais); + } +} diff --git a/src/mightypork/utils/files/zip/ZipBuilder.java b/src/mightypork/utils/files/zip/ZipBuilder.java new file mode 100644 index 0000000..334b8ae --- /dev/null +++ b/src/mightypork/utils/files/zip/ZipBuilder.java @@ -0,0 +1,131 @@ +package mightypork.utils.files.zip; + + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import mightypork.utils.files.FileUtils; +import mightypork.utils.logging.Log; + + +/** + * Class for building a zip file + * + * @author Ondřej Hruška (MightyPork) + */ +public class ZipBuilder { + + private final ZipOutputStream out; + private final HashSet 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(); + } +} diff --git a/src/mightypork/utils/files/zip/ZipUtils.java b/src/mightypork/utils/files/zip/ZipUtils.java new file mode 100644 index 0000000..9605ce5 --- /dev/null +++ b/src/mightypork/utils/files/zip/ZipUtils.java @@ -0,0 +1,187 @@ +package mightypork.utils.files.zip; + + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import mightypork.utils.files.FileUtils; +import mightypork.utils.logging.Log; +import mightypork.utils.string.validation.StringFilter; + + +/** + * Utilities for manipulating zip files + * + * @author Ondřej Hruška (MightyPork) + */ +public class ZipUtils { + + private static final int BUFFER_SIZE = 2048; + + + /** + * Extract zip file to target directory + * + * @param file zip file + * @param outputDir target directory + * @param filter string filter (will be used to test entry names (paths)) + * @return list of entries extracted (paths) + * @throws IOException + */ + public static List 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 extractZip(ZipFile zip, File outputDir, StringFilter filter) throws IOException + { + final ArrayList files = new ArrayList<>(); + + if (!outputDir.mkdirs()) throw new IOException("Could not create output directory."); + + final Enumeration zipFileEntries = zip.entries(); + + // process each entry + while (zipFileEntries.hasMoreElements()) { + final ZipEntry entry = zipFileEntries.nextElement(); + + // parse filename and path + final String entryPath = entry.getName(); + final File destFile = new File(outputDir, entryPath); + final File destinationParent = destFile.getParentFile(); + + if (entry.isDirectory() || (filter != null && !filter.isValid(entryPath))) continue; + + // make sure directories exist + if (!destinationParent.mkdirs()) throw new IOException("Could not create directory."); + + if (!entry.isDirectory()) { + extractZipEntry(zip, entry, destFile); + files.add(entryPath); + } + } + + return files; + } + + + /** + * Read zip entries and add their paths to a list + * + * @param zipFile open zip file + * @return list of entry names + * @throws IOException on error + */ + public static List 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 listZip(ZipFile zip) throws IOException + { + final ArrayList files = new ArrayList<>(); + + final Enumeration 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; + } + + } +} diff --git a/src/mightypork/utils/interfaces/Destroyable.java b/src/mightypork/utils/interfaces/Destroyable.java new file mode 100644 index 0000000..d8cd13c --- /dev/null +++ b/src/mightypork/utils/interfaces/Destroyable.java @@ -0,0 +1,15 @@ +package mightypork.utils.interfaces; + + +/** + * Object that can be destroyed (free resources etc) + * + * @author Ondřej Hruška (MightyPork) + */ +public interface Destroyable { + + /** + * Destroy this object + */ + public void destroy(); +} diff --git a/src/mightypork/utils/interfaces/Enableable.java b/src/mightypork/utils/interfaces/Enableable.java new file mode 100644 index 0000000..fbdbf19 --- /dev/null +++ b/src/mightypork/utils/interfaces/Enableable.java @@ -0,0 +1,25 @@ +package mightypork.utils.interfaces; + + +/** + * Can be enabled or disabled.
+ * Implementations should take appropriate action (ie. stop listening to events, + * updating etc.) + * + * @author Ondřej Hruška (MightyPork) + */ +public interface Enableable { + + /** + * Change enabled state + * + * @param yes enabled + */ + public void setEnabled(boolean yes); + + + /** + * @return true if enabled + */ + public boolean isEnabled(); +} diff --git a/src/mightypork/utils/interfaces/Pauseable.java b/src/mightypork/utils/interfaces/Pauseable.java new file mode 100644 index 0000000..662e870 --- /dev/null +++ b/src/mightypork/utils/interfaces/Pauseable.java @@ -0,0 +1,28 @@ +package mightypork.utils.interfaces; + + +/** + * Can be paused & resumed + * + * @author Ondřej Hruška (MightyPork) + */ +public interface Pauseable { + + /** + * Pause operation + */ + public void pause(); + + + /** + * Resume operation + */ + public void resume(); + + + /** + * @return paused state + */ + public boolean isPaused(); + +} diff --git a/src/mightypork/utils/interfaces/Pollable.java b/src/mightypork/utils/interfaces/Pollable.java new file mode 100644 index 0000000..bc10bd6 --- /dev/null +++ b/src/mightypork/utils/interfaces/Pollable.java @@ -0,0 +1,15 @@ +package mightypork.utils.interfaces; + + +/** + * Can be asked to update it's state + * + * @author Ondřej Hruška (MightyPork) + */ +public interface Pollable { + + /** + * Update internal state + */ + void poll(); +} diff --git a/src/mightypork/utils/interfaces/Updateable.java b/src/mightypork/utils/interfaces/Updateable.java new file mode 100644 index 0000000..dfd1310 --- /dev/null +++ b/src/mightypork/utils/interfaces/Updateable.java @@ -0,0 +1,17 @@ +package mightypork.utils.interfaces; + + +/** + * Uses delta timing + * + * @author Ondřej Hruška (MightyPork) + */ +public interface Updateable { + + /** + * Update item state based on elapsed time + * + * @param delta time elapsed since last update, in seconds + */ + public void update(double delta); +} diff --git a/src/mightypork/utils/ion/Ion.java b/src/mightypork/utils/ion/Ion.java new file mode 100644 index 0000000..4164ce2 --- /dev/null +++ b/src/mightypork/utils/ion/Ion.java @@ -0,0 +1,415 @@ +package mightypork.utils.ion; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + + +/** + * Universal data storage system (main API class) + * + * @author Ondřej Hruška (MightyPork) + */ +public class Ion { + + private static final int RESERVED_LOW = 0; + private static final int RESERVED_HIGH = 49; + + private static final int RANGE_LOW = 0; + private static final int RANGE_HIGH = 255; + + // marks for object saving + /** Null mark */ + static final int NULL = 0; + /** Boolean mark */ + static final int BOOLEAN = 1; + /** Byte mark */ + static final int BYTE = 2; + /** Character mark */ + static final int CHAR = 3; + /** Short mark */ + static final int SHORT = 4; + /** Integer mark */ + static final int INT = 5; + /** Long mark */ + static final int LONG = 6; + /** Float mark */ + static final int FLOAT = 7; + /** Double mark */ + static final int DOUBLE = 8; + /** String mark */ + static final int STRING = 9; + /** Boolean array mark */ + static final int BOOLEAN_ARRAY = 10; + /** Byte array mark */ + static final int BYTE_ARRAY = 11; + /** Character array mark */ + static final int CHAR_ARRAY = 12; + /** Short array mark */ + static final int SHORT_ARRAY = 13; + /** Integer array mark */ + static final int INT_ARRAY = 14; + /** Long array mark */ + static final int LONG_ARRAY = 15; + /** Float array mark */ + static final int FLOAT_ARRAY = 16; + /** Double array mark */ + static final int DOUBLE_ARRAY = 17; + /** String array mark */ + static final int STRING_ARRAY = 18; + /** Entry mark - start of map or sequence entry */ + static final int ENTRY = 19; + /** End mark - end of sequence or map */ + static final int END = 20; + /** Bundle */ + static final int ION_BUNDLE = 21; + /** Sequence wrapper for bundle */ + static final int SEQUENCE_WRAPPER = 22; + /** Map wrapper for bundle */ + static final int MAP_WRAPPER = 23; + /** Sequence saved as object */ + public static final int SEQUENCE = 24; + /** Map saved as object */ + public static final int MAP = 25; + /** Array of arbitrary objects */ + public static final int OBJECT_ARRAY = 26; + + + /** Ionizables */ + private static Map> markToClass = new HashMap<>(); + private static Map, Integer> classToMark = new HashMap<>(); + + private static boolean reservedMarkChecking; + + static { + reservedMarkChecking = false; + + // register built-ins + register(ION_BUNDLE, IonBundle.class); + register(SEQUENCE_WRAPPER, IonSequenceWrapper.class); + register(MAP_WRAPPER, IonMapWrapper.class); + + reservedMarkChecking = true; + } + + + /** + * Register new {@link IonObjBinary} class for writing/loading. + * + * @param mark mark to be used 50..255, unless internal + * @param objClass class of the registered object + */ + public static void register(int mark, Class objClass) + { + // negative marks are allowed. + if (mark > RANGE_HIGH) throw new IllegalArgumentException("Mark must be < 256."); + if (mark < RANGE_LOW) throw new IllegalArgumentException("Mark must be positive."); + + if (reservedMarkChecking && isMarkReserved(mark)) { + throw new IllegalArgumentException("Marks " + RESERVED_LOW + ".." + RESERVED_HIGH + " are reserved."); + } + + if (markToClass.containsKey(objClass)) { + throw new IllegalArgumentException("Mark " + mark + " is already in use."); + } + + if (classToMark.containsKey(objClass)) { + throw new IllegalArgumentException(objClass + " is already registered."); + } + + if (!IonObjBundled.class.isAssignableFrom(objClass)) { + if (!IonObjBinary.class.isAssignableFrom(objClass)) { + throw new IllegalArgumentException(objClass + " cannot be registered to Ion."); + } + } + + // make sure the type has implicit constructor + try { + objClass.getConstructor(); + } catch (NoSuchMethodException | SecurityException e) { + throw new RuntimeException("Class " + objClass + " doesn't have an implicit constructor."); + } + + markToClass.put(mark, objClass); + classToMark.put(objClass, mark); + } + + + /** + * Try to register the type using a static final ION_MARK int field. + * + * @param objClass type class + */ + public static void register(Class objClass) + { + try { + final Field fld = objClass.getDeclaredField("ION_MARK"); + + final int modif = fld.getModifiers(); + + if (!Modifier.isFinal(modif) || !Modifier.isStatic(modif)) { + throw new RuntimeException("The ION_MARK field must be static and final."); + } + + fld.setAccessible(true); + final int mark = fld.getInt(null); + + register(mark, objClass); + + } catch (final Exception e) { + throw new RuntimeException("Could not register " + objClass + " using an ION_MARK field.", e); + } + } + + + /** + * Load binary from file and cast. + */ + public static T fromFile(String path) throws IOException + { + return fromFile(new File(path)); + } + + + /** + * Load binary from file and cast. + */ + public static T fromFile(File file) throws IOException + { + try(InputStream in = new FileInputStream(file)) { + return fromStream(in); + } + } + + + /** + * Write binary to file with mark. + */ + public static void toFile(String path, Object obj) throws IOException + { + toFile(new File(path), obj); + } + + + /** + * Write binary to file with mark. + */ + public static void toFile(File file, Object obj) throws IOException + { + try(OutputStream out = new FileOutputStream(file)) { + + toStream(out, obj); + + out.flush(); + } catch (final Exception e) { + throw new IOException("Error writing to ION file.", e); + } + } + + + /** + * Load object from stream based on mark, try to cast. + */ + public static T fromStream(InputStream in) throws IOException + { + try(final IonInput inp = new IonInput(in)) { + return (T) inp.readObject(); + } + } + + + /** + * Write object to output with a mark. + */ + public static void toStream(OutputStream out, Object obj) throws IOException + { + try(IonOutput iout = new IonOutput(out)) { + iout.writeObject(obj); + } + } + + + /** + * Get ion input + * + * @param path file path to read + * @return input + * @throws IOException + */ + public static IonInput getInput(String path) throws IOException + { + return getInput(new File(path)); + } + + + /** + * Get ion input + * + * @param file file to read + * @return input + * @throws IOException + */ + @SuppressWarnings("resource") + public static IonInput getInput(File file) throws IOException + { + return new IonInput(new FileInputStream(file)); + } + + + /** + * Get ion output + * + * @param path file path to write + * @return output + * @throws IOException + */ + public static IonOutput getOutput(String path) throws IOException + { + return getOutput(new File(path)); + } + + + /** + * Get ion output + * + * @param file file to write + * @return output + * @throws IOException + */ + @SuppressWarnings("resource") + public static IonOutput getOutput(File file) throws IOException + { + return new IonOutput(new FileOutputStream(file)); + } + + + /** + * Create new bundle and write the object to it. + */ + public static IonBundle wrapBundled(IonObjBundled content) throws IOException + { + final IonBundle ib = new IonBundle(); + content.save(ib); + return ib; + } + + + /** + * Try to unwrap an object from bundle. The object class must have implicit + * accessible constructor. + * + * @param bundle unwrapped bundle + * @param objClass class of desired object + * @return the object unwrapped + * @throws IOException + */ + public static T unwrapBundled(IonBundle bundle, Class objClass) throws IOException + { + try { + final T inst = objClass.newInstance(); + inst.load(bundle); + return inst; + } catch (InstantiationException | IllegalAccessException e) { + throw new IOException("Could not instantiate " + objClass + "."); + } + } + + + static Class getClassForMark(int mark) + { + return markToClass.get(mark); + } + + + public static int getMark(Object object) + { + assertRegistered(object); + + return classToMark.get(object.getClass()); + } + + + /** + * @return true if the mark is for a registered {@link IonObjBinary} object + */ + static boolean isMarkForBinary(int mark) + { + if (!markToClass.containsKey(mark)) return false; + + return IonObjBinary.class.isAssignableFrom(markToClass.get(mark)); + } + + + /** + * @return true if the mark is for a registered {@link IonObjBinary} object + */ + static boolean isMarkForBundled(int mark) + { + if (!markToClass.containsKey(mark)) return false; + + return IonObjBundled.class.isAssignableFrom(markToClass.get(mark)); + } + + + /** + * @return true if the mark is reserved for internal use + */ + static boolean isMarkReserved(int mark) + { + return mark >= RESERVED_LOW && mark <= RESERVED_HIGH; + } + + + /** + * @return true if the mark is for a registered {@link IonObjBinary} object + */ + static boolean isRegistered(Object object) + { + return classToMark.containsKey(object.getClass()); + } + + + /** + * Make sure object is registered in the table. + * + * @throws IOException if not registered or class mismatch + */ + static void assertRegistered(Object obj) + { + if (!isRegistered(obj)) { + throw new RuntimeException("Type not registered: " + (obj.getClass())); + } + } + + + /** + * For get all external registered types - just like if the class was + * freshly loaded. Can be used for unit tests. + */ + public static void reset() + { + final List toRemove = new ArrayList<>(); + for (final Entry> e : markToClass.entrySet()) { + final int mark = e.getKey(); + + if (!isMarkReserved(mark)) { + toRemove.add(mark); + } + } + + for (final int i : toRemove) { + classToMark.remove(markToClass.remove(i)); + } + } +} diff --git a/src/mightypork/utils/ion/IonBundle.java b/src/mightypork/utils/ion/IonBundle.java new file mode 100644 index 0000000..8a84ef3 --- /dev/null +++ b/src/mightypork/utils/ion/IonBundle.java @@ -0,0 +1,434 @@ +package mightypork.utils.ion; + + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * Ion data bundle - simplified Map with facilities for storing maps and + * sequences. + * + * @author Ondřej Hruška (MightyPork) + */ +public class IonBundle implements IonObjBinary { + + private final Map backingMap = new HashMap<>(); + + + /** + * Clear & fill a provided bundle with elements from a bundle value + * + * @param key key + * @param filled bundle to fill + */ + public void loadBundle(String key, IonBundle filled) + { + if (!containsKey(key)) return; + + final IonBundle ib = get(key, new IonBundle()); + + filled.clear(); + filled.putAll(ib); + } + + + /** + * Check if a key is used in the bundle + * + * @param key key to check + * @return true if this key is used in the bundle + */ + public boolean containsKey(Object key) + { + return backingMap.containsKey(key); + } + + + /** + * Check if a value is contained in the bundle + * + * @param value value to check + * @return true if this value is contained in the bundle + */ + public boolean containsValue(Object value) + { + return backingMap.containsValue(value); + } + + + /** + * Get a map value + * + * @param key key + * @return a new Map with elements from that value + */ + public Map getMap(String key) + { + return loadMap(key, new LinkedHashMap()); + } + + + /** + * Clear & fill the provided map with elements from a map value + * + * @param key key + * @param filled Map to fill + */ + public Map loadMap(String key, Map filled) + { + final IonMapWrapper imw = get(key, null); + if (imw == null) throw new RuntimeException("No such key: " + key); + filled.clear(); + imw.fill(filled); + return filled; + } + + + /** + * Get a sequence value + * + * @param key key + * @return a new Collection with elements from that value + */ + public Collection getSequence(String key) + { + return loadSequence(key, new ArrayList()); + } + + + /** + * Clear & fill the provided Collection with elements from a sequence value + * + * @param key key + * @param filled Collection to fill + * @return the filled collection + */ + public Collection loadSequence(String key, Collection filled) + { + final IonSequenceWrapper isw = get(key, null); + if (isw == null) throw new RuntimeException("No such key: " + key); + filled.clear(); + isw.fill(filled); + + return filled; + } + + + /** + * Load a bundled object from a bundle value.
+ * The object does not have to be registered. + * + * @param key key + * @param loaded loaded object + * @return the loaded object + */ + public T loadBundled(String key, T loaded) + { + final IonBundle bu = get(key, null); + if (bu == null) throw new RuntimeException("No such key: " + key); + + loaded.load(bu); + + return loaded; + } + + + /** + * Save a bundled object to a bundle value.
+ * The object does not have to be registered. + * + * @param key key + * @param saved saved object + */ + public void putBundled(String key, IonObjBundled saved) + { + final IonBundle bu = new IonBundle(); + saved.save(bu); + put(key, bu); + } + + + /** + * Get value, or fallback (if none found of with bad type). + * + * @param key + * @param fallback value + * @return value + */ + public T get(String key, T fallback) + { + try { + final T itm = (T) backingMap.get(key); + if (itm == null) return fallback; + return itm; + } catch (final ClassCastException e) { + return fallback; + } + } + + + /** + * Get value, or null (if none found of with bad type). + * + * @param key + * @return value + */ + public T get(String key) + { + return get(key, (T) null); + } + + + public void put(String key, IonObjBundled value) + { + if (key == null || value == null) return; + if (!Ion.isRegistered(value)) throw new IllegalArgumentException("Cannot add to bundle, not registered: " + value); + backingMap.put(key, value); + } + + + public void put(String key, IonObjBinary value) + { + if (key == null || value == null) return; + if (!Ion.isRegistered(value)) throw new IllegalArgumentException("Cannot add to bundle, not registered: " + value); + backingMap.put(key, value); + } + + + public void put(String key, boolean value) + { + backingMap.put(key, value); + } + + + public void put(String key, byte value) + { + backingMap.put(key, value); + } + + + public void put(String key, char value) + { + backingMap.put(key, value); + } + + + public void put(String key, short value) + { + backingMap.put(key, value); + } + + + public void put(String key, int value) + { + backingMap.put(key, value); + } + + + public void put(String key, long value) + { + backingMap.put(key, value); + } + + + public void put(String key, double value) + { + backingMap.put(key, value); + } + + + public void put(String key, float value) + { + backingMap.put(key, value); + } + + + public void put(String key, String value) + { + backingMap.put(key, value); + } + + + public void put(String key, boolean[] value) + { + backingMap.put(key, value); + } + + + public void put(String key, char[] value) + { + backingMap.put(key, value); + } + + + public void put(String key, short[] value) + { + backingMap.put(key, value); + } + + + public void put(String key, int[] value) + { + backingMap.put(key, value); + } + + + public void put(String key, long[] value) + { + backingMap.put(key, value); + } + + + public void put(String key, double[] value) + { + backingMap.put(key, value); + } + + + public void put(String key, float[] value) + { + backingMap.put(key, value); + } + + + public void put(String key, String[] value) + { + backingMap.put(key, value); + } + + + public void put(String key, Object[] value) + { + backingMap.put(key, value); + } + + + /** + * Put a sequence to the bundle. + * + * @param key key + * @param c value (Collection) + */ + @SuppressWarnings("rawtypes") + public void putSequence(String key, Collection c) + { + backingMap.put(key, new IonSequenceWrapper(c)); + } + + + /** + * Put a map to the bundle. + * + * @param key key + * @param m value (Map) + */ + @SuppressWarnings("rawtypes") + public void putMap(String key, Map m) + { + backingMap.put(key, new IonMapWrapper(m)); + } + + + @Override + public void load(IonInput in) throws IOException + { + in.readMap(backingMap); + } + + + @Override + public void save(IonOutput out) throws IOException + { + out.writeMap(backingMap); + } + + + /** + * Get number of elements in the bundle + * + * @return size + */ + public int size() + { + return backingMap.size(); + } + + + /** + * Check whether the bundle is empty + * + * @return true if empty + */ + public boolean isEmpty() + { + return backingMap.isEmpty(); + } + + + /** + * Remove all elements + */ + public void clear() + { + backingMap.clear(); + } + + + /** + * Remove a value by key + * + * @param key key to remove + * @return the removed object + */ + public Object remove(Object key) + { + return backingMap.remove(key); + } + + + /** + * Put all from another bundle + * + * @param anotherBundle another bundle + */ + public void putAll(IonBundle anotherBundle) + { + backingMap.putAll(anotherBundle.backingMap); + } + + + @Override + public String toString() + { + return backingMap.toString(); + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((backingMap == null) ? 0 : backingMap.hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof IonBundle)) return false; + final IonBundle other = (IonBundle) obj; + if (backingMap == null) { + if (other.backingMap != null) return false; + } else if (!backingMap.equals(other.backingMap)) return false; + return true; + } +} diff --git a/src/mightypork/utils/ion/IonInput.java b/src/mightypork/utils/ion/IonInput.java new file mode 100644 index 0000000..34310ba --- /dev/null +++ b/src/mightypork/utils/ion/IonInput.java @@ -0,0 +1,495 @@ +package mightypork.utils.ion; + + +import java.io.Closeable; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import mightypork.utils.exceptions.CorruptDataException; + + +/** + * Ion input stream + * + * @author Ondřej Hruška (MightyPork) + */ +public class IonInput implements Closeable { + + private final DataInput in; + private final InputStream stream; + + + @SuppressWarnings("resource") + public IonInput(File inFile) throws FileNotFoundException + { + this(new FileInputStream(inFile)); + } + + + public IonInput(InputStream in) + { + this.stream = in; + this.in = new DataInputStream(in); + } + + + /** + * Read int 0-255. Suitable when the int was written using + * writeIntByte() method. + * + * @return int + * @throws IOException + */ + public int readIntByte() throws IOException + { + return in.readUnsignedByte(); + } + + + /** + * Read an int 0-65535. Suitable when the int was written using + * writeIntShort() method. + * + * @return int + * @throws IOException + */ + public int readIntShort() throws IOException + { + return in.readUnsignedShort(); + } + + + public boolean readBoolean() throws IOException + { + return in.readBoolean(); + } + + + public byte readByte() throws IOException + { + return in.readByte(); + } + + + public short readShort() throws IOException + { + return in.readShort(); + } + + + public char readChar() throws IOException + { + return in.readChar(); + } + + + public int readInt() throws IOException + { + return in.readInt(); + } + + + public long readLong() throws IOException + { + return in.readLong(); + } + + + public float readFloat() throws IOException + { + return in.readFloat(); + } + + + public double readDouble() throws IOException + { + return in.readDouble(); + } + + + public String readString() throws IOException + { + return in.readUTF(); + } + + + public boolean[] readBooleans() throws IOException + { + final int length = readLength(); + final boolean[] arr = new boolean[length]; + for (int i = 0; i < length; i++) { + arr[i] = in.readBoolean(); + } + return arr; + } + + + public byte[] readBytes() throws IOException + { + final int length = readLength(); + final byte[] arr = new byte[length]; + for (int i = 0; i < length; i++) { + arr[i] = in.readByte(); + } + return arr; + } + + + public char[] readChars() throws IOException + { + final int length = readLength(); + final char[] arr = new char[length]; + for (int i = 0; i < length; i++) { + arr[i] = in.readChar(); + } + return arr; + } + + + public short[] readShorts() throws IOException + { + final int length = readLength(); + final short[] arr = new short[length]; + for (int i = 0; i < length; i++) { + arr[i] = in.readShort(); + } + return arr; + } + + + public int[] readInts() throws IOException + { + final int length = readLength(); + final int[] arr = new int[length]; + for (int i = 0; i < length; i++) { + arr[i] = in.readInt(); + } + return arr; + } + + + public long[] readLongs() throws IOException + { + final int length = readLength(); + final long[] arr = new long[length]; + for (int i = 0; i < length; i++) { + arr[i] = in.readLong(); + } + return arr; + } + + + public float[] readFloats() throws IOException + { + final int length = readLength(); + final float[] arr = new float[length]; + for (int i = 0; i < length; i++) { + arr[i] = in.readFloat(); + } + return arr; + } + + + public double[] readDoubles() throws IOException + { + final int length = readLength(); + final double[] arr = new double[length]; + for (int i = 0; i < length; i++) { + arr[i] = in.readDouble(); + } + return arr; + } + + + public String[] readStrings() throws IOException + { + final int length = readLength(); + final String[] arr = new String[length]; + for (int i = 0; i < length; i++) { + arr[i] = in.readUTF(); + } + return arr; + } + + + public Object[] readObjects() throws IOException + { + final int length = readLength(); + final Object[] arr = new Object[length]; + for (int i = 0; i < length; i++) { + arr[i] = readObject(); + } + return arr; + } + + + /** + * Read bundle without a mark + */ + public IonBundle readBundle() throws IOException + { + final IonBundle ib = new IonBundle(); + ib.load(this); + return ib; + } + + + /** + * Read bundle without a mark, load into a provided one + */ + public void readBundle(IonBundle filled) throws IOException + { + filled.clear(); + filled.load(this); + } + + + private int readMark() throws IOException + { + return readIntByte(); + } + + + private int readLength() throws IOException + { + return readInt(); + } + + + /** + *

+ * Read object based on mark; if null mark is found, returns default value. + *

+ *

+ * If, however, an object of invalid or different type is found, an + * exception will be thrown. + *

+ * + * @param def default value. + * @return the loaded object + * @throws IOException + */ + @SuppressWarnings("unchecked") + public T readObject(T def) throws IOException + { + try { + final Object o = readObject(); + return (T) (o == null ? def : o); + } catch (final Exception e) { + throw new IOException("Could not load object.", e); + } + } + + + /** + * Read single object, preceded by a mark. + * + * @return the loaded object + * @throws IOException + */ + public Object readObject() throws IOException + { + final int mark = readMark(); + if (Ion.isMarkForBinary(mark)) { + IonObjBinary loaded; + + try { + loaded = (IonObjBinary) Ion.getClassForMark(mark).newInstance(); + } catch (final Exception e) { + throw new RuntimeException("Could not load binary object with mark: " + mark, e); + } + + loaded.load(this); + return loaded; + } + + if (Ion.isMarkForBundled(mark)) { + IonObjBundled loaded; + + try { + loaded = (IonObjBundled) Ion.getClassForMark(mark).newInstance(); + } catch (final Exception e) { + throw new RuntimeException("Could not load bundled object with mark: " + mark, e); + } + + final IonBundle ib = readBundle(); + loaded.load(ib); + return loaded; + } + + switch (mark) { + case Ion.NULL: + return null; + + case Ion.BOOLEAN: + return readBoolean(); + + case Ion.BYTE: + return readByte(); + + case Ion.CHAR: + return readChar(); + + case Ion.SHORT: + return readShort(); + + case Ion.INT: + return readInt(); + + case Ion.LONG: + return readLong(); + + case Ion.FLOAT: + return readFloat(); + + case Ion.DOUBLE: + return readDouble(); + + case Ion.STRING: + return readString(); + + case Ion.BOOLEAN_ARRAY: + return readBooleans(); + + case Ion.BYTE_ARRAY: + return readBytes(); + + case Ion.CHAR_ARRAY: + return readChars(); + + case Ion.SHORT_ARRAY: + return readShorts(); + + case Ion.INT_ARRAY: + return readInts(); + + case Ion.LONG_ARRAY: + return readLongs(); + + case Ion.FLOAT_ARRAY: + return readFloats(); + + case Ion.DOUBLE_ARRAY: + return readDoubles(); + + case Ion.STRING_ARRAY: + return readStrings(); + + case Ion.OBJECT_ARRAY: + return readObjects(); + + case Ion.MAP: + return readMap(); + + case Ion.SEQUENCE: + return readSequence(); + + default: + throw new CorruptDataException("Invalid mark: " + mark); + } + } + + + /** + * Reads mark and returns true if the mark is ENTRY, false if the mark is + * END. Throws an exception otherwise. + * + * @return mark was ENTRY + * @throws IOException when the mark is neither ENTRY or END. + */ + public boolean hasNextEntry() throws IOException + { + final int mark = readMark(); + if (mark == Ion.ENTRY) return true; + if (mark == Ion.END) return false; + + throw new CorruptDataException("Unexpected mark in sequence: " + mark); + } + + + /** + * Read a sequence of elements into an ArrayList + * + * @return the collection + * @throws IOException + */ + public Collection readSequence() throws IOException + { + return readSequence(new ArrayList()); + } + + + /** + * Load entries into a collection. The collection is cleaned first. + * + * @param filled collection to populate + * @return the collection + * @throws IOException + */ + @SuppressWarnings("unchecked") + public Collection readSequence(Collection filled) throws IOException + { + try { + filled.clear(); + while (hasNextEntry()) { + filled.add((T) readObject()); + } + return filled; + } catch (final ClassCastException e) { + throw new CorruptDataException("Unexpected element type in sequence.", e); + } + } + + + /** + * Read element pairs into a HashMap + * + * @return the map + * @throws IOException + */ + public Map readMap() throws IOException + { + return readMap(new HashMap()); + } + + + /** + * Load data into a map. The map is cleaned first. + * + * @param filled filled map + * @return the map + * @throws IOException + */ + @SuppressWarnings("unchecked") + public Map readMap(Map filled) throws IOException + { + try { + filled.clear(); + while (hasNextEntry()) { + final K key = (K) readObject(); + final V value = (V) readObject(); + + filled.put(key, value); + } + return filled; + } catch (final ClassCastException e) { + throw new CorruptDataException("Unexpected element type in map.", e); + } + } + + + @Override + public void close() throws IOException + { + stream.close(); + } +} diff --git a/src/mightypork/utils/ion/IonMapWrapper.java b/src/mightypork/utils/ion/IonMapWrapper.java new file mode 100644 index 0000000..29bc840 --- /dev/null +++ b/src/mightypork/utils/ion/IonMapWrapper.java @@ -0,0 +1,54 @@ +package mightypork.utils.ion; + + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + + +@SuppressWarnings({ "rawtypes", "unchecked" }) +class IonMapWrapper implements IonObjBinary { + + private final Map map; + + + public IonMapWrapper() + { + map = new LinkedHashMap<>(); + } + + + public IonMapWrapper(Map saved) + { + map = saved; + } + + + @Override + public void load(IonInput in) throws IOException + { + map.clear(); + in.readMap(map); + } + + + @Override + public void save(IonOutput out) throws IOException + { + out.writeMap(map); + } + + + public void fill(Map o) + { + o.clear(); + o.putAll(map); + } + + + public Map getMap() + { + return map; + } + +} diff --git a/src/mightypork/utils/ion/IonObjBinary.java b/src/mightypork/utils/ion/IonObjBinary.java new file mode 100644 index 0000000..7824334 --- /dev/null +++ b/src/mightypork/utils/ion/IonObjBinary.java @@ -0,0 +1,32 @@ +package mightypork.utils.ion; + + +import java.io.IOException; + + +/** + * Binary ion object. If a class implements both binary and bundled, then binary + * will be preferred by both IonInput and IonOutput. + * + * @author Ondřej Hruška (MightyPork) + */ +public interface IonObjBinary { + + /** + * Load data from the input stream. + * + * @param in input stream + * @throws IOException + */ + void load(IonInput in) throws IOException; + + + /** + * Store data to output stream (in such way that the load method will later + * be able to read it). + * + * @param out Output stream + * @throws IOException + */ + void save(IonOutput out) throws IOException; +} diff --git a/src/mightypork/utils/ion/IonObjBundled.java b/src/mightypork/utils/ion/IonObjBundled.java new file mode 100644 index 0000000..17b00e6 --- /dev/null +++ b/src/mightypork/utils/ion/IonObjBundled.java @@ -0,0 +1,16 @@ +package mightypork.utils.ion; + + +/** + * Bundled ion object. If a class implements both binary and bundled, then + * binary will be preferred by both IonInput and IonOutput. + * + * @author Ondřej Hruška (MightyPork) + */ +public interface IonObjBundled { + + void load(IonBundle in); + + + void save(IonBundle out); +} diff --git a/src/mightypork/utils/ion/IonOutput.java b/src/mightypork/utils/ion/IonOutput.java new file mode 100644 index 0000000..589e795 --- /dev/null +++ b/src/mightypork/utils/ion/IonOutput.java @@ -0,0 +1,437 @@ +package mightypork.utils.ion; + + +import java.io.Closeable; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + + +/** + * Ion output stream + * + * @author Ondřej Hruška (MightyPork) + */ +public class IonOutput implements Closeable { + + private final DataOutput out; + private final OutputStream stream; + + + @SuppressWarnings("resource") + public IonOutput(File outFile) throws FileNotFoundException + { + this(new FileOutputStream(outFile)); + } + + + public IonOutput(OutputStream out) + { + this.stream = out; + this.out = new DataOutputStream(out); + } + + + public void writeBoolean(boolean a) throws IOException + { + out.writeBoolean(a); + } + + + public void writeByte(int a) throws IOException + { + out.writeByte(a); + } + + + public void writeShort(int a) throws IOException + { + out.writeShort(a); + } + + + public void writeChar(int a) throws IOException + { + out.writeChar(a); + } + + + public void writeInt(int a) throws IOException + { + out.writeInt(a); + } + + + public void writeIntShort(int a) throws IOException + { + out.writeShort(a); + } + + + public void writeIntByte(int a) throws IOException + { + out.writeByte(a); + } + + + public void writeLong(long a) throws IOException + { + out.writeLong(a); + } + + + public void writeFloat(float a) throws IOException + { + out.writeFloat(a); + } + + + public void writeDouble(double a) throws IOException + { + out.writeDouble(a); + } + + + public void writeBytes(String a) throws IOException + { + out.writeBytes(a); + } + + + public void writeString(String a) throws IOException + { + out.writeUTF(a); + } + + + public void writeBooleans(boolean[] arr) throws IOException + { + writeLength(arr.length); + for (final boolean a : arr) { + out.writeBoolean(a); + } + } + + + public void writeBytes(byte[] arr) throws IOException + { + writeLength(arr.length); + for (final byte a : arr) { + out.writeByte(a); + } + } + + + public void writeChars(char[] arr) throws IOException + { + writeLength(arr.length); + for (final char a : arr) { + out.writeChar(a); + } + } + + + public void writeShorts(short[] arr) throws IOException + { + writeLength(arr.length); + for (final short a : arr) { + out.writeShort(a); + } + } + + + public void writeInts(int[] arr) throws IOException + { + writeLength(arr.length); + for (final int a : arr) { + out.writeInt(a); + } + } + + + public void writeLongs(long[] arr) throws IOException + { + writeLength(arr.length); + for (final long a : arr) { + out.writeLong(a); + } + } + + + public void writeFloats(float[] arr) throws IOException + { + writeLength(arr.length); + for (final float a : arr) { + out.writeFloat(a); + } + } + + + public void writeDoubles(double[] arr) throws IOException + { + writeLength(arr.length); + for (final double a : arr) { + out.writeDouble(a); + } + } + + + public void writeStrings(String[] arr) throws IOException + { + writeLength(arr.length); + for (final String a : arr) { + out.writeUTF(a); + } + } + + + /** + * Write a bundle without a mark + */ + public void writeBundle(IonBundle bundle) throws IOException + { + bundle.save(this); + } + + + /** + * Write array of objects. Works with all that is supported by writeObject() + * + * @param arr array to write + * @throws IOException on IO error or on invalid object type. + */ + public void writeObjects(Object[] arr) throws IOException + { + writeLength(arr.length); + for (final Object a : arr) { + writeObject(a); + } + } + + + public void writeSequence(Collection sequence) throws IOException + { + for (final T element : sequence) { + startEntry(); + writeObject(element); + } + endSequence(); + } + + + public void writeMap(Map map) throws IOException + { + for (final Entry e : map.entrySet()) { + if (e.getValue() == null) { + continue; + } + + startEntry(); + writeObject(e.getKey()); + writeObject(e.getValue()); + } + endSequence(); + } + + + public void endSequence() throws IOException + { + writeMark(Ion.END); + } + + + public void startEntry() throws IOException + { + writeMark(Ion.ENTRY); + } + + + private void writeMark(int mark) throws IOException + { + writeIntByte(mark); + } + + + private void writeLength(int length) throws IOException + { + writeInt(length); + } + + + /** + * Write an object. Supported are built-in types and types registered to + * Ion. + * + * @param obj obj to write + * @throws IOException on IO error or invalid object type. + */ + public void writeObject(Object obj) throws IOException + { + if (obj == null) { + writeMark(Ion.NULL); + return; + } + + if (obj instanceof IonObjBinary) { + final IonObjBinary iObj = (IonObjBinary) obj; + + writeMark(Ion.getMark(iObj)); + iObj.save(this); + return; + } + + if (obj instanceof IonObjBundled) { + final IonObjBundled iObj = (IonObjBundled) obj; + + writeMark(Ion.getMark(iObj)); + + final IonBundle bundle = new IonBundle(); + iObj.save(bundle); + writeBundle(bundle); + + return; + } + + if (obj instanceof Map) { + writeMark(Ion.MAP); + writeMap((Map) obj); + return; + } + + if (obj instanceof Collection) { + writeMark(Ion.SEQUENCE); + writeSequence((Collection) obj); + return; + } + + if (obj instanceof Boolean) { + writeMark(Ion.BOOLEAN); + writeBoolean((Boolean) obj); + return; + } + + if (obj instanceof Byte) { + writeMark(Ion.BYTE); + writeByte((Byte) obj); + return; + } + + if (obj instanceof Character) { + writeMark(Ion.CHAR); + writeChar((Character) obj); + return; + } + + if (obj instanceof Short) { + writeMark(Ion.SHORT); + writeShort((Short) obj); + return; + } + + if (obj instanceof Integer) { + writeMark(Ion.INT); + writeInt((Integer) obj); + return; + } + + if (obj instanceof Long) { + writeMark(Ion.LONG); + writeLong((Long) obj); + return; + } + + if (obj instanceof Float) { + writeMark(Ion.FLOAT); + writeFloat((Float) obj); + return; + } + + if (obj instanceof Double) { + writeMark(Ion.DOUBLE); + writeDouble((Double) obj); + return; + } + + if (obj instanceof String) { + writeMark(Ion.STRING); + writeString((String) obj); + return; + } + + if (obj instanceof boolean[]) { + writeMark(Ion.BOOLEAN_ARRAY); + writeBooleans((boolean[]) obj); + return; + } + + if (obj instanceof byte[]) { + writeMark(Ion.BYTE_ARRAY); + writeBytes((byte[]) obj); + return; + } + + if (obj instanceof char[]) { + writeMark(Ion.CHAR_ARRAY); + writeChars((char[]) obj); + return; + } + + if (obj instanceof short[]) { + writeMark(Ion.SHORT_ARRAY); + writeShorts((short[]) obj); + return; + } + + if (obj instanceof int[]) { + writeMark(Ion.INT_ARRAY); + writeInts((int[]) obj); + return; + } + + if (obj instanceof long[]) { + writeMark(Ion.LONG_ARRAY); + writeLongs((long[]) obj); + return; + } + + if (obj instanceof float[]) { + writeMark(Ion.FLOAT_ARRAY); + writeFloats((float[]) obj); + return; + } + + if (obj instanceof double[]) { + writeMark(Ion.DOUBLE_ARRAY); + writeDoubles((double[]) obj); + return; + } + + if (obj instanceof String[]) { + writeMark(Ion.STRING_ARRAY); + writeStrings((String[]) obj); + return; + } + + if (obj instanceof Object[]) { + writeMark(Ion.OBJECT_ARRAY); + writeObjects((Object[]) obj); + return; + } + + throw new IOException("Object " + obj + " could not be be written to stream."); + } + + + @Override + public void close() throws IOException + { + stream.close(); + } +} diff --git a/src/mightypork/utils/ion/IonSequenceWrapper.java b/src/mightypork/utils/ion/IonSequenceWrapper.java new file mode 100644 index 0000000..35e17d7 --- /dev/null +++ b/src/mightypork/utils/ion/IonSequenceWrapper.java @@ -0,0 +1,54 @@ +package mightypork.utils.ion; + + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + + +@SuppressWarnings({ "rawtypes", "unchecked" }) +class IonSequenceWrapper implements IonObjBinary { + + private Collection collection = new ArrayList(); + + + public IonSequenceWrapper() + { + collection = new ArrayList(); + } + + + public IonSequenceWrapper(Collection saved) + { + collection = saved; + } + + + @Override + public void load(IonInput in) throws IOException + { + collection.clear(); + in.readSequence(collection); + } + + + @Override + public void save(IonOutput out) throws IOException + { + out.writeSequence(collection); + } + + + public void fill(Collection o) + { + o.clear(); + o.addAll(collection); + } + + + public Collection getSequence() + { + return collection; + } + +} diff --git a/src/mightypork/utils/logging/Log.java b/src/mightypork/utils/logging/Log.java new file mode 100644 index 0000000..6b6eb9b --- /dev/null +++ b/src/mightypork/utils/logging/Log.java @@ -0,0 +1,338 @@ +package mightypork.utils.logging; + + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.logging.Level; + +import mightypork.utils.annotations.FactoryMethod; +import mightypork.utils.logging.monitors.LogMonitor; +import mightypork.utils.logging.monitors.LogMonitorStdout; +import mightypork.utils.logging.writers.ArchivingLog; +import mightypork.utils.logging.writers.LogWriter; +import mightypork.utils.logging.writers.SimpleLog; +import mightypork.utils.string.StringUtil; + + +/** + * A log. + * + * @author Ondřej Hruška (MightyPork) + */ +public class Log { + + private static LogWriter main = null; + private static boolean enabled = true; + private static final LogMonitorStdout sysoMonitor = new LogMonitorStdout(); + private static final long start_ms = System.currentTimeMillis(); + + private static HashMap logs = new HashMap<>(); + + + /** + * Create a logger. If another with the name already exists, it'll be + * retrieved instead of creating a new one. + * + * @param logName log name (used for filename, should be application-unique) + * @param logFile log file; old logs will be kept here too. + * @param oldLogsCount number of old logs to keep, -1 infinite, 0 none. + * @return the created Log instance + */ + @FactoryMethod + public static synchronized LogWriter create(String logName, File logFile, int oldLogsCount) + { + if (logs.containsKey(logName)) return logs.get(logName); + + final ArchivingLog log = new ArchivingLog(logName, logFile, oldLogsCount); + log.init(); + + logs.put(logName, log); + + return log; + } + + + /** + * Create a logger. If another with the name already exists, it'll be + * retrieved instead of creating a new one. + * + * @param logName log name (used for filename, must be application-unique) + * @param logFile log file; old logs will be kept here too. + * @return the created Log instance + */ + @FactoryMethod + public static synchronized LogWriter create(String logName, File logFile) + { + if (logs.containsKey(logName)) return logs.get(logName); + + final SimpleLog log = new SimpleLog(logName, logFile); + log.init(); + + logs.put(logName, log); + + return log; + } + + + public static void setMainLogger(LogWriter log) + { + main = log; + } + + + public static void addMonitor(LogMonitor mon) + { + assertInited(); + + main.addMonitor(mon); + } + + + public static void removeMonitor(LogMonitor mon) + { + assertInited(); + + main.removeMonitor(mon); + } + + + private static void assertInited() + { + if (main == null) throw new IllegalStateException("Main logger not initialized."); + } + + + /** + * Log a message + * + * @param level message level + * @param msg message text + */ + public static void log(Level level, String msg) + { + if (enabled) { + sysoMonitor.onMessageLogged(level, formatMessage(level, msg, null, start_ms)); + + if (main != null) { + main.log(level, msg); + } + } + } + + + /** + * Log a message + * + * @param level message level + * @param msg message text + * @param t thrown exception + */ + public static void log(Level level, String msg, Throwable t) + { + if (enabled) { + sysoMonitor.onMessageLogged(level, formatMessage(level, msg, t, start_ms)); + + if (main != null) { + main.log(level, msg, t); + } + } + } + + + /** + * Log FINE message + * + * @param msg message + */ + public static void f1(String msg) + { + log(Level.FINE, msg); + } + + + /** + * Log FINER message + * + * @param msg message + */ + public static void f2(String msg) + { + log(Level.FINER, msg); + } + + + /** + * Log FINEST message + * + * @param msg message + */ + public static void f3(String msg) + { + log(Level.FINEST, msg); + } + + + /** + * Log INFO message + * + * @param msg message + */ + public static void i(String msg) + { + log(Level.INFO, msg); + } + + + /** + * Log WARNING message (less severe than ERROR) + * + * @param msg message + */ + public static void w(String msg) + { + log(Level.WARNING, msg); + } + + + /** + * Log ERROR message + * + * @param msg message + */ + public static void e(String msg) + { + log(Level.SEVERE, msg); + } + + + /** + * Log warning message with exception + * + * @param msg message + * @param thrown thrown exception + */ + public static void w(String msg, Throwable thrown) + { + log(Level.WARNING, msg, thrown); + } + + + /** + * Log exception thrown as warning + * + * @param thrown thrown exception + */ + public static void w(Throwable thrown) + { + log(Level.WARNING, null, thrown); + } + + + /** + * Log error message + * + * @param msg message + * @param thrown thrown exception + */ + public static void e(String msg, Throwable thrown) + { + log(Level.SEVERE, msg, thrown); + } + + + /** + * Log exception thrown as error + * + * @param thrown thrown exception + */ + public static void e(Throwable thrown) + { + log(Level.SEVERE, null, thrown); + } + + + public static void enable(boolean flag) + { + enabled = flag; + } + + + public static void setSysoutLevel(Level level) + { + sysoMonitor.setLevel(level); + } + + + public static void setLevel(Level level) + { + assertInited(); + + main.setLevel(level); + } + + + /** + * Get stack trace from throwable + * + * @param t + * @return trace + */ + public static String getStackTrace(Throwable t) + { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw, true); + t.printStackTrace(pw); + pw.flush(); + sw.flush(); + return sw.toString(); + } + + + public static String formatMessage(Level level, String message, Throwable throwable, long start_ms) + { + if (message == null) message = ""; + + final String nl = System.getProperty("line.separator"); + + if (message.length() > 0) { + if (message.equals("\n")) { + return nl; + } + + if (message.charAt(0) == '\n') { + message = nl + message.substring(1); + } + } + + final long time_ms = (System.currentTimeMillis() - start_ms); + final double time_s = time_ms / 1000D; + final String time = String.format("%6.2f ", time_s); + final String time_blank = StringUtil.repeat(" ", time.length()); + + String prefix = "[ ? ]"; + + if (level == Level.FINE) { + prefix = "[ # ] "; + } else if (level == Level.FINER) { + prefix = "[ - ] "; + } else if (level == Level.FINEST) { + prefix = "[ ] "; + } else if (level == Level.INFO) { + prefix = "[ i ] "; + } else if (level == Level.SEVERE) { + prefix = "[!E!] "; + } else if (level == Level.WARNING) { + prefix = "[!W!] "; + } + + message = time + prefix + message.replaceAll("\n", nl + time_blank + prefix) + nl; + + if (throwable != null) { + message += getStackTrace(throwable); + } + + return message; + } +} diff --git a/src/mightypork/utils/logging/monitors/LogMonitor.java b/src/mightypork/utils/logging/monitors/LogMonitor.java new file mode 100644 index 0000000..aaef36e --- /dev/null +++ b/src/mightypork/utils/logging/monitors/LogMonitor.java @@ -0,0 +1,36 @@ +package mightypork.utils.logging.monitors; + + +import java.util.logging.Level; + + +public abstract class LogMonitor { + + private boolean enabled = true; + private Level accepted = Level.ALL; + + + public void onMessageLogged(Level level, String message) + { + if (!enabled) return; + if (accepted.intValue() > level.intValue()) return; + + logMessage(level, message); + } + + + protected abstract void logMessage(Level level, String message); + + + public void setLevel(Level level) + { + this.accepted = level; + } + + + public void enable(boolean flag) + { + this.enabled = flag; + } + +} diff --git a/src/mightypork/utils/logging/monitors/LogMonitorStdout.java b/src/mightypork/utils/logging/monitors/LogMonitorStdout.java new file mode 100644 index 0000000..964f6b5 --- /dev/null +++ b/src/mightypork/utils/logging/monitors/LogMonitorStdout.java @@ -0,0 +1,19 @@ +package mightypork.utils.logging.monitors; + + +import java.util.logging.Level; + + +public class LogMonitorStdout extends LogMonitor { + + @Override + protected void logMessage(Level level, String message) + { + if (level == Level.SEVERE || level == Level.WARNING) { + System.err.print(message); + } else { + System.out.print(message); + } + } + +} diff --git a/src/mightypork/utils/logging/writers/ArchivingLog.java b/src/mightypork/utils/logging/writers/ArchivingLog.java new file mode 100644 index 0000000..29abda9 --- /dev/null +++ b/src/mightypork/utils/logging/writers/ArchivingLog.java @@ -0,0 +1,130 @@ +package mightypork.utils.logging.writers; + + +import java.io.File; +import java.io.FileFilter; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import mightypork.utils.files.FileUtils; +import mightypork.utils.string.StringUtil; + + +/** + * Logger that cleans directory & archives old logs + * + * @author Ondřej Hruška (MightyPork) + * @copy (c) 2014 + */ +public class ArchivingLog extends SimpleLog { + + /** Number of old logs to keep */ + private final int logs_to_keep; + + + /** + * Log + * + * @param name log name + * @param file log file (in log directory) + * @param oldLogCount number of old log files to keep: -1 all, 0 none. + */ + public ArchivingLog(String name, File file, int oldLogCount) + { + super(name, file); + this.logs_to_keep = oldLogCount; + } + + + /** + * Log, not keeping 5 last log files (default); + * + * @param name log name + * @param file log file (in log directory) + */ + public ArchivingLog(String name, File file) + { + super(name, file); + this.logs_to_keep = 5; + } + + + @Override + public void init() + { + cleanLoggingDirectory(); + + super.init(); + } + + + private void cleanLoggingDirectory() + { + if (logs_to_keep == 0) return; // overwrite + + final File log_file = getFile(); + final File log_dir = log_file.getParentFile(); + final String fname = FileUtils.getBasename(log_file.toString()); + + // move old file + for (final File f : FileUtils.listDirectory(log_dir)) { + if (!f.isFile()) continue; + if (f.equals(getFile())) { + + final Date d = new Date(f.lastModified()); + final String fbase = fname + '_' + (new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss")).format(d); + final String suff = "." + getSuffix(); + String cntStr = ""; + File f2; + + for (int cnt = 0; (f2 = new File(log_dir, fbase + cntStr + suff)).exists(); cntStr = "_" + (++cnt)) {} + + if (!f.renameTo(f2)) throw new RuntimeException("Could not move log file."); + } + } + + if (logs_to_keep == -1) return; // keep all + + final List 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() { + + @Override + public int compare(File o1, File o2) + { + return o1.getName().compareTo(o2.getName()); + } + }); + + // playing with fireee + for (int i = 0; i < oldLogs.size() - logs_to_keep; i++) { + if (!oldLogs.get(i).delete()) { + throw new RuntimeException("Could not delete old log file."); + } + } + } + + + /** + * @return log filename suffix + */ + private String getSuffix() + { + return StringUtil.fromLastChar(getFile().toString(), '.'); + } +} diff --git a/src/mightypork/utils/logging/writers/LogWriter.java b/src/mightypork/utils/logging/writers/LogWriter.java new file mode 100644 index 0000000..fc52f5b --- /dev/null +++ b/src/mightypork/utils/logging/writers/LogWriter.java @@ -0,0 +1,72 @@ +package mightypork.utils.logging.writers; + + +import java.util.logging.Level; + +import mightypork.utils.logging.monitors.LogMonitor; + + +/** + * Log interface + * + * @author Ondřej Hruška (MightyPork) + */ +public interface LogWriter { + + /** + * Prepare logs for logging + */ + void init(); + + + /** + * Add log monitor + * + * @param mon monitor + */ + void addMonitor(LogMonitor mon); + + + /** + * Remove a monitor + * + * @param removed monitor to remove + */ + void removeMonitor(LogMonitor removed); + + + /** + * Set logging level + * + * @param level + */ + void setLevel(Level level); + + + /** + * Enable logging. + * + * @param flag do enable logging + */ + void enable(boolean flag); + + + /** + * Log a message + * + * @param level message level + * @param msg message text + */ + void log(Level level, String msg); + + + /** + * Log a message + * + * @param level message level + * @param msg message text + * @param t thrown exception + */ + void log(Level level, String msg, Throwable t); + +} diff --git a/src/mightypork/utils/logging/writers/SimpleLog.java b/src/mightypork/utils/logging/writers/SimpleLog.java new file mode 100644 index 0000000..8747c5b --- /dev/null +++ b/src/mightypork/utils/logging/writers/SimpleLog.java @@ -0,0 +1,166 @@ +package mightypork.utils.logging.writers; + + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.logging.FileHandler; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import mightypork.utils.logging.Log; +import mightypork.utils.logging.monitors.LogMonitor; + + +/** + * Basic logger + * + * @author Ondřej Hruška (MightyPork) + */ +public class SimpleLog implements LogWriter { + + /** + * Log file formatter. + */ + class LogFormatter extends Formatter { + + @Override + public String format(LogRecord record) + { + return Log.formatMessage(record.getLevel(), record.getMessage(), record.getThrown(), started_ms); + } + } + + /** Log file */ + private final File file; + + /** Log name */ + private final String name; + + /** Logger instance. */ + private Logger logger; + + private boolean enabled = true; + private final HashSet monitors = new HashSet<>(); + private final long started_ms; + + + public SimpleLog(String name, File file) + { + this.name = name; + this.file = file; + this.started_ms = System.currentTimeMillis(); + } + + + @Override + public void init() + { + logger = Logger.getLogger(getName()); + + FileHandler handler = null; + try { + handler = new FileHandler(getFile().getPath()); + } catch (final Throwable t) { + throw new RuntimeException("Failed to init log.", t); + } + + handler.setFormatter(new LogFormatter()); + logger.addHandler(handler); + logger.setUseParentHandlers(false); + logger.setLevel(Level.ALL); + + printHeader(); + } + + + protected void printHeader() + { + final String stamp = (new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")).format(new Date()); + log(Level.INFO, "Logger \"" + getName() + "\" initialized.\n" + stamp); + } + + + /** + * Add log monitor + * + * @param mon monitor + */ + @Override + public synchronized void addMonitor(LogMonitor mon) + { + monitors.add(mon); + } + + + /** + * Remove a monitor + * + * @param removed monitor to remove + */ + @Override + public synchronized void removeMonitor(LogMonitor removed) + { + monitors.remove(removed); + } + + + @Override + public void setLevel(Level level) + { + logger.setLevel(level); + } + + + @Override + public void enable(boolean flag) + { + enabled = flag; + } + + + public File getFile() + { + return file; + } + + + public String getName() + { + return name; + } + + + @Override + public void log(Level level, String msg) + { + if (enabled) { + logger.log(level, msg); + + final String fmt = Log.formatMessage(level, msg, null, started_ms); + + for (final LogMonitor mon : monitors) { + mon.onMessageLogged(level, fmt); + } + } + } + + + @Override + public void log(Level level, String msg, Throwable t) + { + if (enabled) { + logger.log(level, msg, t); + + final String fmt = Log.formatMessage(level, msg, t, started_ms); + + for (final LogMonitor mon : monitors) { + mon.onMessageLogged(level, fmt); + } + } + } + +} diff --git a/src/mightypork/utils/math/Calc.java b/src/mightypork/utils/math/Calc.java new file mode 100644 index 0000000..9c50bfe --- /dev/null +++ b/src/mightypork/utils/math/Calc.java @@ -0,0 +1,401 @@ +package mightypork.utils.math; + + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import mightypork.utils.math.angles.Deg; +import mightypork.utils.math.angles.Rad; +import mightypork.utils.math.animation.Easing; +import mightypork.utils.math.constraints.vect.Vect; + + +/** + * Math utils + * + * @author Ondřej Hruška (MightyPork) + */ +public final class Calc { + + private Calc() + { + // not instantiable + } + + /** Square root of two */ + public static final double SQ2 = 1.41421356237; + + + /** + * Get distance from 2D line to 2D point [X,Y] + * + * @param lineDirVec line directional vector + * @param linePoint point of line + * @param point point coordinate + * @return distance + */ + public static double linePointDist(Vect lineDirVec, Vect linePoint, Vect point) + { + // line point L[lx,ly] + final double lx = linePoint.x(); + final double ly = linePoint.y(); + + // line equation ax+by+c=0 + final double a = -lineDirVec.y(); + final double b = lineDirVec.x(); + final double c = -a * lx - b * ly; + + // checked point P[x,y] + final double x = point.x(); + final double y = point.y(); + + // distance + return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b); + } + + public static final Random rand = new Random(); + + + public static double sphereSurface(double radius) + { + return 4D * Math.PI * square(radius); + } + + + public static double sphereVolume(double radius) + { + return (4D / 3D) * Math.PI * cube(radius); + } + + + public static double sphereRadius(double volume) + { + return Math.cbrt((3D * volume) / (4 * Math.PI)); + } + + + public static double circleSurface(double radius) + { + return Math.PI * square(radius); + } + + + public static double circleRadius(double surface) + { + return Math.sqrt(surface / Math.PI); + } + + + /** + * Safe equals that works with nulls + * + * @param a + * @param b + * @return are equal + */ + public static boolean areEqual(Object a, Object b) + { + return a == null ? b == null : a.equals(b); + } + + + /** + * Clamp integer + * + * @param number + * @param min + * @param max + * @return result + */ + public static int clamp(int number, int min, int max) + { + return number < min ? min : number > max ? max : number; + } + + + /** + * Clamp double + * + * @param number + * @param min + * @param max + * @return result + */ + public static double clamp(double number, double min, double max) + { + return number < min ? min : number > max ? max : number; + } + + + public static boolean isInRange(double number, double left, double right) + { + return number >= left && number <= right; + } + + + /** + * Get number from A to B at delta time (A -> B) + * + * @param from + * @param to + * @param elapsed progress ratio 0..1 + * @param easing + * @return result + */ + public static double interpolate(double from, double to, double elapsed, Easing easing) + { + return from + (to - from) * easing.get(elapsed); + } + + + /** + * Get angle [degrees] from A to B at delta time (tween A to B) + * + * @param from + * @param to + * @param elapsed progress ratio 0..1 + * @param easing + * @return result + */ + public static double interpolateDeg(double from, double to, double elapsed, Easing easing) + { + return Deg.norm(from - Deg.delta(to, from) * easing.get(elapsed)); + } + + + /** + * Get angle [radians] from A to B at delta time (tween A to B) + * + * @param from + * @param to + * @param elapsed progress ratio 0..1 + * @param easing + * @return result + */ + public static double interpolateRad(double from, double to, double elapsed, Easing easing) + { + return Rad.norm(from - Rad.delta(to, from) * easing.get(elapsed)); + } + + + public static double max(double... numbers) + { + double highest = numbers[0]; + for (final double num : numbers) { + if (num > highest) highest = num; + } + return highest; + } + + + public static int max(int... numbers) + { + int highest = numbers[0]; + for (final int num : numbers) { + if (num > highest) highest = num; + } + return highest; + } + + + public static double min(double... numbers) + { + double lowest = numbers[0]; + for (final double num : numbers) { + if (num < lowest) lowest = num; + } + return lowest; + } + + + public static int min(int... numbers) + { + int lowest = numbers[0]; + for (final int num : numbers) { + if (num < lowest) lowest = num; + } + return lowest; + } + + + /** + * Split comma separated list of integers. + * + * @param list String containing the list. + * @param delimiter delimiter character + * @return array of integers or null. + */ + public static List parseIntList(String list, char delimiter) + { + if (list == null) { + return null; + } + + final String[] parts = list.split(Character.toString(delimiter)); + + final ArrayList intList = new ArrayList<>(); + + for (final String part : parts) { + try { + intList.add(Integer.parseInt(part.trim())); + } catch (final NumberFormatException e) {} + } + + return intList; + } + + + /** + * Pick random element from a given list. + * + * @param list list of choices + * @return picked element + */ + public static T pick(List list) + { + return pick(rand, list); + } + + + /** + * Pick random element from a given list. + * + * @param rand RNG + * @param list list of choices + * @return picked element + */ + public static T pick(Random rand, List list) + { + if (list.size() == 0) return null; + return list.get(rand.nextInt(list.size())); + } + + + /** + * Take a square + * + * @param a value + * @return value squared + */ + public static double square(double a) + { + return a * a; + } + + + /** + * Take a cube + * + * @param a value + * @return value cubed + */ + public static double cube(double a) + { + return a * a * a; + } + + + /** + * @param d number + * @return fractional part + */ + public static double frag(double d) + { + return d - Math.floor(d); + } + + + /** + * Make sure value is within array length. + * + * @param index tested index + * @param length array length + * @throws IndexOutOfBoundsException if the index is not in range. + */ + public static void assertValidIndex(int index, int length) + { + if (!isInRange(index, 0, length - 1)) { + throw new IndexOutOfBoundsException(); + } + } + + + /** + * Get distance of two coordinates in 2D plane + * + * @param x1 first coordinate X + * @param y1 first coordinate y + * @param x2 second coordinate X + * @param y2 second coordinate Y + * @return distance + */ + public static double dist(double x1, double y1, double x2, double y2) + { + return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); + } + + + public static int randInt(Random rand, int low, int high) + { + final int range = Math.abs(high - low) + 1; + return low + rand.nextInt(range); + } + + + public static int randInt(int low, int high) + { + return randInt(rand, low, high); + } + + + /** + * Get ordinal version of numbers (1 = 1st, 5 = 5th etc.) + * + * @param number number + * @return ordinal, string + */ + public static String ordinal(int number) + { + if (number % 100 < 4 || number % 100 > 13) { + if (number % 10 == 1) return number + "st"; + if (number % 10 == 2) return number + "nd"; + if (number % 10 == 3) return number + "rd"; + } + return number + "th"; + } + + + /** + * Format number with thousands separated. + * + * @param number number + * @param thousandSep + * @return string + */ + public static String separateThousands(long number, char thousandSep) + { + final String num = String.valueOf(number); + final String dot = String.valueOf(thousandSep); + String out = ""; + + int cnt = 1; + for (int i = num.length() - 1; i >= 0; i--) { + out = num.charAt(i) + out; + if (cnt % 3 == 0 && i > 0) out = dot + out; + cnt++; + } + + return out; + } + + + public static int countBits(byte b) + { + int c = 0; + for (int i = 0; i < 8; i++) { + c += (b >> i) & 1; + } + return c; + } +} diff --git a/src/mightypork/utils/math/Polar.java b/src/mightypork/utils/math/Polar.java new file mode 100644 index 0000000..8460fb8 --- /dev/null +++ b/src/mightypork/utils/math/Polar.java @@ -0,0 +1,192 @@ +package mightypork.utils.math; + + +import mightypork.utils.math.constraints.vect.Vect; + + +/** + * Polar coordinate + * + * @author Ondřej Hruška (MightyPork) + */ +public class Polar { + + /** angle in radians */ + private double angle = 0; + + /** distance in units */ + private double radius = 0; + + private Vect coord = null; + + + /** + * Create a polar + * + * @param angle angle in RAD + * @param distance distance from origin + */ + public Polar(double angle, double distance) + { + this(angle, false, distance); + } + + + /** + * Create a polar + * + * @param angle angle + * @param deg angle is in DEG + * @param distance radius + */ + public Polar(double angle, boolean deg, double distance) + { + this.radius = distance; + this.angle = deg ? Math.toRadians(angle) : angle; + } + + + /** + * @return angle in RAD + */ + public double getAngle() + { + return angle; + } + + + /** + * @return angle in DEG + */ + public double getAngleDeg() + { + return Math.toDegrees(angle); + } + + + /** + * @param angle angle in RAD + */ + public void setAngle(double angle) + { + this.angle = angle; + } + + + /** + * @param angle angle in DEG + */ + public void setAngleDeg(double angle) + { + this.angle = Math.toRadians(angle); + } + + + /** + * @return radius + */ + public double getRadius() + { + return radius; + } + + + /** + * @param r radius + */ + public void setRadius(double r) + { + this.radius = r; + } + + + /** + * Make polar from coord + * + * @param coord coord + * @return polar + */ + public static Polar fromCoord(Vect coord) + { + return Polar.fromCoord(coord.x(), coord.y()); + + } + + + /** + * Make polar from coords + * + * @param x x coord + * @param y y coord + * @return polar + */ + public static Polar fromCoord(double x, double y) + { + final double a = Math.atan2(y, x); + final double r = Math.sqrt(x * x + y * y); + + return new Polar(a, r); + } + + + /** + * Get coord from polar + * + * @return coord + */ + public Vect toCoord() + { + // lazy init + if (coord == null) coord = new Vect() { + + @Override + public double x() + { + return radius * Math.cos(angle); + } + + + @Override + public double y() + { + return radius * Math.sin(angle); + } + }; + + return coord; + } + + + @Override + public String toString() + { + return "Polar(" + angle + "rad, " + radius + ")"; + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(angle); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(radius); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Polar)) return false; + final Polar other = (Polar) obj; + if (Double.doubleToLongBits(angle) != Double.doubleToLongBits(other.angle)) return false; + if (Double.doubleToLongBits(radius) != Double.doubleToLongBits(other.radius)) return false; + return true; + } +} diff --git a/src/mightypork/utils/math/Range.java b/src/mightypork/utils/math/Range.java new file mode 100644 index 0000000..3ca15cb --- /dev/null +++ b/src/mightypork/utils/math/Range.java @@ -0,0 +1,246 @@ +package mightypork.utils.math; + + +import java.util.Random; + + +/** + * Numeric range, able to generate random numbers and give min/max values. + * + * @author Ondřej Hruška (MightyPork) + */ +public class Range { + + public static Range make(double low, double high) + { + return new Range(low, high); + } + + private double min = 0; + private double max = 1; + + + /** + * Implicit range constructor 0-1 + */ + public Range() + { + } + + + /** + * Create new range + * + * @param min min number + * @param max max number + */ + public Range(double min, double max) + { + this.min = min; + this.max = max; + norm(); + } + + + /** + * Create new range + * + * @param minmax min = max number + */ + public Range(double minmax) + { + this.min = minmax; + this.max = minmax; + } + + + public static Range fromString(String string) + { + try { + String s = string.trim(); + + // drop whitespace + s = s.replaceAll("\\s", ""); + + // drop brackets + s = s.replaceAll("[\\(\\[\\{\\)\\]\\}]", ""); + + // norm separators + s = s.replaceAll("[:;]", "|").replace("..", "|"); + + // norm floating point + s = s.replaceAll("[,]", "."); + + // dash to pipe, if not a minus sign + s = s.replaceAll("([0-9])\\s?[\\-]", "$1|"); + + final String[] parts = s.split("[|]"); + + if (parts.length >= 1) { + + final double low = Double.parseDouble(parts[0].trim()); + + if (parts.length == 2) { + final double high = Double.parseDouble(parts[1].trim()); + return Range.make(low, high); + } + + return Range.make(low, low); + } + } catch (final RuntimeException e) { + // ignore + } + return null; + } + + + @Override + public String toString() + { + return String.format("(%f : %f)", getMin(), getMax()); + } + + + /** + * Make sure min is <= max + */ + private void norm() + { + if (min > max) { + final double t = min; + min = max; + max = t; + } + } + + + /** + * Get random integer from range + * + * @return random int + */ + public int randInt() + { + return randInt(Calc.rand); + } + + + /** + * Get random double from this range + * + * @return random double + */ + public double randDouble() + { + return randDouble(Calc.rand); + } + + + /** + * Get random integer from range + * + * @param rand RNG + * @return random int + */ + public int randInt(Random rand) + { + return Calc.randInt(rand, (int) Math.round(min), (int) Math.round(min)); + } + + + /** + * Get random double from this range + * + * @param rand RNG + * @return random double + */ + public double randDouble(Random rand) + { + return min + rand.nextDouble() * (max - min); + } + + + /** + * Get min + * + * @return min number + */ + public double getMin() + { + return min; + } + + + /** + * Get max + * + * @return max number + */ + public double getMax() + { + return max; + } + + + /** + * Set min + * + * @param min min value + */ + public void setMin(double min) + { + this.min = min; + norm(); + } + + + /** + * Set max + * + * @param max max value + */ + public void setMax(double max) + { + this.max = max; + norm(); + } + + + /** + * Get identical copy + * + * @return copy + */ + public Range copy() + { + return new Range(min, max); + } + + + /** + * Set to value of other range + * + * @param other copied range + */ + public void setTo(Range other) + { + if (other == null) return; + min = other.min; + max = other.max; + norm(); + } + + + /** + * Set to min-max values + * + * @param min min value + * @param max max value + */ + public void setTo(double min, double max) + { + this.min = min; + this.max = max; + norm(); + } +} diff --git a/src/mightypork/utils/math/algo/Coord.java b/src/mightypork/utils/math/algo/Coord.java new file mode 100644 index 0000000..eb4dcab --- /dev/null +++ b/src/mightypork/utils/math/algo/Coord.java @@ -0,0 +1,168 @@ +package mightypork.utils.math.algo; + + +import mightypork.utils.annotations.FactoryMethod; +import mightypork.utils.math.Calc; +import mightypork.utils.math.constraints.vect.Vect; +import mightypork.utils.math.constraints.vect.VectConst; + + +/** + * Very simple integer coordinate + * + * @author Ondřej Hruška (MightyPork) + */ +public class Coord { + + public int x; + public int y; + + + @FactoryMethod + public static Coord make(int x, int y) + { + return new Coord(x, y); + } + + + @FactoryMethod + public static Coord make(Coord other) + { + return new Coord(other); + } + + + @FactoryMethod + public static Coord zero() + { + return make(0, 0); + } + + + public Coord() + { + // for ion + } + + + public Coord(int x, int y) + { + super(); + this.x = x; + this.y = y; + } + + + public Coord(Coord other) + { + this.x = other.x; + this.y = other.y; + } + + + public Coord add(int addX, int addY) + { + return new Coord(x + addX, y + addY); + } + + + /** + * Add other coord in a copy + * + * @param added + * @return changed copy + */ + public Coord add(Coord added) + { + return add(added.x, added.y); + } + + + public Coord add(Move added) + { + return add(added.x(), added.y()); + } + + + public Coord copy() + { + return make(this); + } + + + public void setTo(int x, int y) + { + this.x = x; + this.y = y; + } + + + public void setTo(Coord pos) + { + setTo(pos.x, pos.y); + } + + + /** + * Check if coord is in a range (inclusive) + * + * @param x0 range min x + * @param y0 range min y + * @param x1 range max x + * @param y1 range max y + * @return is inside + */ + public boolean isInRange(int x0, int y0, int x1, int y1) + { + return !(x < x0 || x > x1 || y < y0 || y > y1); + } + + + public double dist(Coord coord) + { + return Calc.dist(x, y, coord.x, coord.y); + } + + + public VectConst toVect() + { + return Vect.make(x, y); + } + + + @Override + public String toString() + { + return "Coord(" + x + "," + y + ")"; + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + x; + result = prime * result + y; + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Coord)) return false; + final Coord other = (Coord) obj; + if (x != other.x) return false; + if (y != other.y) return false; + return true; + } + + + public static Coord fromVect(Vect vect) + { + return make((int) Math.floor(vect.x()), (int) Math.floor(vect.y())); + } +} diff --git a/src/mightypork/utils/math/algo/Move.java b/src/mightypork/utils/math/algo/Move.java new file mode 100644 index 0000000..b012780 --- /dev/null +++ b/src/mightypork/utils/math/algo/Move.java @@ -0,0 +1,80 @@ +package mightypork.utils.math.algo; + + +/** + * Path step.
+ * Must be binary in order to be saveable in lists. + * + * @author Ondřej Hruška (MightyPork) + */ +public class Move { + + public static final Move NORTH = new Move(0, -1); + public static final Move SOUTH = new Move(0, 1); + public static final Move EAST = new Move(1, 0); + public static final Move WEST = new Move(-1, 0); + public static final Move NONE = new Move(0, 0); + + + public static Move make(int x, int y) + { + x = x < 0 ? -1 : x > 0 ? 1 : 0; + y = y < 0 ? -1 : y > 0 ? 1 : 0; + + if (y == -1 && x == 0) return NORTH; + if (y == 1 && x == 0) return SOUTH; + if (x == -1 && y == 0) return WEST; + if (x == 1 && y == 0) return EAST; + if (x == 0 && y == 0) return NONE; + + return new Move(x, y); + } + + private byte x; + private byte y; + + + public Move() + { + // for ion + } + + + public Move(int x, int y) + { + this.x = (byte) (x < 0 ? -1 : x > 0 ? 1 : 0); + this.y = (byte) (y < 0 ? -1 : y > 0 ? 1 : 0); + } + + + public int x() + { + return x; + } + + + public int y() + { + return y; + } + + + public Coord toCoord() + { + return Coord.make(x, y); + } + + + @Override + public String toString() + { + return "(" + x + " ; " + y + ")"; + } + + + protected void setTo(byte x, byte y) + { + this.x = x; + this.y = y; + } +} diff --git a/src/mightypork/utils/math/algo/Moves.java b/src/mightypork/utils/math/algo/Moves.java new file mode 100644 index 0000000..7c466ad --- /dev/null +++ b/src/mightypork/utils/math/algo/Moves.java @@ -0,0 +1,95 @@ +package mightypork.utils.math.algo; + + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import mightypork.utils.math.Calc; + + +/** + * Move lists, bit masks and other utilities + * + * @author Ondřej Hruška (MightyPork) + */ +public class Moves { + + public static final byte BIT_NW = (byte) 0b10000000; + public static final byte BIT_N = (byte) 0b01000000; + public static final byte BIT_NE = (byte) 0b00100000; + public static final byte BIT_E = (byte) 0b00010000; + public static final byte BIT_SE = (byte) 0b00001000; + public static final byte BIT_S = (byte) 0b00000100; + public static final byte BIT_SW = (byte) 0b00000010; + public static final byte BIT_W = (byte) 0b00000001; + + public static final byte BITS_CARDINAL = BIT_N | BIT_S | BIT_E | BIT_W; + public static final byte BITS_DIAGONAL = BIT_NE | BIT_NW | BIT_SE | BIT_SW; + + public static final byte BITS_NW_CORNER = BIT_W | BIT_NW | BIT_N; + public static final byte BITS_NE_CORNER = BIT_E | BIT_NE | BIT_N; + public static final byte BITS_SW_CORNER = BIT_W | BIT_SW | BIT_S; + public static final byte BITS_SE_CORNER = BIT_E | BIT_SE | BIT_S; + + public static final Move NW = Move.make(-1, -1); + public static final Move N = Move.make(0, -1); + public static final Move NE = Move.make(1, -1); + public static final Move E = Move.make(1, 0); + public static final Move SE = Move.make(1, 1); + public static final Move S = Move.make(0, 1); + public static final Move SW = Move.make(-1, 1); + public static final Move W = Move.make(-1, 0); + + //@formatter:off + /** All sides, in the order of bits. */ + public final static List ALL_SIDES = Collections.unmodifiableList(Arrays.asList( + NW, + N, + NE, + E, + SE, + S, + SW, + W + )); + + public final static List CARDINAL_SIDES = Collections.unmodifiableList(Arrays.asList( + N, + E, + S, + W + )); + + //@formatter:on + + /** + * Get element from all sides + * + * @param i side index + * @return the side coord + */ + public static Move getSide(int i) + { + return ALL_SIDES.get(i); + } + + + public static byte getBit(int i) + { + return (byte) (1 << (7 - i)); + } + + + public static Move randomCardinal() + { + return Calc.pick(CARDINAL_SIDES); + } + + + public static Move randomCardinal(Random rand) + { + return Calc.pick(rand, CARDINAL_SIDES); + } +} diff --git a/src/mightypork/utils/math/algo/floodfill/FloodFill.java b/src/mightypork/utils/math/algo/floodfill/FloodFill.java new file mode 100644 index 0000000..a9a8198 --- /dev/null +++ b/src/mightypork/utils/math/algo/floodfill/FloodFill.java @@ -0,0 +1,84 @@ +package mightypork.utils.math.algo.floodfill; + + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import mightypork.utils.math.algo.Coord; +import mightypork.utils.math.algo.Move; + + +public abstract class FloodFill { + + public abstract boolean canEnter(Coord pos); + + + public abstract boolean canSpreadFrom(Coord pos); + + + public abstract List getSpreadSides(); + + + /** + * Get the max distance filled form start point. Use -1 for unlimited range. + * + * @return max distance + */ + public abstract double getMaxDistance(); + + + /** + * @return true if start should be spread no matter what + */ + public abstract boolean forceSpreadStart(); + + + /** + * Fill an area + * + * @param start start point + * @param foundNodes collection to put filled coords in + * @return true if fill was successful; false if max range was reached. + */ + public final boolean fill(Coord start, Collection foundNodes) + { + final Queue activeNodes = new LinkedList<>(); + + final double maxDist = getMaxDistance(); + + activeNodes.add(start); + + boolean forceSpreadNext = forceSpreadStart(); + + boolean limitReached = false; + + while (!activeNodes.isEmpty()) { + final Coord current = activeNodes.poll(); + foundNodes.add(current); + + if (!canSpreadFrom(current) && !forceSpreadNext) continue; + + forceSpreadNext = false; + + for (final Move spr : getSpreadSides()) { + final Coord next = current.add(spr); + if (activeNodes.contains(next) || foundNodes.contains(next)) continue; + + if (next.dist(start) > maxDist) { + limitReached = true; + continue; + } + + if (canEnter(next)) { + activeNodes.add(next); + } else { + foundNodes.add(next); + } + } + } + + return !limitReached; + } +} diff --git a/src/mightypork/utils/math/algo/pathfinding/Heuristic.java b/src/mightypork/utils/math/algo/pathfinding/Heuristic.java new file mode 100644 index 0000000..ed1d913 --- /dev/null +++ b/src/mightypork/utils/math/algo/pathfinding/Heuristic.java @@ -0,0 +1,17 @@ +package mightypork.utils.math.algo.pathfinding; + + +import mightypork.utils.math.algo.Coord; + + +public abstract class Heuristic { + + /** + * Get tile cost (estimate of how many tiles remain to the target) + * + * @param pos current pos + * @param target target pos + * @return estimated number of tiles + */ + public abstract double getCost(Coord pos, Coord target); +} diff --git a/src/mightypork/utils/math/algo/pathfinding/PathFinder.java b/src/mightypork/utils/math/algo/pathfinding/PathFinder.java new file mode 100644 index 0000000..8da887d --- /dev/null +++ b/src/mightypork/utils/math/algo/pathfinding/PathFinder.java @@ -0,0 +1,250 @@ +package mightypork.utils.math.algo.pathfinding; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +import mightypork.utils.math.algo.Coord; +import mightypork.utils.math.algo.Move; +import mightypork.utils.math.algo.pathfinding.heuristics.DiagonalHeuristic; +import mightypork.utils.math.algo.pathfinding.heuristics.ManhattanHeuristic; + + +/** + * A* pathfinder + * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class PathFinder { + + private static final FComparator F_COMPARATOR = new FComparator(); + + public static final Heuristic CORNER_HEURISTIC = new ManhattanHeuristic(); + public static final Heuristic DIAGONAL_HEURISTIC = new DiagonalHeuristic(); + + private boolean ignoreStart; + private boolean ignoreEnd; + + + public List findPathRelative(Coord start, Coord end) + { + return findPathRelative(start, end, ignoreStart, ignoreEnd); + } + + + public List findPathRelative(Coord start, Coord end, boolean ignoreStart, boolean ignoreEnd) + { + final List path = findPath(start, end, ignoreStart, ignoreEnd); + + if (path == null) return null; + + final List out = new ArrayList<>(); + + final Coord current = start.copy(); + for (final Coord c : path) { + if (c.equals(current)) continue; + out.add(Move.make(c.x - current.x, c.y - current.y)); + current.x = c.x; + current.y = c.y; + } + + return out; + } + + + public List findPath(Coord start, Coord end) + { + return findPath(start, end, ignoreStart, ignoreEnd); + } + + + public List findPath(Coord start, Coord end, boolean ignoreStart, boolean ignoreEnd) + { + final LinkedList open = new LinkedList<>(); + final LinkedList closed = new LinkedList<>(); + + final Heuristic heuristic = getHeuristic(); + + // add first node + { + final Node n = new Node(start); + n.h_cost = (int) (heuristic.getCost(start, end) * getMinCost()); + n.g_cost = 0; + open.add(n); + } + + Node current = null; + + while (true) { + current = open.poll(); + + if (current == null) { + break; + } + + closed.add(current); + + if (current.pos.equals(end)) { + break; + } + + for (final Move go : getWalkSides()) { + + final Coord c = current.pos.add(go); + if (!isAccessible(c) && !(c.equals(end) && ignoreEnd) && !(c.equals(start) && ignoreStart)) continue; + final Node a = new Node(c); + a.g_cost = current.g_cost + getCost(c, a.pos); + a.h_cost = (int) (heuristic.getCost(a.pos, end) * getMinCost()); + a.parent = current; + + + if (!closed.contains(a)) { + + if (open.contains(a)) { + + boolean needSort = false; + + // find where it is + for (final Node n : open) { + if (n.pos.equals(a.pos)) { // found it + if (n.g_cost > a.g_cost) { + n.parent = current; + n.g_cost = a.g_cost; + needSort = true; + } + break; + } + } + + if (needSort) Collections.sort(open, F_COMPARATOR); + + } else { + open.add(a); + } + } + } + + } + + if (current == null) { + return null; // no path found + } + + final LinkedList path = new LinkedList<>(); + + // extract path elements + while (current != null) { + path.addFirst(current.pos); + current = current.parent; + } + + return path; + } + + private static class Node { + + Coord pos; + int g_cost; // to get there + int h_cost; // to target + Node parent; + + + public Node(Coord pos) + { + this.pos = pos; + } + + + int fCost() + { + return g_cost + h_cost; + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((pos == null) ? 0 : pos.hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Node)) return false; + final Node other = (Node) obj; + if (pos == null) { + if (other.pos != null) return false; + } else if (!pos.equals(other.pos)) return false; + return true; + } + + + @Override + public String toString() + { + return "N " + pos + ", G =" + g_cost + ", H = " + h_cost; + } + } + + private static class FComparator implements Comparator { + + @Override + public int compare(Node n1, Node n2) + { + return n1.fCost() - n2.fCost(); + } + } + + + public void setIgnoreEnd(boolean ignoreEnd) + { + this.ignoreEnd = ignoreEnd; + } + + + public void setIgnoreStart(boolean ignoreStart) + { + this.ignoreStart = ignoreStart; + } + + + /** + * @return used heuristic + */ + protected abstract Heuristic getHeuristic(); + + + protected abstract List getWalkSides(); + + + /** + * @param pos tile pos + * @return true if the tile is walkable + */ + public abstract boolean isAccessible(Coord pos); + + + /** + * Cost of walking onto a tile. It's useful to use ie. 10 for basic step. + * + * @param from last tile + * @param to current tile + * @return cost + */ + protected abstract int getCost(Coord from, Coord to); + + + /** + * @return lowest cost. Used to multiply heuristics. + */ + protected abstract int getMinCost(); +} diff --git a/src/mightypork/utils/math/algo/pathfinding/PathFinderProxy.java b/src/mightypork/utils/math/algo/pathfinding/PathFinderProxy.java new file mode 100644 index 0000000..006e045 --- /dev/null +++ b/src/mightypork/utils/math/algo/pathfinding/PathFinderProxy.java @@ -0,0 +1,61 @@ +package mightypork.utils.math.algo.pathfinding; + + +import java.util.List; + +import mightypork.utils.math.algo.Coord; +import mightypork.utils.math.algo.Move; + + +/** + * Pathfinder proxy. Can be used to override individual methods but keep the + * rest as is. + * + * @author Ondřej Hruška (MightyPork) + */ +public class PathFinderProxy extends PathFinder { + + private final PathFinder source; + + + public PathFinderProxy(PathFinder other) + { + this.source = other; + } + + + @Override + public boolean isAccessible(Coord pos) + { + return source.isAccessible(pos); + } + + + @Override + public int getCost(Coord from, Coord to) + { + return source.getCost(from, to); + } + + + @Override + public int getMinCost() + { + return source.getMinCost(); + } + + + @Override + protected Heuristic getHeuristic() + { + return source.getHeuristic(); + } + + + @Override + protected List getWalkSides() + { + return source.getWalkSides(); + } + +} diff --git a/src/mightypork/utils/math/algo/pathfinding/heuristics/DiagonalHeuristic.java b/src/mightypork/utils/math/algo/pathfinding/heuristics/DiagonalHeuristic.java new file mode 100644 index 0000000..4820eac --- /dev/null +++ b/src/mightypork/utils/math/algo/pathfinding/heuristics/DiagonalHeuristic.java @@ -0,0 +1,15 @@ +package mightypork.utils.math.algo.pathfinding.heuristics; + + +import mightypork.utils.math.algo.Coord; +import mightypork.utils.math.algo.pathfinding.Heuristic; + + +public class DiagonalHeuristic extends Heuristic { + + @Override + public double getCost(Coord pos, Coord target) + { + return Math.sqrt(Math.pow(pos.x - target.x, 2) + Math.pow(pos.y - target.y, 2)); + } +} diff --git a/src/mightypork/utils/math/algo/pathfinding/heuristics/ManhattanHeuristic.java b/src/mightypork/utils/math/algo/pathfinding/heuristics/ManhattanHeuristic.java new file mode 100644 index 0000000..9725794 --- /dev/null +++ b/src/mightypork/utils/math/algo/pathfinding/heuristics/ManhattanHeuristic.java @@ -0,0 +1,15 @@ +package mightypork.utils.math.algo.pathfinding.heuristics; + + +import mightypork.utils.math.algo.Coord; +import mightypork.utils.math.algo.pathfinding.Heuristic; + + +public class ManhattanHeuristic extends Heuristic { + + @Override + public double getCost(Coord pos, Coord target) + { + return Math.abs(target.x - pos.x) + Math.abs(target.y - pos.y); + } +} diff --git a/src/mightypork/utils/math/angles/Angles.java b/src/mightypork/utils/math/angles/Angles.java new file mode 100644 index 0000000..20c1cec --- /dev/null +++ b/src/mightypork/utils/math/angles/Angles.java @@ -0,0 +1,59 @@ +package mightypork.utils.math.angles; + + +/** + * Common angles functionality + * + * @author Ondřej Hruška (MightyPork) + */ +class Angles { + + /** + * Delta of two angles (positive or negative - positive is CCW) + * + * @param alpha first angle + * @param beta second angle + * @param fullAngle value of full angle + * @return delta of the two angles + */ + public static double delta(double alpha, double beta, double fullAngle) + { + while (Math.abs(alpha - beta) > fullAngle / 2D) { + alpha = norm(alpha + fullAngle / 2D, fullAngle); + beta = norm(beta + fullAngle / 2D, fullAngle); + } + + return beta - alpha; + } + + + /** + * Difference of two angles (same as delta, but always positive) + * + * @param alpha first angle + * @param beta second angle + * @param fullAngle value of full angle + * @return delta of the two angles + */ + public static double diff(double alpha, double beta, double fullAngle) + { + return Math.abs(delta(alpha, beta, fullAngle)); + } + + + /** + * Normalize angle to 0-full range + * + * @param angle angle + * @param fullAngle full angle + * @return angle normalized + */ + public static double norm(double angle, double fullAngle) + { + while (angle < 0) + angle += fullAngle; + while (angle > fullAngle) + angle -= fullAngle; + return angle; + } +} diff --git a/src/mightypork/utils/math/angles/Deg.java b/src/mightypork/utils/math/angles/Deg.java new file mode 100644 index 0000000..f9e0f4a --- /dev/null +++ b/src/mightypork/utils/math/angles/Deg.java @@ -0,0 +1,162 @@ +package mightypork.utils.math.angles; + + +/** + * Angle calculations for degrees. + * + * @author Ondřej Hruška (MightyPork) + */ +public class Deg { + + /** 180° in degrees */ + public static final double a180 = 180; + /** 270° in degrees */ + public static final double a270 = 270; + /** 360° in degrees */ + public static final double a360 = 360; + /** 45° in degrees */ + public static final double a45 = 45; + /** 90° in degrees */ + public static final double a90 = 90; + + + /** + * Subtract two angles alpha - beta + * + * @param alpha first angle + * @param beta second angle + * @return (alpha - beta) in degrees + */ + public static double delta(double alpha, double beta) + { + return Angles.delta(alpha, beta, a360); + } + + + /** + * Difference of two angles (absolute value of delta) + * + * @param alpha first angle + * @param beta second angle + * @return difference in radians + */ + public static double diff(double alpha, double beta) + { + return Angles.diff(alpha, beta, a360); + } + + + /** + * Cosinus in degrees + * + * @param deg angle in degrees + * @return cosinus + */ + public static double cos(double deg) + { + return Math.cos(toRad(deg)); + } + + + /** + * Sinus in degrees + * + * @param deg angle in degrees + * @return sinus + */ + public static double sin(double deg) + { + return Math.sin(toRad(deg)); + } + + + /** + * Tangents in degrees + * + * @param deg angle in degrees + * @return tangents + */ + public static double tan(double deg) + { + return Math.tan(toRad(deg)); + } + + + /** + * Angle normalized to 0-360 range + * + * @param angle angle to normalize + * @return normalized angle + */ + public static double norm(double angle) + { + return Angles.norm(angle, a360); + } + + + /** + * Convert to radians + * + * @param deg degrees + * @return radians + */ + public static double toRad(double deg) + { + return Math.toRadians(deg); + } + + + /** + * Round angle to 0,45,90,135... + * + * @param deg angle in deg. to round + * @param increment rounding increment (45 - round to 0,45,90...) + * @return rounded + */ + public static int roundToIncrement(double deg, double increment) + { + final double half = increment / 2d; + deg += half; + deg = norm(deg); + final int times = (int) Math.floor(deg / increment); + double a = times * increment; + if (a == 360) a = 0; + return (int) Math.round(a); + } + + + /** + * Round angle to 0,15,30,45,60,75,90... + * + * @param deg angle in deg to round + * @return rounded + */ + public static int roundTo15(double deg) + { + return roundToIncrement(deg, 15); + } + + + /** + * Round angle to 0,45,90,135... + * + * @param deg angle in deg. to round + * @return rounded + */ + public static int roundTo45(double deg) + { + return roundToIncrement(deg, 45); + } + + + /** + * Round angle to 0,90,180,270 + * + * @param deg angle in deg. to round + * @return rounded + */ + public static int roundTo90(double deg) + { + return roundToIncrement(deg, 90); + } +} diff --git a/src/mightypork/utils/math/angles/Rad.java b/src/mightypork/utils/math/angles/Rad.java new file mode 100644 index 0000000..ffe088f --- /dev/null +++ b/src/mightypork/utils/math/angles/Rad.java @@ -0,0 +1,107 @@ +package mightypork.utils.math.angles; + + +/** + * Angle calculations for radians. + * + * @author Ondřej Hruška (MightyPork) + */ +public class Rad { + + /** 180° in radians */ + public static final double a180 = Math.PI; + /** 270° in radians */ + public static final double a270 = Math.PI * 1.5D; + /** 360° in radians */ + public static final double a360 = Math.PI * 2D; + /** 45° in radians */ + public static final double a45 = Math.PI / 4D; + /** 90° in radians */ + public static final double a90 = Math.PI / 2D; + + + /** + * Subtract two angles alpha - beta + * + * @param alpha first angle + * @param beta second angle + * @return (alpha - beta) in radians + */ + public static double delta(double alpha, double beta) + { + return Angles.delta(alpha, beta, a360); + } + + + /** + * Difference of two angles (absolute value of delta) + * + * @param alpha first angle + * @param beta second angle + * @return difference in radians + */ + public static double diff(double alpha, double beta) + { + return Angles.delta(alpha, beta, a360); + } + + + /** + * Cos + * + * @param rad angle in rads + * @return cos + */ + public static double cos(double rad) + { + return Math.cos(rad); + } + + + /** + * Sin + * + * @param rad angle in rads + * @return sin + */ + public static double sin(double rad) + { + return Math.sin(rad); + } + + + /** + * Tan + * + * @param rad angle in rads + * @return tan + */ + public static double tan(double rad) + { + return Math.tan(rad); + } + + + /** + * Angle normalized to 0-2*PI range + * + * @param angle angle to normalize + * @return normalized angle + */ + public static double norm(double angle) + { + return Angles.norm(angle, a360); + } + + + /** + * Convert to degrees + * + * @param rad radians + * @return degrees + */ + public static double toDeg(double rad) + { + return Math.toDegrees(rad); + } +} diff --git a/src/mightypork/utils/math/animation/Animator.java b/src/mightypork/utils/math/animation/Animator.java new file mode 100644 index 0000000..fe4e6f6 --- /dev/null +++ b/src/mightypork/utils/math/animation/Animator.java @@ -0,0 +1,139 @@ +package mightypork.utils.math.animation; + + +import mightypork.utils.annotations.DefaultImpl; +import mightypork.utils.interfaces.Pauseable; +import mightypork.utils.interfaces.Updateable; +import mightypork.utils.math.Calc; +import mightypork.utils.math.constraints.num.Num; +import mightypork.utils.math.constraints.num.NumBound; + + +public abstract class Animator implements NumBound, Updateable, Pauseable { + + private final NumAnimated numAnim; + private final Num animatorValue; + private final double highValue; + private final double lowValue; + + + public Animator(double period) + { + this(0, 1, period, Easing.LINEAR); + } + + + public Animator(double start, double end, double period) + { + this(start, end, period, Easing.LINEAR); + } + + + public Animator(double period, Easing easing) + { + this(0, 1, period, easing); + } + + + public Animator(double start, double end, double period, Easing easing) + { + numAnim = new NumAnimated(0, easing); + numAnim.setDefaultDuration(period); + + this.lowValue = start; + this.highValue = end; + + this.animatorValue = numAnim.mul(end - start).add(start); + } + + + @Override + public void pause() + { + numAnim.pause(); + } + + + public void start() + { + resume(); + } + + + @Override + public void resume() + { + numAnim.resume(); + } + + + @Override + public boolean isPaused() + { + return numAnim.isPaused(); + } + + + public void reset() + { + numAnim.reset(); + } + + + public void restart() + { + reset(); + resume(); + } + + + public void setDuration(double secs) + { + numAnim.setDefaultDuration(secs); + } + + + public double getDuration() + { + return numAnim.getDefaultDuration(); + } + + + @Override + public Num getNum() + { + return animatorValue; + } + + + public double getValue() + { + return animatorValue.value(); + } + + + @Override + public void update(double delta) + { + numAnim.update(delta); + if (numAnim.isFinished()) nextCycle(numAnim); + } + + + @DefaultImpl + protected abstract void nextCycle(NumAnimated anim); + + + public void setProgress(double value) + { + final double target = numAnim.getEnd(); + numAnim.setTo(Calc.clamp(value, lowValue, highValue)); + numAnim.animate((target < value ? highValue : lowValue), target, numAnim.getDefaultDuration()); + } + + + public double getProgress() + { + return numAnim.value(); + } +} diff --git a/src/mightypork/utils/math/animation/AnimatorBounce.java b/src/mightypork/utils/math/animation/AnimatorBounce.java new file mode 100644 index 0000000..b884645 --- /dev/null +++ b/src/mightypork/utils/math/animation/AnimatorBounce.java @@ -0,0 +1,50 @@ +package mightypork.utils.math.animation; + + +/** + * Animator that upon reaching max, animates back down and then up again + * + * @author Ondřej Hruška (MightyPork) + */ +public class AnimatorBounce extends Animator { + + private boolean wasUp = false; + + + public AnimatorBounce(double start, double end, double period, Easing easing) + { + super(start, end, period, easing); + } + + + public AnimatorBounce(double start, double end, double period) + { + super(start, end, period); + } + + + public AnimatorBounce(double period, Easing easing) + { + super(period, easing); + } + + + public AnimatorBounce(double period) + { + super(period); + } + + + @Override + protected void nextCycle(NumAnimated anim) + { + if (wasUp) { + anim.fadeOut(); + } else { + anim.fadeIn(); + } + + wasUp = !wasUp; + } + +} diff --git a/src/mightypork/utils/math/animation/AnimatorRewind.java b/src/mightypork/utils/math/animation/AnimatorRewind.java new file mode 100644 index 0000000..e150e0a --- /dev/null +++ b/src/mightypork/utils/math/animation/AnimatorRewind.java @@ -0,0 +1,43 @@ +package mightypork.utils.math.animation; + + +/** + * Animator that upon reaching top, jumps straight to zero and continues another + * cycle. + * + * @author Ondřej Hruška (MightyPork) + */ +public class AnimatorRewind extends Animator { + + public AnimatorRewind(double start, double end, double period, Easing easing) + { + super(start, end, period, easing); + } + + + public AnimatorRewind(double start, double end, double period) + { + super(start, end, period); + } + + + public AnimatorRewind(double period, Easing easing) + { + super(period, easing); + } + + + public AnimatorRewind(double period) + { + super(period); + } + + + @Override + protected void nextCycle(NumAnimated anim) + { + anim.reset(); + anim.fadeIn(); + } + +} diff --git a/src/mightypork/utils/math/animation/Easing.java b/src/mightypork/utils/math/animation/Easing.java new file mode 100644 index 0000000..efbcd92 --- /dev/null +++ b/src/mightypork/utils/math/animation/Easing.java @@ -0,0 +1,314 @@ +package mightypork.utils.math.animation; + + +/** + * EasingFunction function. + * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class Easing { + + /** + * Get value at time t. + * + * @param t time parameter (t = 1..1) + * @return value at given t (0..1, can exceed if needed) + */ + public abstract double get(double t); + + + /** + * Reverse an easing (factory method) + * + * @param original original easing + * @return reversed easing + */ + public static Easing reverse(Easing original) + { + return new Reverse(original); + } + + + /** + * Combine two easings (factory method) + * + * @param in initial easing + * @param out terminal easing + * @return product + */ + public static Easing combine(Easing in, Easing out) + { + return new Composite(in, out); + } + + + /** + * Create "bilinear" easing - compose of straight and reverse. (factory + * method) + * + * @param in initial easing + * @return product + */ + public static Easing inOut(Easing in) + { + return combine(in, reverse(in)); + } + + /** + * Reverse EasingFunction + * + * @author Ondřej Hruška (MightyPork) + */ + private static class Reverse extends Easing { + + private final Easing ea; + + + /** + * @param in Easing to reverse + */ + public Reverse(Easing in) + { + this.ea = in; + } + + + @Override + public double get(double t) + { + return 1 - ea.get(1 - t); + } + } + + /** + * Composite EasingFunction (0-0.5 EasingFunction A, 0.5-1 EasingFunction B) + * + * @author Ondřej Hruška (MightyPork) + */ + private static class Composite extends Easing { + + private final Easing in; + private final Easing out; + + + /** + * Create a composite EasingFunction + * + * @param in initial EasingFunction + * @param out terminal EasingFunction + */ + public Composite(Easing in, Easing out) + { + this.in = in; + this.out = out; + } + + + @Override + public double get(double t) + { + if (t < 0.5) return in.get(2 * t) * 0.5; + return 0.5 + out.get(2 * t - 1) * 0.5; + } + } + + /** No easing; At t=0.5 goes high. */ + public static final Easing NONE = new Easing() { + + @Override + public double get(double t) + { + return (t < 0.5 ? 0 : 1); + } + }; + + /** Linear (y=t) easing */ + public static final Easing LINEAR = new Easing() { + + @Override + public double get(double t) + { + return t; + } + }; + + /** Quadratic (y=t^2) easing in */ + public static final Easing QUADRATIC_IN = new Easing() { + + @Override + public double get(double t) + { + return t * t; + } + }; + + /** Quadratic (y=t^2) easing out */ + public static final Easing QUADRATIC_OUT = reverse(QUADRATIC_IN); + + /** Quadratic (y=t^2) easing both */ + public static final Easing QUADRATIC_BOTH = inOut(QUADRATIC_IN); + + /** Cubic (y=t^3) easing in */ + public static final Easing CUBIC_IN = new Easing() { + + @Override + public double get(double t) + { + return t * t * t; + } + }; + + /** Cubic (y=t^3) easing out */ + public static final Easing CUBIC_OUT = reverse(CUBIC_IN); + + /** Cubic (y=t^3) easing both */ + public static final Easing CUBIC_BOTH = inOut(CUBIC_IN); + + /** Quartic (y=t^4) easing in */ + public static final Easing QUARTIC_IN = new Easing() { + + @Override + public double get(double t) + { + return t * t * t * t; + } + }; + + /** Quartic (y=t^4) easing out */ + public static final Easing QUARTIC_OUT = reverse(QUADRATIC_IN); + + /** Quartic (y=t^4) easing both */ + public static final Easing QUARTIC_BOTH = inOut(QUADRATIC_IN); + + /** Quintic (y=t^5) easing in */ + public static final Easing QUINTIC_IN = new Easing() { + + @Override + public double get(double t) + { + return t * t * t * t * t; + } + }; + + /** Quintic (y=t^5) easing out */ + public static final Easing QUINTIC_OUT = reverse(QUINTIC_IN); + + /** Quintic (y=t^5) easing both */ + public static final Easing QUINTIC_BOTH = inOut(QUINTIC_IN); + + /** Sine easing in */ + public static final Easing SINE_IN = new Easing() { + + @Override + public double get(double t) + { + return 1 - Math.cos(t * (Math.PI / 2)); + } + }; + + /** Sine easing out */ + public static final Easing SINE_OUT = reverse(SINE_IN); + + /** Sine easing both */ + public static final Easing SINE_BOTH = inOut(SINE_IN); + + /** Exponential easing in */ + public static final Easing EXPO_IN = new Easing() { + + @Override + public double get(double t) + { + return Math.pow(2, 10 * (t - 1)); + } + }; + + /** Exponential easing out */ + public static final Easing EXPO_OUT = reverse(EXPO_IN); + + /** Exponential easing both */ + public static final Easing EXPO_BOTH = inOut(EXPO_IN); + + /** Circular easing in */ + public static final Easing CIRC_IN = new Easing() { + + @Override + public double get(double t) + { + return 1 - Math.sqrt(1 - t * t); + } + }; + + /** Circular easing out */ + public static final Easing CIRC_OUT = reverse(CIRC_IN); + + /** Circular easing both */ + public static final Easing CIRC_BOTH = inOut(CIRC_IN); + + /** Bounce easing in */ + public static final Easing BOUNCE_OUT = new Easing() { + + @Override + public double get(double t) + { + if (t < (1 / 2.75f)) { + return (7.5625f * t * t); + + } else if (t < (2 / 2.75f)) { + t -= (1.5f / 2.75f); + return (7.5625f * t * t + 0.75f); + + } else if (t < (2.5 / 2.75)) { + t -= (2.25f / 2.75f); + return (7.5625f * t * t + 0.9375f); + + } else { + t -= (2.625f / 2.75f); + return (7.5625f * t * t + 0.984375f); + } + } + }; + + /** Bounce easing out */ + public static final Easing BOUNCE_IN = reverse(BOUNCE_OUT); + + /** Bounce easing both */ + public static final Easing BOUNCE_BOTH = inOut(BOUNCE_IN); + + /** Back easing in */ + public static final Easing BACK_IN = new Easing() { + + @Override + public double get(double t) + { + final float s = 1.70158f; + return t * t * ((s + 1) * t - s); + } + }; + + /** Back easing out */ + public static final Easing BACK_OUT = reverse(BACK_IN); + + /** Back easing both */ + public static final Easing BACK_BOTH = inOut(BACK_IN); + + /** Elastic easing in */ + public static final Easing ELASTIC_IN = new Easing() { + + @Override + public double get(double t) + { + if (t == 0) return 0; + if (t == 1) return 1; + + final double p = .3f; + final double s = p / 4; + return -(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + } + }; + + /** Elastic easing out */ + public static final Easing ELASTIC_OUT = reverse(ELASTIC_IN); + + /** Elastic easing both */ + public static final Easing ELASTIC_BOTH = inOut(ELASTIC_IN); +} diff --git a/src/mightypork/utils/math/animation/NumAnimated.java b/src/mightypork/utils/math/animation/NumAnimated.java new file mode 100644 index 0000000..eb0f523 --- /dev/null +++ b/src/mightypork/utils/math/animation/NumAnimated.java @@ -0,0 +1,457 @@ +package mightypork.utils.math.animation; + + +import mightypork.utils.interfaces.Pauseable; +import mightypork.utils.interfaces.Updateable; +import mightypork.utils.math.Calc; +import mightypork.utils.math.constraints.num.var.NumMutable; + + +/** + * Double which supports delta timing.
+ * When both in and out easings are set differently, then they'll be used for + * fade-in and fade-out respectively. Otherwise both use the same. + * + * @author Ondřej Hruška (MightyPork) + */ +public class NumAnimated extends NumMutable implements Updateable, Pauseable { + + /** target double */ + protected double to = 0; + + /** last tick double */ + protected double from = 0; + + /** how long the transition should last */ + protected double duration = 0; + + /** current anim time */ + protected double elapsedTime = 0; + + /** True if this animator is paused */ + protected boolean paused = false; + + /** Easing fn */ + protected Easing easingCurrent = Easing.LINEAR; + protected Easing easingOut = Easing.LINEAR; + protected Easing easingIn = Easing.LINEAR; + + /** Default duration (seconds) */ + private double defaultDuration = 1; + + + /** + * With linear easing + * + * @param value initial value + */ + public NumAnimated(double value) + { + setTo(value); + } + + + /** + * Create animator with easing + * + * @param value initial value + * @param easing easing function + */ + public NumAnimated(double value, Easing easing) + { + this(value); + setEasing(easing); + } + + + /** + * Create animator with easing + * + * @param value initial value + * @param easingIn easing function (fade in) + * @param easingOut easing function (fade out) + */ + public NumAnimated(double value, Easing easingIn, Easing easingOut) + { + this(value); + setEasing(easingIn, easingOut); + } + + + /** + * Create animator with easing + * + * @param value initial value + * @param easing easing function + * @param defaultDuration default fade duration + */ + public NumAnimated(double value, Easing easing, double defaultDuration) + { + this(value); + setEasing(easing); + setDefaultDuration(defaultDuration); + } + + + /** + * Create animator with easing + * + * @param value initial value + * @param easingIn easing function (fade in) + * @param easingOut easing function (fade out) + * @param defaultDuration default fade duration + */ + public NumAnimated(double value, Easing easingIn, Easing easingOut, double defaultDuration) + { + this(value); + setEasing(easingIn, easingOut); + setDefaultDuration(defaultDuration); + } + + + /** + * Create as copy of another + * + * @param other other animator + */ + public NumAnimated(NumAnimated other) + { + setTo(other); + } + + + /** + * @param easing easing function + */ + public void setEasing(Easing easing) + { + this.easingCurrent = this.easingIn = this.easingOut = easing; + } + + + /** + * @param easingIn easing for fade in + * @param easingOut easing for fade out + */ + public void setEasing(Easing easingIn, Easing easingOut) + { + this.easingIn = easingIn; + this.easingOut = easingOut; + this.easingCurrent = easingIn; + } + + + /** + * Get start value + * + * @return number + */ + public double getStart() + { + return from; + } + + + /** + * Get end value + * + * @return number + */ + public double getEnd() + { + return to; + } + + + /** + * @return current animation duration (seconds) + */ + public double getDuration() + { + return duration; + } + + + /** + * @return elapsed time in current animation (seconds) + */ + public double getElapsed() + { + return elapsedTime; + } + + + /** + * @return default animation duration (seconds) + */ + public double getDefaultDuration() + { + return defaultDuration; + } + + + /** + * @param defaultDuration default animation duration (seconds) + */ + public void setDefaultDuration(double defaultDuration) + { + this.defaultDuration = defaultDuration; + } + + + /** + * Get value at delta time + * + * @return the value + */ + @Override + public double value() + { + if (duration == 0) return to; + return Calc.interpolate(from, to, (elapsedTime / duration), easingCurrent); + } + + + /** + * Get how much of the animation is already finished + * + * @return completion ratio (0 to 1) + */ + public double getProgress() + { + if (duration == 0) return 1; + return elapsedTime / duration; + } + + + @Override + public void update(double delta) + { + if (paused || isFinished()) return; + + elapsedTime = Calc.clamp(elapsedTime + delta, 0, duration); + if (isFinished()) { + duration = 0; + elapsedTime = 0; + from = to; + } + } + + + /** + * Get if animation is finished + * + * @return is finished + */ + public boolean isFinished() + { + return duration == 0 || elapsedTime >= duration; + } + + + /** + * Set to a value (without animation) + * + * @param value + */ + @Override + public void setTo(double value) + { + from = to = value; + elapsedTime = 0; + duration = 0; + } + + + /** + * Copy other + * + * @param other + */ + public void setTo(NumAnimated other) + { + this.from = other.from; + this.to = other.to; + this.duration = other.duration; + this.elapsedTime = other.elapsedTime; + this.paused = other.paused; + this.easingCurrent = other.easingCurrent; + this.easingIn = other.easingIn; + this.easingOut = other.easingOut; + this.defaultDuration = other.defaultDuration; + } + + + /** + * Animate between two states, start from current value (if it's in between) + * + * @param from start value + * @param to target state + * @param time animation time (secs) + */ + public void animate(double from, double to, double time) + { + final double current = value(); + + this.from = from; + this.to = to; + + final double progress = getProgressFromValue(current); + + this.from = (progress > 0 ? current : from); + + this.duration = time * (1 - progress); + this.elapsedTime = 0; + } + + + /** + * Get progress already elapsed based on current value.
+ * Used to resume animation from current point in fading etc. + * + * @param value current value + * @return progress ratio 0-1 + */ + protected double getProgressFromValue(double value) + { + double p = 0; + + if (from == to) return 0; + + if (value >= from && value <= to) { // up + p = ((value - from) / (to - from)); + } else if (value >= to && value <= from) { // down + p = ((from - value) / (from - to)); + } + + return p; + } + + + /** + * Animate to a value from current value + * + * @param to target state + * @param duration animation duration (speeds) + */ + public void animate(double to, double duration) + { + this.from = value(); + this.to = to; + this.duration = duration; + this.elapsedTime = 0; + } + + + /** + * Animate 0 to 1 + * + * @param time animation time (secs) + */ + public void fadeIn(double time) + { + easingCurrent = easingIn; + animate(0, 1, time); + } + + + /** + * Animate 1 to 0 + * + * @param time animation time (secs) + */ + public void fadeOut(double time) + { + easingCurrent = easingOut; + animate(1, 0, time); + } + + + /** + * Animate 0 to 1 with default duration + */ + public void fadeIn() + { + easingCurrent = easingIn; + animate(0, 1, defaultDuration); + } + + + /** + * Animate 1 to 0 with default duration + */ + public void fadeOut() + { + easingCurrent = easingOut; + animate(1, 0, defaultDuration); + } + + + /** + * Make a copy + * + * @return copy + */ + @Override + public NumAnimated clone() + { + return new NumAnimated(this); + } + + + @Override + public String toString() + { + return "Animation(" + from + " -> " + to + ", t=" + duration + "s, elapsed=" + elapsedTime + "s)"; + } + + + /** + * Set to zero and stop animation + */ + public void clear() + { + from = to = 0; + elapsedTime = 0; + duration = 0; + paused = false; + } + + + /** + * Stop animation, keep current value + */ + public void stop() + { + from = to = value(); + elapsedTime = 0; + duration = 0; + } + + + @Override + public void pause() + { + paused = true; + } + + + @Override + public void resume() + { + paused = false; + } + + + @Override + public boolean isPaused() + { + return paused; + } + + + public boolean isInProgress() + { + return !isFinished() && !isPaused(); + } +} diff --git a/src/mightypork/utils/math/animation/NumAnimatedDeg.java b/src/mightypork/utils/math/animation/NumAnimatedDeg.java new file mode 100644 index 0000000..1e3122f --- /dev/null +++ b/src/mightypork/utils/math/animation/NumAnimatedDeg.java @@ -0,0 +1,52 @@ +package mightypork.utils.math.animation; + + +import mightypork.utils.math.Calc; +import mightypork.utils.math.angles.Deg; + + +/** + * Degree animator + * + * @author Ondřej Hruška (MightyPork) + */ +public class NumAnimatedDeg extends NumAnimated { + + public NumAnimatedDeg(NumAnimated other) + { + super(other); + } + + + public NumAnimatedDeg(double value) + { + super(value); + } + + + public NumAnimatedDeg(double value, Easing easing) + { + super(value, easing); + } + + + @Override + public double value() + { + if (duration == 0) return Deg.norm(to); + return Calc.interpolateDeg(from, to, (elapsedTime / duration), easingCurrent); + } + + + @Override + protected double getProgressFromValue(double value) + { + final double whole = Deg.diff(from, to); + if (Deg.diff(value, from) < whole && Deg.diff(value, to) < whole) { + final double partial = Deg.diff(from, value); + return partial / whole; + } + + return 0; + } +} diff --git a/src/mightypork/utils/math/animation/NumAnimatedRad.java b/src/mightypork/utils/math/animation/NumAnimatedRad.java new file mode 100644 index 0000000..2801acb --- /dev/null +++ b/src/mightypork/utils/math/animation/NumAnimatedRad.java @@ -0,0 +1,52 @@ +package mightypork.utils.math.animation; + + +import mightypork.utils.math.Calc; +import mightypork.utils.math.angles.Rad; + + +/** + * Radians animator + * + * @author Ondřej Hruška (MightyPork) + */ +public class NumAnimatedRad extends NumAnimated { + + public NumAnimatedRad(NumAnimated other) + { + super(other); + } + + + public NumAnimatedRad(double value) + { + super(value); + } + + + public NumAnimatedRad(double value, Easing easing) + { + super(value, easing); + } + + + @Override + public double value() + { + if (duration == 0) return Rad.norm(to); + return Calc.interpolateRad(from, to, (elapsedTime / duration), easingCurrent); + } + + + @Override + protected double getProgressFromValue(double value) + { + final double whole = Rad.diff(from, to); + if (Rad.diff(value, from) < whole && Rad.diff(value, to) < whole) { + final double partial = Rad.diff(from, value); + return partial / whole; + } + + return 0; + } +} diff --git a/src/mightypork/utils/math/animation/VectAnimated.java b/src/mightypork/utils/math/animation/VectAnimated.java new file mode 100644 index 0000000..12c5c45 --- /dev/null +++ b/src/mightypork/utils/math/animation/VectAnimated.java @@ -0,0 +1,286 @@ +package mightypork.utils.math.animation; + + +import mightypork.utils.annotations.FactoryMethod; +import mightypork.utils.interfaces.Pauseable; +import mightypork.utils.interfaces.Updateable; +import mightypork.utils.math.constraints.vect.Vect; +import mightypork.utils.math.constraints.vect.var.VectMutable; + + +/** + * 3D coordinated with support for transitions, mutable. + * + * @author Ondřej Hruška (MightyPork) + */ +public class VectAnimated extends VectMutable implements Pauseable, Updateable { + + private final NumAnimated x, y, z; + private double defaultDuration = 0.5; + + + /** + * Create an animated vector; This way different easing / settings can be + * specified for each coordinate. + * + * @param x x animator + * @param y y animator + * @param z z animator + */ + public VectAnimated(NumAnimated x, NumAnimated y, NumAnimated z) + { + this.x = x; + this.y = y; + this.z = z; + } + + + /** + * Create an animated vector + * + * @param start initial positioon + * @param easing animation easing + */ + public VectAnimated(Vect start, Easing easing) + { + x = new NumAnimated(start.x(), easing); + y = new NumAnimated(start.y(), easing); + z = new NumAnimated(start.z(), easing); + } + + + @Override + public double x() + { + return x.value(); + } + + + @Override + public double y() + { + return y.value(); + } + + + @Override + public double z() + { + return z.value(); + } + + + @Override + public void setTo(double x, double y, double z) + { + setX(x); + setY(y); + setZ(z); + } + + + @Override + public void setX(double x) + { + this.x.setTo(x); + } + + + @Override + public void setY(double y) + { + this.y.setTo(y); + } + + + @Override + public void setZ(double z) + { + this.z.setTo(z); + } + + + public void add(Vect offset, double duration) + { + animate(this.add(offset), duration); + } + + + public VectAnimated animate(double x, double y, double z, double duration) + { + this.x.animate(x, duration); + this.y.animate(y, duration); + this.z.animate(z, duration); + return this; + } + + + public VectAnimated animate(Vect target, double duration) + { + animate(target.x(), target.y(), target.z(), duration); + return this; + } + + + public VectAnimated animate(double x, double y, double z) + { + this.x.animate(x, defaultDuration); + this.y.animate(y, defaultDuration); + this.z.animate(z, defaultDuration); + return this; + } + + + public VectAnimated animate(Vect target) + { + animate(target.x(), target.y(), target.z()); + return this; + } + + + /** + * @return the default duration (seconds) + */ + public double getDefaultDuration() + { + return defaultDuration; + } + + + /** + * Set default animation duration (when changed without using animate()) + * + * @param defaultDuration default duration (seconds) + */ + public void setDefaultDuration(double defaultDuration) + { + this.defaultDuration = defaultDuration; + } + + + @Override + public void update(double delta) + { + x.update(delta); + y.update(delta); + z.update(delta); + } + + + @Override + public void pause() + { + x.pause(); + y.pause(); + z.pause(); + } + + + @Override + public void resume() + { + x.resume(); + y.resume(); + z.resume(); + } + + + @Override + public boolean isPaused() + { + return x.isPaused(); // BÚNO + } + + + /** + * @return true if the animation is finished + */ + public boolean isFinished() + { + return x.isFinished(); // BÚNO + } + + + /** + * @return current animation duration + */ + public double getDuration() + { + return x.getDuration(); // BÚNO + } + + + /** + * @return elapsed time since the start of the animation + */ + public double getElapsed() + { + return x.getElapsed(); // BÚNO + } + + + /** + * @return animation progress (elapsed / duration) + */ + public double getProgress() + { + return x.getProgress(); // BÚNO + } + + + /** + * Set easing for all three coordinates + * + * @param easing + */ + public void setEasing(Easing easing) + { + x.setEasing(easing); + y.setEasing(easing); + z.setEasing(easing); + } + + + /** + * Create an animated vector; This way different easing / settings can be + * specified for each coordinate. + * + * @param x x animator + * @param y y animator + * @param z z animator + * @return animated mutable vector + */ + @FactoryMethod + public static VectAnimated makeVar(NumAnimated x, NumAnimated y, NumAnimated z) + { + return new VectAnimated(x, y, z); + } + + + /** + * Create an animated vector + * + * @param start initial positioon + * @param easing animation easing + * @return animated mutable vector + */ + @FactoryMethod + public static VectAnimated makeVar(Vect start, Easing easing) + { + return new VectAnimated(start, easing); + } + + + /** + * Create an animated vector, initialized at 0,0,0 + * + * @param easing animation easing + * @return animated mutable vector + */ + @FactoryMethod + public static VectAnimated makeVar(Easing easing) + { + return new VectAnimated(Vect.ZERO, easing); + } + +} diff --git a/src/mightypork/utils/math/color/Color.java b/src/mightypork/utils/math/color/Color.java new file mode 100644 index 0000000..5529e8a --- /dev/null +++ b/src/mightypork/utils/math/color/Color.java @@ -0,0 +1,247 @@ +package mightypork.utils.math.color; + + +import java.util.EmptyStackException; +import java.util.Stack; + +import mightypork.utils.annotations.FactoryMethod; +import mightypork.utils.math.Calc; +import mightypork.utils.math.constraints.num.Num; + + +/** + * Color.
+ * All values are 0-1 + * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class Color { + + private static final Stack alphaStack = new Stack<>(); + private static volatile boolean alphaStackEnabled = true; + + + @FactoryMethod + public static final Color fromHex(int rgb_hex) + { + final int bi = rgb_hex & 0xff; + final int gi = (rgb_hex >> 8) & 0xff; + final int ri = (rgb_hex >> 16) & 0xff; + return rgb(ri / 255D, gi / 255D, bi / 255D); + } + + + @FactoryMethod + public static final Color rgb(double r, double g, double b) + { + return rgba(Num.make(r), Num.make(g), Num.make(b), Num.ONE); + } + + + @FactoryMethod + public static final Color rgba(double r, double g, double b, double a) + { + return rgba(Num.make(r), Num.make(g), Num.make(b), Num.make(a)); + } + + + @FactoryMethod + public static final Color rgb(Num r, Num g, Num b) + { + return rgba(r, g, b, Num.ONE); + } + + + @FactoryMethod + public static final Color rgba(Num r, Num g, Num b, Num a) + { + return new ColorRgb(r, g, b, a); + } + + + @FactoryMethod + public static final Color hsb(double h, double s, double b) + { + return hsba(Num.make(h), Num.make(s), Num.make(b), Num.ONE); + } + + + @FactoryMethod + public static final Color hsba(double h, double s, double b, double a) + { + return hsba(Num.make(h), Num.make(s), Num.make(b), Num.make(a)); + } + + + @FactoryMethod + public static final Color hsb(Num h, Num s, Num b) + { + return hsba(h, s, b, Num.ONE); + } + + + @FactoryMethod + public static final Color hsba(Num h, Num s, Num b, Num a) + { + return new ColorHsb(h, s, b, a); + } + + + @FactoryMethod + public static final Color light(double a) + { + return light(Num.make(a)); + } + + + @FactoryMethod + public static final Color light(Num a) + { + return rgba(Num.ONE, Num.ONE, Num.ONE, a); + } + + + @FactoryMethod + public static final Color dark(double a) + { + return dark(Num.make(a)); + } + + + @FactoryMethod + public static final Color dark(Num a) + { + return rgba(Num.ZERO, Num.ZERO, Num.ZERO, a); + } + + + protected static final double clamp(Num n) + { + return Calc.clamp(n.value(), 0, 1); + } + + + protected static final double clamp(double n) + { + return Calc.clamp(n, 0, 1); + } + + + /** + * @return red 0-1 + */ + public abstract double r(); + + + /** + * @return green 0-1 + */ + public abstract double g(); + + + /** + * @return blue 0-1 + */ + public abstract double b(); + + + /** + * @return alpha 0-1 + */ + public final double a() + { + double alpha = rawAlpha(); + + if (alphaStackEnabled) { + + for (final Num n : alphaStack) { + alpha *= clamp(n.value()); + } + } + + return clamp(alpha); + } + + + /** + * @return alpha 0-1, before multiplying with the global alpha value. + */ + protected abstract double rawAlpha(); + + + /** + *

+ * 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). + *

+ *

+ * multiplier value should not exceed the range 0..1, otherwise it will be + * clamped to it. + *

+ * + * @param alpha alpha multiplier + */ + public static void pushAlpha(Num alpha) + { + if (!alphaStackEnabled) { + return; + } + + alphaStack.push(alpha); + } + + + /** + * Remove a pushed alpha multiplier from the stack. If there's no remaining + * multiplier on the stack, an exception is raised. + * + * @throws EmptyStackException if the stack is empty + */ + public static void popAlpha() + { + if (!alphaStackEnabled) { + return; + } + + if (alphaStack.isEmpty()) { + throw new EmptyStackException(); + } + + alphaStack.pop(); + } + + + /** + * Enable alpha stack. When disabled, pushAlpha() and popAlpha() have no + * effect. + * + * @param yes + */ + public static void enableAlphaStack(boolean yes) + { + alphaStackEnabled = yes; + } + + + /** + * @return true if alpha stack is enabled. + */ + public static boolean isAlphaStackEnabled() + { + return alphaStackEnabled; + } + + + public Color withAlpha(double multiplier) + { + return new ColorAlphaAdjuster(this, Num.make(multiplier)); + } + + + public Color withAlpha(Num multiplier) + { + return new ColorAlphaAdjuster(this, multiplier); + } +} diff --git a/src/mightypork/utils/math/color/ColorAlphaAdjuster.java b/src/mightypork/utils/math/color/ColorAlphaAdjuster.java new file mode 100644 index 0000000..b7466a1 --- /dev/null +++ b/src/mightypork/utils/math/color/ColorAlphaAdjuster.java @@ -0,0 +1,47 @@ +package mightypork.utils.math.color; + + +import mightypork.utils.math.constraints.num.Num; + + +public class ColorAlphaAdjuster extends Color { + + private final Color source; + private final Num alphaAdjust; + + + public ColorAlphaAdjuster(Color source, Num alphaMul) + { + this.source = source; + this.alphaAdjust = alphaMul; + } + + + @Override + public double r() + { + return source.r(); + } + + + @Override + public double g() + { + return source.g(); + } + + + @Override + public double b() + { + return source.b(); + } + + + @Override + protected double rawAlpha() + { + return source.rawAlpha() * alphaAdjust.value(); + } + +} diff --git a/src/mightypork/utils/math/color/ColorHsb.java b/src/mightypork/utils/math/color/ColorHsb.java new file mode 100644 index 0000000..45e12c3 --- /dev/null +++ b/src/mightypork/utils/math/color/ColorHsb.java @@ -0,0 +1,62 @@ +package mightypork.utils.math.color; + + +import mightypork.utils.math.constraints.num.Num; + + +public class ColorHsb extends Color { + + private final Num h; + private final Num s; + private final Num b; + private final Num a; + + + public ColorHsb(Num h, Num s, Num b, Num a) + { + this.h = h; + this.s = s; + this.b = b; + this.a = a; + } + + + private double[] asRgb() + { + final int hex = java.awt.Color.HSBtoRGB((float) clamp(h), (float) clamp(s), (float) clamp(b)); + + final int bi = hex & 0xff; + final int gi = (hex >> 8) & 0xff; + final int ri = (hex >> 16) & 0xff; + return new double[] { ri / 255D, gi / 255D, bi / 255D, clamp(a) }; + } + + + @Override + public double r() + { + return asRgb()[0]; + } + + + @Override + public double g() + { + return asRgb()[1]; + } + + + @Override + public double b() + { + return asRgb()[2]; + } + + + @Override + protected double rawAlpha() + { + return asRgb()[3]; + } + +} diff --git a/src/mightypork/utils/math/color/ColorRgb.java b/src/mightypork/utils/math/color/ColorRgb.java new file mode 100644 index 0000000..c7ea99b --- /dev/null +++ b/src/mightypork/utils/math/color/ColorRgb.java @@ -0,0 +1,51 @@ +package mightypork.utils.math.color; + + +import mightypork.utils.math.constraints.num.Num; + + +public class ColorRgb extends Color { + + private final Num r; + private final Num g; + private final Num b; + private final Num a; + + + public ColorRgb(Num r, Num g, Num b, Num a) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + + @Override + public double r() + { + return clamp(r); + } + + + @Override + public double g() + { + return clamp(g); + } + + + @Override + public double b() + { + return clamp(b); + } + + + @Override + protected double rawAlpha() + { + return clamp(a); + } + +} diff --git a/src/mightypork/utils/math/color/pal/CGA.java b/src/mightypork/utils/math/color/pal/CGA.java new file mode 100644 index 0000000..82a8125 --- /dev/null +++ b/src/mightypork/utils/math/color/pal/CGA.java @@ -0,0 +1,32 @@ +package mightypork.utils.math.color.pal; + + +import mightypork.utils.math.color.Color; + + +/** + * CGA palette + * + * @author Ondřej Hruška (MightyPork) + */ +public interface CGA { + + Color BLACK = Color.fromHex(0x000000); + Color GRAY_DARK = Color.fromHex(0x686868); + Color GRAY_LIGHT = Color.fromHex(0xB8B8B8); + Color WHITE = Color.fromHex(0xFFFFFF); + + Color RED_DARK = Color.fromHex(0xC41F0C); + Color RED_LIGHT = Color.fromHex(0xFF706A); + Color MAGENTA_DARK = Color.fromHex(0xC12BB6); + Color MAGENTA_LIGHT = Color.fromHex(0xFF76FD); + Color BLUE_DARK = Color.fromHex(0x0019B6); + Color BLUE_LIGHT = Color.fromHex(0x5F6EFC); + Color CYAN_DARK = Color.fromHex(0x00B6B8); + Color CYAN_LIGHT = Color.fromHex(0x23FCFE); + Color GREEN_DARK = Color.fromHex(0x00B41D); + Color GREEN_LIGHT = Color.fromHex(0x39FA6F); + + Color YELLOW = Color.fromHex(0xFFFD72); + Color BROWN = Color.fromHex(0xC16A14); +} diff --git a/src/mightypork/utils/math/color/pal/CMDR.java b/src/mightypork/utils/math/color/pal/CMDR.java new file mode 100644 index 0000000..d8d5b75 --- /dev/null +++ b/src/mightypork/utils/math/color/pal/CMDR.java @@ -0,0 +1,30 @@ +package mightypork.utils.math.color.pal; + + +import mightypork.utils.math.color.Color; + + +/** + * COMMODORE palette + * + * @author Ondřej Hruška (MightyPork) + */ +public interface CMDR { + + Color BLACK = Color.fromHex(0x040013); + Color WHITE = Color.fromHex(0xFFFFFF); + Color RED = Color.fromHex(0x883932); + Color CYAN = Color.fromHex(0x67B6BD); + Color PURPLE = Color.fromHex(0x8B3F96); + Color GREEN = Color.fromHex(0x55A049); + Color BLUE = Color.fromHex(0x40318D); + Color YELLOW = Color.fromHex(0xBFCE72); + Color ORANGE = Color.fromHex(0x8B5429); + Color BROWN = Color.fromHex(0x574200); + Color RED_LIGHT = Color.fromHex(0xB86962); + Color GRAY_DARK = Color.fromHex(0x505050); + Color GRAY = Color.fromHex(0x787878); + Color GREEN_LIGHT = Color.fromHex(0x94E089); + Color BLUE_LIGHT = Color.fromHex(0x7869C4); + Color GRAY_LIGHT = Color.fromHex(0x9F9F9F); +} diff --git a/src/mightypork/utils/math/color/pal/PAL16.java b/src/mightypork/utils/math/color/pal/PAL16.java new file mode 100644 index 0000000..fb32202 --- /dev/null +++ b/src/mightypork/utils/math/color/pal/PAL16.java @@ -0,0 +1,34 @@ +package mightypork.utils.math.color.pal; + + +import mightypork.utils.math.color.Color; + + +/** + * PAL16 palette via http://androidarts.com/palette/16pal.htm + * + * @author Ondřej Hruška (MightyPork) + */ +public interface PAL16 { + + Color VOID = Color.fromHex(0x000000); + Color ASH = Color.fromHex(0x9D9D9D); + Color BLIND = Color.fromHex(0xFFFFFF); + + Color BLOODRED = Color.fromHex(0xBE2633); + Color PIGMEAT = Color.fromHex(0xE06F8B); + + Color OLDPOOP = Color.fromHex(0x493C2B); + Color NEWPOOP = Color.fromHex(0xA46422); + Color BLAZE = Color.fromHex(0xEB8931); + Color ZORNSKIN = Color.fromHex(0xF7E26B); + + Color SHADEGREEN = Color.fromHex(0x2F484E); + Color LEAFGREEN = Color.fromHex(0x44891A); + Color SLIMEGREEN = Color.fromHex(0xA3CE27); + + Color NIGHTBLUE = Color.fromHex(0x1B2632); + Color SEABLUE = Color.fromHex(0x005784); + Color SKYBLUE = Color.fromHex(0x31A2F2); + Color CLOUDBLUE = Color.fromHex(0xB2DCEF); +} diff --git a/src/mightypork/utils/math/color/pal/RGB.java b/src/mightypork/utils/math/color/pal/RGB.java new file mode 100644 index 0000000..225986b --- /dev/null +++ b/src/mightypork/utils/math/color/pal/RGB.java @@ -0,0 +1,44 @@ +package mightypork.utils.math.color.pal; + + +import mightypork.utils.math.color.Color; + + +/** + * Basic RGB palette + * + * @author Ondřej Hruška (MightyPork) + */ +public class RGB { + + public static final Color BLACK_10 = Color.rgba(0, 0, 0, 0.1); + public static final Color BLACK_20 = Color.rgba(0, 0, 0, 0.2); + public static final Color BLACK_30 = Color.rgba(0, 0, 0, 0.3); + public static final Color BLACK_40 = Color.rgba(0, 0, 0, 0.4); + public static final Color BLACK_50 = Color.rgba(0, 0, 0, 0.5); + public static final Color BLACK_60 = Color.rgba(0, 0, 0, 0.6); + public static final Color BLACK_70 = Color.rgba(0, 0, 0, 0.7); + public static final Color BLACK_80 = Color.rgba(0, 0, 0, 0.8); + public static final Color BLACK_90 = Color.rgba(0, 0, 0, 0.9); + + + public static final Color WHITE = Color.fromHex(0xFFFFFF); + public static final Color BLACK = Color.fromHex(0x000000); + public static final Color GRAY_DARK = Color.fromHex(0x808080); + public static final Color GRAY = Color.fromHex(0xA0A0A0); + public static final Color GRAY_LIGHT = Color.fromHex(0xC0C0C0); + + public static final Color RED = Color.fromHex(0xFF0000); + public static final Color GREEN = Color.fromHex(0x00FF00); + public static final Color BLUE = Color.fromHex(0x0000FF); + + public static final Color YELLOW = Color.fromHex(0xFFFF00); + public static final Color CYAN = Color.fromHex(0x00FFFF); + public static final Color MAGENTA = Color.fromHex(0xFF00FF); + + public static final Color PINK = Color.fromHex(0xFF3FFC); + public static final Color ORANGE = Color.fromHex(0xFC4800); + public static final Color BROWN = Color.fromHex(0x83501B); + + public static final Color NONE = Color.rgba(0, 0, 0, 0); +} diff --git a/src/mightypork/utils/math/color/pal/ZX.java b/src/mightypork/utils/math/color/pal/ZX.java new file mode 100644 index 0000000..ac2baf3 --- /dev/null +++ b/src/mightypork/utils/math/color/pal/ZX.java @@ -0,0 +1,31 @@ +package mightypork.utils.math.color.pal; + + +import mightypork.utils.math.color.Color; + + +/** + * ZX Spectrum palette + * + * @author Ondřej Hruška (MightyPork) + */ +public interface ZX { + + Color BLACK = Color.fromHex(0x000000); + Color GRAY = Color.fromHex(0xCBCBCB); + Color WHITE = Color.fromHex(0xFFFFFF); + + Color RED_DARK = Color.fromHex(0xD8240F); + Color RED_LIGHT = Color.fromHex(0xFF3016); + Color MAGENTA_DARK = Color.fromHex(0xD530C9); + Color MAGENTA_LIGHT = Color.fromHex(0xFF3FFC); + Color BLUE_DARK = Color.fromHex(0x001DC8); + Color BLUE_LIGHT = Color.fromHex(0x0027FB); + Color CYAN_DARK = Color.fromHex(0x00C9CB); + Color CYAN_LIGHT = Color.fromHex(0xFFFD33); + Color GREEN_DARK = Color.fromHex(0x00C721); + Color GREEN_LIGHT = Color.fromHex(0x00F92C); + + Color YELLOW_DARK = Color.fromHex(0xCECA26); + Color YELLOW_LIGHT = Color.fromHex(0xFFFD33); +} diff --git a/src/mightypork/utils/math/constraints/CachedConstraint.java b/src/mightypork/utils/math/constraints/CachedConstraint.java new file mode 100644 index 0000000..dede4e2 --- /dev/null +++ b/src/mightypork/utils/math/constraints/CachedConstraint.java @@ -0,0 +1,47 @@ +package mightypork.utils.math.constraints; + + +import mightypork.utils.interfaces.Pollable; + + +/** + * Constraint that is cached + * + * @author Ondřej Hruška (MightyPork) + * @param constraint type + */ +public interface CachedConstraint extends Pollable { + + /** + * Called after the cache has changed value (and digest). + */ + void onConstraintChanged(); + + + /** + * @return the cached value + */ + C getCacheSource(); + + + /** + * Enable caching & digest caching + * + * @param yes enable caching + */ + void enableCaching(boolean yes); + + + /** + * @return true if caching is on + */ + boolean isCachingEnabled(); + + + /** + * Update cached value and cached digest (if digest caching is enabled).
+ * source constraint is polled beforehand. + */ + @Override + void poll(); +} diff --git a/src/mightypork/utils/math/constraints/CachedDigestable.java b/src/mightypork/utils/math/constraints/CachedDigestable.java new file mode 100644 index 0000000..e0060a3 --- /dev/null +++ b/src/mightypork/utils/math/constraints/CachedDigestable.java @@ -0,0 +1,51 @@ +package mightypork.utils.math.constraints; + + +/** + *

+ * 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). + *

+ *

+ * 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. + *

+ *

+ * 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. + *

+ * + * @author Ondřej Hruška (MightyPork) + * @param digest class + */ +public interface CachedDigestable extends Digestable { + + /** + *

+ * Toggle digest caching. + *

+ *

+ * To trigger update of the cache, call the poll() method. + *

+ * + * @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.
+ */ + void markDigestDirty(); + +} diff --git a/src/mightypork/utils/math/constraints/DigestCache.java b/src/mightypork/utils/math/constraints/DigestCache.java new file mode 100644 index 0000000..36e0da3 --- /dev/null +++ b/src/mightypork/utils/math/constraints/DigestCache.java @@ -0,0 +1,60 @@ +package mightypork.utils.math.constraints; + + +/** + * Parametrized implementation of a {@link CachedDigestable} + * + * @author Ondřej Hruška (MightyPork) + * @param digest class + */ +public abstract class DigestCache implements CachedDigestable { + + private D last_digest; + private boolean caching_enabled = false; + private boolean dirty = true; + + + @Override + public final D digest() + { + if (caching_enabled) { + if (dirty || last_digest == null) { + last_digest = createDigest(); + dirty = false; + } + + return last_digest; + } + + return createDigest(); + } + + + /** + * @return fresh new digest + */ + protected abstract D createDigest(); + + + @Override + public final void enableDigestCaching(boolean yes) + { + caching_enabled = yes; + markDigestDirty(); // mark dirty + } + + + @Override + public final boolean isDigestCachingEnabled() + { + return caching_enabled; + } + + + @Override + public final void markDigestDirty() + { + dirty = true; + } + +} diff --git a/src/mightypork/utils/math/constraints/Digestable.java b/src/mightypork/utils/math/constraints/Digestable.java new file mode 100644 index 0000000..7fd28b4 --- /dev/null +++ b/src/mightypork/utils/math/constraints/Digestable.java @@ -0,0 +1,20 @@ +package mightypork.utils.math.constraints; + + +/** + * COnstraint that can be converted to a digest, representing current state + * + * @author Ondřej Hruška (MightyPork) + * @param + */ +public interface Digestable { + + /** + * Take a digest. If digest caching is enabled and the cached digest is + * marked as dirty, a new one should be made. + * + * @return digest + */ + D digest(); + +} diff --git a/src/mightypork/utils/math/constraints/num/Num.java b/src/mightypork/utils/math/constraints/num/Num.java new file mode 100644 index 0000000..415eb88 --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/Num.java @@ -0,0 +1,758 @@ +package mightypork.utils.math.constraints.num; + + +import mightypork.utils.annotations.FactoryMethod; +import mightypork.utils.math.constraints.CachedDigestable; +import mightypork.utils.math.constraints.DigestCache; +import mightypork.utils.math.constraints.num.caching.NumCache; +import mightypork.utils.math.constraints.num.caching.NumDigest; +import mightypork.utils.math.constraints.num.proxy.NumProxy; +import mightypork.utils.math.constraints.num.var.NumVar; +import mightypork.utils.math.constraints.vect.Vect; + + +public abstract class Num implements NumBound, CachedDigestable { + + public static final NumConst ZERO = Num.make(0); + public static final NumConst ONE = Num.make(1); + + + @FactoryMethod + public static Num make(NumBound bound) + { + return new NumProxy(bound); + } + + + @FactoryMethod + public static NumConst make(double value) + { + return new NumConst(value); + } + + + @FactoryMethod + public static NumVar makeVar() + { + return makeVar(0); + } + + + @FactoryMethod + public static NumVar makeVar(double value) + { + return new NumVar(value); + } + + + @FactoryMethod + public static NumVar makeVar(Num copied) + { + return new NumVar(copied.value()); + } + + private Num p_ceil; + private Num p_floor; + private Num p_sgn; + private Num p_round; + private Num p_atan; + private Num p_acos; + private Num p_asin; + private Num p_tan; + private Num p_cos; + private Num p_sin; + private Num p_cbrt; + private Num p_sqrt; + private Num p_cube; + private Num p_square; + private Num p_neg; + private Num p_abs; + + private final DigestCache dc = new DigestCache() { + + @Override + protected NumDigest createDigest() + { + return new NumDigest(Num.this); + } + }; + + + public NumConst freeze() + { + return new NumConst(value()); + } + + + /** + * Wrap this constraint into a caching adapter. Value will stay fixed (ie. + * no re-calculations) until cache receives a poll() call. + * + * @return the caching adapter + */ + public NumCache cached() + { + return new NumCache(this); + } + + + /** + * Get a snapshot of the current state, to be used for processing. + * + * @return digest + */ + + @Override + public NumDigest digest() + { + return dc.digest(); + } + + + @Override + public void enableDigestCaching(boolean yes) + { + dc.enableDigestCaching(yes); + } + + + @Override + public boolean isDigestCachingEnabled() + { + return dc.isDigestCachingEnabled(); + } + + + @Override + public void markDigestDirty() + { + dc.markDigestDirty(); + } + + + @Override + public Num getNum() + { + return this; + } + + + /** + * @return the number + */ + public abstract double value(); + + + public Num add(final double addend) + { + return new Num() { + + private final Num t = Num.this; + + + @Override + public double value() + { + return t.value() + addend; + } + }; + } + + + public Num add(final Num addend) + { + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return t.value() + addend.value(); + } + }; + } + + + public Num sub(final double subtrahend) + { + return add(-subtrahend); + } + + + public Num abs() + { + if (p_abs == null) p_abs = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.abs(t.value()); + } + }; + + return p_abs; + } + + + public Num sub(final Num subtrahend) + { + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return t.value() - subtrahend.value(); + } + }; + } + + + public Num div(final double factor) + { + return mul(1 / factor); + } + + + public Num div(final Num factor) + { + + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return t.value() / factor.value(); + } + }; + } + + + public Num mul(final double factor) + { + return new Num() { + + private final Num t = Num.this; + + + @Override + public double value() + { + return t.value() * factor; + } + }; + } + + + public Num mul(final Num factor) + { + + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return t.value() * factor.value(); + } + }; + } + + + public Num average(final double other) + { + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return (t.value() + other) / 2; + } + }; + } + + + public Num average(final Num other) + { + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return (t.value() + other.value()) / 2; + } + }; + } + + + public Num perc(final double percent) + { + return mul(percent / 100D); + } + + + public Num perc(final Num percent) + { + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return t.value() * (percent.value() / 100); + } + }; + } + + + public Num cos() + { + if (p_cos == null) p_cos = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.cos(t.value()); + } + }; + + return p_cos; + } + + + public Num acos() + { + if (p_acos == null) p_acos = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.acos(t.value()); + } + }; + + return p_acos; + } + + + public Num sin() + { + if (p_sin == null) p_sin = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.sin(t.value()); + } + }; + + return p_sin; + } + + + public Num asin() + { + if (p_asin == null) p_asin = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.asin(t.value()); + } + }; + + return p_asin; + } + + + public Num tan() + { + if (p_tan == null) p_tan = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.tan(t.value()); + } + }; + + return p_tan; + } + + + public Num atan() + { + if (p_atan == null) p_atan = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.atan(t.value()); + } + }; + + return p_atan; + } + + + public Num cbrt() + { + if (p_cbrt == null) p_cbrt = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.cbrt(t.value()); + } + }; + + return p_cbrt; + } + + + public Num sqrt() + { + if (p_sqrt == null) p_sqrt = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.sqrt(t.value()); + } + }; + + return p_sqrt; + } + + + public Num neg() + { + if (p_neg == null) p_neg = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return -1 * t.value(); + } + }; + + return p_neg; + } + + + public Num round() + { + if (p_round == null) p_round = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.round(t.value()); + } + }; + + return p_round; + } + + + public Num floor() + { + if (p_floor == null) p_floor = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.floor(t.value()); + } + }; + + return p_floor; + } + + + public Num ceil() + { + if (p_ceil == null) p_ceil = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.round(t.value()); + } + }; + + return p_ceil; + } + + + public Num pow(final double other) + { + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.pow(t.value(), other); + } + }; + } + + + public Num pow(final Num power) + { + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.pow(t.value(), power.value()); + } + }; + } + + + public Num cube() + { + if (p_cube == null) p_cube = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + final double v = t.value(); + return v * v * v; + } + }; + + return p_cube; + } + + + public Num square() + { + if (p_square == null) p_square = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + final double v = t.value(); + return v * v; + } + }; + + return p_square; + } + + + public Num half() + { + return mul(0.5); + } + + + public Num max(final double other) + { + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.max(t.value(), other); + } + }; + } + + + public Num max(final Num other) + { + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.max(t.value(), other.value()); + } + }; + } + + + public Num min(final Num other) + { + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.min(t.value(), other.value()); + } + }; + } + + + public Num min(final double other) + { + return new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.min(t.value(), other); + } + }; + } + + + public Num signum() + { + if (p_sgn == null) p_sgn = new Num() { + + final Num t = Num.this; + + + @Override + public double value() + { + return Math.signum(t.value()); + } + }; + + return p_sgn; + } + + + public boolean isNegative() + { + return value() < 0; + } + + + public boolean isPositive() + { + return value() > 0; + } + + + public boolean isZero() + { + return value() == 0; + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(value()); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Num)) return false; + final Num other = (Num) obj; + + return value() == other.value(); + } + + + @Override + public String toString() + { + return Double.toString(value()); + } + + + /** + * @return vect with both coords of this size + */ + public Vect toVectXY() + { + return Vect.make(this); + } +} diff --git a/src/mightypork/utils/math/constraints/num/NumBound.java b/src/mightypork/utils/math/constraints/num/NumBound.java new file mode 100644 index 0000000..168f32d --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/NumBound.java @@ -0,0 +1,16 @@ +package mightypork.utils.math.constraints.num; + + +/** + * Interface for something that has/is a Num. Num itself implements it as well. + * + * @author Ondřej Hruška (MightyPork) + */ +public interface NumBound { + + /** + * @return the Num value + */ + Num getNum(); + +} diff --git a/src/mightypork/utils/math/constraints/num/NumConst.java b/src/mightypork/utils/math/constraints/num/NumConst.java new file mode 100644 index 0000000..22dc915 --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/NumConst.java @@ -0,0 +1,270 @@ +package mightypork.utils.math.constraints.num; + + +import mightypork.utils.math.constraints.num.caching.NumDigest; + + +/** + * Constant number.
+ * It's arranged so that operations with constant arguments yield constant + * results. + * + * @author Ondřej Hruška (MightyPork) + */ +public class NumConst extends Num { + + private final double value; + private NumDigest digest; + + + NumConst(Num copied) + { + this.value = copied.value(); + } + + + NumConst(double value) + { + this.value = value; + } + + + @Override + public double value() + { + return value; + } + + + /** + * @deprecated No good to copy a constant. + */ + @Override + @Deprecated + public NumConst freeze() + { + return this; + } + + + @Override + public NumDigest digest() + { + return (digest != null) ? digest : (digest = super.digest()); + } + + + @Override + public NumConst add(double addend) + { + return Num.make(value() + addend); + } + + + public NumConst add(NumConst addend) + { + return Num.make(value + addend.value); + } + + + @Override + public NumConst sub(double subtrahend) + { + return add(-subtrahend); + } + + + public NumConst sub(NumConst addend) + { + return Num.make(value - addend.value); + } + + + @Override + public NumConst mul(double factor) + { + return Num.make(value() * factor); + } + + + public NumConst mul(NumConst addend) + { + return Num.make(value * addend.value); + } + + + @Override + public NumConst div(double factor) + { + return mul(1 / factor); + } + + + public NumConst div(NumConst addend) + { + return Num.make(value / addend.value); + } + + + @Override + public NumConst perc(double percents) + { + return mul(percents / 100); + } + + + @Override + public NumConst neg() + { + return mul(-1); + } + + + @Override + public NumConst abs() + { + return Num.make(Math.abs(value())); + } + + + @Override + public NumConst max(double other) + { + return Num.make(Math.max(value(), other)); + } + + + @Override + public NumConst min(double other) + { + return Num.make(Math.min(value(), other)); + } + + + @Override + public NumConst pow(double power) + { + return Num.make(Math.pow(value(), power)); + } + + + @Override + public NumConst square() + { + final double v = value(); + return Num.make(v * v); + } + + + @Override + public NumConst cube() + { + final double v = value(); + return Num.make(v * v * v); + } + + + @Override + public NumConst sqrt() + { + return Num.make(Math.sqrt(value())); + } + + + @Override + public NumConst cbrt() + { + return Num.make(Math.cbrt(value())); + } + + + @Override + public NumConst sin() + { + return Num.make(Math.sin(value())); + } + + + @Override + public NumConst cos() + { + return Num.make(Math.cos(value())); + } + + + @Override + public NumConst tan() + { + return Num.make(Math.tan(value())); + } + + + @Override + public NumConst asin() + { + return Num.make(Math.asin(value())); + } + + + @Override + public NumConst acos() + { + return Num.make(Math.acos(value())); + } + + + @Override + public NumConst atan() + { + return Num.make(Math.atan(value())); + } + + + @Override + public NumConst signum() + { + return Num.make(Math.signum(value())); + } + + + @Override + public NumConst average(double other) + { + return Num.make((value() + other) / 2); + } + + + public NumConst average(NumConst other) + { + return super.average(other).freeze(); + } + + + @Override + public NumConst round() + { + return Num.make(Math.round(value())); + } + + + @Override + public NumConst ceil() + { + return Num.make(Math.ceil(value())); + } + + + @Override + public NumConst floor() + { + return Num.make(Math.floor(value())); + } + + + @Override + public NumConst half() + { + return mul(0.5); + } + +} diff --git a/src/mightypork/utils/math/constraints/num/PluggableNumBound.java b/src/mightypork/utils/math/constraints/num/PluggableNumBound.java new file mode 100644 index 0000000..032bdc9 --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/PluggableNumBound.java @@ -0,0 +1,16 @@ +package mightypork.utils.math.constraints.num; + + +/** + * Pluggable numeric constraint + * + * @author Ondřej Hruška (MightyPork) + */ +public interface PluggableNumBound extends NumBound { + + /** + * @param num bound to set + */ + abstract void setNum(NumBound num); + +} diff --git a/src/mightypork/utils/math/constraints/num/batch/NumMul.java b/src/mightypork/utils/math/constraints/num/batch/NumMul.java new file mode 100644 index 0000000..016bea5 --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/batch/NumMul.java @@ -0,0 +1,51 @@ +package mightypork.utils.math.constraints.num.batch; + + +import java.util.ArrayList; +import java.util.List; + +import mightypork.utils.math.constraints.num.Num; + + +/** + * Expandable multiplication of multiple numbers + * + * @author Ondřej Hruška (MightyPork) + */ +public class NumMul extends Num { + + private final List factors = new ArrayList<>(); + + + @Override + public double value() + { + double v = 1; + for (final Num n : factors) { + if (n != null) v *= n.value(); + } + return v; + } + + + /** + * Add a number to the multiplication + * + * @param factor added number + */ + public void addFactor(Num factor) + { + factors.add(factor); + } + + + /** + * Add a number to the multiplication + * + * @param factor added number + */ + public void addFactor(double factor) + { + factors.add(Num.make(factor)); + } +} diff --git a/src/mightypork/utils/math/constraints/num/batch/NumSum.java b/src/mightypork/utils/math/constraints/num/batch/NumSum.java new file mode 100644 index 0000000..f63f5fe --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/batch/NumSum.java @@ -0,0 +1,51 @@ +package mightypork.utils.math.constraints.num.batch; + + +import java.util.ArrayList; +import java.util.List; + +import mightypork.utils.math.constraints.num.Num; + + +/** + * Expandable sum of multiple numbers + * + * @author Ondřej Hruška (MightyPork) + */ +public class NumSum extends Num { + + private final List summands = new ArrayList<>(); + + + @Override + public double value() + { + double v = 0; + for (final Num n : summands) { + if (n != null) v += n.value(); + } + return v; + } + + + /** + * Add a number to the sum + * + * @param summand added number + */ + public void addSummand(Num summand) + { + summands.add(summand); + } + + + /** + * Add a number to the sum + * + * @param summand added number + */ + public void addSummand(double summand) + { + summands.add(Num.make(summand)); + } +} diff --git a/src/mightypork/utils/math/constraints/num/caching/AbstractNumCache.java b/src/mightypork/utils/math/constraints/num/caching/AbstractNumCache.java new file mode 100644 index 0000000..fc768b6 --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/caching/AbstractNumCache.java @@ -0,0 +1,84 @@ +package mightypork.utils.math.constraints.num.caching; + + +import mightypork.utils.math.constraints.CachedConstraint; +import mightypork.utils.math.constraints.num.Num; +import mightypork.utils.math.constraints.num.proxy.NumAdapter; +import mightypork.utils.math.constraints.num.var.NumVar; + + +/** + *

+ * A Num cache. + *

+ *

+ * Values are held in a caching VectVar, and digest caching is enabled by + * default. + *

+ * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class AbstractNumCache extends NumAdapter implements CachedConstraint { + + 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(); + + onConstraintChanged(); + } + + + @Override + public abstract void onConstraintChanged(); + + + @Override + public abstract Num getCacheSource(); + + + @Override + public final void enableCaching(boolean yes) + { + cachingEnabled = yes; + enableDigestCaching(yes); + } + + + @Override + public final boolean isCachingEnabled() + { + return cachingEnabled; + } + +} diff --git a/src/mightypork/utils/math/constraints/num/caching/NumCache.java b/src/mightypork/utils/math/constraints/num/caching/NumCache.java new file mode 100644 index 0000000..36d8389 --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/caching/NumCache.java @@ -0,0 +1,35 @@ +package mightypork.utils.math.constraints.num.caching; + + +import mightypork.utils.math.constraints.num.Num; + + +/** + * Num cache implementation + * + * @author Ondřej Hruška (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 + public void onConstraintChanged() + { + } + +} diff --git a/src/mightypork/utils/math/constraints/num/caching/NumDigest.java b/src/mightypork/utils/math/constraints/num/caching/NumDigest.java new file mode 100644 index 0000000..62247f8 --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/caching/NumDigest.java @@ -0,0 +1,23 @@ +package mightypork.utils.math.constraints.num.caching; + + +import mightypork.utils.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); + } +} diff --git a/src/mightypork/utils/math/constraints/num/proxy/NumAdapter.java b/src/mightypork/utils/math/constraints/num/proxy/NumAdapter.java new file mode 100644 index 0000000..5b2693d --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/proxy/NumAdapter.java @@ -0,0 +1,18 @@ +package mightypork.utils.math.constraints.num.proxy; + + +import mightypork.utils.math.constraints.num.Num; + + +public abstract class NumAdapter extends Num { + + protected abstract Num getSource(); + + + @Override + public double value() + { + return getSource().value(); + } + +} diff --git a/src/mightypork/utils/math/constraints/num/proxy/NumProxy.java b/src/mightypork/utils/math/constraints/num/proxy/NumProxy.java new file mode 100644 index 0000000..ea70816 --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/proxy/NumProxy.java @@ -0,0 +1,43 @@ +package mightypork.utils.math.constraints.num.proxy; + + +import mightypork.utils.math.constraints.num.Num; +import mightypork.utils.math.constraints.num.NumBound; +import mightypork.utils.math.constraints.num.PluggableNumBound; + + +/** + * Pluggable num proxy + * + * @author Ondřej Hruška (MightyPork) + */ +public class NumProxy extends NumAdapter implements PluggableNumBound { + + private NumBound backing = null; + + + public NumProxy() + { + } + + + public NumProxy(NumBound bound) + { + backing = bound; + } + + + @Override + public void setNum(NumBound num) + { + this.backing = num; + } + + + @Override + protected Num getSource() + { + return backing.getNum(); + } + +} diff --git a/src/mightypork/utils/math/constraints/num/var/NumMutable.java b/src/mightypork/utils/math/constraints/num/var/NumMutable.java new file mode 100644 index 0000000..e359473 --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/var/NumMutable.java @@ -0,0 +1,41 @@ +package mightypork.utils.math.constraints.num.var; + + +import mightypork.utils.math.constraints.num.Num; + + +/** + * Mutable numeric variable + * + * @author Ondřej Hruška (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); + } + +} diff --git a/src/mightypork/utils/math/constraints/num/var/NumVar.java b/src/mightypork/utils/math/constraints/num/var/NumVar.java new file mode 100644 index 0000000..dfb48d1 --- /dev/null +++ b/src/mightypork/utils/math/constraints/num/var/NumVar.java @@ -0,0 +1,42 @@ +package mightypork.utils.math.constraints.num.var; + + +import mightypork.utils.math.constraints.num.Num; + + +/** + * Mutable numeric variable. + * + * @author Ondřej Hruška (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; + } + +} diff --git a/src/mightypork/utils/math/constraints/rect/PluggableRectBound.java b/src/mightypork/utils/math/constraints/rect/PluggableRectBound.java new file mode 100644 index 0000000..c29a0e9 --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/PluggableRectBound.java @@ -0,0 +1,16 @@ +package mightypork.utils.math.constraints.rect; + + +/** + * Pluggable rect bound + * + * @author Ondřej Hruška (MightyPork) + */ +public interface PluggableRectBound extends RectBound { + + /** + * @param rect context to set + */ + abstract void setRect(RectBound rect); + +} diff --git a/src/mightypork/utils/math/constraints/rect/Rect.java b/src/mightypork/utils/math/constraints/rect/Rect.java new file mode 100644 index 0000000..65432e0 --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/Rect.java @@ -0,0 +1,1062 @@ +package mightypork.utils.math.constraints.rect; + + +import mightypork.utils.annotations.FactoryMethod; +import mightypork.utils.math.constraints.CachedDigestable; +import mightypork.utils.math.constraints.DigestCache; +import mightypork.utils.math.constraints.num.Num; +import mightypork.utils.math.constraints.num.NumConst; +import mightypork.utils.math.constraints.rect.builders.TiledRect; +import mightypork.utils.math.constraints.rect.caching.RectCache; +import mightypork.utils.math.constraints.rect.caching.RectDigest; +import mightypork.utils.math.constraints.rect.proxy.RectProxy; +import mightypork.utils.math.constraints.rect.proxy.RectVectAdapter; +import mightypork.utils.math.constraints.rect.var.RectVar; +import mightypork.utils.math.constraints.vect.Vect; +import mightypork.utils.math.constraints.vect.VectConst; + + +/** + * Common methods for all kinds of Rects + * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class Rect implements RectBound, CachedDigestable { + + 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 RectProxy(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 Rect p_axis_v; + private Rect p_axis_h; + + private final DigestCache dc = new DigestCache() { + + @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); + } + + + public Rect shrink(Num x, Num 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 growRight(-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); + } + + + public Rect grow(Num x, Num 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(); + } + + }; + } + + + /** + * Round coords down + * + * @return result + */ + public Rect floor() + { + + return new Rect() { + + private final Rect t = Rect.this; + + + @Override + public Vect size() + { + return t.size().floor(); + } + + + @Override + public Vect origin() + { + return t.origin().floor(); + } + + }; + } + + + /** + * Round coords up + * + * @return result + */ + public Rect ceil() + { + + return new Rect() { + + private final Rect t = Rect.this; + + + @Override + public Vect size() + { + return t.size().ceil(); + } + + + @Override + public Vect origin() + { + return t.origin().ceil(); + } + + }; + } + + + 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)); + } + + + public Rect axisV() + { + return p_axis_v != null ? p_axis_v : (p_axis_v = topCenter().expand(Num.ZERO, Num.ZERO, Num.ZERO, height())); + } + + + public Rect axisH() + { + return p_axis_h != null ? p_axis_h : (p_axis_h = centerLeft().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); + } + + + /** + * Check for intersection + * + * @param other other rect + * @return true if they intersect + */ + public boolean intersectsWith(Rect other) + { + double tw = this.size().x(); + double th = this.size().y(); + double rw = other.size().x(); + double rh = other.size().y(); + + if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) { + return false; + } + + final double tx = this.origin().x(); + final double ty = this.origin().y(); + final double rx = other.origin().x(); + final double ry = other.origin().y(); + + rw += rx; + rh += ry; + tw += tx; + th += ty; + + // overflow || intersect + return ((rw < rx || rw > tx) && (rh < ry || rh > ty) && (tw < tx || tw > rx) && (th < ty || th > ry)); + } + + +} diff --git a/src/mightypork/utils/math/constraints/rect/RectBound.java b/src/mightypork/utils/math/constraints/rect/RectBound.java new file mode 100644 index 0000000..900913b --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/RectBound.java @@ -0,0 +1,16 @@ +package mightypork.utils.math.constraints.rect; + + +/** + * Interface for something that has/is a Rect. Rect itself implements it as + * well. + * + * @author Ondřej Hruška (MightyPork) + */ +public interface RectBound { + + /** + * @return the Rect value + */ + Rect getRect(); +} diff --git a/src/mightypork/utils/math/constraints/rect/RectConst.java b/src/mightypork/utils/math/constraints/rect/RectConst.java new file mode 100644 index 0000000..3ed6f1a --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/RectConst.java @@ -0,0 +1,486 @@ +package mightypork.utils.math.constraints.rect; + + +import mightypork.utils.math.constraints.num.NumConst; +import mightypork.utils.math.constraints.rect.caching.RectDigest; +import mightypork.utils.math.constraints.vect.Vect; +import mightypork.utils.math.constraints.vect.VectConst; + + +/** + * Rectangle with constant bounds, that can never change.
+ * It's arranged so that operations with constant arguments yield constant + * results. + * + * @author Ondřej Hruška (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; + private RectConst v_floor; + private RectConst v_ceil; + private RectConst v_axis_v; + private RectConst v_axis_h; + + + /** + * 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 RectConst floor() + { + return (v_floor != null) ? v_floor : (v_floor = Rect.make(pos.floor(), size.floor())); + } + + + @Override + public RectConst ceil() + { + return (v_ceil != null) ? v_ceil : (v_ceil = Rect.make(pos.ceil(), size.ceil())); + } + + + @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 axisV() + { + return (v_axis_v != null) ? v_axis_v : (v_axis_v = super.axisV().freeze()); + } + + + @Override + public Rect axisH() + { + return (v_axis_h != null) ? v_axis_h : (v_axis_h = super.axisH().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(); + } + + + public RectConst shrink(NumConst x, NumConst y) + { + return super.shrink(x, y).freeze(); + } + + + public RectConst grow(NumConst x, NumConst y) + { + return super.grow(x, y).freeze(); + } +} diff --git a/src/mightypork/utils/math/constraints/rect/builders/TiledRect.java b/src/mightypork/utils/math/constraints/rect/builders/TiledRect.java new file mode 100644 index 0000000..24eafa5 --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/builders/TiledRect.java @@ -0,0 +1,129 @@ +package mightypork.utils.math.constraints.rect.builders; + + +import mightypork.utils.math.constraints.num.Num; +import mightypork.utils.math.constraints.rect.Rect; +import mightypork.utils.math.constraints.rect.proxy.RectProxy; +import mightypork.utils.math.constraints.vect.Vect; + + +/** + * Utility for cutting rect into evenly sized cells. + * + * @author Ondřej Hruška (MightyPork) + */ +public class TiledRect extends RectProxy { + + final private int tilesY; + final private int tilesX; + final private Num perRow; + final private Num perCol; + + /** Left top tile */ + private Rect aTile; + + + 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); + + this.aTile = Rect.make(origin(), perCol, perRow); + } + + + /** + * Set tile overlap. Applies only to tile, not span. + * + * @param overlap how far to overlap to neighbouring tiles on all sides + */ + public void setOverlap(double overlap) + { + aTile = aTile.grow(overlap); + } + + + /** + * 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) + { + if (x >= tilesX || x < 0) { + throw new IndexOutOfBoundsException("X coordinate out fo range: " + x); + } + + if (y >= tilesY || y < 0) { + throw new IndexOutOfBoundsException("Y coordinate out of range: " + y); + } + + return aTile.move(perCol.mul(x), perRow.mul(y)); + } + + + /** + * Get a span (tile spanning across multiple cells) + * + * @param x x start position + * @param y y start position + * @param size_x horizontal size (columns) + * @param size_y vertical size (rows) + * @return tile the tile + * @throws IndexOutOfBoundsException when invalid index / size is specified. + */ + public Rect span(int x, int y, int size_x, int size_y) + { + final int x_to = x + size_x - 1; + final int y_to = y + size_y - 1; + + if (size_x <= 0 || size_y <= 0) { + throw new IndexOutOfBoundsException("Size must be > 0."); + } + + if (x >= tilesX || x < 0 || x_to >= tilesX || x_to < 0) { + throw new IndexOutOfBoundsException("X coordinate(s) out of range."); + } + + if (y >= tilesY || y < 0 || y_to >= tilesY || y_to < 0) { + throw new IndexOutOfBoundsException("Y coordinate(s) out of range."); + } + + final Vect orig = origin().add(perCol.mul(x), perRow.mul(y)); + + return Rect.make(orig, perCol.mul(size_x), perRow.mul(size_y)); + } + + + /** + * 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, 0); + } + + + /** + * 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(0, n); + } +} diff --git a/src/mightypork/utils/math/constraints/rect/caching/AbstractRectCache.java b/src/mightypork/utils/math/constraints/rect/caching/AbstractRectCache.java new file mode 100644 index 0000000..b193b37 --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/caching/AbstractRectCache.java @@ -0,0 +1,75 @@ +package mightypork.utils.math.constraints.rect.caching; + + +import mightypork.utils.math.constraints.CachedConstraint; +import mightypork.utils.math.constraints.rect.Rect; +import mightypork.utils.math.constraints.rect.proxy.RectAdapter; +import mightypork.utils.math.constraints.rect.var.RectVar; + + +/** + *

+ * A rect cache. + *

+ *

+ * Values are held in a caching VectVar, and digest caching is enabled by + * default. + *

+ * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class AbstractRectCache extends RectAdapter implements CachedConstraint { + + 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(); + + onConstraintChanged(); + } + + + @Override + public final void enableCaching(boolean yes) + { + cachingEnabled = yes; + enableDigestCaching(yes); + } + + + @Override + public final boolean isCachingEnabled() + { + return cachingEnabled; + } + +} diff --git a/src/mightypork/utils/math/constraints/rect/caching/RectCache.java b/src/mightypork/utils/math/constraints/rect/caching/RectCache.java new file mode 100644 index 0000000..dc364a8 --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/caching/RectCache.java @@ -0,0 +1,35 @@ +package mightypork.utils.math.constraints.rect.caching; + + +import mightypork.utils.math.constraints.rect.Rect; + + +/** + * Rect cache implementation + * + * @author Ondřej Hruška (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 + public void onConstraintChanged() + { + } + +} diff --git a/src/mightypork/utils/math/constraints/rect/caching/RectDigest.java b/src/mightypork/utils/math/constraints/rect/caching/RectDigest.java new file mode 100644 index 0000000..263feaf --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/caching/RectDigest.java @@ -0,0 +1,41 @@ +package mightypork.utils.math.constraints.rect.caching; + + +import mightypork.utils.math.constraints.rect.Rect; + + +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) + { + this.x = rect.origin().x(); + this.y = rect.origin().y(); + + this.width = rect.size().x(); + this.height = rect.size().y(); + + this.left = x; + this.right = x + width; + this.top = y; + this.bottom = y + height; + } + + + @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); + } +} diff --git a/src/mightypork/utils/math/constraints/rect/proxy/RectAdapter.java b/src/mightypork/utils/math/constraints/rect/proxy/RectAdapter.java new file mode 100644 index 0000000..cd1cd85 --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/proxy/RectAdapter.java @@ -0,0 +1,58 @@ +package mightypork.utils.math.constraints.rect.proxy; + + +import mightypork.utils.math.constraints.rect.Rect; +import mightypork.utils.math.constraints.vect.Vect; +import mightypork.utils.math.constraints.vect.proxy.VectAdapter; + + +/** + * Rect proxy with abstract method for plugging in / generating rect + * dynamically. + * + * @author Ondřej Hruška (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; + } + +} diff --git a/src/mightypork/utils/math/constraints/rect/proxy/RectProxy.java b/src/mightypork/utils/math/constraints/rect/proxy/RectProxy.java new file mode 100644 index 0000000..aab3159 --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/proxy/RectProxy.java @@ -0,0 +1,43 @@ +package mightypork.utils.math.constraints.rect.proxy; + + +import mightypork.utils.math.constraints.rect.PluggableRectBound; +import mightypork.utils.math.constraints.rect.Rect; +import mightypork.utils.math.constraints.rect.RectBound; + + +/** + * Pluggable rect proxy + * + * @author Ondřej Hruška (MightyPork) + */ +public class RectProxy extends RectAdapter implements PluggableRectBound { + + private RectBound backing = null; + + + public RectProxy() + { + } + + + public RectProxy(RectBound proxied) + { + backing = proxied; + } + + + @Override + public void setRect(RectBound proxied) + { + this.backing = proxied; + } + + + @Override + public Rect getSource() + { + return backing.getRect(); + } + +} diff --git a/src/mightypork/utils/math/constraints/rect/proxy/RectVectAdapter.java b/src/mightypork/utils/math/constraints/rect/proxy/RectVectAdapter.java new file mode 100644 index 0000000..47f832f --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/proxy/RectVectAdapter.java @@ -0,0 +1,39 @@ +package mightypork.utils.math.constraints.rect.proxy; + + +import mightypork.utils.math.constraints.rect.Rect; +import mightypork.utils.math.constraints.vect.Vect; + + +/** + * Rect made of two {@link Vect}s + * + * @author Ondřej Hruška (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; + } + +} diff --git a/src/mightypork/utils/math/constraints/rect/var/RectMutable.java b/src/mightypork/utils/math/constraints/rect/var/RectMutable.java new file mode 100644 index 0000000..0506dd8 --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/var/RectMutable.java @@ -0,0 +1,91 @@ +package mightypork.utils.math.constraints.rect.var; + + +import mightypork.utils.math.constraints.rect.Rect; +import mightypork.utils.math.constraints.vect.Vect; + + +/** + * Mutable rectangle; operations change it's state. + * + * @author Ondřej Hruška (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); + } + + + public abstract void setOrigin(double x, double y); + + + public void setOrigin(Vect origin) + { + setOrigin(origin.x(), origin.y()); + } + + + public void setSize(Vect size) + { + setSize(size.x(), size.y()); + } + + + public abstract void setSize(double x, double y); +} diff --git a/src/mightypork/utils/math/constraints/rect/var/RectVar.java b/src/mightypork/utils/math/constraints/rect/var/RectVar.java new file mode 100644 index 0000000..88a82c3 --- /dev/null +++ b/src/mightypork/utils/math/constraints/rect/var/RectVar.java @@ -0,0 +1,55 @@ +package mightypork.utils.math.constraints.rect.var; + + +import mightypork.utils.math.constraints.vect.Vect; +import mightypork.utils.math.constraints.vect.var.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(double x, double y) + { + this.pos.setTo(x, y); + } + + + @Override + public void setSize(double x, double y) + { + this.size.setTo(x, y); + } +} diff --git a/src/mightypork/utils/math/constraints/vect/PluggableVectBound.java b/src/mightypork/utils/math/constraints/vect/PluggableVectBound.java new file mode 100644 index 0000000..29c9884 --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/PluggableVectBound.java @@ -0,0 +1,16 @@ +package mightypork.utils.math.constraints.vect; + + +/** + * Pluggable vector constraint + * + * @author Ondřej Hruška (MightyPork) + */ +public interface PluggableVectBound extends VectBound { + + /** + * @param num bound to set + */ + abstract void setVect(VectBound num); + +} diff --git a/src/mightypork/utils/math/constraints/vect/Vect.java b/src/mightypork/utils/math/constraints/vect/Vect.java new file mode 100644 index 0000000..4841f9b --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/Vect.java @@ -0,0 +1,1270 @@ +package mightypork.utils.math.constraints.vect; + + +import mightypork.utils.annotations.FactoryMethod; +import mightypork.utils.math.constraints.CachedDigestable; +import mightypork.utils.math.constraints.DigestCache; +import mightypork.utils.math.constraints.num.Num; +import mightypork.utils.math.constraints.num.NumConst; +import mightypork.utils.math.constraints.rect.Rect; +import mightypork.utils.math.constraints.vect.caching.VectCache; +import mightypork.utils.math.constraints.vect.caching.VectDigest; +import mightypork.utils.math.constraints.vect.proxy.VectNumAdapter; +import mightypork.utils.math.constraints.vect.proxy.VectProxy; +import mightypork.utils.math.constraints.vect.var.VectVar; + + +/** + * The most basic Vec methods + * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class Vect implements VectBound, CachedDigestable { + + public static final VectConst ZERO = new VectConst(0, 0, 0); + public static final VectConst ONE = new VectConst(1, 1, 1); + + + @FactoryMethod + public static Vect make(Num xy) + { + return make(xy, xy); + } + + + @FactoryMethod + public static Vect make(Num xc, Num yc) + { + return Vect.make(xc, yc, Num.ZERO); + } + + + @FactoryMethod + public static Vect make(Num xc, Num yc, Num zc) + { + return new VectNumAdapter(xc, yc, zc); + } + + + @FactoryMethod + public static Vect make(VectBound bound) + { + return new VectProxy(bound); + } + + + @FactoryMethod + public static VectConst make(NumConst xy) + { + return make(xy, xy); + } + + + @FactoryMethod + public static VectConst make(NumConst xc, NumConst yc) + { + return Vect.make(xc, yc, Num.ZERO); + } + + + @FactoryMethod + public static VectConst make(NumConst xc, NumConst yc, NumConst zc) + { + return new VectConst(xc.value(), yc.value(), zc.value()); + } + + + @FactoryMethod + public static VectConst make(double xy) + { + return make(xy, xy); + } + + + @FactoryMethod + public static VectConst make(double x, double y) + { + return Vect.make(x, y, 0); + } + + + @FactoryMethod + public static VectConst make(double x, double y, double z) + { + return new VectConst(x, y, z); + } + + + @FactoryMethod + public static VectVar makeVar() + { + return Vect.makeVar(Vect.ZERO); + } + + + @FactoryMethod + public static VectVar makeVar(double x, double y) + { + return Vect.makeVar(x, y, 0); + } + + + @FactoryMethod + public static VectVar makeVar(Vect copied) + { + return Vect.makeVar(copied.x(), copied.y(), copied.z()); + } + + + @FactoryMethod + public static VectVar makeVar(double x, double y, double z) + { + return new VectVar(x, y, z); + } + + + public static VectConst fromString(String string) + { + try { + String s = string.trim(); + + // drop whitespace + s = s.replaceAll("\\s", ""); + + // drop brackets + s = s.replaceAll("[\\(\\[\\{\\)\\]\\}]", ""); + + // norm separators + s = s.replaceAll("[:;]", "|"); + + // norm floating point + s = s.replaceAll("[,]", "."); + + final String[] parts = s.split("[|]"); + + if (parts.length >= 2) { + + final double x = Double.parseDouble(parts[0].trim()); + final double y = Double.parseDouble(parts[1].trim()); + + if (parts.length == 2) { + return Vect.make(x, y); + } + + final double z = Double.parseDouble(parts[2].trim()); + + return Vect.make(x, y, z); + } + + } catch (final RuntimeException e) { + return null; + } + return null; + } + + + @Override + public String toString() + { + return String.format("(%.1f ; %.1f ; %.1f)", x(), y(), z()); + } + + private Num p_size; + private Vect p_neg; + private Vect p_half; + private Vect p_abs; + + private Num p_xc; + private Num p_yc; + private Num p_zc; + + private final DigestCache dc = new DigestCache() { + + @Override + protected VectDigest createDigest() + { + return new VectDigest(Vect.this); + } + }; + + + /** + * @return X coordinate + */ + public abstract double x(); + + + /** + * @return Y coordinate + */ + public abstract double y(); + + + /** + * (Implemented in Vect for convenience when creating 2D vects) + * + * @return Z coordinate + */ + public double z() + { + return 0; + } + + + /** + * @return X rounded + */ + public int xi() + { + return (int) Math.round(x()); + } + + + /** + * @return Y rounded + */ + public int yi() + { + return (int) Math.round(y()); + } + + + /** + * @return Z rounded + */ + public int zi() + { + return (int) Math.round(z()); + } + + + /** + * @return X constraint + */ + public Num xn() + { + if (p_xc == null) p_xc = new Num() { + + @Override + public double value() + { + return x(); + } + }; + + return p_xc; + } + + + /** + * @return Y constraint + */ + public Num yn() + { + if (p_yc == null) p_yc = new Num() { + + @Override + public double value() + { + return y(); + } + }; + + return p_yc; + } + + + /** + * @return Z constraint + */ + public Num zn() + { + if (p_zc == null) p_zc = new Num() { + + @Override + public double value() + { + return z(); + } + }; + + return p_zc; + } + + + @Override + public final Vect getVect() + { + return this; + } + + + /** + * @return true if zero + */ + public boolean isZero() + { + return x() == 0 && y() == 0 && z() == 0; + } + + + /** + * Get a static immutable copy of the current state. + * + * @return a immutable copy + */ + public VectConst freeze() + { + return new VectConst(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 VectCache cached() + { + return new VectCache(this); + } + + + @Override + public VectDigest 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(); + } + + + /** + * Get a view with X set to given value + * + * @param x x coordinate + * @return result + */ + public Vect withX(double x) + { + return withX(Num.make(x)); + } + + + /** + * Get a view with Y set to given value + * + * @param y y coordinate + * @return result + */ + public Vect withY(double y) + { + return withY(Num.make(y)); + } + + + /** + * Get a view with Z set to given value + * + * @param z z coordinate + * @return result + */ + public Vect withZ(double z) + { + return withZ(Num.make(z)); + } + + + public Vect withX(final Num x) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return x.value(); + } + + + @Override + public double y() + { + return t.z(); + } + + + @Override + public double z() + { + return t.z(); + } + }; + } + + + public Vect withY(final Num y) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return t.x(); + } + + + @Override + public double y() + { + return y.value(); + } + + + @Override + public double z() + { + return t.z(); + } + }; + } + + + public Vect withZ(final Num z) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return t.x(); + } + + + @Override + public double y() + { + return t.y(); + } + + + @Override + public double z() + { + return z.value(); + } + }; + } + + + /** + * Get absolute value (positive) + * + * @return result + */ + public Vect abs() + { + if (p_abs == null) p_abs = new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return Math.abs(t.x()); + } + + + @Override + public double y() + { + return Math.abs(t.y()); + } + + + @Override + public double z() + { + return Math.abs(t.z()); + } + }; + + return p_abs; + } + + + /** + * Add a vector. + * + * @param vec offset + * @return result + */ + public Vect add(Vect vec) + { + return add(vec.xn(), vec.yn(), vec.zn()); + } + + + /** + * Add to each component.
+ * Z is unchanged. + * + * @param x x offset + * @param y y offset + * @return result + */ + public Vect add(double x, double y) + { + return add(x, y, 0); + } + + + /** + * Add to each component. + * + * @param x x offset + * @param y y offset + * @param z z offset + * @return result + */ + public Vect add(final double x, final double y, final double z) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return t.x() + x; + } + + + @Override + public double y() + { + return t.y() + y; + } + + + @Override + public double z() + { + return t.z() + z; + } + }; + } + + + public Vect add(Num x, Num y) + { + return add(x, y, Num.ZERO); + } + + + public Vect add(final Num x, final Num y, final Num z) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return t.x() + x.value(); + } + + + @Override + public double y() + { + return t.y() + y.value(); + } + + + @Override + public double z() + { + return t.z() + z.value(); + } + }; + } + + + /** + * Get copy divided by two + * + * @return result + */ + public Vect half() + { + if (p_half == null) p_half = mul(0.5); + return p_half; + } + + + /** + * Multiply each component. + * + * @param d multiplier + * @return result + */ + public Vect mul(double d) + { + return mul(d, d, d); + } + + + /** + * Multiply each component. + * + * @param vec vector of multipliers + * @return result + */ + public Vect mul(Vect vec) + { + return mul(vec.xn(), vec.yn(), vec.zn()); + } + + + /** + * Multiply each component.
+ * Z is unchanged. + * + * @param x x multiplier + * @param y y multiplier + * @return result + */ + public Vect mul(double x, double y) + { + return mul(x, y, 1); + } + + + /** + * Multiply each component. + * + * @param x x multiplier + * @param y y multiplier + * @param z z multiplier + * @return result + */ + public Vect mul(final double x, final double y, final double z) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return t.x() * x; + } + + + @Override + public double y() + { + return t.y() * y; + } + + + @Override + public double z() + { + return t.z() * z; + } + }; + } + + + /** + * Multiply each component. + * + * @param d multiplier + * @return result + */ + public Vect mul(final Num d) + { + return mul(d, d, d); + } + + + /** + * Multiply each component. + * + * @param x x multiplier + * @param y y multiplier + * @return result + */ + public Vect mul(final Num x, final Num y) + { + return mul(x, y, Num.ONE); + } + + + /** + * Multiply each component. + * + * @param x x multiplier + * @param y y multiplier + * @param z z multiplier + * @return result + */ + public Vect mul(final Num x, final Num y, final Num z) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return t.x() * x.value(); + } + + + @Override + public double y() + { + return t.y() * y.value(); + } + + + @Override + public double z() + { + return t.z() * z.value(); + } + }; + } + + + /** + * Round coordinates. + * + * @return result + */ + public Vect round() + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return Math.round(t.x()); + } + + + @Override + public double y() + { + return Math.round(t.y()); + } + + + @Override + public double z() + { + return Math.round(t.z()); + } + }; + } + + + /** + * Round coordinates down. + * + * @return result + */ + public Vect floor() + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return Math.floor(t.x()); + } + + + @Override + public double y() + { + return Math.floor(t.y()); + } + + + @Override + public double z() + { + return Math.floor(t.z()); + } + }; + } + + + /** + * Round coordinates up. + * + * @return result + */ + public Vect ceil() + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return Math.ceil(t.x()); + } + + + @Override + public double y() + { + return Math.ceil(t.y()); + } + + + @Override + public double z() + { + return Math.ceil(t.z()); + } + }; + } + + + /** + * Subtract vector. + * + * @param vec offset + * @return result + */ + public Vect sub(Vect vec) + { + return sub(vec.xn(), vec.yn(), vec.zn()); + } + + + /** + * Subtract a 2D vector.
+ * Z is unchanged. + * + * @param x x offset + * @param y y offset + * @return result + */ + public Vect sub(double x, double y) + { + return add(-x, -y, 0); + } + + + /** + * Subtract a 3D vector. + * + * @param x x offset + * @param y y offset + * @param z z offset + * @return result + */ + public Vect sub(double x, double y, double z) + { + return add(-x, -y, -z); + } + + + public Vect sub(Num x, Num y) + { + return sub(x, y, Num.ZERO); + } + + + public Vect sub(final Num x, final Num y, final Num z) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return t.x() - x.value(); + } + + + @Override + public double y() + { + return t.y() - y.value(); + } + + + @Override + public double z() + { + return t.z() - z.value(); + } + }; + } + + + /** + * Negate all coordinates (* -1) + * + * @return result + */ + public Vect neg() + { + if (p_neg == null) p_neg = mul(-1); + return p_neg; + } + + + /** + * Scale vector to given size. + * + * @param size size we need + * @return result + */ + public Vect norm(final Num size) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + final double tSize = t.size().value(); + final double nSize = size.value(); + + if (tSize == 0 || nSize == 0) return 0; + + return t.x() / (nSize / tSize); + } + + + @Override + public double y() + { + final double tSize = t.size().value(); + final double nSize = size.value(); + + if (tSize == 0 || nSize == 0) return 0; + + return t.y() / (nSize / tSize); + } + + + @Override + public double z() + { + final double tSize = t.size().value(); + final double nSize = size.value(); + + if (tSize == 0 || nSize == 0) return 0; + + return t.z() / (nSize / tSize); + } + }; + } + + + public Vect norm(final double size) + { + return norm(Num.make(size)); + } + + + /** + * Get distance to other point + * + * @param point other point + * @return distance + */ + public Num dist(final Vect point) + { + return new Num() { + + final Vect t = Vect.this; + + + @Override + public double value() + { + final double dx = t.x() - point.x(); + final double dy = t.y() - point.y(); + final double dz = t.z() - point.z(); + + return Math.sqrt(dx * dx + dy * dy + dz * dz); + } + }; + } + + + /** + * Get middle of line to other point + * + * @param point other point + * @return result + */ + public Vect midTo(final Vect point) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return (point.x() + t.x()) * 0.5; + } + + + @Override + public double y() + { + return (point.y() + t.y()) * 0.5; + } + + + @Override + public double z() + { + return (point.z() + t.z()) * 0.5; + } + }; + } + + + /** + * Create vector from this point to other point + * + * @param point second point + * @return result + */ + public Vect vectTo(final Vect point) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return (point.x() - t.x()); + } + + + @Override + public double y() + { + return (point.y() - t.y()); + } + + + @Override + public double z() + { + return (point.z() - t.z()); + } + }; + } + + + /** + * Get cross product (vector multiplication) + * + * @param vec other vector + * @return result + */ + public Vect cross(final Vect vec) + { + return new Vect() { + + final Vect t = Vect.this; + + + @Override + public double x() + { + return t.y() * vec.z() - t.z() * vec.y(); + } + + + @Override + public double y() + { + return t.z() * vec.x() - t.x() * vec.z(); + } + + + @Override + public double z() + { + return t.x() * vec.y() - t.y() * vec.x(); + } + }; + } + + + /** + * Get dot product (scalar multiplication) + * + * @param vec other vector + * @return dot product + */ + public Num dot(final Vect vec) + { + return new Num() { + + final Vect t = Vect.this; + + + @Override + public double value() + { + return t.x() * vec.x() + t.y() * vec.y() + t.z() * vec.z(); + } + }; + } + + + /** + * Get vector size + * + * @return size + */ + public Num size() + { + if (p_size == null) p_size = new Num() { + + final Vect t = Vect.this; + + + @Override + public double value() + { + final double x = t.x(), y = t.y(), z = t.z(); + return Math.sqrt(x * x + y * y + z * z); + } + }; + + return p_size; + } + + + /** + * Expand to a rect, with given growth to each side. + * + * @param left + * @param right + * @param top + * @param bottom + * @return the rect + */ + public Rect expand(double left, double right, double top, double bottom) + { + return Rect.make(this, Vect.ZERO).grow(left, right, top, bottom); + } + + + /** + * Expand to a rect, with given growth to each side. + * + * @param left + * @param right + * @param top + * @param bottom + * @return the rect + */ + public Rect expand(Num left, Num right, Num top, Num bottom) + { + return Rect.make(this, Vect.ZERO).grow(left, right, top, bottom); + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + Double.valueOf(x()).hashCode(); + result = prime * result + Double.valueOf(y()).hashCode(); + result = prime * result + Double.valueOf(z()).hashCode(); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Vect)) return false; + final Vect other = (Vect) obj; + + return x() == other.x() && y() == other.y() && z() == other.z(); + } + + + public final boolean isInside(Rect bounds) + { + return bounds.contains(this); + } + + + public Rect startRect() + { + return expand(0, 0, 0, 0); + } +} diff --git a/src/mightypork/utils/math/constraints/vect/VectBound.java b/src/mightypork/utils/math/constraints/vect/VectBound.java new file mode 100644 index 0000000..5d7bd3c --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/VectBound.java @@ -0,0 +1,16 @@ +package mightypork.utils.math.constraints.vect; + + +/** + * Interface for something that has/is a Vect. Vect itself implements it as + * well. + * + * @author Ondřej Hruška (MightyPork) + */ +public interface VectBound { + + /** + * @return the Vect value + */ + public Vect getVect(); +} diff --git a/src/mightypork/utils/math/constraints/vect/VectConst.java b/src/mightypork/utils/math/constraints/vect/VectConst.java new file mode 100644 index 0000000..0a11cc9 --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/VectConst.java @@ -0,0 +1,375 @@ +package mightypork.utils.math.constraints.vect; + + +import mightypork.utils.math.constraints.num.Num; +import mightypork.utils.math.constraints.num.NumConst; +import mightypork.utils.math.constraints.rect.RectConst; +import mightypork.utils.math.constraints.vect.caching.VectDigest; + + +/** + * Coordinate with immutable numeric values.
+ * This coordinate is guaranteed to never change, as opposed to view.
+ * It's arranged so that operations with constant arguments yield constant + * results. + * + * @author Ondřej Hruška (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(); + } + +} diff --git a/src/mightypork/utils/math/constraints/vect/caching/AbstractVectCache.java b/src/mightypork/utils/math/constraints/vect/caching/AbstractVectCache.java new file mode 100644 index 0000000..313c84f --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/caching/AbstractVectCache.java @@ -0,0 +1,75 @@ +package mightypork.utils.math.constraints.vect.caching; + + +import mightypork.utils.math.constraints.CachedConstraint; +import mightypork.utils.math.constraints.vect.Vect; +import mightypork.utils.math.constraints.vect.proxy.VectAdapter; +import mightypork.utils.math.constraints.vect.var.VectVar; + + +/** + *

+ * A vect cache. + *

+ *

+ * Values are held in a caching VectVar, and digest caching is enabled by + * default. + *

+ * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class AbstractVectCache extends VectAdapter implements CachedConstraint { + + 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(); + + onConstraintChanged(); + } + + + @Override + public final void enableCaching(boolean yes) + { + cachingEnabled = yes; + enableDigestCaching(yes); + } + + + @Override + public final boolean isCachingEnabled() + { + return cachingEnabled; + } + +} diff --git a/src/mightypork/utils/math/constraints/vect/caching/VectCache.java b/src/mightypork/utils/math/constraints/vect/caching/VectCache.java new file mode 100644 index 0000000..b292874 --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/caching/VectCache.java @@ -0,0 +1,35 @@ +package mightypork.utils.math.constraints.vect.caching; + + +import mightypork.utils.math.constraints.vect.Vect; + + +/** + * Vect cache implementation + * + * @author Ondřej Hruška (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 + public void onConstraintChanged() + { + } +} diff --git a/src/mightypork/utils/math/constraints/vect/caching/VectDigest.java b/src/mightypork/utils/math/constraints/vect/caching/VectDigest.java new file mode 100644 index 0000000..12a8847 --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/caching/VectDigest.java @@ -0,0 +1,27 @@ +package mightypork.utils.math.constraints.vect.caching; + + +import mightypork.utils.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); + } +} diff --git a/src/mightypork/utils/math/constraints/vect/proxy/VectAdapter.java b/src/mightypork/utils/math/constraints/vect/proxy/VectAdapter.java new file mode 100644 index 0000000..e5ade38 --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/proxy/VectAdapter.java @@ -0,0 +1,41 @@ +package mightypork.utils.math.constraints.vect.proxy; + + +import mightypork.utils.math.constraints.vect.Vect; + + +/** + * Vect proxy with abstract method for plugging in / generating coordinates + * dynamically. + * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class VectAdapter extends Vect { + + /** + * @return the proxied coord + */ + protected abstract Vect getSource(); + + + @Override + public double x() + { + return getSource().x(); + } + + + @Override + public double y() + { + return getSource().y(); + } + + + @Override + public double z() + { + return getSource().z(); + } + +} diff --git a/src/mightypork/utils/math/constraints/vect/proxy/VectNumAdapter.java b/src/mightypork/utils/math/constraints/vect/proxy/VectNumAdapter.java new file mode 100644 index 0000000..1f02d20 --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/proxy/VectNumAdapter.java @@ -0,0 +1,57 @@ +package mightypork.utils.math.constraints.vect.proxy; + + +import mightypork.utils.math.constraints.num.Num; +import mightypork.utils.math.constraints.num.NumBound; +import mightypork.utils.math.constraints.vect.Vect; + + +/** + * Coord view composed of given {@link NumBound}s, using their current values. + * + * @author Ondřej Hruška (MightyPork) + */ +public class VectNumAdapter extends Vect { + + private final Num constrX; + private final Num constrY; + private final Num constrZ; + + + public VectNumAdapter(Num x, Num y, Num z) + { + this.constrX = x; + this.constrY = y; + this.constrZ = z; + } + + + public VectNumAdapter(Num x, Num y) + { + this.constrX = x; + this.constrY = y; + this.constrZ = Num.ZERO; + } + + + @Override + public double x() + { + return constrX.value(); + } + + + @Override + public double y() + { + return constrY.value(); + } + + + @Override + public double z() + { + return constrZ.value(); + } + +} diff --git a/src/mightypork/utils/math/constraints/vect/proxy/VectProxy.java b/src/mightypork/utils/math/constraints/vect/proxy/VectProxy.java new file mode 100644 index 0000000..572d14f --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/proxy/VectProxy.java @@ -0,0 +1,43 @@ +package mightypork.utils.math.constraints.vect.proxy; + + +import mightypork.utils.math.constraints.vect.PluggableVectBound; +import mightypork.utils.math.constraints.vect.Vect; +import mightypork.utils.math.constraints.vect.VectBound; + + +/** + * Pluggable vect proxy + * + * @author Ondřej Hruška (MightyPork) + */ +public class VectProxy extends VectAdapter implements PluggableVectBound { + + private VectBound backing = null; + + + public VectProxy() + { + } + + + public VectProxy(VectBound proxied) + { + backing = proxied; + } + + + @Override + public void setVect(VectBound proxied) + { + this.backing = proxied; + } + + + @Override + protected Vect getSource() + { + return backing.getVect(); + } + +} diff --git a/src/mightypork/utils/math/constraints/vect/var/VectMutable.java b/src/mightypork/utils/math/constraints/vect/var/VectMutable.java new file mode 100644 index 0000000..dd00e06 --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/var/VectMutable.java @@ -0,0 +1,80 @@ +package mightypork.utils.math.constraints.vect.var; + + +import mightypork.utils.math.constraints.vect.Vect; + + +/** + * Mutable coord + * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class VectMutable extends Vect { + + /** + * Set all to zeros. + */ + public void reset() + { + setTo(0, 0, 0); + } + + + /** + * Set coordinates to match other coord. + * + * @param copied coord whose coordinates are used + */ + public void setTo(Vect copied) + { + setTo(copied.x(), copied.y(), copied.z()); + } + + + /** + * Set 2D coordinates.
+ * Z is unchanged. + * + * @param x x coordinate + * @param y y coordinate + */ + public void setTo(double x, double y) + { + setX(x); + setY(y); + } + + + /** + * Set coordinates. + * + * @param x x coordinate + * @param y y coordinate + * @param z z coordinate + */ + public abstract void setTo(double x, double y, double z); + + + /** + * Set X coordinate. + * + * @param x x coordinate + */ + public abstract void setX(double x); + + + /** + * Set Y coordinate. + * + * @param y y coordinate + */ + public abstract void setY(double y); + + + /** + * Set Z coordinate. + * + * @param z z coordinate + */ + public abstract void setZ(double z); +} diff --git a/src/mightypork/utils/math/constraints/vect/var/VectVar.java b/src/mightypork/utils/math/constraints/vect/var/VectVar.java new file mode 100644 index 0000000..04221f6 --- /dev/null +++ b/src/mightypork/utils/math/constraints/vect/var/VectVar.java @@ -0,0 +1,78 @@ +package mightypork.utils.math.constraints.vect.var; + + +/** + * Mutable coordinate.
+ * All Vec methods (except copy) alter data values and return this instance. + * + * @author Ondřej Hruška (MightyPork) + */ +public class VectVar extends VectMutable { + + private double x, y, z; + + + /** + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + */ + public VectVar(double x, double y, double z) + { + super(); + 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; + } + + + @Override + public void setTo(double x, double y, double z) + { + this.x = x; + this.y = y; + this.z = z; + } + + + @Override + public void setX(double x) + { + this.x = x; + } + + + @Override + public void setY(double y) + { + this.y = y; + } + + + @Override + public void setZ(double z) + { + this.z = z; + } +} diff --git a/src/mightypork/utils/math/noise/NoiseGen.java b/src/mightypork/utils/math/noise/NoiseGen.java new file mode 100644 index 0000000..c2e6ebb --- /dev/null +++ b/src/mightypork/utils/math/noise/NoiseGen.java @@ -0,0 +1,109 @@ +package mightypork.utils.math.noise; + + +/** + * 2D Perlin noise generator + * + * @author Ondřej Hruška (MightyPork) + */ +public class NoiseGen { + + private static final double lowBound = -0.7072; + private static final double highBound = 0.7072; + + private final PerlinNoiseGenerator noiser; + + private final double lowMul; + private final double highMul; + private final double middle; + private final double density; + + + /** + * make a new noise generator with a random seed + * + * @param density noise density (0..1). Lower density means larger "spots". + * @param low low bound ("valley") + * @param middle middle bound ("surface") + * @param high high bound ("hill") + */ + public NoiseGen(double density, double low, double middle, double high) + { + this(density, low, middle, high, Double.doubleToLongBits(Math.random())); + } + + + /** + * make a new noise generator + * + * @param density noise density (0..1). Lower density means larger "spots". + * @param low low bound ("valley") + * @param middle middle bound ("surface") + * @param high high bound ("hill") + * @param seed random seed to use + */ + public NoiseGen(double density, double low, double middle, double high, long seed) + { + if (low > middle || middle > high) throw new IllegalArgumentException("Invalid value range."); + + this.density = density; + + // norm low and high to be around zero + low -= middle; + high -= middle; + + // scale + this.middle = middle; + + lowMul = Math.abs(low / lowBound); + highMul = Math.abs(high / highBound); + + noiser = new PerlinNoiseGenerator(seed); + } + + + /** + * Get value at coord + * + * @param x x coordinate + * @param y y coordinate + * @return value + */ + public double valueAt(double x, double y) + { + double raw = noiser.noise2(x * density, y * density); + + if (raw < lowBound) { + raw = lowBound; + } else if (raw > highBound) { + raw = highBound; + } + + if (raw < 0) { + return middle + lowMul * raw; + } else { + return middle + highMul * raw; + } + } + + + /** + * Build a map [height][width] of noise values + * + * @param width map width (number of columns) + * @param height map height (number of rows ) + * @return the map + */ + public double[][] buildMap(int width, int height) + { + final double[][] map = new double[height][width]; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + map[y][x] = valueAt(x, y); + } + } + + return map; + } +} diff --git a/src/mightypork/utils/math/noise/PerlinNoiseGenerator.java b/src/mightypork/utils/math/noise/PerlinNoiseGenerator.java new file mode 100644 index 0000000..1b3f2ef --- /dev/null +++ b/src/mightypork/utils/math/noise/PerlinNoiseGenerator.java @@ -0,0 +1,577 @@ +/***************************************************************************** + * J3D.org Copyright (c) 2000 + * Java Source + * + * This source is licensed under the GNU LGPL v2.1 + * Please read http://www.gnu.org/copyleft/lgpl.html for more information + * + * This software comes with the standard NO WARRANTY disclaimer for any + * purpose. Use it at your own risk. If there's a problem you get to fix it. + * + ****************************************************************************/ +package mightypork.utils.math.noise; + + +import java.util.Random; + + +/** + * Computes Perlin Noise for three dimensions. + *

+ * The result is a continuous function that interpolates a smooth path along a + * series random points. The function is consitent, so given the same + * parameters, it will always return the same value. The smoothing function is + * based on the Improving Noise paper presented at Siggraph 2002. + *

+ * Computing noise for one and two dimensions can make use of the 3D problem + * space by just setting the un-needed dimensions to a fixed value. + * + * @author Justin Couch + * @edited by Ondřej Hruška + * @version $Revision: 1.4 $ + * @source http://code.j3d.org/download.html + */ +public class PerlinNoiseGenerator { + + // Constants for setting up the Perlin-1 noise functions + private static final int B = 0x1000; + private static final int BM = 0xff; + + private static final int N = 0x1000; + + /** Default seed to use for the random number generation */ + private static final int DEFAULT_SEED = 100; + + /** Default sample size to work with */ + private static final int DEFAULT_SAMPLE_SIZE = 256; + + private final Random rand = new Random(DEFAULT_SEED); + + /** Permutation array for the improved noise function */ + private final int[] p_imp; + + /** P array for perline 1 noise */ + private int[] p; + private double[][] g3; + private double[][] g2; + private double[] g1; + + + /** + * Create a new noise creator with the default seed value + */ + public PerlinNoiseGenerator() + { + this(DEFAULT_SEED); + } + + + /** + * Create a new noise creator with the given seed value for the randomness + * + * @param seed The seed value to use + */ + public PerlinNoiseGenerator(long seed) + { + p_imp = new int[DEFAULT_SAMPLE_SIZE << 1]; + + int i, j, k; + rand.setSeed(seed); + + // Calculate the table of psuedo-random coefficients. + for (i = 0; i < DEFAULT_SAMPLE_SIZE; i++) + p_imp[i] = i; + + // generate the psuedo-random permutation table. + while (--i > 0) { + k = p_imp[i]; + j = (int) (rand.nextLong() & DEFAULT_SAMPLE_SIZE); + p_imp[i] = p_imp[j]; + p_imp[j] = k; + } + + initPerlin1(); + } + + + /** + * Computes noise function for three dimensions at the point (x,y,z). + * + * @param x x dimension parameter + * @param y y dimension parameter + * @param z z dimension parameter + * @return the noise value at the point (x, y, z) + */ + public double improvedNoise(double x, double y, double z) + { + // Constraint the point to a unit cube + final int uc_x = (int) Math.floor(x) & 255; + final int uc_y = (int) Math.floor(y) & 255; + final int uc_z = (int) Math.floor(z) & 255; + + // Relative location of the point in the unit cube + final double xo = x - Math.floor(x); + final double yo = y - Math.floor(y); + final double zo = z - Math.floor(z); + + // Fade curves for x, y and z + final double u = fade(xo); + final double v = fade(yo); + final double w = fade(zo); + + // Generate a hash for each coordinate to find out where in the cube + // it lies. + final int a = p_imp[uc_x] + uc_y; + final int aa = p_imp[a] + uc_z; + final int ab = p_imp[a + 1] + uc_z; + + final int b = p_imp[uc_x + 1] + uc_y; + final int ba = p_imp[b] + uc_z; + final int bb = p_imp[b + 1] + uc_z; + + // blend results from the 8 corners based on the noise function + final double c1 = grad(p_imp[aa], xo, yo, zo); + final double c2 = grad(p_imp[ba], xo - 1, yo, zo); + final double c3 = grad(p_imp[ab], xo, yo - 1, zo); + final double c4 = grad(p_imp[bb], xo - 1, yo - 1, zo); + final double c5 = grad(p_imp[aa + 1], xo, yo, zo - 1); + final double c6 = grad(p_imp[ba + 1], xo - 1, yo, zo - 1); + final double c7 = grad(p_imp[ab + 1], xo, yo - 1, zo - 1); + final double c8 = grad(p_imp[bb + 1], xo - 1, yo - 1, zo - 1); + + return lerp(w, lerp(v, lerp(u, c1, c2), lerp(u, c3, c4)), lerp(v, lerp(u, c5, c6), lerp(u, c7, c8))); + } + + + /** + * 1-D noise generation function using the original perlin algorithm. + * + * @param x Seed for the noise function + * @return The noisy output + */ + public double noise1(double x) + { + final double t = x + N; + final int bx0 = ((int) t) & BM; + final int bx1 = (bx0 + 1) & BM; + final double rx0 = t - (int) t; + final double rx1 = rx0 - 1; + + final double sx = sCurve(rx0); + + final double u = rx0 * g1[p[bx0]]; + final double v = rx1 * g1[p[bx1]]; + + return lerp(sx, u, v); + } + + + /** + * Create noise in a 2D space using the orignal perlin noise algorithm. + * + * @param x The X coordinate of the location to sample + * @param y The Y coordinate of the location to sample + * @return A noisy value at the given position + */ + public double noise2(double x, double y) + { + double t = x + N; + final int bx0 = ((int) t) & BM; + final int bx1 = (bx0 + 1) & BM; + final double rx0 = t - (int) t; + final double rx1 = rx0 - 1; + + t = y + N; + final int by0 = ((int) t) & BM; + final int by1 = (by0 + 1) & BM; + final double ry0 = t - (int) t; + final double ry1 = ry0 - 1; + + final int i = p[bx0]; + final int j = p[bx1]; + + final int b00 = p[i + by0]; + final int b10 = p[j + by0]; + final int b01 = p[i + by1]; + final int b11 = p[j + by1]; + + final double sx = sCurve(rx0); + final double sy = sCurve(ry0); + + double[] q = g2[b00]; + double u = rx0 * q[0] + ry0 * q[1]; + q = g2[b10]; + double v = rx1 * q[0] + ry0 * q[1]; + final double a = lerp(sx, u, v); + + q = g2[b01]; + u = rx0 * q[0] + ry1 * q[1]; + q = g2[b11]; + v = rx1 * q[0] + ry1 * q[1]; + final double b = lerp(sx, u, v); + + return lerp(sy, a, b); + } + + + /** + * Create noise in a 3D space using the orignal perlin noise algorithm. + * + * @param x The X coordinate of the location to sample + * @param y The Y coordinate of the location to sample + * @param z The Z coordinate of the location to sample + * @return A noisy value at the given position + */ + public double noise3(double x, double y, double z) + { + double t = x + N; + final int bx0 = ((int) t) & BM; + final int bx1 = (bx0 + 1) & BM; + final double rx0 = t - (int) t; + final double rx1 = rx0 - 1; + + t = y + N; + final int by0 = ((int) t) & BM; + final int by1 = (by0 + 1) & BM; + final double ry0 = t - (int) t; + final double ry1 = ry0 - 1; + + t = z + N; + final int bz0 = ((int) t) & BM; + final int bz1 = (bz0 + 1) & BM; + final double rz0 = t - (int) t; + final double rz1 = rz0 - 1; + + final int i = p[bx0]; + final int j = p[bx1]; + + final int b00 = p[i + by0]; + final int b10 = p[j + by0]; + final int b01 = p[i + by1]; + final int b11 = p[j + by1]; + + t = sCurve(rx0); + final double sy = sCurve(ry0); + final double sz = sCurve(rz0); + + double[] q = g3[b00 + bz0]; + double u = (rx0 * q[0] + ry0 * q[1] + rz0 * q[2]); + q = g3[b10 + bz0]; + double v = (rx1 * q[0] + ry0 * q[1] + rz0 * q[2]); + double a = lerp(t, u, v); + + q = g3[b01 + bz0]; + u = (rx0 * q[0] + ry1 * q[1] + rz0 * q[2]); + q = g3[b11 + bz0]; + v = (rx1 * q[0] + ry1 * q[1] + rz0 * q[2]); + double b = lerp(t, u, v); + + final double c = lerp(sy, a, b); + + q = g3[b00 + bz1]; + u = (rx0 * q[0] + ry0 * q[1] + rz1 * q[2]); + q = g3[b10 + bz1]; + v = (rx1 * q[0] + ry0 * q[1] + rz1 * q[2]); + a = lerp(t, u, v); + + q = g3[b01 + bz1]; + u = (rx0 * q[0] + ry1 * q[1] + rz1 * q[2]); + q = g3[b11 + bz1]; + v = (rx1 * q[0] + ry1 * q[1] + rz1 * q[2]); + b = lerp(t, u, v); + + final double d = lerp(sy, a, b); + + return lerp(sz, c, d); + } + + + /** + * Create a turbulent noise output based on the core noise function. This + * uses the noise as a base function and is suitable for creating clouds, + * marble and explosion effects. For example, a typical marble effect would + * set the colour to be: + * + *

+     *    sin(point + turbulence(point) * point.x);
+     * 
+ * + * @param x + * @param y + * @param z + * @param loF + * @param hiF + * @return value + */ + public double imporvedTurbulence(double x, double y, double z, double loF, double hiF) + { + double p_x = x + 123.456f; + double p_y = y; + double p_z = z; + double t = 0; + double f; + + for (f = loF; f < hiF; f *= 2) { + t += Math.abs(improvedNoise(p_x, p_y, p_z)) / f; + + p_x *= 2; + p_y *= 2; + p_z *= 2; + } + + return t - 0.3; + } + + + /** + * Create a turbulance function in 2D using the original perlin noise + * function. + * + * @param x The X coordinate of the location to sample + * @param y The Y coordinate of the location to sample + * @param freq The frequency of the turbluance to create + * @return The value at the given coordinates + */ + public double turbulence2(double x, double y, double freq) + { + double t = 0; + + do { + t += noise2(freq * x, freq * y) / freq; + freq *= 0.5f; + } while (freq >= 1); + + return t; + } + + + /** + * Create a turbulance function in 3D using the original perlin noise + * function. + * + * @param x The X coordinate of the location to sample + * @param y The Y coordinate of the location to sample + * @param z The Z coordinate of the location to sample + * @param freq The frequency of the turbluance to create + * @return The value at the given coordinates + */ + public double turbulence3(double x, double y, double z, double freq) + { + double t = 0; + + do { + t += noise3(freq * x, freq * y, freq * z) / freq; + freq *= 0.5f; + } while (freq >= 1); + + return t; + } + + + /** + * Create a 1D tileable noise function for the given width. + * + * @param x The X coordinate to generate the noise for + * @param w The width of the tiled block + * @return The value of the noise at the given coordinate + */ + public double tileableNoise1(double x, double w) + { + return (noise1(x) * (w - x) + noise1(x - w) * x) / w; + } + + + /** + * Create a 2D tileable noise function for the given width and height. + * + * @param x The X coordinate to generate the noise for + * @param y The Y coordinate to generate the noise for + * @param w The width of the tiled block + * @param h The height of the tiled block + * @return The value of the noise at the given coordinate + */ + public double tileableNoise2(double x, double y, double w, double h) + { + return (noise2(x, y) * (w - x) * (h - y) + noise2(x - w, y) * x * (h - y) + noise2(x, y - h) * (w - x) * y + noise2(x - w, y - h) * x * y) / (w * h); + } + + + /** + * Create a 3D tileable noise function for the given width, height and + * depth. + * + * @param x The X coordinate to generate the noise for + * @param y The Y coordinate to generate the noise for + * @param z The Z coordinate to generate the noise for + * @param w The width of the tiled block + * @param h The height of the tiled block + * @param d The depth of the tiled block + * @return The value of the noise at the given coordinate + */ + public double tileableNoise3(double x, double y, double z, double w, double h, double d) + { + return (noise3(x, y, z) * (w - x) * (h - y) * (d - z) + noise3(x - w, y, z) * x * (h - y) * (d - z) + noise3(x, y - h, z) * (w - x) * y * (d - z) + + noise3(x - w, y - h, z) * x * y * (d - z) + noise3(x, y, z - d) * (w - x) * (h - y) * z + noise3(x - w, y, z - d) * x * (h - y) * z + + noise3(x, y - h, z - d) * (w - x) * y * z + noise3(x - w, y - h, z - d) * x * y * z) + / (w * h * d); + } + + + /** + * Create a turbulance function that can be tiled across a surface in 2D. + * + * @param x The X coordinate of the location to sample + * @param y The Y coordinate of the location to sample + * @param w The width to tile over + * @param h The height to tile over + * @param freq The frequency of the turbluance to create + * @return The value at the given coordinates + */ + public double tileableTurbulence2(double x, double y, double w, double h, double freq) + { + double t = 0; + + do { + t += tileableNoise2(freq * x, freq * y, w * freq, h * freq) / freq; + freq *= 0.5f; + } while (freq >= 1); + + return t; + } + + + /** + * Create a turbulance function that can be tiled across a surface in 3D. + * + * @param x The X coordinate of the location to sample + * @param y The Y coordinate of the location to sample + * @param z The Z coordinate of the location to sample + * @param w The width to tile over + * @param h The height to tile over + * @param d The depth to tile over + * @param freq The frequency of the turbluance to create + * @return The value at the given coordinates + */ + public double tileableTurbulence3(double x, double y, double z, double w, double h, double d, double freq) + { + double t = 0; + + do { + t += tileableNoise3(freq * x, freq * y, freq * z, w * freq, h * freq, d * freq) / freq; + freq *= 0.5f; + } while (freq >= 1); + + return t; + } + + + /** + * Simple lerp function using doubles. + */ + private double lerp(double t, double a, double b) + { + return a + t * (b - a); + } + + + /** + * Fade curve calculation which is 6t^5 - 15t^4 + 10t^3. This is the new + * algorithm, where the old one used to be 3t^2 - 2t^3. + * + * @param t The t parameter to calculate the fade for + * @return the drop-off amount. + */ + private double fade(double t) + { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + + /** + * Calculate the gradient function based on the hash code. + */ + private double grad(int hash, double x, double y, double z) + { + // Convert low 4 bits of hash code into 12 gradient directions. + final int h = hash & 15; + final double u = (h < 8 || h == 12 || h == 13) ? x : y; + final double v = (h < 4 || h == 12 || h == 13) ? y : z; + + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + + + /** + * S-curve function for value distribution for Perlin-1 noise function. + */ + private double sCurve(double t) + { + return (t * t * (3 - 2 * t)); + } + + + /** + * 2D-vector normalisation function. + */ + private void normalize2(double[] v) + { + final double s = 1 / Math.sqrt(v[0] * v[0] + v[1] * v[1]); + v[0] *= s; + v[1] *= s; + } + + + /** + * 3D-vector normalisation function. + */ + private void normalize3(double[] v) + { + final double s = 1 / Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + v[0] *= s; + v[1] *= s; + v[2] *= s; + } + + + /** + * Initialise the lookup arrays used by Perlin 1 function. + */ + private void initPerlin1() + { + p = new int[B + B + 2]; + g3 = new double[B + B + 2][3]; + g2 = new double[B + B + 2][2]; + g1 = new double[B + B + 2]; + int i, j, k; + + for (i = 0; i < B; i++) { + p[i] = i; + + g1[i] = (((rand.nextDouble() * Integer.MAX_VALUE) % (B + B)) - B) / B; + + for (j = 0; j < 2; j++) + g2[i][j] = (((rand.nextDouble() * Integer.MAX_VALUE) % (B + B)) - B) / B; + normalize2(g2[i]); + + for (j = 0; j < 3; j++) + g3[i][j] = (((rand.nextDouble() * Integer.MAX_VALUE) % (B + B)) - B) / B; + normalize3(g3[i]); + } + + while (--i > 0) { + k = p[i]; + j = (int) ((rand.nextDouble() * Integer.MAX_VALUE) % B); + p[i] = p[j]; + p[j] = k; + } + + for (i = 0; i < B + 2; i++) { + p[B + i] = p[i]; + g1[B + i] = g1[i]; + for (j = 0; j < 2; j++) + g2[B + i][j] = g2[i][j]; + for (j = 0; j < 3; j++) + g3[B + i][j] = g3[i][j]; + } + } +} diff --git a/src/mightypork/utils/math/timing/FpsMeter.java b/src/mightypork/utils/math/timing/FpsMeter.java new file mode 100644 index 0000000..1c22e49 --- /dev/null +++ b/src/mightypork/utils/math/timing/FpsMeter.java @@ -0,0 +1,39 @@ +package mightypork.utils.math.timing; + + +/** + * Class for counting FPS in games.
+ * This class can be used also as a simple frequency meter - output is in Hz. + * + * @author Ondřej Hruška (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++; + } +} diff --git a/src/mightypork/utils/math/timing/TaskRepeater.java b/src/mightypork/utils/math/timing/TaskRepeater.java new file mode 100644 index 0000000..ef1308a --- /dev/null +++ b/src/mightypork/utils/math/timing/TaskRepeater.java @@ -0,0 +1,45 @@ +package mightypork.utils.math.timing; + + +import mightypork.utils.interfaces.Enableable; +import mightypork.utils.math.animation.AnimatorRewind; +import mightypork.utils.math.animation.NumAnimated; + + +public abstract class TaskRepeater extends AnimatorRewind implements Runnable, Enableable { + + private boolean enabled = true; + + + public TaskRepeater(double period) + { + super(period); + } + + + @Override + protected void nextCycle(NumAnimated anim) + { + if (isEnabled()) run(); + super.nextCycle(anim); + } + + + @Override + public void setEnabled(boolean yes) + { + this.enabled = yes; + } + + + @Override + public boolean isEnabled() + { + return enabled; + } + + + @Override + public abstract void run(); + +} diff --git a/src/mightypork/utils/math/timing/TimedTask.java b/src/mightypork/utils/math/timing/TimedTask.java new file mode 100644 index 0000000..7bebf05 --- /dev/null +++ b/src/mightypork/utils/math/timing/TimedTask.java @@ -0,0 +1,52 @@ +package mightypork.utils.math.timing; + + +import mightypork.utils.interfaces.Updateable; +import mightypork.utils.math.animation.NumAnimated; + + +/** + * Delayed runnable controlled by delta timing. + * + * @author Ondřej Hruška (MightyPork) + */ +public abstract class TimedTask implements Runnable, Updateable { + + private final NumAnimated timer = new NumAnimated(0); + private boolean running = false; + + + @Override + public void update(double delta) + { + if (running) { + timer.update(delta); + if (timer.isFinished()) { + running = false; + run(); + } + } + } + + + public boolean isRunning() + { + return !timer.isFinished(); + } + + + public void start(double seconds) + { + timer.reset(); + timer.animate(1, seconds); + running = true; + } + + + public void stop() + { + running = false; + timer.reset(); + } + +} diff --git a/src/mightypork/utils/math/timing/TimerDelta.java b/src/mightypork/utils/math/timing/TimerDelta.java new file mode 100644 index 0000000..63688f2 --- /dev/null +++ b/src/mightypork/utils/math/timing/TimerDelta.java @@ -0,0 +1,48 @@ +package mightypork.utils.math.timing; + + +/** + * Timer for delta timing + * + * @author Ondřej Hruška (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; + } +} diff --git a/src/mightypork/utils/math/timing/TimerFps.java b/src/mightypork/utils/math/timing/TimerFps.java new file mode 100644 index 0000000..0cee2ad --- /dev/null +++ b/src/mightypork/utils/math/timing/TimerFps.java @@ -0,0 +1,104 @@ +package mightypork.utils.math.timing; + + +/** + * Timer for interpolated timing + * + * @author Ondřej Hruška (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; + } +} diff --git a/src/mightypork/utils/string/AlphanumComparator.java b/src/mightypork/utils/string/AlphanumComparator.java new file mode 100644 index 0000000..db8ab08 --- /dev/null +++ b/src/mightypork/utils/string/AlphanumComparator.java @@ -0,0 +1,119 @@ +/* + * The Alphanum Algorithm is an improved sorting algorithm for strings + * containing numbers. Instead of sorting numbers in ASCII order like + * a standard sort, this algorithm sorts numbers in numeric order. + * + * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +package mightypork.utils.string; + + +import java.util.Comparator; + + +/** + * String comparator taking care of strings with numbers. + * + * @author Daniel Migowski + * @author Andre Bogus + * @author David Koelle + * @author Ondřej Hruška (MightyPork) + */ +public class AlphanumComparator implements Comparator { + + public static final AlphanumComparator instance = new AlphanumComparator(); + + + private final boolean isDigit(char ch) + { + return ch >= '0' && ch <= '9'; + } + + + /** + * Length of string is passed in for improved efficiency (only need to + * calculate it once) + **/ + private final String getChunk(String s, int slength, int marker) + { + final StringBuilder chunk = new StringBuilder(); + char c = s.charAt(marker); + chunk.append(c); + marker++; + + if (isDigit(c)) { + while (marker < slength) { + c = s.charAt(marker); + if (!isDigit(c)) break; + chunk.append(c); + marker++; + } + } else { + while (marker < slength) { + c = s.charAt(marker); + if (isDigit(c)) break; + chunk.append(c); + marker++; + } + } + return chunk.toString(); + } + + + @Override + public int compare(String s1, String s2) + { + int thisMarker = 0; + int thatMarker = 0; + final int s1Length = s1.length(); + final int s2Length = s2.length(); + + while (thisMarker < s1Length && thatMarker < s2Length) { + final String thisChunk = getChunk(s1, s1Length, thisMarker); + thisMarker += thisChunk.length(); + + final String thatChunk = getChunk(s2, s2Length, thatMarker); + thatMarker += thatChunk.length(); + + // If both chunks contain numeric characters, sort them numerically + int result = 0; + if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) { + // Simple chunk comparison by length. + final int thisChunkLength = thisChunk.length(); + result = thisChunkLength - thatChunk.length(); + // If equal, the first different number counts + if (result == 0) { + for (int i = 0; i < thisChunkLength; i++) { + result = thisChunk.charAt(i) - thatChunk.charAt(i); + if (result != 0) { + return result; + } + } + } + } else { + result = thisChunk.compareTo(thatChunk); + } + + if (result != 0) return result; + } + + return s1Length - s2Length; + } +} diff --git a/src/mightypork/utils/string/StringProvider.java b/src/mightypork/utils/string/StringProvider.java new file mode 100644 index 0000000..5376c91 --- /dev/null +++ b/src/mightypork/utils/string/StringProvider.java @@ -0,0 +1,12 @@ +package mightypork.utils.string; + + +/** + * Can be used for dynamic string generating + * + * @author Ondřej Hruška (MightyPork) + */ +public interface StringProvider { + + String getString(); +} diff --git a/src/mightypork/utils/string/StringUtil.java b/src/mightypork/utils/string/StringUtil.java new file mode 100644 index 0000000..1c2e472 --- /dev/null +++ b/src/mightypork/utils/string/StringUtil.java @@ -0,0 +1,82 @@ +package mightypork.utils.string; + + +/** + * General purpose string utilities + * + * @author Ondřej Hruška (MightyPork) + */ +public class StringUtil { + + public static String fromLastDot(String s) + { + return fromLastChar(s, '.'); + } + + + public static String toLastDot(String s) + { + return toLastChar(s, '.'); + } + + + public static String fromLastChar(String s, char c) + { + if (s == null) return null; + return s.substring(s.lastIndexOf(c) + 1, s.length()); + } + + + public static String toLastChar(String s, char c) + { + if (s == null) return null; + if (s.lastIndexOf(c) == -1) return s; + return s.substring(0, s.lastIndexOf(c)); + } + + + /** + * Repeat a string + * + * @param repeated string + * @param count + * @return output + */ + public static String repeat(String repeated, int count) + { + String s = ""; + for (int i = 0; i < count; i++) + s += repeated; + return s; + } + + + public static boolean isValidFilenameChar(char ch) + { + return isValidFilenameString(Character.toString(ch)); + } + + + public static boolean isValidFilenameString(String filename) + { + return filename.matches("[a-zA-Z0-9 +\\-.,_%@#!]+"); + } + + + public static String ellipsisStart(String orig, int length) + { + if (orig.length() > length) { + orig = "\u2026" + orig.substring(length, orig.length()); + } + return orig; + } + + + public static String ellipsisEnd(String orig, int length) + { + if (orig.length() > length) { + orig = orig.substring(0, length - 1) + "\u2026"; + } + return orig; + } +} diff --git a/src/mightypork/utils/string/StringWrapper.java b/src/mightypork/utils/string/StringWrapper.java new file mode 100644 index 0000000..2f12dbe --- /dev/null +++ b/src/mightypork/utils/string/StringWrapper.java @@ -0,0 +1,26 @@ +package mightypork.utils.string; + + +/** + * String provider with constant string + * + * @author Ondřej Hruška (MightyPork) + */ +public class StringWrapper implements StringProvider { + + private final String value; + + + public StringWrapper(String value) + { + this.value = value; + } + + + @Override + public String getString() + { + return value; + } + +} diff --git a/src/mightypork/utils/string/validation/CharFilter.java b/src/mightypork/utils/string/validation/CharFilter.java new file mode 100644 index 0000000..33ecd31 --- /dev/null +++ b/src/mightypork/utils/string/validation/CharFilter.java @@ -0,0 +1,7 @@ +package mightypork.utils.string.validation; + + +public interface CharFilter { + + public boolean isValid(char c); +} diff --git a/src/mightypork/utils/string/validation/CharFilterRegex.java b/src/mightypork/utils/string/validation/CharFilterRegex.java new file mode 100644 index 0000000..8d05927 --- /dev/null +++ b/src/mightypork/utils/string/validation/CharFilterRegex.java @@ -0,0 +1,21 @@ +package mightypork.utils.string.validation; + + +public class CharFilterRegex implements CharFilter { + + private final String formula; + + + public CharFilterRegex(String regex) + { + this.formula = regex; + } + + + @Override + public boolean isValid(char c) + { + return Character.toString(c).matches(formula); + } + +} diff --git a/src/mightypork/utils/string/validation/CharFilterWhitelist.java b/src/mightypork/utils/string/validation/CharFilterWhitelist.java new file mode 100644 index 0000000..d9e62dd --- /dev/null +++ b/src/mightypork/utils/string/validation/CharFilterWhitelist.java @@ -0,0 +1,21 @@ +package mightypork.utils.string.validation; + + +public class CharFilterWhitelist implements CharFilter { + + private final String whitelist; + + + public CharFilterWhitelist(String allowed) + { + this.whitelist = allowed; + } + + + @Override + public boolean isValid(char c) + { + return whitelist.contains(Character.toString(c)); + } + +} diff --git a/src/mightypork/utils/string/validation/StringFilter.java b/src/mightypork/utils/string/validation/StringFilter.java new file mode 100644 index 0000000..79a883a --- /dev/null +++ b/src/mightypork/utils/string/validation/StringFilter.java @@ -0,0 +1,12 @@ +package mightypork.utils.string.validation; + + +/** + * Utility interface for string filters (accepting filepaths and similar) + * + * @author Ondřej Hruška (MightyPork) + */ +public interface StringFilter { + + public boolean isValid(String entry); +} diff --git a/src/mightypork/utils/struct/Mutable.java b/src/mightypork/utils/struct/Mutable.java new file mode 100644 index 0000000..acad2db --- /dev/null +++ b/src/mightypork/utils/struct/Mutable.java @@ -0,0 +1,79 @@ +package mightypork.utils.struct; + + +/** + * Mutable object + * + * @author Ondřej Hruška (MightyPork) + * @param type + */ +public class Mutable { + + /** The wrapped value */ + private T o = null; + + + /** + * New mutable object + * + * @param o value + */ + public Mutable(T o) + { + this.o = o; + } + + + /** + * Get the wrapped value + * + * @return value + */ + public T get() + { + return o; + } + + + /** + * Set value + * + * @param o new value to set + */ + public void set(T o) + { + this.o = o; + } + + + @Override + public String toString() + { + if (o == null) return ""; + return o.toString(); + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((o == null) ? 0 : o.hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Mutable)) return false; + final Mutable other = (Mutable) obj; + if (o == null) { + if (other.o != null) return false; + } else if (!o.equals(other.o)) return false; + return true; + } +} diff --git a/src/mightypork/utils/struct/Pair.java b/src/mightypork/utils/struct/Pair.java new file mode 100644 index 0000000..a2465f7 --- /dev/null +++ b/src/mightypork/utils/struct/Pair.java @@ -0,0 +1,90 @@ +package mightypork.utils.struct; + + +/** + * Structure of 2 objects. + * + * @author Ondřej Hruška (MightyPork) + * @copy (c) 2012 + * @param 1st object class + * @param 2nd object class + */ +public class Pair { + + /** + * 1st object + */ + public T1 first; + + /** + * 2nd object + */ + public T2 second; + + + /** + * Make structure of 2 objects + * + * @param first 1st object + * @param second 2nd object + */ + public Pair(T1 first, T2 second) + { + this.first = first; + this.second = second; + } + + + /** + * @return 1st object + */ + public T1 getFirst() + { + return first; + } + + + /** + * @return 2nd object + */ + public T2 getSecond() + { + return second; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Pair)) return false; + final Pair other = (Pair) obj; + if (first == null) { + if (other.first != null) return false; + } else if (!first.equals(other.first)) return false; + if (second == null) { + if (other.second != null) return false; + } else if (!second.equals(other.second)) return false; + return true; + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((first == null) ? 0 : first.hashCode()); + result = prime * result + ((second == null) ? 0 : second.hashCode()); + return result; + } + + + @Override + public String toString() + { + return "PAIR{" + first + "," + second + "}"; + } + +} diff --git a/src/mightypork/utils/struct/Triad.java b/src/mightypork/utils/struct/Triad.java new file mode 100644 index 0000000..7c9fc05 --- /dev/null +++ b/src/mightypork/utils/struct/Triad.java @@ -0,0 +1,87 @@ +package mightypork.utils.struct; + + +/** + * Structure of 3 objects. + * + * @author Ondřej Hruška (MightyPork) + * @copy (c) 2012 + * @param 1st object class + * @param 2nd object class + * @param 3rd object class + */ +public class Triad extends Pair { + + /** + * 3rd object + */ + public T3 third; + + + /** + * Make structure of 3 objects + * + * @param objA 1st object + * @param objB 2nd object + * @param objC 3rd object + */ + public Triad(T1 objA, T2 objB, T3 objC) + { + super(objA, objB); + third = objC; + } + + + /** + * @return 3rd object + */ + public T3 getThird() + { + return third; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void setThird(T3 obj) + { + third = obj; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Triad)) return false; + + if (!super.equals(obj)) return false; + + final Triad other = (Triad) obj; + + if (third == null) { + if (other.third != null) return false; + } else if (!third.equals(other.third)) return false; + + return true; + } + + + @Override + public int hashCode() + { + return super.hashCode() + (third == null ? 0 : third.hashCode()); + } + + + @Override + public String toString() + { + return "TRIAD{" + first + "," + second + "," + third + "}"; + } + +}