From 37fe627477286d8440b51e6579d7b23e8ef4de32 Mon Sep 17 00:00:00 2001 From: "459985746472861716 (TudbuT#2624)" Date: Sat, 22 Aug 2020 14:26:35 +0200 Subject: [PATCH] Initial commit --- .gitignore | 10 + .gitlab-ci.yml | 37 ++ .idea/.gitignore | 8 + .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/discord.xml | 6 + .idea/misc.xml | 17 + .idea/serialmonitor_settings.xml | 4 + .idea/uiDesigner.xml | 124 ++++ LICENSE | 41 ++ README.md | 8 + build.gradle | 84 +++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 ++++++ gradlew.bat | 89 +++ settings.gradle | 2 + .../java/de/tudbut/algorithm/Algorithm.java | 17 + .../java/de/tudbut/algorithm/AlgorithmAI.java | 124 ++++ src/main/java/de/tudbut/async/Async.java | 53 ++ src/main/java/de/tudbut/async/Callback.java | 11 + .../java/de/tudbut/async/CallbackList.java | 38 ++ .../java/de/tudbut/async/ComposeCallback.java | 11 + .../java/de/tudbut/async/InternalReject.java | 11 + src/main/java/de/tudbut/async/Reject.java | 23 + src/main/java/de/tudbut/async/Resolve.java | 23 + src/main/java/de/tudbut/async/Task.java | 115 ++++ .../java/de/tudbut/async/TaskCallable.java | 11 + src/main/java/de/tudbut/async/TaskQueue.java | 193 ++++++ src/main/java/de/tudbut/debug/Debug.java | 26 + .../java/de/tudbut/debug/DebugProfiler.java | 194 ++++++ .../de/tudbut/global/DebugStateManager.java | 111 ++++ .../de/tudbut/global/GlobalSyncQueue.java | 116 ++++ src/main/java/de/tudbut/gui/SettingsGUI.java | 166 +++++ .../de/tudbut/gui/settingsgui/Setting.java | 17 + .../tudbut/io/AdaptiveSocketInputStream.java | 50 ++ src/main/java/de/tudbut/io/Buffer.java | 18 + .../java/de/tudbut/io/CLSPrintWriter.java | 61 ++ src/main/java/de/tudbut/io/CharBuffer.java | 28 + src/main/java/de/tudbut/io/DirectBuffers.java | 15 + src/main/java/de/tudbut/io/DoubleBuffer.java | 24 + src/main/java/de/tudbut/io/FileBus.java | 111 ++++ .../java/de/tudbut/io/FileBusTransmitter.java | 102 +++ src/main/java/de/tudbut/io/ImageIOUtils.java | 30 + src/main/java/de/tudbut/io/IntBuffer.java | 24 + src/main/java/de/tudbut/io/LineReader.java | 47 ++ src/main/java/de/tudbut/io/RawLineReader.java | 60 ++ src/main/java/de/tudbut/io/StreamReader.java | 156 +++++ .../java/de/tudbut/io/StreamRedirect.java | 33 + src/main/java/de/tudbut/io/StreamWriter.java | 68 ++ .../java/de/tudbut/io/TypedInputStream.java | 161 +++++ .../java/de/tudbut/io/TypedOutputStream.java | 141 +++++ .../java/de/tudbut/io/WriteDirection.java | 29 + .../java/de/tudbut/logger/DetailedLogger.java | 53 ++ .../java/de/tudbut/logger/EmptyLogger.java | 43 ++ src/main/java/de/tudbut/logger/Logger.java | 184 ++++++ .../java/de/tudbut/logger/LoggerSink.java | 155 +++++ .../java/de/tudbut/math/ComplexFunction.java | 10 + .../java/de/tudbut/math/ComplexNumber.java | 55 ++ src/main/java/de/tudbut/math/Function.java | 10 + .../de/tudbut/net/http/HTTPContentType.java | 89 +++ .../java/de/tudbut/net/http/HTTPHeader.java | 91 +++ .../java/de/tudbut/net/http/HTTPRequest.java | 224 +++++++ .../de/tudbut/net/http/HTTPRequestType.java | 58 ++ .../java/de/tudbut/net/http/HTTPResponse.java | 148 +++++ .../de/tudbut/net/http/HTTPResponseCode.java | 84 +++ .../tudbut/net/http/HTTPResponseFactory.java | 55 ++ .../java/de/tudbut/net/http/HTTPServer.java | 213 +++++++ .../de/tudbut/net/http/HTTPServerRequest.java | 221 +++++++ .../java/de/tudbut/net/http/HTTPUtils.java | 80 +++ .../de/tudbut/net/http/ParsedHTTPValue.java | 65 ++ .../net/http/serverimpl/ContentType.java | 14 + .../net/http/serverimpl/HTTPServerImpl.java | 292 +++++++++ .../de/tudbut/net/http/serverimpl/Header.java | 12 + .../de/tudbut/net/http/serverimpl/Method.java | 77 +++ .../tudbut/net/http/serverimpl/Parameter.java | 12 + .../de/tudbut/net/http/serverimpl/Path.java | 13 + .../tudbut/net/http/serverimpl/Response.java | 15 + .../de/tudbut/net/http/serverimpl/Serve.java | 21 + src/main/java/de/tudbut/net/ic/Bus.java | 82 +++ src/main/java/de/tudbut/net/ic/PBIC.java | 272 ++++++++ .../java/de/tudbut/net/nethandler/Packet.java | 249 ++++++++ .../de/tudbut/net/nethandler/PacketData.java | 82 +++ .../tudbut/net/nethandler/PacketDataID.java | 6 + src/main/java/de/tudbut/net/pbic2/PBIC2.java | 40 ++ .../de/tudbut/net/pbic2/PBIC2ADisconnect.java | 4 + .../tudbut/net/pbic2/PBIC2AEventHandler.java | 98 +++ .../de/tudbut/net/pbic2/PBIC2AListener.java | 10 + .../java/de/tudbut/net/pbic2/PBIC2Client.java | 96 +++ .../de/tudbut/net/pbic2/PBIC2Passthrough.java | 6 + .../java/de/tudbut/net/pbic2/PBIC2Server.java | 89 +++ .../de/tudbut/net/smtp/SMTPDataOutput.java | 99 +++ .../java/de/tudbut/net/smtp/SMTPSender.java | 103 +++ .../net/smtp/SMTPServerErrorException.java | 7 + .../de/tudbut/net/websocket/Operation.java | 11 + .../de/tudbut/net/websocket/WebSocket.java | 59 ++ .../tudbut/net/websocket/WebSocketServer.java | 4 + src/main/java/de/tudbut/net/ws/Client.java | 9 + .../java/de/tudbut/net/ws/Connection.java | 113 ++++ .../de/tudbut/net/ws/ConnectionHandler.java | 8 + src/main/java/de/tudbut/net/ws/Server.java | 48 ++ src/main/java/de/tudbut/obj/Atomic.java | 15 + src/main/java/de/tudbut/obj/AtomicSink.java | 7 + src/main/java/de/tudbut/obj/AtomicUtils.java | 4 + .../java/de/tudbut/obj/CarrierException.java | 11 + src/main/java/de/tudbut/obj/Closable.java | 29 + .../tudbut/obj/ClosedClosableException.java | 7 + src/main/java/de/tudbut/obj/DoubleObject.java | 33 + .../java/de/tudbut/obj/DoubleTypedObject.java | 32 + .../de/tudbut/obj/IgnoreThrowRunnable.java | 12 + .../java/de/tudbut/obj/InstanceBoundMap.java | 99 +++ src/main/java/de/tudbut/obj/Mappable.java | 8 + .../de/tudbut/obj/NotSupportedException.java | 11 + src/main/java/de/tudbut/obj/PAtomic.java | 15 + src/main/java/de/tudbut/obj/Partial.java | 60 ++ .../de/tudbut/obj/PrintThrowRunnable.java | 14 + src/main/java/de/tudbut/obj/RayCast.java | 39 ++ .../java/de/tudbut/obj/RelativeVector2d.java | 48 ++ .../java/de/tudbut/obj/RelativeVector3d.java | 53 ++ src/main/java/de/tudbut/obj/Save.java | 11 + .../java/de/tudbut/obj/TLClassLoader.java | 9 + src/main/java/de/tudbut/obj/TLMap.java | 199 ++++++ src/main/java/de/tudbut/obj/Transient.java | 11 + src/main/java/de/tudbut/obj/TypedArray.java | 118 ++++ src/main/java/de/tudbut/obj/TypedList.java | 138 ++++ src/main/java/de/tudbut/obj/Vector2i.java | 104 +++ .../java/de/tudbut/parsing/AddressedTCN.java | 88 +++ .../de/tudbut/parsing/ArgumentParser.java | 74 +++ .../java/de/tudbut/parsing/AsyncJSON.java | 437 +++++++++++++ src/main/java/de/tudbut/parsing/JSON.java | 424 +++++++++++++ src/main/java/de/tudbut/parsing/Lang.java | 68 ++ .../de/tudbut/parsing/StringMapParser.java | 32 + src/main/java/de/tudbut/parsing/TCN.java | 516 +++++++++++++++ src/main/java/de/tudbut/parsing/TCNArray.java | 127 ++++ src/main/java/de/tudbut/parsing/TudSort.java | 102 +++ src/main/java/de/tudbut/pluginapi/Plugin.java | 114 ++++ .../java/de/tudbut/pluginapi/PluginEvent.java | 4 + .../de/tudbut/pluginapi/PluginException.java | 7 + .../de/tudbut/pluginapi/PluginGetEvent.java | 10 + .../de/tudbut/pluginapi/PluginLoadEvent.java | 4 + .../de/tudbut/pluginapi/PluginManager.java | 149 +++++ .../tudbut/pluginapi/PluginUnloadEvent.java | 4 + .../java/de/tudbut/rendering/GIFEncoder.java | 72 +++ src/main/java/de/tudbut/rendering/Graph.java | 6 + .../de/tudbut/rendering/GraphRenderer.java | 62 ++ .../java/de/tudbut/rendering/Maths2D.java | 218 +++++++ .../java/de/tudbut/rendering/Maths3D.java | 113 ++++ .../de/tudbut/rendering/Projection2D.java | 247 ++++++++ .../de/tudbut/rendering/Projection3D.java | 256 ++++++++ .../java/de/tudbut/rendering/Rectangle2D.java | 30 + .../java/de/tudbut/rendering/Rectangle3D.java | 44 ++ .../de/tudbut/rendering/RenderAssistant.java | 183 ++++++ .../de/tudbut/rendering/RenderObject2D.java | 67 ++ .../de/tudbut/rendering/RenderObject3D.java | 54 ++ .../de/tudbut/rendering/RenderObjectType.java | 9 + .../de/tudbut/rendering/RenderOutputType.java | 7 + .../java/de/tudbut/rendering/tph/TPH300.java | 63 ++ .../java/de/tudbut/rendering/tph/TPH301.java | 96 +++ .../java/de/tudbut/rendering/tph/TPH302.java | 37 ++ .../java/de/tudbut/security/AccessKiller.java | 133 ++++ .../java/de/tudbut/security/DataKeeper.java | 137 ++++ .../tudbut/security/ExtendedStrictness.java | 25 + .../de/tudbut/security/PermissionManager.java | 71 +++ .../java/de/tudbut/security/Strictness.java | 30 + .../de/tudbut/security/StrictnessBuilder.java | 68 ++ .../AllowAllRestriction.java | 21 + .../CallClassRestriction.java | 65 ++ .../ClassLoaderRestriction.java | 108 ++++ .../permissionmanager/DenyAllRestriction.java | 21 + .../HideErrorRestriction.java | 14 + .../permissionmanager/PermissionAND.java | 50 ++ .../permissionmanager/PermissionOR.java | 47 ++ .../permissionmanager/Restriction.java | 47 ++ .../java/de/tudbut/timer/AsyncCatcher.java | 5 + .../java/de/tudbut/timer/AsyncRunnable.java | 5 + src/main/java/de/tudbut/timer/AsyncTask.java | 111 ++++ .../de/tudbut/timer/AsyncThenRunnable.java | 5 + src/main/java/de/tudbut/timer/SyncQueue.java | 112 ++++ src/main/java/de/tudbut/timer/Ticker.java | 13 + src/main/java/de/tudbut/timer/Ticks.java | 58 ++ .../java/de/tudbut/tools/Application.java | 144 +++++ .../java/de/tudbut/tools/ArrayGetter.java | 14 + src/main/java/de/tudbut/tools/ArrayTools.java | 115 ++++ .../java/de/tudbut/tools/AudioPlayer.java | 65 ++ src/main/java/de/tudbut/tools/BYMLFile.java | 36 ++ src/main/java/de/tudbut/tools/Bash.java | 129 ++++ .../de/tudbut/tools/BetterClassLoader.java | 51 ++ src/main/java/de/tudbut/tools/BetterJ.java | 138 ++++ .../java/de/tudbut/tools/BufferManager.java | 33 + src/main/java/de/tudbut/tools/CMD.java | 139 ++++ src/main/java/de/tudbut/tools/Cache.java | 187 ++++++ .../tudbut/tools/CalibratingInterpolator.java | 27 + src/main/java/de/tudbut/tools/Catcher.java | 38 ++ .../de/tudbut/tools/ClassLoaderAdapter.java | 28 + src/main/java/de/tudbut/tools/Config.java | 85 +++ .../java/de/tudbut/tools/ConfigSaverTCN.java | 78 +++ .../java/de/tudbut/tools/ConfigSaverTCN2.java | 263 ++++++++ src/main/java/de/tudbut/tools/DiscordRPC.java | 16 + .../java/de/tudbut/tools/DiscoverClasses.java | 107 ++++ .../java/de/tudbut/tools/ExtendedMath.java | 259 ++++++++ src/main/java/de/tudbut/tools/FileRW.java | 154 +++++ .../de/tudbut/tools/FloatInterpolator.java | 20 + src/main/java/de/tudbut/tools/Hasher.java | 144 +++++ src/main/java/de/tudbut/tools/ImageUtils.java | 442 +++++++++++++ .../java/de/tudbut/tools/JButtonList.java | 31 + src/main/java/de/tudbut/tools/Keyboard.java | 113 ++++ src/main/java/de/tudbut/tools/Lock.java | 216 +++++++ src/main/java/de/tudbut/tools/MapTools.java | 14 + src/main/java/de/tudbut/tools/MappableIO.java | 47 ++ src/main/java/de/tudbut/tools/MoreMath.java | 32 + src/main/java/de/tudbut/tools/Mouse.java | 133 ++++ .../tudbut/tools/MultidimensionalMaths.java | 38 ++ .../java/de/tudbut/tools/NoiseGenerator.java | 57 ++ src/main/java/de/tudbut/tools/NoiseMap.java | 37 ++ src/main/java/de/tudbut/tools/Nullable.java | 70 ++ src/main/java/de/tudbut/tools/OValue.java | 9 + .../de/tudbut/tools/ObjectSerializerTCN.java | 598 ++++++++++++++++++ src/main/java/de/tudbut/tools/Queue.java | 86 +++ .../java/de/tudbut/tools/ReflectUtil.java | 129 ++++ src/main/java/de/tudbut/tools/Registry.java | 74 +++ src/main/java/de/tudbut/tools/Retriever.java | 6 + .../java/de/tudbut/tools/Serializing.java | 544 ++++++++++++++++ .../java/de/tudbut/tools/SimpleTimer.java | 45 ++ src/main/java/de/tudbut/tools/Stack.java | 25 + src/main/java/de/tudbut/tools/Stopwatch.java | 28 + .../java/de/tudbut/tools/StringTools.java | 30 + src/main/java/de/tudbut/tools/ThreadPool.java | 87 +++ .../de/tudbut/tools/ThrowingRunnable.java | 5 + src/main/java/de/tudbut/tools/Time.java | 34 + src/main/java/de/tudbut/tools/Timer.java | 72 +++ src/main/java/de/tudbut/tools/Tools.java | 532 ++++++++++++++++ src/main/java/de/tudbut/tools/Tools2.java | 144 +++++ src/main/java/de/tudbut/tools/TrayApp.java | 32 + src/main/java/de/tudbut/tools/Value.java | 9 + src/main/java/de/tudbut/tools/ValueArray.java | 27 + src/main/java/de/tudbut/tools/VarTools.java | 30 + src/main/java/de/tudbut/tools/YMLFile.java | 54 ++ .../de/tudbut/tools/bintools/BinFileRW.java | 39 ++ .../tudbut/tools/bintools/encoding/Seed.java | 25 + .../tools/bintools/encoding/TCryptV1.java | 173 +++++ .../tools/bintools/encoding/TCryptV2.java | 34 + .../tudbut/tools/bintools/encoding/Value.java | 54 ++ .../java/de/tudbut/tools/encryption/Key.java | 145 +++++ .../de/tudbut/tools/encryption/KeyStream.java | 23 + .../de/tudbut/tools/encryption/RawKey.java | 89 +++ .../tudbut/tools/encryption/SaferRawKey.java | 59 ++ .../java/de/tudbut/type/ByteArrayList.java | 19 + .../java/de/tudbut/type/CharArrayList.java | 19 + src/main/java/de/tudbut/type/FInfo.java | 5 + .../de/tudbut/type/FileFormatException.java | 5 + src/main/java/de/tudbut/type/INothing.java | 5 + .../java/de/tudbut/type/IntArrayList.java | 17 + src/main/java/de/tudbut/type/Nothing.java | 8 + src/main/java/de/tudbut/type/O.java | 5 + .../java/de/tudbut/type/ShortArrayList.java | 19 + src/main/java/de/tudbut/type/Stoppable.java | 25 + src/main/java/de/tudbut/type/StringArray.java | 55 ++ src/main/java/de/tudbut/type/Token.java | 38 ++ src/main/java/de/tudbut/type/Vector2d.java | 92 +++ src/main/java/de/tudbut/type/Vector3d.java | 182 ++++++ src/main/java/de/tudbut/type/WIP.java | 5 + .../de/tudbut/ui/consoleui/ConsoleUI.java | 56 ++ .../tudbut/ui/windowgui/AdaptedGraphics.java | 30 + .../java/de/tudbut/ui/windowgui/Design.java | 6 + .../de/tudbut/ui/windowgui/FontRenderer.java | 66 ++ .../de/tudbut/ui/windowgui/GUIWindow.java | 7 + .../tudbut/ui/windowgui/RenderableWindow.java | 197 ++++++ .../de/tudbut/ui/windowgui/WindowGUI.java | 126 ++++ src/main/java/de/tudbut/window/Window.java | 146 +++++ src/test/java/AsyncTest.java | 109 ++++ src/test/java/CSTCN2.java | 50 ++ src/test/java/ClassDiscoverTest.java | 15 + src/test/java/DiscordRPCTest.java | 9 + src/test/java/KeyTest.java | 10 + src/test/java/PBIC2Test.java | 53 ++ src/test/java/RenderTest.java | 21 + src/test/java/SecurityTest.java | 43 ++ src/test/java/TCNDBTest.java | 13 + tuddylib | 1 + 278 files changed, 20987 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .idea/.gitignore create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/discord.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/serialmonitor_settings.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/de/tudbut/algorithm/Algorithm.java create mode 100644 src/main/java/de/tudbut/algorithm/AlgorithmAI.java create mode 100644 src/main/java/de/tudbut/async/Async.java create mode 100644 src/main/java/de/tudbut/async/Callback.java create mode 100644 src/main/java/de/tudbut/async/CallbackList.java create mode 100644 src/main/java/de/tudbut/async/ComposeCallback.java create mode 100644 src/main/java/de/tudbut/async/InternalReject.java create mode 100644 src/main/java/de/tudbut/async/Reject.java create mode 100644 src/main/java/de/tudbut/async/Resolve.java create mode 100644 src/main/java/de/tudbut/async/Task.java create mode 100644 src/main/java/de/tudbut/async/TaskCallable.java create mode 100644 src/main/java/de/tudbut/async/TaskQueue.java create mode 100644 src/main/java/de/tudbut/debug/Debug.java create mode 100644 src/main/java/de/tudbut/debug/DebugProfiler.java create mode 100644 src/main/java/de/tudbut/global/DebugStateManager.java create mode 100644 src/main/java/de/tudbut/global/GlobalSyncQueue.java create mode 100644 src/main/java/de/tudbut/gui/SettingsGUI.java create mode 100644 src/main/java/de/tudbut/gui/settingsgui/Setting.java create mode 100644 src/main/java/de/tudbut/io/AdaptiveSocketInputStream.java create mode 100644 src/main/java/de/tudbut/io/Buffer.java create mode 100644 src/main/java/de/tudbut/io/CLSPrintWriter.java create mode 100644 src/main/java/de/tudbut/io/CharBuffer.java create mode 100644 src/main/java/de/tudbut/io/DirectBuffers.java create mode 100644 src/main/java/de/tudbut/io/DoubleBuffer.java create mode 100644 src/main/java/de/tudbut/io/FileBus.java create mode 100644 src/main/java/de/tudbut/io/FileBusTransmitter.java create mode 100644 src/main/java/de/tudbut/io/ImageIOUtils.java create mode 100644 src/main/java/de/tudbut/io/IntBuffer.java create mode 100644 src/main/java/de/tudbut/io/LineReader.java create mode 100644 src/main/java/de/tudbut/io/RawLineReader.java create mode 100644 src/main/java/de/tudbut/io/StreamReader.java create mode 100644 src/main/java/de/tudbut/io/StreamRedirect.java create mode 100644 src/main/java/de/tudbut/io/StreamWriter.java create mode 100644 src/main/java/de/tudbut/io/TypedInputStream.java create mode 100644 src/main/java/de/tudbut/io/TypedOutputStream.java create mode 100644 src/main/java/de/tudbut/io/WriteDirection.java create mode 100644 src/main/java/de/tudbut/logger/DetailedLogger.java create mode 100644 src/main/java/de/tudbut/logger/EmptyLogger.java create mode 100644 src/main/java/de/tudbut/logger/Logger.java create mode 100644 src/main/java/de/tudbut/logger/LoggerSink.java create mode 100644 src/main/java/de/tudbut/math/ComplexFunction.java create mode 100644 src/main/java/de/tudbut/math/ComplexNumber.java create mode 100644 src/main/java/de/tudbut/math/Function.java create mode 100644 src/main/java/de/tudbut/net/http/HTTPContentType.java create mode 100644 src/main/java/de/tudbut/net/http/HTTPHeader.java create mode 100644 src/main/java/de/tudbut/net/http/HTTPRequest.java create mode 100644 src/main/java/de/tudbut/net/http/HTTPRequestType.java create mode 100644 src/main/java/de/tudbut/net/http/HTTPResponse.java create mode 100644 src/main/java/de/tudbut/net/http/HTTPResponseCode.java create mode 100644 src/main/java/de/tudbut/net/http/HTTPResponseFactory.java create mode 100644 src/main/java/de/tudbut/net/http/HTTPServer.java create mode 100644 src/main/java/de/tudbut/net/http/HTTPServerRequest.java create mode 100644 src/main/java/de/tudbut/net/http/HTTPUtils.java create mode 100644 src/main/java/de/tudbut/net/http/ParsedHTTPValue.java create mode 100644 src/main/java/de/tudbut/net/http/serverimpl/ContentType.java create mode 100644 src/main/java/de/tudbut/net/http/serverimpl/HTTPServerImpl.java create mode 100644 src/main/java/de/tudbut/net/http/serverimpl/Header.java create mode 100644 src/main/java/de/tudbut/net/http/serverimpl/Method.java create mode 100644 src/main/java/de/tudbut/net/http/serverimpl/Parameter.java create mode 100644 src/main/java/de/tudbut/net/http/serverimpl/Path.java create mode 100644 src/main/java/de/tudbut/net/http/serverimpl/Response.java create mode 100644 src/main/java/de/tudbut/net/http/serverimpl/Serve.java create mode 100644 src/main/java/de/tudbut/net/ic/Bus.java create mode 100644 src/main/java/de/tudbut/net/ic/PBIC.java create mode 100644 src/main/java/de/tudbut/net/nethandler/Packet.java create mode 100644 src/main/java/de/tudbut/net/nethandler/PacketData.java create mode 100644 src/main/java/de/tudbut/net/nethandler/PacketDataID.java create mode 100644 src/main/java/de/tudbut/net/pbic2/PBIC2.java create mode 100644 src/main/java/de/tudbut/net/pbic2/PBIC2ADisconnect.java create mode 100644 src/main/java/de/tudbut/net/pbic2/PBIC2AEventHandler.java create mode 100644 src/main/java/de/tudbut/net/pbic2/PBIC2AListener.java create mode 100644 src/main/java/de/tudbut/net/pbic2/PBIC2Client.java create mode 100644 src/main/java/de/tudbut/net/pbic2/PBIC2Passthrough.java create mode 100644 src/main/java/de/tudbut/net/pbic2/PBIC2Server.java create mode 100644 src/main/java/de/tudbut/net/smtp/SMTPDataOutput.java create mode 100644 src/main/java/de/tudbut/net/smtp/SMTPSender.java create mode 100644 src/main/java/de/tudbut/net/smtp/SMTPServerErrorException.java create mode 100644 src/main/java/de/tudbut/net/websocket/Operation.java create mode 100644 src/main/java/de/tudbut/net/websocket/WebSocket.java create mode 100644 src/main/java/de/tudbut/net/websocket/WebSocketServer.java create mode 100644 src/main/java/de/tudbut/net/ws/Client.java create mode 100644 src/main/java/de/tudbut/net/ws/Connection.java create mode 100644 src/main/java/de/tudbut/net/ws/ConnectionHandler.java create mode 100644 src/main/java/de/tudbut/net/ws/Server.java create mode 100644 src/main/java/de/tudbut/obj/Atomic.java create mode 100644 src/main/java/de/tudbut/obj/AtomicSink.java create mode 100644 src/main/java/de/tudbut/obj/AtomicUtils.java create mode 100644 src/main/java/de/tudbut/obj/CarrierException.java create mode 100644 src/main/java/de/tudbut/obj/Closable.java create mode 100644 src/main/java/de/tudbut/obj/ClosedClosableException.java create mode 100644 src/main/java/de/tudbut/obj/DoubleObject.java create mode 100644 src/main/java/de/tudbut/obj/DoubleTypedObject.java create mode 100644 src/main/java/de/tudbut/obj/IgnoreThrowRunnable.java create mode 100644 src/main/java/de/tudbut/obj/InstanceBoundMap.java create mode 100644 src/main/java/de/tudbut/obj/Mappable.java create mode 100644 src/main/java/de/tudbut/obj/NotSupportedException.java create mode 100644 src/main/java/de/tudbut/obj/PAtomic.java create mode 100644 src/main/java/de/tudbut/obj/Partial.java create mode 100644 src/main/java/de/tudbut/obj/PrintThrowRunnable.java create mode 100644 src/main/java/de/tudbut/obj/RayCast.java create mode 100644 src/main/java/de/tudbut/obj/RelativeVector2d.java create mode 100644 src/main/java/de/tudbut/obj/RelativeVector3d.java create mode 100644 src/main/java/de/tudbut/obj/Save.java create mode 100644 src/main/java/de/tudbut/obj/TLClassLoader.java create mode 100644 src/main/java/de/tudbut/obj/TLMap.java create mode 100644 src/main/java/de/tudbut/obj/Transient.java create mode 100644 src/main/java/de/tudbut/obj/TypedArray.java create mode 100644 src/main/java/de/tudbut/obj/TypedList.java create mode 100644 src/main/java/de/tudbut/obj/Vector2i.java create mode 100644 src/main/java/de/tudbut/parsing/AddressedTCN.java create mode 100644 src/main/java/de/tudbut/parsing/ArgumentParser.java create mode 100644 src/main/java/de/tudbut/parsing/AsyncJSON.java create mode 100644 src/main/java/de/tudbut/parsing/JSON.java create mode 100644 src/main/java/de/tudbut/parsing/Lang.java create mode 100644 src/main/java/de/tudbut/parsing/StringMapParser.java create mode 100644 src/main/java/de/tudbut/parsing/TCN.java create mode 100644 src/main/java/de/tudbut/parsing/TCNArray.java create mode 100644 src/main/java/de/tudbut/parsing/TudSort.java create mode 100644 src/main/java/de/tudbut/pluginapi/Plugin.java create mode 100644 src/main/java/de/tudbut/pluginapi/PluginEvent.java create mode 100644 src/main/java/de/tudbut/pluginapi/PluginException.java create mode 100644 src/main/java/de/tudbut/pluginapi/PluginGetEvent.java create mode 100644 src/main/java/de/tudbut/pluginapi/PluginLoadEvent.java create mode 100644 src/main/java/de/tudbut/pluginapi/PluginManager.java create mode 100644 src/main/java/de/tudbut/pluginapi/PluginUnloadEvent.java create mode 100644 src/main/java/de/tudbut/rendering/GIFEncoder.java create mode 100644 src/main/java/de/tudbut/rendering/Graph.java create mode 100644 src/main/java/de/tudbut/rendering/GraphRenderer.java create mode 100644 src/main/java/de/tudbut/rendering/Maths2D.java create mode 100644 src/main/java/de/tudbut/rendering/Maths3D.java create mode 100644 src/main/java/de/tudbut/rendering/Projection2D.java create mode 100644 src/main/java/de/tudbut/rendering/Projection3D.java create mode 100644 src/main/java/de/tudbut/rendering/Rectangle2D.java create mode 100644 src/main/java/de/tudbut/rendering/Rectangle3D.java create mode 100644 src/main/java/de/tudbut/rendering/RenderAssistant.java create mode 100644 src/main/java/de/tudbut/rendering/RenderObject2D.java create mode 100644 src/main/java/de/tudbut/rendering/RenderObject3D.java create mode 100644 src/main/java/de/tudbut/rendering/RenderObjectType.java create mode 100644 src/main/java/de/tudbut/rendering/RenderOutputType.java create mode 100644 src/main/java/de/tudbut/rendering/tph/TPH300.java create mode 100644 src/main/java/de/tudbut/rendering/tph/TPH301.java create mode 100644 src/main/java/de/tudbut/rendering/tph/TPH302.java create mode 100644 src/main/java/de/tudbut/security/AccessKiller.java create mode 100644 src/main/java/de/tudbut/security/DataKeeper.java create mode 100644 src/main/java/de/tudbut/security/ExtendedStrictness.java create mode 100644 src/main/java/de/tudbut/security/PermissionManager.java create mode 100644 src/main/java/de/tudbut/security/Strictness.java create mode 100644 src/main/java/de/tudbut/security/StrictnessBuilder.java create mode 100644 src/main/java/de/tudbut/security/permissionmanager/AllowAllRestriction.java create mode 100644 src/main/java/de/tudbut/security/permissionmanager/CallClassRestriction.java create mode 100644 src/main/java/de/tudbut/security/permissionmanager/ClassLoaderRestriction.java create mode 100644 src/main/java/de/tudbut/security/permissionmanager/DenyAllRestriction.java create mode 100644 src/main/java/de/tudbut/security/permissionmanager/HideErrorRestriction.java create mode 100644 src/main/java/de/tudbut/security/permissionmanager/PermissionAND.java create mode 100644 src/main/java/de/tudbut/security/permissionmanager/PermissionOR.java create mode 100644 src/main/java/de/tudbut/security/permissionmanager/Restriction.java create mode 100644 src/main/java/de/tudbut/timer/AsyncCatcher.java create mode 100644 src/main/java/de/tudbut/timer/AsyncRunnable.java create mode 100644 src/main/java/de/tudbut/timer/AsyncTask.java create mode 100644 src/main/java/de/tudbut/timer/AsyncThenRunnable.java create mode 100644 src/main/java/de/tudbut/timer/SyncQueue.java create mode 100644 src/main/java/de/tudbut/timer/Ticker.java create mode 100644 src/main/java/de/tudbut/timer/Ticks.java create mode 100644 src/main/java/de/tudbut/tools/Application.java create mode 100644 src/main/java/de/tudbut/tools/ArrayGetter.java create mode 100644 src/main/java/de/tudbut/tools/ArrayTools.java create mode 100644 src/main/java/de/tudbut/tools/AudioPlayer.java create mode 100644 src/main/java/de/tudbut/tools/BYMLFile.java create mode 100644 src/main/java/de/tudbut/tools/Bash.java create mode 100644 src/main/java/de/tudbut/tools/BetterClassLoader.java create mode 100644 src/main/java/de/tudbut/tools/BetterJ.java create mode 100644 src/main/java/de/tudbut/tools/BufferManager.java create mode 100644 src/main/java/de/tudbut/tools/CMD.java create mode 100644 src/main/java/de/tudbut/tools/Cache.java create mode 100644 src/main/java/de/tudbut/tools/CalibratingInterpolator.java create mode 100644 src/main/java/de/tudbut/tools/Catcher.java create mode 100644 src/main/java/de/tudbut/tools/ClassLoaderAdapter.java create mode 100644 src/main/java/de/tudbut/tools/Config.java create mode 100644 src/main/java/de/tudbut/tools/ConfigSaverTCN.java create mode 100644 src/main/java/de/tudbut/tools/ConfigSaverTCN2.java create mode 100644 src/main/java/de/tudbut/tools/DiscordRPC.java create mode 100644 src/main/java/de/tudbut/tools/DiscoverClasses.java create mode 100644 src/main/java/de/tudbut/tools/ExtendedMath.java create mode 100644 src/main/java/de/tudbut/tools/FileRW.java create mode 100644 src/main/java/de/tudbut/tools/FloatInterpolator.java create mode 100644 src/main/java/de/tudbut/tools/Hasher.java create mode 100644 src/main/java/de/tudbut/tools/ImageUtils.java create mode 100644 src/main/java/de/tudbut/tools/JButtonList.java create mode 100644 src/main/java/de/tudbut/tools/Keyboard.java create mode 100644 src/main/java/de/tudbut/tools/Lock.java create mode 100644 src/main/java/de/tudbut/tools/MapTools.java create mode 100644 src/main/java/de/tudbut/tools/MappableIO.java create mode 100644 src/main/java/de/tudbut/tools/MoreMath.java create mode 100644 src/main/java/de/tudbut/tools/Mouse.java create mode 100644 src/main/java/de/tudbut/tools/MultidimensionalMaths.java create mode 100644 src/main/java/de/tudbut/tools/NoiseGenerator.java create mode 100644 src/main/java/de/tudbut/tools/NoiseMap.java create mode 100644 src/main/java/de/tudbut/tools/Nullable.java create mode 100644 src/main/java/de/tudbut/tools/OValue.java create mode 100644 src/main/java/de/tudbut/tools/ObjectSerializerTCN.java create mode 100644 src/main/java/de/tudbut/tools/Queue.java create mode 100644 src/main/java/de/tudbut/tools/ReflectUtil.java create mode 100644 src/main/java/de/tudbut/tools/Registry.java create mode 100644 src/main/java/de/tudbut/tools/Retriever.java create mode 100644 src/main/java/de/tudbut/tools/Serializing.java create mode 100644 src/main/java/de/tudbut/tools/SimpleTimer.java create mode 100644 src/main/java/de/tudbut/tools/Stack.java create mode 100644 src/main/java/de/tudbut/tools/Stopwatch.java create mode 100644 src/main/java/de/tudbut/tools/StringTools.java create mode 100644 src/main/java/de/tudbut/tools/ThreadPool.java create mode 100644 src/main/java/de/tudbut/tools/ThrowingRunnable.java create mode 100644 src/main/java/de/tudbut/tools/Time.java create mode 100644 src/main/java/de/tudbut/tools/Timer.java create mode 100644 src/main/java/de/tudbut/tools/Tools.java create mode 100644 src/main/java/de/tudbut/tools/Tools2.java create mode 100644 src/main/java/de/tudbut/tools/TrayApp.java create mode 100644 src/main/java/de/tudbut/tools/Value.java create mode 100644 src/main/java/de/tudbut/tools/ValueArray.java create mode 100644 src/main/java/de/tudbut/tools/VarTools.java create mode 100644 src/main/java/de/tudbut/tools/YMLFile.java create mode 100644 src/main/java/de/tudbut/tools/bintools/BinFileRW.java create mode 100644 src/main/java/de/tudbut/tools/bintools/encoding/Seed.java create mode 100644 src/main/java/de/tudbut/tools/bintools/encoding/TCryptV1.java create mode 100644 src/main/java/de/tudbut/tools/bintools/encoding/TCryptV2.java create mode 100644 src/main/java/de/tudbut/tools/bintools/encoding/Value.java create mode 100644 src/main/java/de/tudbut/tools/encryption/Key.java create mode 100644 src/main/java/de/tudbut/tools/encryption/KeyStream.java create mode 100644 src/main/java/de/tudbut/tools/encryption/RawKey.java create mode 100644 src/main/java/de/tudbut/tools/encryption/SaferRawKey.java create mode 100644 src/main/java/de/tudbut/type/ByteArrayList.java create mode 100644 src/main/java/de/tudbut/type/CharArrayList.java create mode 100644 src/main/java/de/tudbut/type/FInfo.java create mode 100644 src/main/java/de/tudbut/type/FileFormatException.java create mode 100644 src/main/java/de/tudbut/type/INothing.java create mode 100644 src/main/java/de/tudbut/type/IntArrayList.java create mode 100644 src/main/java/de/tudbut/type/Nothing.java create mode 100644 src/main/java/de/tudbut/type/O.java create mode 100644 src/main/java/de/tudbut/type/ShortArrayList.java create mode 100644 src/main/java/de/tudbut/type/Stoppable.java create mode 100644 src/main/java/de/tudbut/type/StringArray.java create mode 100644 src/main/java/de/tudbut/type/Token.java create mode 100644 src/main/java/de/tudbut/type/Vector2d.java create mode 100644 src/main/java/de/tudbut/type/Vector3d.java create mode 100644 src/main/java/de/tudbut/type/WIP.java create mode 100644 src/main/java/de/tudbut/ui/consoleui/ConsoleUI.java create mode 100644 src/main/java/de/tudbut/ui/windowgui/AdaptedGraphics.java create mode 100644 src/main/java/de/tudbut/ui/windowgui/Design.java create mode 100644 src/main/java/de/tudbut/ui/windowgui/FontRenderer.java create mode 100644 src/main/java/de/tudbut/ui/windowgui/GUIWindow.java create mode 100644 src/main/java/de/tudbut/ui/windowgui/RenderableWindow.java create mode 100644 src/main/java/de/tudbut/ui/windowgui/WindowGUI.java create mode 100644 src/main/java/de/tudbut/window/Window.java create mode 100644 src/test/java/AsyncTest.java create mode 100644 src/test/java/CSTCN2.java create mode 100644 src/test/java/ClassDiscoverTest.java create mode 100644 src/test/java/DiscordRPCTest.java create mode 100644 src/test/java/KeyTest.java create mode 100644 src/test/java/PBIC2Test.java create mode 100644 src/test/java/RenderTest.java create mode 100644 src/test/java/SecurityTest.java create mode 100644 src/test/java/TCNDBTest.java create mode 120000 tuddylib diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f047d31 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/build/classes/ +/build/generated/ +/build/libs/ +/build/tmp/ +/build/reports +/.gradle +/build/test-results +/.idea +*.png +deployToGitlab \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..aee97f1 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,37 @@ +# This file is a template, and might need editing before it works on your project. +# To contribute improvements to CI/CD templates, please follow the Development guide at: +# https://docs.gitlab.com/ee/development/cicd/templates.html +# This specific template is located at: +# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml + +# This is a sample GitLab CI/CD configuration file that should run without any modifications. +# It demonstrates a basic 3 stage CI/CD pipeline. Instead of real tests or scripts, +# it uses echo commands to simulate the pipeline execution. +# +# A pipeline is composed of independent jobs that run scripts, grouped into stages. +# Stages run in sequential order, but jobs within stages run in parallel. +# +# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages +# + +default: + image: openjdk:8 + +stages: # List of stages for jobs, and their order of execution + - build + - deploy + +build-job: # This job runs in the build stage, which runs first. + stage: build + script: + - echo "Compiling the code..." + - ./gradlew jar + - echo "Compile complete." + + +deploy-job: # This job runs in the deploy stage. + stage: deploy # It only runs when *both* jobs in the test stage complete successfully. + script: + - echo "Deploying application..." + - ./gradlew publish + - echo "Application successfully deployed." diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..cd711a0 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8fe4d04 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/serialmonitor_settings.xml b/.idea/serialmonitor_settings.xml new file mode 100644 index 0000000..751f26b --- /dev/null +++ b/.idea/serialmonitor_settings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e289f75 --- /dev/null +++ b/LICENSE @@ -0,0 +1,41 @@ +Lesser TudbuT License + +Copyright (c) 2023 Daniel H. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, and/or distribute copies of the +Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice, this permission notice, and these conditions + shall be included in all copies or substantial portions of the Software. + +2. The Software must not be (re-)sold or sublicensed without significant + modifications. + +3. If this Software is an application, that is a piece of Software which the + end-user directly interacts with in order to do some task, or one which + is run in a stand-alone fashion in order to provide a service, then the + addition or improvement of usable and/or meaningful functionality is a + significant modification. The user should also quickly be able to tell that + they are using the modified Software, upon beginning to use it, on the + basis of major improvements. + +4. If this Software is a library, that is a collection of utilities that is + NOT an application by itself, or is marketed as such, then the addition + of any aspects that make it an application usable by an end-user are + deemed significant modifications. An application may also be an extension + of an already-existing application adding functionality which is usable + and meaningful to the end-user. The developer should also quickly be able + to tell that they are using the modified Software on the basis of major + improvements. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0782cde --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/b57be0d44d5c4a10a22124f5815cc2bb)](https://www.codacy.com/gh/TudbuT/tuddylib/dashboard?utm_source=github.com&utm_medium=referral&utm_content=TudbuT/tuddylib&utm_campaign=Badge_Grade) + +# tuddylib + + +TuddyLIB's package hell has been unified. + +To convert your own code, run `bash -c "find src -type f -print0 | while IFS= read -d \$'\0' s ; do (cat \"\$s\" | sed -E 's/([^.])tudbut\./\1de.tudbut./g' > \"\$s.tmp\") && mv \"\$s.tmp\" \"\$s\" && echo processed \"\$s\" ; done"` \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..4a9a08a --- /dev/null +++ b/build.gradle @@ -0,0 +1,84 @@ +import java.nio.file.Paths +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +plugins { + id 'java' + id 'maven-publish' +} + +repositories { + mavenCentral() +} + +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + +compileJava { + options.encoding = "UTF-8" +} + +dependencies { + implementation 'org.jetbrains:annotations:20.1.0' + implementation 'junit:junit:4.13.1' + implementation 'org.junit.jupiter:junit-jupiter:5.7.0' +} + +test { +} + +javadoc { + options.encoding = "UTF-8" + source = sourceSets.main.allJava + classpath = configurations.compile +} + +javadoc { + doLast { + def f; (f = new FileOutputStream("build/docs/javadoc/stylesheet.css")).write( + new URL("https://raw.githubusercontent.com/TudbuT/tools/master/dark_javadoc.css").newInputStream(). + readLines().join("\n").getBytes() + ); f.close() + } +} + +jar { + doLast { + File jar = new File("build/libs/tuddylib.jar") + File loc = new File("TuddyLIB.jar") + jar.renameTo(loc) + + ZipOutputStream out = new ZipOutputStream(new FileOutputStream("TuddyLIB-javadoc.zip")) + new File("build/docs/javadoc").eachFileRecurse(groovy.io.FileType.FILES) { + out.putNextEntry(new ZipEntry(Paths.get("build/docs/javadoc").relativize(it.toPath()).toString())) + byte[] bytes = new byte[it.length() as int] + new FileInputStream(it).read(bytes) + out.write(bytes) + out.closeEntry() + } + out.close() + } +} + +//jar.dependsOn("javadoc") + +publishing { + publications { + maven(MavenPublication) { + artifact('TuddyLIB.jar') + } + } + repositories { + maven { + url = "${System.getenv('CI_API_V4_URL')}/groups//-/packages/maven" + name = "GitLab" + credentials(HttpHeaderCredentials) { + name = 'Job-Token' + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..be52383 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..cba06f8 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'tuddylib' + diff --git a/src/main/java/de/tudbut/algorithm/Algorithm.java b/src/main/java/de/tudbut/algorithm/Algorithm.java new file mode 100644 index 0000000..16e6465 --- /dev/null +++ b/src/main/java/de/tudbut/algorithm/Algorithm.java @@ -0,0 +1,17 @@ +package de.tudbut.algorithm; + +public abstract class Algorithm { + + public abstract T solve(Input[] inputs); + + + public static class Input { + protected T t; + protected String[] data; + + public Input(T t, String[] data) { + this.t = t; + this.data = data; + } + } +} diff --git a/src/main/java/de/tudbut/algorithm/AlgorithmAI.java b/src/main/java/de/tudbut/algorithm/AlgorithmAI.java new file mode 100644 index 0000000..7457fa1 --- /dev/null +++ b/src/main/java/de/tudbut/algorithm/AlgorithmAI.java @@ -0,0 +1,124 @@ +package de.tudbut.algorithm; + +import de.tudbut.parsing.TudSort; + +import java.util.ArrayList; +import java.util.Arrays; + +public class AlgorithmAI extends Algorithm { + + private ArrayList associations = new ArrayList<>(); + private int nextChoiceID = 1; + + public AlgorithmAI(Association[] associations) { + this.associations = new ArrayList<>(Arrays.asList(associations)); + for (int i = 0; i < associations.length; i++) { + if(associations[i].choiceID > nextChoiceID + 1) { + nextChoiceID = associations[i].choiceID + 1; + } + } + } + + public AlgorithmAI() { + } + + public Association[] getAssociations() { + return associations.toArray(new Association[0]); + } + + public void reward(float amount) { + int lastChoiceID = nextChoiceID - 1; + associations = new ArrayList<>(Arrays.asList(TudSort.sort(associations.toArray(new Association[0]), association -> association.choiceID))); + for (int j = 1, i = associations.size() - 1; i >= 0; i--) { + Association association = associations.get(i); + if(association.choiceID < lastChoiceID) { + j *= 0.5; + lastChoiceID = association.choiceID; + } + association.score += amount * j; + } + } + + public void punish(float amount) { + int lastChoiceID = nextChoiceID - 1; + associations = new ArrayList<>(Arrays.asList(TudSort.sort(associations.toArray(new Association[0]), association -> association.choiceID))); + for (int j = 1, i = associations.size() - 1; i >= 0; i--) { + Association association = associations.get(i); + if(association.choiceID < lastChoiceID) { + j *= 0.5; + lastChoiceID = association.choiceID; + } + association.score -= amount * j; + } + } + + @Override + public T solve(Input[] inputs) { + float highest = -Float.MAX_VALUE; + Input best = null; + for (int i = 0; i < inputs.length; i++) { + float score = score(inputs[i]); + if(score > highest) { + highest = score; + best = inputs[i]; + } + } + if(best != null) { + for (int i = 0; i < best.data.length; i++) { + Association association = associationFromString(best.data[i]); + if(association == null) + continue; + association.choiceID = nextChoiceID; + } + nextChoiceID++; + return best.t; + } else { + nextChoiceID++; + return null; + } + } + + private float score(Input input) { + float r = 0; + for (int i = 0; i < input.data.length; i++) { + try { + int finalI = i; + if (associations.stream().noneMatch(association -> association.string != null && input.data[finalI] != null && association.string.equals(input.data[finalI]))) { + Association association = new Association(); + association.string = input.data[i]; + associations.add(association); + } + Association association = associationFromString(input.data[i]); + if (association == null) + continue; + r += association.score; + } catch (NullPointerException ignore) { } + } + return r; + } + + private Association associationFromString(String s) { + for (int i = 0; i < associations.size(); i++) { + if(associations.get(i).string.equals(s)) + return associations.get(i); + } + return null; + } + + public static class Association { + private String string; + private float score = 0.0f; + private int choiceID = 0; + + public Association() { } + + public Association(String s, float score) { + this.string = s; + this.score = score; + } + + public String toString() { + return string + ":" + score + " " + choiceID + ";"; + } + } +} diff --git a/src/main/java/de/tudbut/async/Async.java b/src/main/java/de/tudbut/async/Async.java new file mode 100644 index 0000000..5a37dfe --- /dev/null +++ b/src/main/java/de/tudbut/async/Async.java @@ -0,0 +1,53 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + * Include this with import static. + */ +public class Async { + + static final ThreadLocal context = ThreadLocal.withInitial(() -> TaskQueue.main); + + public static T await(Task task) { + return task.await(); + } + + public static void context(TaskQueue queue) { + context.set(queue); + } + + public static Task t(TaskCallable callable) { + return new Task<>(callable); + } + + /** + * This can be problematic if errors might happen in the callable. use t() instead. + */ + public static Task s(TaskCallable callable) { + return new Task<>(callable).ok(); + } + + public static Task loop(TaskCallable condition, TaskCallable body) { + return new Task<>((res, rej) -> { + while(new Task<>(condition).err(rej).ok().await()) { + new Task<>(body).err(rej).ok().await(); + ((TaskQueue)Thread.currentThread()).processNextHere(); + } + res.call(null); + }); + } + + public static boolean unblockQueue() { + if(Thread.currentThread() instanceof TaskQueue) { + TaskQueue q = (TaskQueue)Thread.currentThread(); + if(q.queue.hasNext()) { + while(q.queue.hasNext()) { + q.processNextHere(); + } + return true; + } + } + return false; + } +} diff --git a/src/main/java/de/tudbut/async/Callback.java b/src/main/java/de/tudbut/async/Callback.java new file mode 100644 index 0000000..149098f --- /dev/null +++ b/src/main/java/de/tudbut/async/Callback.java @@ -0,0 +1,11 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public interface Callback { + + void call(T t); +} diff --git a/src/main/java/de/tudbut/async/CallbackList.java b/src/main/java/de/tudbut/async/CallbackList.java new file mode 100644 index 0000000..63835e9 --- /dev/null +++ b/src/main/java/de/tudbut/async/CallbackList.java @@ -0,0 +1,38 @@ +package de.tudbut.async; + +import java.util.ArrayList; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class CallbackList implements Callback { + + private final ArrayList> callbacks = new ArrayList<>(); + private boolean done = false; + + public void add(Callback callback) { + callbacks.add(callback); + } + + @Override + public void call(T t) { + done = true; + for (int i = 0 ; i < callbacks.size() ; i++) { + callbacks.get(i).call(t); + } + } + + public boolean done() { + return done; + } + + public void setDone() { + done = true; + } + + public boolean exists() { + return callbacks.size() != 0; + } +} diff --git a/src/main/java/de/tudbut/async/ComposeCallback.java b/src/main/java/de/tudbut/async/ComposeCallback.java new file mode 100644 index 0000000..5d5e5bf --- /dev/null +++ b/src/main/java/de/tudbut/async/ComposeCallback.java @@ -0,0 +1,11 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public interface ComposeCallback { + + void call(T t, Callback resolve, Callback reject); +} diff --git a/src/main/java/de/tudbut/async/InternalReject.java b/src/main/java/de/tudbut/async/InternalReject.java new file mode 100644 index 0000000..2b720bc --- /dev/null +++ b/src/main/java/de/tudbut/async/InternalReject.java @@ -0,0 +1,11 @@ +package de.tudbut.async; + +class InternalReject extends Error { + Task task; + Reject real; + + InternalReject(Task task, Reject real) { + this.task = task; + this.real = real; + } +} diff --git a/src/main/java/de/tudbut/async/Reject.java b/src/main/java/de/tudbut/async/Reject.java new file mode 100644 index 0000000..7564bee --- /dev/null +++ b/src/main/java/de/tudbut/async/Reject.java @@ -0,0 +1,23 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class Reject extends Error { + final Object real; + + public Reject(Throwable throwable) { + super(throwable); + real = throwable; + } + public Reject(Object object) { + super(object.toString()); + real = object; + } + + public T getReal() { + return (T) real; + } +} diff --git a/src/main/java/de/tudbut/async/Resolve.java b/src/main/java/de/tudbut/async/Resolve.java new file mode 100644 index 0000000..516d803 --- /dev/null +++ b/src/main/java/de/tudbut/async/Resolve.java @@ -0,0 +1,23 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class Resolve extends RuntimeException { + final Object real; + + public Resolve(Throwable throwable) { + super(throwable); + real = throwable; + } + public Resolve(Object object) { + super(object.toString()); + real = object; + } + + public T getReal() { + return (T) real; + } +} diff --git a/src/main/java/de/tudbut/async/Task.java b/src/main/java/de/tudbut/async/Task.java new file mode 100644 index 0000000..b33c315 --- /dev/null +++ b/src/main/java/de/tudbut/async/Task.java @@ -0,0 +1,115 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class Task { + TaskQueue queue; + final TaskCallable callable; + final CallbackList resolve = new CallbackList<>(); + final CallbackList reject = new CallbackList<>(); + private boolean done = false; + private T result = null; + private Throwable rejection = null; + boolean isAwaiting = false; + Task parent = null; + + public Task(TaskCallable callable) { + this.callable = callable; + resolve.add((t) -> this.result = t); + } + public Task(TaskCallable callable, Class clazz) { + this.callable = callable; + resolve.add((t) -> this.result = t); + } + + public Task then(Callback resolve) { + this.resolve.add(resolve); + if(this.resolve.done()) { + resolve.call(result); + } + return this; + } + + public Task compose(ComposeCallback callback) { + return new Task((res, rej) -> { + callback.call(result, res, rej); + }).appendTo(this); + } + + private Task appendTo(Task task) { + this.parent = task; + task.then(done -> { + task.queue.register(this); + }); + return this; + } + + public Task err(Callback reject) { + if(parent != null) + parent.err(reject); + this.reject.add(reject); + return this; + } + + public T await() { + try { + if(!done) { + isAwaiting = true; + // If it is in the queue already + if(Thread.currentThread() == queue) { + while(!done) { + // Work while awaiting + queue.processNextHere(); + } + } + else { + synchronized (this) { + if(!done) { + wait(); + } + } + } + } + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + if(rejection != null && !reject.exists()) + throw new Reject(rejection); + return result; + } + + void setDone(Throwable rejection) { + if(this.rejection == null) { + this.rejection = rejection; + } + if (!done) { + synchronized (this) { + done = true; + notifyAll(); + } + } + } + + public boolean done() { + return done; + } + + public Task ok() { + return ok(Async.context.get()); + } + public Task ok(TaskQueue queue) { + if(this.queue != null) + return this; + this.queue = queue; + if (parent != null) { + parent.ok(queue); + return this; + } + queue.register(this); + return this; + } +} diff --git a/src/main/java/de/tudbut/async/TaskCallable.java b/src/main/java/de/tudbut/async/TaskCallable.java new file mode 100644 index 0000000..4efb89f --- /dev/null +++ b/src/main/java/de/tudbut/async/TaskCallable.java @@ -0,0 +1,11 @@ +package de.tudbut.async; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public interface TaskCallable { + + void execute(Callback resolve, Callback reject) throws Resolve, Reject; +} diff --git a/src/main/java/de/tudbut/async/TaskQueue.java b/src/main/java/de/tudbut/async/TaskQueue.java new file mode 100644 index 0000000..ace9a9a --- /dev/null +++ b/src/main/java/de/tudbut/async/TaskQueue.java @@ -0,0 +1,193 @@ +package de.tudbut.async; + +import java.util.concurrent.atomic.AtomicBoolean; + +import de.tudbut.global.DebugStateManager; +import de.tudbut.tools.Queue; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class TaskQueue extends Thread { + + public static final TaskQueue main = new TaskQueue(); + + private boolean stop = false; + final Queue> queue = new Queue<>(); + public final CallbackList rejectionHandlers = new CallbackList<>(); + private AtomicBoolean waiting = new AtomicBoolean(); + private boolean running = false; + private boolean queueEmpty = true; + + public TaskQueue() { + this.start(); + while (!waiting.get()); + try { + Thread.sleep(5); + } catch (InterruptedException e) {} + } + + public Queue> stopProcessing() throws IllegalAccessException { + if(this == main) + throw new IllegalAccessException("Can't stop main queue!"); + if(stop) + throw new IllegalStateException("Already stopped!"); + stop = true; + synchronized (this) { + this.notifyAll(); + } + return queue; + } + + public void finish() { + if(stop) + throw new IllegalStateException("Already stopped!"); + synchronized (this) { + this.notifyAll(); + } + while (queue.hasNext() || running || !queueEmpty) { + synchronized (queue) { + try { + queue.wait(); + } + catch (InterruptedException ignored) { + } + } + } + stop = true; + } + + Task register(Task task) { + task.queue = this; + queue.add(task); + synchronized (this) { + this.notifyAll(); + } + return task; + } + + /** + * + * @param task The task to make + * @param clazz The clazz of the return value. This is only used to determine T. + * @return The task that was created + */ + Task register(TaskCallable task, Class clazz) { + return register(new Task<>(task)); + } + + @Override + public void run() { + if(DebugStateManager.isDebugEnabled()) + System.out.println("[TaskQueue] Thread started."); + while (!stop) { + try { + synchronized (this) { + waiting.set(true); + wait(); + } + } + catch (InterruptedException e) { + return; + } + finally { + waiting.set(false); + } + if(stop) + return; + + process(); + } + } + + public void process() { + while(queue.hasNext()) { + queueEmpty = false; + try { + processNextHere(); + } + catch (Throwable e) { + if(!rejectionHandlers.exists()) { + System.err.println("!! Unhandled Task rejection:"); + e.printStackTrace(); + continue; + } + rejectionHandlers.call(e); + } + } + queueEmpty = true; + synchronized (queue) { + queue.notifyAll(); + } + } + + public void processNextHere() { + try { + if(!queue.hasNext()) + return; + + running = true; + Task task = (Task) queue.next(); + + // Execute the task. If it rejects using reject(), throw a reject to be handled by the exception block. + // If it resolves using throwResolve, redirect that to task.resolve. + // If it throws something, redirect that to task.reject if possible, otherwise throw a Reject of it to be handled. + try { + try { + task.callable.execute((t) -> { + if (!task.resolve.done()) + task.resolve.call(t); + task.setDone(null); + }, (t) -> { + throw new InternalReject(task, new Reject(t)); + }); + } + catch (Resolve resolve) { + if (!task.resolve.done()) + task.resolve.call(resolve.getReal()); + task.setDone(null); + } + catch (Reject reject) { + throw new InternalReject(task, reject); + } + catch (InternalReject r) { throw r; } + catch (Throwable throwable) { + throw new InternalReject(task, new Reject(throwable)); + } + } + catch(InternalReject rej) { + reject(rej.task, rej.real.getReal()); + } finally { + running = false; + } + } + catch (Reject e) { + if(!rejectionHandlers.exists()) { + System.err.println("!! Unhandled Task rejection:"); + e.printStackTrace(); + e.getCause().printStackTrace(); + return; + } + rejectionHandlers.call(e); + } + } + + private void reject(Task task, Throwable real) { + if (!task.reject.done()) { + task.setDone(real); + if (task.reject.exists() || task.isAwaiting) { + try { + task.reject.call(real); + } catch(InternalReject r) { + reject(r.task, r.real.getReal()); + } + } + else { + throw new Reject(real); + } + } + } + +} diff --git a/src/main/java/de/tudbut/debug/Debug.java b/src/main/java/de/tudbut/debug/Debug.java new file mode 100644 index 0000000..57abb48 --- /dev/null +++ b/src/main/java/de/tudbut/debug/Debug.java @@ -0,0 +1,26 @@ +package de.tudbut.debug; + +import java.util.HashMap; +import java.util.Map; + +public class Debug { + + private static final Map> debugProfilerMap = new HashMap<>(); + + public static DebugProfiler getDebugProfiler(java.lang.reflect.GenericDeclaration o, boolean allowFinished) { + if(!debugProfilerMap.containsKey(Thread.currentThread())) + debugProfilerMap.put(Thread.currentThread(), new HashMap<>()); + + if(debugProfilerMap.get(Thread.currentThread()).containsKey(o)) { + DebugProfiler profiler = debugProfilerMap.get(Thread.currentThread()).get(o); + if(allowFinished && profiler.isLocked()) { + return profiler; + } + else if(!profiler.isLocked()) { + return profiler; + } + } + debugProfilerMap.get(Thread.currentThread()).put(o, new DebugProfiler(o.toString(), "init")); + return debugProfilerMap.get(Thread.currentThread()).get(o); + } +} diff --git a/src/main/java/de/tudbut/debug/DebugProfiler.java b/src/main/java/de/tudbut/debug/DebugProfiler.java new file mode 100644 index 0000000..a315045 --- /dev/null +++ b/src/main/java/de/tudbut/debug/DebugProfiler.java @@ -0,0 +1,194 @@ +package de.tudbut.debug; + +import de.tudbut.parsing.TudSort; + +import java.util.ArrayList; +import java.util.Date; + +import static java.lang.System.currentTimeMillis; + +public class DebugProfiler { + + private final String name; + private final long start = currentTimeMillis(); + private long end; + private final ArrayList

sections = new ArrayList<>(); + private Section currentSection; + private boolean locked = false; + private Results results = null; + private boolean dirty = true; + + public static final class Section { + public final String name; + private long start; + private long end = 0; + private Results results; + + private Section(String s) { + name = s; + start = new Date().getTime(); + } + + private void end() { + end = new Date().getTime(); + } + + public long getTime() { + return end - start; + } + + public float getTimeRelative() { + return (float) getTime() / (float) results.getTotalTime(); + } + + @Override + public String toString() { + return name + ": " + getTime() + " (/" + results.getTotalTime() + ") (" + (getTimeRelative() * 100) + "%)"; + } + } + + public static class Results { + private long time; + private Section[] sections; + + public long getTotalTime() { + return time; + } + + public Section[] getSections() { + return sections; + } + + public Section getSectionByName(String name) { + for (int i = 0 ; i < sections.length ; i++) { + if(sections[i].name.equals(name)) + return sections[i]; + } + return null; + } + + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + for (int i = 0 ; i < sections.length ; i++) { + s.append(sections[i].toString()); + if(i < sections.length - 1) + s.append("\n"); + } + return s.toString(); + } + } + + public DebugProfiler(String name, String startingSection) { + this.name = name; + currentSection = new Section(startingSection); + } + + public synchronized DebugProfiler next(String next) { + checkLocked(); + dirty = true; + currentSection.end(); + synchronized (sections) { + sections.add(currentSection); + } + currentSection = new Section(next); + return this; + } + + public synchronized DebugProfiler endAll() { + checkLocked(); + dirty = true; + currentSection.end(); + synchronized (sections) { + sections.add(currentSection); + } + currentSection = null; + end = currentTimeMillis(); + + locked = true; + return this; + } + + public synchronized Results getResults() { + if(!locked) + throw new RuntimeException("DebugProfiler results requested before call to endAll()!"); + if(results == null) { + return createResults(); + } + return results; + } + + public synchronized Results getTempResults() { + checkLocked(); + end = currentTimeMillis(); + return createResults(); + } + + private Results createResults() { + results = new Results(); + results.time = end - start; + optimize(); + results.sections = TudSort.sort(sections.toArray(new Section[0]), section -> -section.getTime()); + return results; + } + + public void optimize() { + synchronized (sections) { + if(dirty) { + ArrayList
realSections = new ArrayList<>(); + for (int i = 0 ; i < sections.size() ; i++) { + Section sec = sections.get(i); + sec.results = results; + if (realSections.stream().noneMatch(section -> section.name.equals(sec.name))) { + Section section = new Section(sec.name); + section.start = 0; + section.end = sec.getTime(); + section.results = results; + realSections.add(sec); + } + else { + for (int j = 0 ; j < realSections.size() ; j++) { + if (realSections.get(j).name.equals(sections.get(i).name)) { + realSections.get(j).end += sec.getTime(); + } + } + } + } + sections.clear(); + sections.addAll(realSections); + dirty = false; + } + } + } + + private synchronized void checkLocked() throws RuntimeException { + if(locked) + throw new RuntimeException("DebugProfiler modify requested after call to endAll()!"); + } + + public boolean isLocked() { + return locked; + } + + public String getName() { + return name; + } + + public synchronized void delete() { + locked = true; + sections.clear(); + currentSection = null; + results = null; + dirty = true; + } + + public void finalize() { + delete(); + } + + @Override + public String toString() { + return name + ":\n" + getTempResults().toString(); + } +} diff --git a/src/main/java/de/tudbut/global/DebugStateManager.java b/src/main/java/de/tudbut/global/DebugStateManager.java new file mode 100644 index 0000000..b1227c1 --- /dev/null +++ b/src/main/java/de/tudbut/global/DebugStateManager.java @@ -0,0 +1,111 @@ +package de.tudbut.global; + +import de.tudbut.logger.DetailedLogger; +import de.tudbut.logger.EmptyLogger; +import de.tudbut.logger.Logger; +import de.tudbut.logger.LoggerSink; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public class DebugStateManager { + private static final ArrayList> toBeUpdated = new ArrayList<>(); + private static final ArrayList> dToBeUpdated = new ArrayList<>(); + private static final Logger debugLogger = new Logger("DEBUG"); + private static final DetailedLogger debugDLogger = new DetailedLogger("DEBUG"); + private static boolean debugEnabled = false; + + static { + updateState(); + } + + public static void enableDebug() { + debugEnabled = true; + updateState(); + } + + public static void disableDebug() { + debugEnabled = false; + updateState(); + } + + public static boolean isDebugEnabled() { + return debugEnabled; + } + + public static AtomicBoolean debugEnabled() { + return new AtomicBoolean(debugEnabled); + } + + public static void updateState() { + for (AtomicReference reference : toBeUpdated) { + reference.set(getDebugLogger(reference.get().getName())); + } + for (AtomicReference reference : dToBeUpdated) { + reference.set(getDebugDLogger(reference.get().getName())); + } + } + + public static LoggerSink getDebugLogger() { + if (isDebugEnabled()) + return debugLogger; + else + return new EmptyLogger("DEBUG"); + } + + public static LoggerSink getDebugLogger(String name) { + if (isDebugEnabled()) { + if (name == null || name.equals("DEBUG")) { + return debugLogger; + } + else + return new Logger(name); + } + else + return new EmptyLogger(name); + } + + public static AtomicReference debugLoggerReference() { + AtomicReference reference = new AtomicReference<>(getDebugLogger()); + toBeUpdated.add(reference); + return reference; + } + + public static AtomicReference debugLoggerReference(String name) { + AtomicReference reference = new AtomicReference<>(getDebugLogger(name)); + toBeUpdated.add(reference); + return reference; + } + + public static LoggerSink getDebugDLogger() { + if (isDebugEnabled()) + return debugDLogger; + else + return new EmptyLogger("DEBUG"); + } + + public static LoggerSink getDebugDLogger(String name) { + if (isDebugEnabled()) { + if (name == null || name.equals("DEBUG")) { + return debugDLogger; + } + else + return new DetailedLogger(name); + } + else + return new EmptyLogger(name); + } + + public static AtomicReference debugDLoggerReference() { + AtomicReference reference = new AtomicReference<>(getDebugDLogger()); + dToBeUpdated.add(reference); + return reference; + } + + public static AtomicReference debugDLoggerReference(String name) { + AtomicReference reference = new AtomicReference<>(getDebugDLogger(name)); + dToBeUpdated.add(reference); + return reference; + } +} diff --git a/src/main/java/de/tudbut/global/GlobalSyncQueue.java b/src/main/java/de/tudbut/global/GlobalSyncQueue.java new file mode 100644 index 0000000..c0e328c --- /dev/null +++ b/src/main/java/de/tudbut/global/GlobalSyncQueue.java @@ -0,0 +1,116 @@ +package de.tudbut.global; + +import de.tudbut.timer.Ticker; +import de.tudbut.tools.ThrowingRunnable; +import de.tudbut.tools.Queue; + +import java.util.ArrayList; +import java.util.Date; +import java.util.concurrent.atomic.AtomicBoolean; + +public class GlobalSyncQueue { + private static final Queue queue = new Queue<>(); + private static final Queue immediateQueue = new Queue<>(); + private static final ArrayList timers = new ArrayList<>(); + + static { + initAndRunAsynchronously(); + } + + public static void add(ThrowingRunnable runnable) { + if(runnable == null) + throw new IllegalArgumentException(); + queue.add(runnable); + } + + public static void addImmediate(ThrowingRunnable runnable) { + if(runnable == null) + throw new IllegalArgumentException(); + immediateQueue.add(runnable); + } + + public static void addTimer(Ticker ticker, AtomicBoolean stop, int delay) { + addTimer(new Ticker() { + public int getDelay() { + return delay; + } + + public void run() { + ticker.run(); + } + + public boolean doRun() { + return !stop.get(); + } + }); + } + + public static void addTimer(Ticker ticker, int delay) { + addTimer(new Ticker() { + public int getDelay() { + return delay; + } + + public void run() { + ticker.run(); + } + }); + } + + public static void addTimer(Ticker ticker) { + timers.add(new Object[]{ticker, 0L}); + } + + private static void initAndRun() { + while (true) { + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } + catch (Exception exception) { + exception.printStackTrace(); + } + continue; + } + if (queue.hasNext()) { + try { + queue.next().run(); + } + catch (Exception exception) { + exception.printStackTrace(); + } + } + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } + catch (Exception exception) { + exception.printStackTrace(); + } + continue; + } + // An enhanced loop will throw an exception, fuck you intellij for this shit suggestion + for (int i = 0; i < timers.size(); i++) { + Object[] timer = timers.get(i); + if (new Date().getTime() - ((Long) timer[1]) >= ((Ticker) timer[0]).getDelay()) { + if (((Ticker) timer[0]).doRun()) { + ((Ticker) timer[0]).run(); + timer[1] = new Date().getTime(); + } + } + } + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } + catch (Exception exception) { + exception.printStackTrace(); + } + } + } + } + + private static void initAndRunAsynchronously() { + new Thread(GlobalSyncQueue::initAndRun, "GlobalSyncQueue").start(); + } +} diff --git a/src/main/java/de/tudbut/gui/SettingsGUI.java b/src/main/java/de/tudbut/gui/SettingsGUI.java new file mode 100644 index 0000000..6474833 --- /dev/null +++ b/src/main/java/de/tudbut/gui/SettingsGUI.java @@ -0,0 +1,166 @@ +package de.tudbut.gui; + +import de.tudbut.global.DebugStateManager; +import de.tudbut.tools.FileRW; +import de.tudbut.tools.Tools; +import de.tudbut.window.Window; +import de.tudbut.gui.settingsgui.Setting; + +import javax.swing.*; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class SettingsGUI { + public final ArrayList settings = new ArrayList<>(); + public final ArrayList changeListeners = new ArrayList<>(); + private final boolean save; + private final Window window; + private final String name; + + public SettingsGUI(boolean saveToFile, String name) { + this.save = saveToFile; + window = new Window(name, name, false); + this.name = name; + + if (saveToFile) + loadFromFile(); + } + + public void open() { + window.setSize(500, 500); + window.open(); + + for (int i = 0; i < settings.size(); i++) { + Setting setting = settings.get(i); + for (int j = 0; j < i; j++) { + Setting s = settings.get(j); + if (s.id.equals(setting.id)) { + setting = s; + settings.remove(i); + i = j; + } + } + JButton button = new JButton( + setting.name + ( + setting.type != Setting.Type.BUTTON ? ( + ": " + ( + setting.value ? + setting.type == Setting.Type.ON_OFF ? "ON" : "YES" : + setting.type == Setting.Type.ON_OFF ? "OFF" : "NO" + ) + ) : "" + ) + ); + + Setting finalSetting = setting; + button.addActionListener(actionEvent -> { + finalSetting.value = !finalSetting.value; + button.setText( + finalSetting.name + ( + finalSetting.type != Setting.Type.BUTTON ? ( + ": " + ( + finalSetting.value ? + finalSetting.type == Setting.Type.ON_OFF ? "ON" : "YES" : + finalSetting.type == Setting.Type.ON_OFF ? "OFF" : "NO" + ) + ) : "" + ) + ); + for (Runnable changeListener : finalSetting.onChange) { + changeListener.run(); + } + for (Runnable changeListener : changeListeners) { + changeListener.run(); + } + if (save) { + try { + saveSettings(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + }); + + button.setLocation(0, i * 25); + button.setSize(500, 25); + window.frame.addComponentListener(new ComponentListener() { + @Override + public void componentResized(ComponentEvent componentEvent) { + button.setSize(window.frame.getSize().width, 25); + } + + @Override + public void componentMoved(ComponentEvent componentEvent) { + } + + @Override + public void componentShown(ComponentEvent componentEvent) { + } + + @Override + public void componentHidden(ComponentEvent componentEvent) { + } + }); + button.setVisible(true); + window.frame.add(button); + } + } + + public void saveSettings() throws IOException { + FileRW file = new FileRW("settings_" + name + ".map"); + file.setContent(Tools.mapToString(toMap()).replaceAll(";", ";\n")); + } + + public void loadFromFile() { + if (Files.exists(Paths.get("settings_" + name + ".map"))) { + try { + fromMap(Tools.stringToMap(new FileRW("settings_" + name + ".map").getContent().join(""))); + } + catch (Exception e) { + DebugStateManager.getDebugDLogger("Setting parser").subChannel(name).error("Couldn't read settings! Resetting them."); + try { + new FileRW("settings_" + name + ".map").setContent(":ON_OFF@%I false;"); + } + catch (IOException ioException) { + ioException.printStackTrace(); + } + } + } + } + + public Map toMap() { + Map map = new HashMap<>(); + for (Setting setting : settings) { + map.put(setting.id, setting.type + "@" + setting.name + ": " + setting.value); + } + return map; + } + + public void fromMap(Map map) { + for (String key : map.keySet()) { + try { + Setting setting = new Setting(); + setting.id = key; + String value = map.get(key); + + setting.type = Setting.Type.valueOf(value.split("@")[0]); + value = value.split("@")[1]; + setting.name = value.split(": ")[0]; + value = value.split(": ")[1]; + setting.value = Boolean.parseBoolean(value); + + settings.add(setting); + } + catch (Exception e) { + DebugStateManager.getDebugDLogger("Setting parser").subChannel(name).warn("Skipping bad setting: " + key); + } + } + } +} diff --git a/src/main/java/de/tudbut/gui/settingsgui/Setting.java b/src/main/java/de/tudbut/gui/settingsgui/Setting.java new file mode 100644 index 0000000..3f31305 --- /dev/null +++ b/src/main/java/de/tudbut/gui/settingsgui/Setting.java @@ -0,0 +1,17 @@ +package de.tudbut.gui.settingsgui; + +import java.util.ArrayList; + +public class Setting { + public final ArrayList onChange = new ArrayList<>(); + public Type type = Type.ON_OFF; + public String name = ""; + public String id = ""; + public boolean value = false; + + public enum Type { + ON_OFF, + YES_NO, + BUTTON + } +} diff --git a/src/main/java/de/tudbut/io/AdaptiveSocketInputStream.java b/src/main/java/de/tudbut/io/AdaptiveSocketInputStream.java new file mode 100644 index 0000000..5a3a560 --- /dev/null +++ b/src/main/java/de/tudbut/io/AdaptiveSocketInputStream.java @@ -0,0 +1,50 @@ +package de.tudbut.io; + + +import de.tudbut.tools.Queue; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.net.SocketTimeoutException; + +public class AdaptiveSocketInputStream extends InputStream { + + public final InputStream parent; + public final Socket socket; + private final Queue cache = new Queue<>(); + + public AdaptiveSocketInputStream(Socket socket) throws IOException { + this.socket = socket; + this.parent = socket.getInputStream(); + } + + @Override + public int read() throws IOException { + if(cache.hasNext()) + return cache.next(); + else + return parent.read(); + } + + @Override + public int available() throws IOException { + int timeout = socket.getSoTimeout(); + socket.setSoTimeout(1); + try { + while(true) cache.add(parent.read()); + } catch (IOException e) { + Throwable throwable = e; + // Expected! + while (!(throwable instanceof SocketTimeoutException) && throwable != null) { + throwable = throwable.getCause(); + } + if (throwable == null) + throw e; + } + socket.setSoTimeout(timeout); + if(parent.available() != 0) + return parent.available(); + return cache.size() + parent.available(); + } +} diff --git a/src/main/java/de/tudbut/io/Buffer.java b/src/main/java/de/tudbut/io/Buffer.java new file mode 100644 index 0000000..354f643 --- /dev/null +++ b/src/main/java/de/tudbut/io/Buffer.java @@ -0,0 +1,18 @@ +package de.tudbut.io; + +public class Buffer { + private byte[] content; + + protected Buffer() { + } + + public static Buffer create(int length) { + Buffer buffer = new Buffer(); + buffer.content = new byte[length]; + return buffer; + } + + public Object get() { + return content; + } +} diff --git a/src/main/java/de/tudbut/io/CLSPrintWriter.java b/src/main/java/de/tudbut/io/CLSPrintWriter.java new file mode 100644 index 0000000..e8eca93 --- /dev/null +++ b/src/main/java/de/tudbut/io/CLSPrintWriter.java @@ -0,0 +1,61 @@ +package de.tudbut.io; + +import de.tudbut.tools.ReflectUtil; + +import java.io.*; + +public class CLSPrintWriter extends PrintWriter { + + public String customLineSeparator = "\n"; + + public CLSPrintWriter(Writer writer) { + super(writer); + } + + public CLSPrintWriter(Writer writer, boolean b) { + super(writer, b); + } + + public CLSPrintWriter(OutputStream outputStream) { + super(outputStream); + } + + public CLSPrintWriter(OutputStream outputStream, boolean b) { + super(outputStream, b); + } + + public CLSPrintWriter(String s) throws FileNotFoundException { + super(s); + } + + public CLSPrintWriter(String s, String s1) throws FileNotFoundException, UnsupportedEncodingException { + super(s, s1); + } + + public CLSPrintWriter(File file) throws FileNotFoundException { + super(file); + } + + public CLSPrintWriter(File file, String s) throws FileNotFoundException, UnsupportedEncodingException { + super(file, s); + } + + @Override + public void println() { + try { + synchronized(this.lock) { + if (this.out == null) { + throw new IOException("Stream closed"); + } + this.out.write(customLineSeparator); + if (ReflectUtil.getPrivateFieldByTypeIndex(PrintWriter.class, this, boolean.class, 0)) { + this.out.flush(); + } + } + } catch (InterruptedIOException var4) { + Thread.currentThread().interrupt(); + } catch (IOException var5) { + ReflectUtil.setPrivateFieldByTypeIndex(PrintWriter.class, this, boolean.class, 1, true); + } + } +} diff --git a/src/main/java/de/tudbut/io/CharBuffer.java b/src/main/java/de/tudbut/io/CharBuffer.java new file mode 100644 index 0000000..bb5e8c0 --- /dev/null +++ b/src/main/java/de/tudbut/io/CharBuffer.java @@ -0,0 +1,28 @@ +package de.tudbut.io; + +public class CharBuffer extends Buffer { + private char[] content; + + private CharBuffer() { + } + + public static CharBuffer create(int length) { + CharBuffer buffer = new CharBuffer(); + buffer.content = new char[length / Character.BYTES]; + return buffer; + } + + public static CharBuffer createN(int length) { + CharBuffer buffer = new CharBuffer(); + buffer.content = new char[length]; + return buffer; + } + + public Object get() { + return content; + } + + public String getAsString() { + return new String(content); + } +} diff --git a/src/main/java/de/tudbut/io/DirectBuffers.java b/src/main/java/de/tudbut/io/DirectBuffers.java new file mode 100644 index 0000000..7048b13 --- /dev/null +++ b/src/main/java/de/tudbut/io/DirectBuffers.java @@ -0,0 +1,15 @@ +package de.tudbut.io; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class DirectBuffers { + + public static ByteBuffer createDirectByteBuffer(int size) { + return prepareBuffer(ByteBuffer.allocateDirect(size)); + } + + private static ByteBuffer prepareBuffer(ByteBuffer buffer) { + return buffer.order(ByteOrder.nativeOrder()); + } +} \ No newline at end of file diff --git a/src/main/java/de/tudbut/io/DoubleBuffer.java b/src/main/java/de/tudbut/io/DoubleBuffer.java new file mode 100644 index 0000000..233342f --- /dev/null +++ b/src/main/java/de/tudbut/io/DoubleBuffer.java @@ -0,0 +1,24 @@ +package de.tudbut.io; + +public class DoubleBuffer extends Buffer { + private double[] content; + + private DoubleBuffer() { + } + + public static DoubleBuffer create(int length) { + DoubleBuffer buffer = new DoubleBuffer(); + buffer.content = new double[length / Double.BYTES]; + return buffer; + } + + public static DoubleBuffer createN(int length) { + DoubleBuffer buffer = new DoubleBuffer(); + buffer.content = new double[length]; + return buffer; + } + + public Object get() { + return content; + } +} diff --git a/src/main/java/de/tudbut/io/FileBus.java b/src/main/java/de/tudbut/io/FileBus.java new file mode 100644 index 0000000..daa5a00 --- /dev/null +++ b/src/main/java/de/tudbut/io/FileBus.java @@ -0,0 +1,111 @@ +package de.tudbut.io; + +import de.tudbut.tools.Lock; + +import java.io.*; +import java.net.URI; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; + +/** + * Use files as Sockets/SocketServers, unlimited clients/servers supported on every file + */ +public class FileBus extends File { + { + try { + if(createNewFile()); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + final RandomAccessFile file = new RandomAccessFile(this, "rw"); + final InputStream i; + final OutputStream o; + + FileLock lock; + final Lock localLock = new Lock(); + + { + try { + file.seek((int) file.length()); + } + catch (Exception e) { + e.printStackTrace(); + } + i = new InputStream() { + @Override + public int read() throws IOException { + return file.read(); + } + }; + o = new OutputStream() { + @Override + public void write(int i) throws IOException { + file.write(i); + } + }; + } + final TypedInputStream ir = new TypedInputStream(i); + final TypedOutputStream ow = new TypedOutputStream(o); + + public FileBus(String s) throws FileNotFoundException { + super(s); + } + + public FileBus(String s, String s1) throws FileNotFoundException { + super(s, s1); + } + + public FileBus(File file, String s) throws FileNotFoundException { + super(file, s); + } + + public FileBus(URI uri) throws FileNotFoundException { + super(uri); + } + + public FileBus(File file) throws FileNotFoundException { + super(file.getAbsolutePath()); + } + + public TypedInputStream getTypedReader() { + return ir; + } + + public TypedOutputStream getTypedWriter() { + return ow; + } + + public OutputStream getWriter() { + return o; + } + + public InputStream getReader() { + return i; + } + + public void waitForInput() throws IOException { + ir.waitForInput(); + } + + public void startWrite() throws IOException { + localLock.waitHere(); + localLock.lock(); + while (lock == null) { + try { + lock = file.getChannel().lock(); + } catch (OverlappingFileLockException ignore) {} + } + } + + public void stopWrite() throws IOException { + if(lock != null) { + lock.release(); + localLock.unlock(); + lock = null; + } + o.flush(); + } +} diff --git a/src/main/java/de/tudbut/io/FileBusTransmitter.java b/src/main/java/de/tudbut/io/FileBusTransmitter.java new file mode 100644 index 0000000..4cace77 --- /dev/null +++ b/src/main/java/de/tudbut/io/FileBusTransmitter.java @@ -0,0 +1,102 @@ +package de.tudbut.io; + +import de.tudbut.obj.NotSupportedException; +import de.tudbut.timer.AsyncCatcher; +import de.tudbut.type.Stoppable; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.Executor; + +/** + * Transmits a FileBus over a different InputStream. always use a separate instance of FileBus + * to avoid losing data! + */ +public class FileBusTransmitter implements Stoppable { + + @Override + public void start() throws NotSupportedException { + throw new NotSupportedException(); + } + + public FileBusTransmitter(FileBus bus, InputStream i, OutputStream o, Executor e0, Executor e1, AsyncCatcher onError) { + e0.execute(() -> runI(bus,i,onError)); + e1.execute(() -> runO(bus,o,onError)); + } + + private void runI(FileBus bus, InputStream i, AsyncCatcher onError) { + boolean locked = false; + boolean wait = false; + while (!isStopped()) { + try { + if (i.available() == 0) { + if (!wait) { + wait = true; + Thread.sleep(1); + continue; + } + else if(locked) { + wait = false; + locked = false; + bus.stopWrite(); + } + } + int input = i.read(); + if(input != -1) { + if(!locked) { + locked = true; + bus.startWrite(); + } + bus.o.write(input); + } + else if(!wait) { + wait = true; + Thread.sleep(1); + continue; + } + else if(locked) { + wait = false; + locked = false; + bus.stopWrite(); + } + wait = false; + } + catch (IOException | InterruptedException e) { + try { + onError.run(e); + } + catch (Exception exception) { + exception.printStackTrace(); + } + } + } + } + + private void runO(FileBus bus, OutputStream o, AsyncCatcher onError) { + boolean locked = false; + boolean wait = false; + while (!isStopped()) { + try { + int input = bus.i.read(); + if(input != -1) { + o.write(input); + } + else if(!wait) { + wait = true; + Thread.sleep(1); + continue; + } + wait = false; + } + catch (IOException | InterruptedException e) { + try { + onError.run(e); + } + catch (Exception exception) { + exception.printStackTrace(); + } + } + } + } +} diff --git a/src/main/java/de/tudbut/io/ImageIOUtils.java b/src/main/java/de/tudbut/io/ImageIOUtils.java new file mode 100644 index 0000000..8dd6259 --- /dev/null +++ b/src/main/java/de/tudbut/io/ImageIOUtils.java @@ -0,0 +1,30 @@ +package de.tudbut.io; + +import de.tudbut.tools.Tools; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class ImageIOUtils { + + public static int[] imageToBytes(BufferedImage image) { + DataBuffer buffer = image.getData().getDataBuffer(); + + if(buffer.getClass() == DataBufferInt.class) { + return ((DataBufferInt) buffer).getData(); + } + if(buffer.getClass() == DataBufferByte.class) { + return Tools.byteArrayToIntArray(((DataBufferByte) buffer).getData()); + } + throw new IllegalStateException(); + } + + public static BufferedImage imageFromBytes(int[] ints) throws IOException { + return ImageIO.read(new ByteArrayInputStream(Tools.charArrayToByteArray(Tools.intArrayToCharArray(ints)))); + } +} diff --git a/src/main/java/de/tudbut/io/IntBuffer.java b/src/main/java/de/tudbut/io/IntBuffer.java new file mode 100644 index 0000000..f9862b2 --- /dev/null +++ b/src/main/java/de/tudbut/io/IntBuffer.java @@ -0,0 +1,24 @@ +package de.tudbut.io; + +public class IntBuffer extends Buffer { + private int[] content; + + private IntBuffer() { + } + + public static IntBuffer create(int length) { + IntBuffer buffer = new IntBuffer(); + buffer.content = new int[length / Integer.BYTES]; + return buffer; + } + + public static IntBuffer createN(int length) { + IntBuffer buffer = new IntBuffer(); + buffer.content = new int[length]; + return buffer; + } + + public Object get() { + return content; + } +} diff --git a/src/main/java/de/tudbut/io/LineReader.java b/src/main/java/de/tudbut/io/LineReader.java new file mode 100644 index 0000000..231d831 --- /dev/null +++ b/src/main/java/de/tudbut/io/LineReader.java @@ -0,0 +1,47 @@ +package de.tudbut.io; + +import java.io.IOException; +import java.io.Reader; + +public class LineReader { + + public static String readLineLF(Reader stream) throws IOException { + StringBuilder buffer = new StringBuilder(); + char[] chars = new char[1]; + stream.read(chars); + while (chars[0] != 0x0A) { + stream.read(chars); + buffer.append(chars[0]); + } + buffer.setLength(buffer.length()-1); + buffer.trimToSize(); + return buffer.toString(); + } + + public static String readLineCRLForLF(Reader stream) throws IOException { + StringBuilder buffer = new StringBuilder(); + char[] chars = new char[1]; + stream.read(chars); + while (chars[0] != 0x0A) { + stream.read(chars); + buffer.append(chars[0]); + } + if(buffer.toString().endsWith("\r\n")) + buffer.setLength(buffer.length()-1); + buffer.trimToSize(); + return buffer.toString(); + } + + public static String readLineCRLF(Reader stream) throws IOException { + StringBuilder buffer = new StringBuilder(); + char[] chars = new char[1]; + stream.read(chars); + while (chars[0] != 0x0A) { + buffer.append(chars[0]); + stream.read(chars); + } + buffer.setLength(buffer.length()-1); + buffer.trimToSize(); + return buffer.toString(); + } +} diff --git a/src/main/java/de/tudbut/io/RawLineReader.java b/src/main/java/de/tudbut/io/RawLineReader.java new file mode 100644 index 0000000..4aa445a --- /dev/null +++ b/src/main/java/de/tudbut/io/RawLineReader.java @@ -0,0 +1,60 @@ +package de.tudbut.io; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author TudbuT + * @since 03 Oct 2021 + */ + +// Fuck BufferedReader, im making my own. +public class RawLineReader extends InputStream { + + private boolean isAtCR = false; + private boolean readLineInvoked = false; + + private final InputStream stream; + + public RawLineReader(InputStream stream) { + this.stream = stream; + } + + @Override + public synchronized int read() throws IOException { + int i = stream.read(); + if(readLineInvoked && i == '\n' && isAtCR) { + readLineInvoked = false; + return read(); + } + if(isAtCR) { + isAtCR = false; + } + if(i == '\r') { + isAtCR = true; + } + return i; + } + + @Override + public int available() throws IOException { + return stream.available(); + } + + @Override + public void close() throws IOException { + stream.close(); + } + + public synchronized String readLine() throws IOException { + String line = ""; + int i; + boolean _isAtCR = isAtCR; + while ((i = read()) != '\r' && (_isAtCR || i != '\n')) { + if(i != '\n') + line += (char) i; + } + readLineInvoked = true; + return line; + } +} diff --git a/src/main/java/de/tudbut/io/StreamReader.java b/src/main/java/de/tudbut/io/StreamReader.java new file mode 100644 index 0000000..5431556 --- /dev/null +++ b/src/main/java/de/tudbut/io/StreamReader.java @@ -0,0 +1,156 @@ +package de.tudbut.io; + +import de.tudbut.type.IntArrayList; +import de.tudbut.obj.CarrierException; + +import java.io.*; + + +/** + * Helper for reading {@link java.io.InputStream} + */ +public class StreamReader { + + /** + * Buffer size for reading. Directly affects speed. + */ + public static int BUFFER_SIZE = 4096; + + /** + * The stream to read from + */ + public final InputStream inputStream; + + private boolean endReached = false; + + /** + * Constructs a StreamReader for an InputStream + * @param stream The stream to read + */ + public StreamReader(InputStream stream) { + this.inputStream = stream; + } + + /** + * Reads a byte from the input stream. Unaffected by {@link StreamReader#BUFFER_SIZE} + * @return read byte + * @throws IOException Inherited from {@link InputStream#read} + * @throws ArrayIndexOutOfBoundsException When the stream end was reached + */ + public byte readNextByte() throws IOException, ArrayIndexOutOfBoundsException { + int got = inputStream.read(); + if (got < 0) { + endReached = true; + throw new ArrayIndexOutOfBoundsException("Stream end reached"); + } + return (byte) got; + } + + /** + * Reads a byte from the input stream. Unaffected by {@link StreamReader#BUFFER_SIZE}. + * Byte is converted to int, being unsigned in the process. + * @return read unsigned bytes + * @throws IOException Inherited from {@link StreamReader#readNextByte()} + * @throws ArrayIndexOutOfBoundsException Inherited from {@link StreamReader#readNextByte()} + */ + public int readNextUnsignedByte() throws IOException, ArrayIndexOutOfBoundsException { + return Byte.toUnsignedInt(readNextByte()); + } + + /** + * Reads bytes from the input stream until end is reached. Unaffected by {@link StreamReader#BUFFER_SIZE}. + * Byte is converted to int, being unsigned in the process. + * @return read unsigned bytes + * @throws IOException Inherited from {@link StreamReader#readNextUnsignedByte()} + */ + public int[] readAllAsUnsignedBytes() throws IOException { + IntArrayList bytes = new IntArrayList(); + int currentByte; + while (!endReached) { + try { + currentByte = readNextUnsignedByte(); + bytes.add(currentByte); + } + catch (ArrayIndexOutOfBoundsException ignore) { + } + } + return bytes.toIntArray(); + } + + /** + * Reads bytes from the input stream until end is reached. Affected by {@link StreamReader#BUFFER_SIZE}. + * @return read bytes + * @throws IOException Inherited from {@link StreamReader#readNextByte()} + */ + public byte[] readAllAsBytes() throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + byte[] currentBytes = new byte[BUFFER_SIZE]; + int len; + try { + while ((len = inputStream.read(currentBytes)) > 0) { + bytes.write(currentBytes, 0, len); + } + } catch (IOException e) { + throw new CarrierException(e, bytes.toByteArray()); + } + return bytes.toByteArray(); + } + + /** + * Reads all bytes in the stream and converts them to a char[] + * @return {@link Character} array (native) + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public char[] readAllAsChars() throws IOException { + return new String(readAllAsBytes()).toCharArray(); + } + + /** + * Reads all bytes in the stream and converts them to a char[] + * @param encoding The encoding to use for conversion + * @return {@link Character} array (native) + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public char[] readAllAsChars(String encoding) throws IOException { + return new String(readAllAsBytes(), encoding).toCharArray(); + } + + /** + * Same as {@link StreamReader#readAllAsChars()}, but returns a string instead. + * @return The string + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public String readAllAsString() throws IOException { + return new String(readAllAsBytes()); + } + + /** + * Returns all lines in the file. All line ending are supported (\r\n, \n, \r). + * @return The lines + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public String[] readAllAsLines() throws IOException { + return new String(readAllAsBytes()).replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n"); + } + + /** + * Same as {@link StreamReader#readAllAsChars()}, but returns a string instead. + * @param encoding The encoding to use + * @return The string + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public String readAllAsString(String encoding) throws IOException { + return new String(readAllAsBytes(), encoding); + } + + /** + * Returns all lines in the file. All line ending are supported (\r\n, \n, \r). + * @param encoding The encoding to use + * @return The lines + * @throws IOException Inherited from {@link StreamReader#readAllAsBytes()} + */ + public String[] readAllAsLines(String encoding) throws IOException { + return new String(readAllAsBytes(), encoding).replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n"); + } + +} diff --git a/src/main/java/de/tudbut/io/StreamRedirect.java b/src/main/java/de/tudbut/io/StreamRedirect.java new file mode 100644 index 0000000..f728a91 --- /dev/null +++ b/src/main/java/de/tudbut/io/StreamRedirect.java @@ -0,0 +1,33 @@ +package de.tudbut.io; + +import de.tudbut.timer.AsyncTask; +import de.tudbut.type.Nothing; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; + +public class StreamRedirect { + + public static AsyncTask redirect(InputStream from, OutputStream to, Map replacers) { + return new AsyncTask<>(() -> { + int i; + while ((i = from.read()) != -1) { + block0: + { + for (Integer key : replacers.keySet()) { + if (i == key) { + Integer[] r = replacers.get(key); + for (Integer j : r) { + to.write(j); + } + break block0; + } + } + to.write(i); + } + } + return null; + }); + } +} diff --git a/src/main/java/de/tudbut/io/StreamWriter.java b/src/main/java/de/tudbut/io/StreamWriter.java new file mode 100644 index 0000000..b7a1821 --- /dev/null +++ b/src/main/java/de/tudbut/io/StreamWriter.java @@ -0,0 +1,68 @@ +package de.tudbut.io; + +import java.io.IOException; +import java.io.OutputStream; + + +/** + * Write data to an {@link OutputStream} + */ +public class StreamWriter { + /** + * The output stream to write to + */ + public final OutputStream stream; + + /** + * Constructs a new StreamWriter + * @param stream + */ + public StreamWriter(OutputStream stream) { + this.stream = stream; + } + + public void writeChars(char[] c) throws IOException { + byte[] bytes = new String(c).getBytes(); + stream.write(bytes); + stream.flush(); + } + + public void writeChars(char[] c, String encoding) throws IOException { + byte[] bytes = new String(c).getBytes(encoding); + for (int i = 0; i < bytes.length; i++) { + writeByte(bytes[i]); + } + stream.flush(); + } + + public void writeByte(byte b) throws IOException { + stream.write(Byte.toUnsignedInt(b)); + } + + public void writeUnsignedByte(short b) throws IOException { + stream.write(b); + } + + public void writeUnsignedByte(int b) throws IOException { + stream.write(b); + } + + public void writeBytes(byte[] bytes) throws IOException { + stream.write(bytes); + stream.flush(); + } + + public void writeUnsignedBytes(int[] bytes) throws IOException { + for (int theByte : bytes) { + stream.write(theByte); + } + stream.flush(); + } + + public void writeUnsignedBytes(short[] bytes) throws IOException { + for (short theByte : bytes) { + stream.write(theByte); + } + stream.flush(); + } +} diff --git a/src/main/java/de/tudbut/io/TypedInputStream.java b/src/main/java/de/tudbut/io/TypedInputStream.java new file mode 100644 index 0000000..920dd72 --- /dev/null +++ b/src/main/java/de/tudbut/io/TypedInputStream.java @@ -0,0 +1,161 @@ +package de.tudbut.io; + +import java.io.IOException; +import java.io.InputStream; + +/** + * InputStream for more data types + */ +public class TypedInputStream { + + protected InputStream stream; + public int last = -1; + private final Object waitForInputLock = new Object(); + private final Object readLock = new Object(); + + public InputStream getStream() { + return stream; + } + + private WriteDirection direction = WriteDirection.HIGH_FIRST; + + public TypedInputStream(InputStream stream) { + this.stream = stream; + } + + public byte readByte() throws IOException { + return (byte) read(); + } + + public short readShort() throws IOException { + short s = 0; + if(direction == WriteDirection.HIGH_FIRST) { + s += (read() << 8 * 1); + s += (read() << 8 * 0); + } + else { + s += (read() << 8 * 0); + s += (read() << 8 * 1); + } + return s; + } + + public char readChar() throws IOException { + char c = 0; + if(direction == WriteDirection.HIGH_FIRST) { + c += (read() << 8 * 1); + c += (read() << 8 * 0); + } + else { + c += (read() << 8 * 0); + c += (read() << 8 * 1); + } + return c; + } + + public int readInt() throws IOException { + int i = 0; + if(direction == WriteDirection.HIGH_FIRST) { + i += (read() << 8 * 3); + i += (read() << 8 * 2); + i += (read() << 8 * 1); + i += (read() << 8 * 0); + } + else { + i += (read() << 8 * 0); + i += (read() << 8 * 1); + i += (read() << 8 * 2); + i += (read() << 8 * 3); + } + return i; + } + + public float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + public long readLong() throws IOException { + long l = 0; + if(direction == WriteDirection.HIGH_FIRST) { + l += ((long) read() << 8 * 7); + l += ((long) read() << 8 * 6); + l += ((long) read() << 8 * 5); + l += ((long) read() << 8 * 4); + l += ((long) read() << 8 * 3); + l += ((long) read() << 8 * 2); + l += ((long) read() << 8 * 1); + l += ((long) read() << 8 * 0); + } + else { + l += ((long) read() << 8 * 0); + l += ((long) read() << 8 * 1); + l += ((long) read() << 8 * 2); + l += ((long) read() << 8 * 3); + l += ((long) read() << 8 * 4); + l += ((long) read() << 8 * 5); + l += ((long) read() << 8 * 6); + l += ((long) read() << 8 * 7); + } + return l; + } + + public double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + public boolean readBoolean() throws IOException { + return (read() & 1) != 0; + } + + public boolean[] readBooleans() throws IOException { + boolean[] booleans = new boolean[8]; + int i = read() & 0xff; + for (int j = 0 ; j < 8 ; ) { + booleans[j] = (i & 1 << 8 >> ++j ) != 0; + } + return booleans; + } + + public String readString() throws IOException { + int i = readInt(); + StringBuilder builder = new StringBuilder(); + for (int j = 0 ; j < i ; j++) { + builder.append(readChar()); + } + return builder.toString(); + } + + public void waitForInput() throws IOException { + synchronized (waitForInputLock) { + if(last != -1) + return; + while ((last = stream.read()) == -1) ; + } + } + + public int read() throws IOException { + synchronized (readLock) { + synchronized (waitForInputLock) { + int i; + if (last == -1) { + while ((i = stream.read()) == -1) ; + } + else { + i = last; + last = -1; + } + return i; + } + } + } + + public WriteDirection getDirection() { + return direction; + } + + public void setDirection(WriteDirection direction) { + if(direction == null) + throw new IllegalArgumentException(); + this.direction = direction; + } +} diff --git a/src/main/java/de/tudbut/io/TypedOutputStream.java b/src/main/java/de/tudbut/io/TypedOutputStream.java new file mode 100644 index 0000000..f3017a8 --- /dev/null +++ b/src/main/java/de/tudbut/io/TypedOutputStream.java @@ -0,0 +1,141 @@ +package de.tudbut.io; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * OutputStream for more data types + */ +public class TypedOutputStream { + + public OutputStream getStream() { + return stream; + } + + OutputStream stream; + private WriteDirection direction = WriteDirection.HIGH_FIRST; + + public TypedOutputStream(OutputStream stream) { + this.stream = stream; + } + + public byte writeByte(byte b) throws IOException { + stream.write(Byte.toUnsignedInt(b)); + stream.flush(); + return b; + } + + public short writeShort(short s) throws IOException { + if(direction == WriteDirection.HIGH_FIRST) { + stream.write(s >> 8 * 1 & 0xff); + stream.write(s >> 8 * 0 & 0xff); + } + else { + stream.write(s >> 8 * 0 & 0xff); + stream.write(s >> 8 * 1 & 0xff); + } + stream.flush(); + return s; + } + + public char writeChar(char c) throws IOException { + if(direction == WriteDirection.HIGH_FIRST) { + stream.write(c >> 8 * 1 & 0xff); + stream.write(c >> 8 * 0 & 0xff); + } + else { + stream.write(c >> 8 * 0 & 0xff); + stream.write(c >> 8 * 1 & 0xff); + } + stream.flush(); + return c; + } + + public int writeInt(int i) throws IOException { + if(direction == WriteDirection.HIGH_FIRST) { + stream.write(i >> 8 * 3 & 0xff); + stream.write(i >> 8 * 2 & 0xff); + stream.write(i >> 8 * 1 & 0xff); + stream.write(i >> 8 * 0 & 0xff); + } + else { + stream.write(i >> 8 * 0 & 0xff); + stream.write(i >> 8 * 1 & 0xff); + stream.write(i >> 8 * 2 & 0xff); + stream.write(i >> 8 * 3 & 0xff); + } + stream.flush(); + return i; + } + + public float writeFloat(float f) throws IOException { + writeInt(Float.floatToIntBits(f)); + return f; + } + + public long writeLong(long l) throws IOException { + if(direction == WriteDirection.HIGH_FIRST) { + stream.write((int) (l >> 8 * 7 & 0xff)); + stream.write((int) (l >> 8 * 6 & 0xff)); + stream.write((int) (l >> 8 * 5 & 0xff)); + stream.write((int) (l >> 8 * 4 & 0xff)); + stream.write((int) (l >> 8 * 3 & 0xff)); + stream.write((int) (l >> 8 * 2 & 0xff)); + stream.write((int) (l >> 8 * 1 & 0xff)); + stream.write((int) (l >> 8 * 0 & 0xff)); + } + else { + stream.write((int) (l >> 8 * 0 & 0xff)); + stream.write((int) (l >> 8 * 1 & 0xff)); + stream.write((int) (l >> 8 * 2 & 0xff)); + stream.write((int) (l >> 8 * 3 & 0xff)); + stream.write((int) (l >> 8 * 4 & 0xff)); + stream.write((int) (l >> 8 * 5 & 0xff)); + stream.write((int) (l >> 8 * 6 & 0xff)); + stream.write((int) (l >> 8 * 7 & 0xff)); + } + stream.flush(); + return l; + } + + public double writeDouble(double d) throws IOException { + writeLong(Double.doubleToLongBits(d)); + return d; + } + + public boolean writeBoolean(boolean b) throws IOException { + stream.write(b ? 1 : 0); + stream.flush(); + return b; + } + + public boolean[] writeBooleans(boolean[] booleans) throws IOException { + int i = 0; + for (int j = 0 ; j < 8 ; ) { + i += (booleans[j] ? 1 : 0) << 8 >> ++j; + } + stream.write(i); + stream.flush(); + return booleans; + } + + public String writeString(String string) throws IOException { + int i = string.length(); + writeInt(i); + char[] chars = string.toCharArray(); + for (int j = 0 ; j < i ; j++) { + writeChar(chars[j]); + } + return string; + } + + public WriteDirection getDirection() { + return direction; + } + + public void setDirection(WriteDirection direction) { + if(direction == null) + throw new IllegalArgumentException(); + this.direction = direction; + } +} diff --git a/src/main/java/de/tudbut/io/WriteDirection.java b/src/main/java/de/tudbut/io/WriteDirection.java new file mode 100644 index 0000000..5098b6b --- /dev/null +++ b/src/main/java/de/tudbut/io/WriteDirection.java @@ -0,0 +1,29 @@ +package de.tudbut.io; + +/** + * Indicator for how to write/read in TypedOutputStream and TypedInputStream + */ +public enum WriteDirection { + + /** + * Integer 1 writes as + * 0x00 0x00 0x00 0x01
+ *
+ * Integer 256 writes as + * 0x00 0x00 0x01 0x00
+ *
+ * This is the standard for most java applications + */ + HIGH_FIRST, + /** + * Integer 1 writes as + * 0x01 0x00 0x00 0x00
+ *
+ * Integer 256 writes as + * 0x00 0x01 0x00 0x00
+ *
+ * This is most commonly used in C/C++ + */ + LOW_FIRST, + ; +} diff --git a/src/main/java/de/tudbut/logger/DetailedLogger.java b/src/main/java/de/tudbut/logger/DetailedLogger.java new file mode 100644 index 0000000..40524af --- /dev/null +++ b/src/main/java/de/tudbut/logger/DetailedLogger.java @@ -0,0 +1,53 @@ +package de.tudbut.logger; + +import java.io.PrintStream; +import java.sql.Time; +import java.util.Date; + +public class DetailedLogger implements LoggerSink { + String name; + public PrintStream out; + + { + out = System.out; + } + + + public DetailedLogger(String name) { + this.name = name; + } + + public DetailedLogger subChannel(String subChannel) { + PrintStream stream = out; + return new DetailedLogger(name + "] [" + subChannel) { + { + out = stream; + } + }; + } + + @Override + public String getName() { + return name; + } + + public void info(String string) { + String time = String.valueOf(new Date().getTime()); + out.println("[ " + new Time(new Date().getTime()) + "." + time.substring(time.length() - 3) + " " + Thread.currentThread().getName() + " INFO ] [" + this.name + "] " + string); + } + + public void debug(String string) { + String time = String.valueOf(new Date().getTime()); + out.println("[ " + new Time(new Date().getTime()) + "." + time.substring(time.length() - 3) + " " + Thread.currentThread().getName() + " DEBUG ] [" + this.name + "] " + string); + } + + public void warn(String string) { + String time = String.valueOf(new Date().getTime()); + out.println("[ " + new Time(new Date().getTime()) + "." + time.substring(time.length() - 3) + " " + Thread.currentThread().getName() + " WARN ] [" + this.name + "] " + string); + } + + public void error(String string) { + String time = String.valueOf(new Date().getTime()); + out.println("[ " + new Time(new Date().getTime()) + "." + time.substring(time.length() - 3) + " " + Thread.currentThread().getName() + " ERROR ] [" + this.name + "] " + string); + } +} diff --git a/src/main/java/de/tudbut/logger/EmptyLogger.java b/src/main/java/de/tudbut/logger/EmptyLogger.java new file mode 100644 index 0000000..4567dca --- /dev/null +++ b/src/main/java/de/tudbut/logger/EmptyLogger.java @@ -0,0 +1,43 @@ +package de.tudbut.logger; + +public class EmptyLogger implements LoggerSink { + private final String name; + + public EmptyLogger(String name) { + this.name = name; + } + + public EmptyLogger() { + this.name = null; + } + + public LoggerSink subChannel(String subChannel) { + return new EmptyLogger(name + "] [" + subChannel); + } + + @Override + public void info(String string) { + + } + + @Override + public void debug(String string) { + + } + + @Override + public void warn(String string) { + + } + + @Override + public void error(String string) { + + } + + @Override + public String getName() { + return name; + } + +} diff --git a/src/main/java/de/tudbut/logger/Logger.java b/src/main/java/de/tudbut/logger/Logger.java new file mode 100644 index 0000000..7b2faf3 --- /dev/null +++ b/src/main/java/de/tudbut/logger/Logger.java @@ -0,0 +1,184 @@ +package de.tudbut.logger; + +import java.io.*; +import java.sql.Time; +import java.util.Date; + +public class Logger implements LoggerSink { + String name; + public PrintStream out; + + { + out = System.out; + } + + public Logger(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + public Logger subChannel(String subChannel) { + PrintStream stream = out; + return new Logger(name + "] [" + subChannel) { + { + out = stream; + } + }; + } + + public PrintWriter infoAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + info(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + public PrintStream infoAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + info(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + public PrintWriter debugAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + debug(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + public PrintStream debugAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + debug(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + public PrintWriter warnAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + warn(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + public PrintStream warnAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + warn(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + public PrintWriter errorAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + error(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + public PrintStream errorAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + error(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + public void info(String string) { + out.println("[" + new Time(new Date().getTime()) + " INFO] [" + this.name + "] " + string); + } + + public void debug(String string) { + out.println("[" + new Time(new Date().getTime()) + " DEBUG] [" + this.name + "] " + string); + } + + public void debug(Throwable throwable) { + throwable.printStackTrace(debugAsWriter()); + } + + public void warn(String string) { + out.println("[" + new Time(new Date().getTime()) + " WARN] [" + this.name + "] " + string); + } + + public void warn(Exception exception) { + exception.printStackTrace(warnAsWriter()); + } + + public void error(String string) { + out.println("[" + new Time(new Date().getTime()) + " ERROR] [" + this.name + "] " + string); + } + + public void error(Throwable throwable) { + throwable.printStackTrace(errorAsWriter()); + } +} diff --git a/src/main/java/de/tudbut/logger/LoggerSink.java b/src/main/java/de/tudbut/logger/LoggerSink.java new file mode 100644 index 0000000..8f1cf49 --- /dev/null +++ b/src/main/java/de/tudbut/logger/LoggerSink.java @@ -0,0 +1,155 @@ +package de.tudbut.logger; + +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.PrintStream; + +public interface LoggerSink { + String getName(); + + LoggerSink subChannel(String subChannel); + + default PrintWriter infoAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + info(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + default PrintStream infoAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + info(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + default PrintWriter debugAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + debug(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + default PrintStream debugAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + debug(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + default PrintWriter warnAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + warn(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + default PrintStream warnAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + warn(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + default PrintWriter errorAsWriter() { + return new PrintWriter(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + error(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + default PrintStream errorAsStream() { + return new PrintStream(new OutputStream() { + String s = ""; + + @Override + public void write(int i) { + if((char) i == '\n') { + error(s); + s = ""; + return; + } + s += (char) i; + } + }, true); + } + + void info(String string); + + void debug(String string); + + default void debug(Throwable throwable) { + throwable.printStackTrace(debugAsWriter()); + } + + void warn(String string); + + default void warn(Exception exception) { + exception.printStackTrace(warnAsWriter()); + } + + void error(String string); + + default void error(Throwable throwable) { + throwable.printStackTrace(errorAsWriter()); + } +} diff --git a/src/main/java/de/tudbut/math/ComplexFunction.java b/src/main/java/de/tudbut/math/ComplexFunction.java new file mode 100644 index 0000000..f4b75e9 --- /dev/null +++ b/src/main/java/de/tudbut/math/ComplexFunction.java @@ -0,0 +1,10 @@ +package de.tudbut.math; + +public interface ComplexFunction { + + ComplexNumber solve(ComplexNumber n); + + default ComplexNumber solve(double n) { + return solve(ComplexNumber.create(n, 0)); + } +} diff --git a/src/main/java/de/tudbut/math/ComplexNumber.java b/src/main/java/de/tudbut/math/ComplexNumber.java new file mode 100644 index 0000000..824762e --- /dev/null +++ b/src/main/java/de/tudbut/math/ComplexNumber.java @@ -0,0 +1,55 @@ +package de.tudbut.math; + +import de.tudbut.type.Vector2d; + +public class ComplexNumber { + + private double real = 0; + private double imaginary = 0; + private Vector2d vec; + public boolean autoRecalculate = true; + + public double getReal() { + return real; + } + + public double getImaginary() { + return imaginary; + } + + public Vector2d getVec() { + return vec; + } + + public void setReal(double real) { + this.real = real; + vec = null; + if(autoRecalculate) + recalculateVector(); + } + + public void setImaginary(double imaginary) { + this.imaginary = imaginary; + vec = null; + if(autoRecalculate) + recalculateVector(); + } + + private ComplexNumber() {} + + public static ComplexNumber create(double real, double imaginary) { + ComplexNumber n = new ComplexNumber(); + n.real = real; + n.imaginary = imaginary; + return n; + } + + public Vector2d recalculateVector() { + vec = new Vector2d(real, imaginary); + return vec; + } + + public Vector2d getVector() { + return vec == null ? recalculateVector() : vec; + } +} diff --git a/src/main/java/de/tudbut/math/Function.java b/src/main/java/de/tudbut/math/Function.java new file mode 100644 index 0000000..451ead2 --- /dev/null +++ b/src/main/java/de/tudbut/math/Function.java @@ -0,0 +1,10 @@ +package de.tudbut.math; + +public interface Function { + + double solve(double n); + + default double solve(ComplexNumber n) { + return solve(n.getReal()); + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPContentType.java b/src/main/java/de/tudbut/net/http/HTTPContentType.java new file mode 100644 index 0000000..a45d7dc --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPContentType.java @@ -0,0 +1,89 @@ +package de.tudbut.net.http; + +/** + * Content types + */ +public enum HTTPContentType { + AAC( "audio/aac"), + ABW( "application/x-abiword"), + ARC( "application/x-freearc"), + AVI( "video/x-msvideo"), + AZW( "application/vnd.amazon.ebook"), + BIN( "application/octet-stream"), + BMP( "image/bmp"), + BZ( "application/x-bzip"), + BZ2( "application/x-bzip2"), + CSH( "application/x-csh"), + CSS( "text/css"), + CSV( "text/csv"), + DOC( "application/msword"), + DOCX( "application/vnd.openxmlformats-officedocument.wordprocessingml.document"), + EOT( "application/vnd.ms-fontobject"), + EPUB( "application/epub+zip"), + GZ( "application/gzip"), + GIF( "image/gif"), + HTML( "text/html"), + HTTP( "message/http"), + ICO( "image/vnd.microsoft.icon"), + ICS( "text/calendar"), + JAR( "application/java-archive"), + JPG( "image/jpeg"), + JS( "text/javascript"), + JSON( "application/json"), + JSONLD( "application/ld+json"), + MID( "audio/midi"), + MJS( "text/javascript"), + MP3( "audio/mpeg"), + MPEG( "video/mpeg"), + MPKG( "application/vnd.apple.installer+xml"), + ODP( "application/vnd.oasis.opendocument.presentation"), + ODS( "application/vnd.oasis.opendocument.spreadsheet"), + ODT( "application/vnd.oasis.opendocument.text"), + OGG( "audio/ogg"), + OGV( "video/ogg"), + OGX( "application/ogg"), + OPUS( "audio/opus"), + OTF( "font/otf"), + PNG( "image/png"), + PDF( "application/pdf"), + PHP( "application/x-httpd-php"), + PPT( "application/vnd.ms-powerpoint"), + PPTX( "application/vnd.openxmlformats-officedocument.presentationml.presentation"), + RAR( "application/vnd.rar"), + RTF( "application/rtf"), + SH( "application/x-sh"), + SVG( "image/svg+xml"), + SWF( "application/x-shockwave-flash"), + TAR( "application/x-tar"), + TCN( "text/tcn"), + TCNMAP( "application/tcnmap"), + TS( "video/mp2t"), + TTF( "font/ttf"), + TXT( "text/plain"), + VSD( "application/vnd.visio"), + WAV( "audio/wav"), + WEBA( "audio/webm"), + WEBM( "video/webm"), + WEBP( "image/webp"), + WOFF( "font/woff"), + WOFF2( "font/woff2"), + XHTML( "application/xhtml+xml"), + XLS( "application/vnd.ms-excel"), + XLSX( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + XML( "application/xml"), + XML_READABLE("text/xml"), + XUL( "application/vnd.mozilla.xul+xml"), + ZIP( "application/zip"), + _3GP_AUDIO( "audio/3gpp"), + _3GP_VIDEO( "video/3gpp"), + _3G2_AUDIO( "audio/3gpp2"), + _3G2_VIDEO( "video/3gpp2"), + _7Z( "application/x-7z-compressed"), + ANY( "*/*"), + ; + public final String asHeaderString; + + HTTPContentType(String asHeaderStringIn) { + asHeaderString = asHeaderStringIn; + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPHeader.java b/src/main/java/de/tudbut/net/http/HTTPHeader.java new file mode 100644 index 0000000..b9a51f9 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPHeader.java @@ -0,0 +1,91 @@ +package de.tudbut.net.http; + +import de.tudbut.obj.TLMap; + +/** + * Header for HTTP exchanges + */ +public class HTTPHeader { + private final String name; + private final String value; + private final TLMap param; + + public HTTPHeader(String s) { + String[] spl = s.split(": ", 2); + name = spl[0]; + spl = spl[1].split("; "); + value = spl[0]; + param = new TLMap<>(); + for (int i = 1 ; i < spl.length ; i++) { + try { + String[] kv = spl[i].split("=", 2); + if(kv[1].startsWith("\"")) { + kv[1] = kv[1] + .split("\"", 2)[1] + .replaceAll("%", "%P") + .replaceAll("\\\\", "%B") + .replaceAll("%B%B", "\\") + .replaceAll("%Bn", "\n") + .replaceAll("%B\"", "\"") + .replaceAll("%B", "\\") + .replaceAll("%P", "%"); + kv[1] = kv[1].substring(0, kv.length - 2); + } + else { + kv[1] = kv[1] + .replaceAll("%", "%P") + .replaceAll("\\\\", "%B") + .replaceAll("%B%B", "\\") + .replaceAll("%Bn", "\n") + .replaceAll("%B", "\\") + .replaceAll("%P", "%"); + } + param.set(kv[0], kv[1]); + } catch (Exception ignored) {} + } + } + + /** + * Constructs a HTTPHeader from a key and a value + * @param nameIn Key + * @param valueIn Value + */ + public HTTPHeader(String nameIn, String valueIn) { + this(nameIn, valueIn, new TLMap<>()); + } + + /** + * Constructs a HTTPHeader from a key, a value, and a parameter + * @param nameIn Key + * @param valueIn Value + * @param paramIn Parameter + */ + public HTTPHeader(String nameIn, String valueIn, TLMap paramIn) { + name = nameIn; + value = valueIn; + param = paramIn; + } + + /** + * + * @return The form used in the request + */ + public String toString() { + String m = TLMap.mapToString(param).replaceAll(":", "=\"").replaceAll(";", "\"; "); + if(param.size() > 0) + m = m.substring(0, m.length()-2); + return name + ": " + value + (param.size() == 0 ? "" : "; " + m); + } + + public String key() { + return name; + } + + public String value() { + return value; + } + + public TLMap parameter() { + return param.clone(); + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPRequest.java b/src/main/java/de/tudbut/net/http/HTTPRequest.java new file mode 100644 index 0000000..593a294 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPRequest.java @@ -0,0 +1,224 @@ +package de.tudbut.net.http; + +import de.tudbut.global.DebugStateManager; +import de.tudbut.io.StreamReader; +import de.tudbut.io.StreamWriter; +import de.tudbut.obj.DoubleTypedObject; +import de.tudbut.obj.Partial; +import de.tudbut.timer.AsyncTask; + +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Fully compatible, fast version of java HTTP requests + */ +public class HTTPRequest { + public final ArrayList headers = new ArrayList<>(); + public final String content; + private final HTTPRequestType requestType; + private final String path; + private final String host; + private final int port; + private final boolean ssl; + + /** + * Constructs a HTTPRequest without sending it + * @param requestType The type of the request, see {@link HTTPRequestType}. + * @param host Host, add "https://" in front to attempt an HTTPS connection, otherwise, dont specify protocol + * @param port Port, 80 is standard for HTTP, 443 is standard for HTTPS + * @param path Path to request, use "/" for main path + * @param headers HTTPHeaders to use, can be empty + */ + public HTTPRequest(HTTPRequestType requestType, String host, int port, String path, HTTPHeader... headers) { + this(requestType, host, port, path, (String) null, "", headers); + } + + /** + * Constructs a HTTPRequest without sending it + * @param requestTypeIn The type of the request, see {@link HTTPRequestType}. + * @param hostIn Host, add "https://" in front to attempt a HTTPS connection, otherwise, dont specify protocol + * @param portIn Port, 80 is standard for HTTP, 443 is standard for HTTPS + * @param pathIn Path to request, use "/" for main path + * @param type The type of the body + * @param contentIn The body + * @param headersIn HTTPHeaders to use, can be empty + */ + public HTTPRequest(HTTPRequestType requestTypeIn, String hostIn, int portIn, String pathIn, HTTPContentType type, String contentIn, HTTPHeader... headersIn) { + this(requestTypeIn, hostIn, portIn, pathIn, type.asHeaderString, contentIn, headersIn); + } + /** + * Constructs a HTTPRequest without sending it + * @param requestTypeIn The type of the request, see {@link HTTPRequestType}. + * @param hostIn Host, add "https://" in front to attempt a HTTPS connection, otherwise, dont specify protocol + * @param portIn Port, 80 is standard for HTTP, 443 is standard for HTTPS + * @param pathIn Path to request, use "/" for main path + * @param type The type of the body + * @param contentIn The body + * @param headersIn HTTPHeaders to use, can be empty + */ + public HTTPRequest(HTTPRequestType requestTypeIn, String hostIn, int portIn, String pathIn, String type, String contentIn, HTTPHeader... headersIn) { + this(requestTypeIn, hostIn, portIn, pathIn, new HTTPHeader("Content-Type", type), contentIn, headersIn); + } + + /** + * Constructs a HTTPRequest without sending it + * @param requestTypeIn The type of the request, see {@link HTTPRequestType}. + * @param hostIn Host, add "https://" in front to attempt a HTTPS connection, otherwise, dont specify protocol + * @param portIn Port, 80 is standard for HTTP, 443 is standard for HTTPS + * @param pathIn Path to request, use "/" for main path + * @param type The type of the body + * @param contentIn The body + * @param headersIn HTTPHeaders to use, can be empty + */ + public HTTPRequest(HTTPRequestType requestTypeIn, String hostIn, int portIn, String pathIn, HTTPHeader type, String contentIn, HTTPHeader... headersIn) { + ssl = hostIn.startsWith("https://"); + + requestType = requestTypeIn; + path = pathIn; + host = hostIn; + port = portIn; + headers.add(new HTTPHeader("Host", ssl ? host.split("https://")[1] : host)); + if(!contentIn.equals("")) { + headers.add(new HTTPHeader("Content-Type", type.value(), type.parameter())); + headers.add(new HTTPHeader("Content-Length", String.valueOf(contentIn.getBytes(StandardCharsets.ISO_8859_1).length))); + } + if(Arrays.stream(headersIn).noneMatch(httpHeader -> httpHeader.toString().startsWith("Connection: "))) + headers.add(new HTTPHeader("Connection", "Close")); + headers.addAll(Arrays.asList(headersIn)); + content = contentIn; + } + + /** + * @return The string to send to the server + */ + public String toString() { + String s = ""; + + s += requestType.name() + " " + path + " HTTP/1.1\r\n"; + for (HTTPHeader header : headers) { + s += header.toString() + "\r\n"; + } + s += "\r\n"; + s += content; + + return s; + } + + /** + * Sends the request synchronously + * @return The response + * @throws IOException Inherited + */ + public HTTPResponse send() throws IOException { + Socket socket = connect(); + return new HTTPResponse(new String(new StreamReader(socket.getInputStream()).readAllAsBytes(), StandardCharsets.ISO_8859_1)); + } + + /** + * Sends the request synchronously without returning a response + * @return The socket + * @throws IOException Inherited + */ + public Socket sendNoRead() throws IOException { + return connect(); + } + + /** + * Sends the request asynchronously + * @return The response enclosed in a {@link Partial} + */ + public Partial sendKeepAlive() { + return sendKeepAlive(-1); + } + + /** + * Sends the request asynchronously with optional timeout + * @param timeout The timeout at which to stop receiving, use -1 for infinity + * @return The response enclosed in a {@link Partial} + */ + public Partial sendKeepAlive(int timeout) { + Partial partialResponse = new Partial<>(null); + AsyncTask task = new AsyncTask<>(() -> { + try { + Socket socket = connect(); + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.ISO_8859_1)); + int c; + StringBuilder builder = new StringBuilder(); + while ((c = reader.read()) != -1) { + builder.append((char)c); + if(c == '\n') + partialResponse.change(new HTTPResponse(builder.toString())); + } + socket.close(); + partialResponse.change(new HTTPResponse(builder.toString())); + partialResponse.complete(partialResponse.get()); + } catch (Exception ignored) { } + return partialResponse.get(); + }); + task.setTimeout(timeout); + return partialResponse; + } + + /** + * Sends the request asynchronously with optional timeout + * @param timeout The timeout at which to stop receiving, use -1 for infinity + * @return The task and enclosed response (in a {@link Partial}), enclosed in a {@link DoubleTypedObject} + */ + public DoubleTypedObject, AsyncTask> sendKeepAliveWithTask(int timeout) { + Partial partialResponse = new Partial<>(null); + AsyncTask task = new AsyncTask<>(() -> { + try { + Socket socket = connect(); + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.ISO_8859_1)); + int c; + StringBuilder builder = new StringBuilder(); + while ((c = reader.read()) != -1) { + builder.append((char)c); + if(c == '\n') + partialResponse.change(new HTTPResponse(builder.toString())); + } + socket.close(); + partialResponse.change(new HTTPResponse(builder.toString())); + partialResponse.complete(partialResponse.get()); + return partialResponse.get(); + } catch (Exception ignored) { } + return partialResponse.get(); + }); + task.setTimeout(timeout); + return new DoubleTypedObject<>(partialResponse, task); + } + /** + * Sends the request and return the socket from which {@link #send} and {@link #sendKeepAlive} will read + * @return The created socket + * @throws IOException Inherited + */ + private Socket connect() throws IOException { + Socket socket; + if (ssl) { + socket = new Socket(host.substring("https://".length()), port); + SSLSocket sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(socket, host.substring("https://".length()), port, true); + sslSocket.startHandshake(); + if(DebugStateManager.isDebugEnabled()) { + DebugStateManager.getDebugLogger().debug("HTTPRequest using SSL/TLS version " + sslSocket.getSession().getProtocol()); + } + socket = sslSocket; + } + else + socket = new Socket(InetAddress.getByName(host), port); + socket.setSoTimeout(10000); + socket.setSoLinger(true, 1000); + StreamWriter writer = new StreamWriter(socket.getOutputStream()); + writer.writeChars(toString().toCharArray(), "ISO_8859_1"); + return socket; + } + +} diff --git a/src/main/java/de/tudbut/net/http/HTTPRequestType.java b/src/main/java/de/tudbut/net/http/HTTPRequestType.java new file mode 100644 index 0000000..d9770a4 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPRequestType.java @@ -0,0 +1,58 @@ +package de.tudbut.net.http; + +/** + * Request types + */ +public enum HTTPRequestType { + /** + * Get request, default for browsers + */ + GET, + + /** + * Post request, common for APIs + */ + POST, + + /** + * Head request, asks the server to respond with the same headers + * as a {@link #GET}, but without content. Commonly used in browsers + * that use caching. More + */ + HEAD, + + /** + * Put request, used for writing files to a server + */ + PUT, + + /** + * Patch request, used for changing files on a server. + * More + */ + PATCH, + + /** + * Delete request, used for deleting files on a server. + * More + */ + DELETE, + + /** + * Trace request, used for debugging + */ + TRACE, + + /** + * Options request, used for asking the server for allowed communication methods. + * More + */ + OPTIONS, + + /** + * Connect request, used to indicate start of a two-way transfer. + * More + */ + CONNECT, + ; +} diff --git a/src/main/java/de/tudbut/net/http/HTTPResponse.java b/src/main/java/de/tudbut/net/http/HTTPResponse.java new file mode 100644 index 0000000..432f989 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPResponse.java @@ -0,0 +1,148 @@ +package de.tudbut.net.http; + +import de.tudbut.tools.Value; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Map; + +/** + * Response to {@link HTTPRequest} + */ +public class HTTPResponse extends Value { + + /** + * Constructs a HTTPResponse from non-exact content + * @param value The content + */ + public HTTPResponse(String value) { + super(spl(value)); + } + + /** + * Constructs a HTTPResponse from content + * @param value The content + */ + public HTTPResponse(String value, boolean exact) { + super(exact ? value : spl(value)); + } + + private static String spl(String s) { + String[] splitString = s.split("\r\n\r\n", 2); + if(splitString.length == 1) { + return s.replaceAll("\r", ""); + } + else { + return splitString[0].replaceAll("\r", "") + "\n\n" + splitString[1]; + } + } + + /** + * Parses the response + * @return The {@link ParsedHTTPValue} of the response + */ + public ParsedHTTPValue parse() { + String[] splitValue = value.split("\n", 2)[0].split(" "); + + String httpVersion = splitValue[0]; + int statusCode = Integer.parseInt(splitValue[1]); + String statusCodeString = splitValue[2]; + HTTPResponseCode code = null; + for (int i = 0 ; i < HTTPResponseCode.values().length; i++) { + HTTPResponseCode responseCode = HTTPResponseCode.values()[i]; + if(responseCode.asInt == statusCode) { + code = responseCode; + } + } + ArrayList headersList = new ArrayList<>(); + String s = value; + s = s.substring(s.split("\n")[0].length() + 1); + for (String line : s.split("\n")) { + if (line.equals("")) + break; + headersList.add(new HTTPHeader(line)); + } + HTTPHeader[] headers = headersList.toArray(new HTTPHeader[0]); + StringBuilder body = new StringBuilder(); + try { + int start = value.indexOf("\n\n") + 2; + HTTPHeader header = null; + for (int i = 0; i < headers.length; i++) { + if(headers[i].key().equalsIgnoreCase("Content-Length")) + header = headers[i]; + } + if(header != null) { + int end = value.length(); + body = new StringBuilder(value.substring(start, end)); + } + else { + + /* + * INCREDIBLY hacky way to make chunk transfer work, will make better later + */ + ByteArrayInputStream b = new ByteArrayInputStream(value.substring(start).getBytes(StandardCharsets.ISO_8859_1)); + + for (int i = -1 ; i != 0 ; ) { + StringBuilder sbuf = new StringBuilder(); + int c; + while (!sbuf.toString().endsWith("\n") && (c = b.read()) != -1) { + sbuf.append((char) c); + } + i = Integer.parseInt(sbuf.toString().replaceAll("\r", "").split("\n")[0], 16); + byte[] buf = new byte[i]; + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + b.read(buf); + stream.write(buf); + body.append(stream.toString()); + } + } + } catch (Exception ignored) { + } + + HTTPResponseCode finalCode = code; + String finalBody = body.toString(); + return new ParsedHTTPValue() { + @Override + public String getHTTPVersion() { + return httpVersion; + } + + @Override + public int getStatusCode() { + return statusCode; + } + + @Override + public String getStatusCodeAsString() { + return statusCodeString; + } + + @Override + public String getPath() { + return null; + } + + @Override + public Map getQuery() { + return null; + } + + @Override + public HTTPResponseCode getStatusCodeAsEnum() { + return finalCode; + } + + @Override + public String getBodyRaw() { + return finalBody; + } + + @Override + public HTTPHeader[] getHeaders() { + return headers; + } + }; + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPResponseCode.java b/src/main/java/de/tudbut/net/http/HTTPResponseCode.java new file mode 100644 index 0000000..c5bff46 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPResponseCode.java @@ -0,0 +1,84 @@ +package de.tudbut.net.http; + +/** + * Response codes to HTTPRequests + */ +public enum HTTPResponseCode { + Continue(100), + SwitchingProtocols(101), + Processing(102), + EarlyHints(103), + + OK(200), + Created(201), + Accepted(202), + NonAuthoritativeInformation(203), + NoContent(204), + ResetContent(205), + PartialContent(206), + MultiStatus(207), + AlreadyReported(208), + IM_Used(226), + + MultipleChoices(300), + MovedPermanently(301), + MovedTemporarily(302), + SeeOther(303), + NotModified(304), + UseProxy(305), + RESERVED__SwitchProxy(306), + TemporaryRedirect(307), + PermanentRedirect(308), + + BadRequest(400), + Unauthorized(401), + PaymentRequired(402), + Forbidden(403), + NotFound(404), + MethodNotAllowed(405), + NotAcceptable(406), + ProxyAuthenticationRequired(407), + RequestTimeout(408), + Conflict(409), + Gone(410), + LengthRequired(411), + PreconditionFailed(412), + PayloadTooLarge(413), + URI_TooLong(414), + UnsupportedMediaType(415), + RangeNotSatisfiable(416), + ExpectationFailed(417), + MisdirectedRequest(421), + UnprocessableEntity(422), + Locked(423), + FailedDependency(424), + TooEarly(425), + UpgradeRequired(426), + PreconditionRequired(428), + TooManyRequests(429), + RequestHeaderFieldsTooLarge(431), + UnavailableForLegalReasons(451), + + InternalServerError(500), + NotImplemented(501), + BadGateway(502), + ServiceUnavailable(503), + GatewayTimeout(504), + HTTPVersionNotSupported(505), + VariantAlsoNegotiates(506), + InsufficientStorage(507), + LoopDetected(508), + BandwidthLimitExceeded(509), + NotExtended(510), + NetworkAuthenticationRequired(511), + ; + + /** + * The id + */ + public final int asInt; + + HTTPResponseCode(int asIntIn) { + asInt = asIntIn; + } +} \ No newline at end of file diff --git a/src/main/java/de/tudbut/net/http/HTTPResponseFactory.java b/src/main/java/de/tudbut/net/http/HTTPResponseFactory.java new file mode 100644 index 0000000..f6ed1fe --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPResponseFactory.java @@ -0,0 +1,55 @@ +package de.tudbut.net.http; + +import java.nio.charset.StandardCharsets; + +/** + * Class to build HTTP responses + */ +public class HTTPResponseFactory { + + /** + * Creates a HTTPResponse + * @param responseCode {@link HTTPResponseCode} + * @param body The body of the request + * @param contentType {@link HTTPContentType} + * @param headers {@link HTTPHeader} Headers for the response + * @return The constructed {@link HTTPResponse} + */ + public static HTTPResponse create(HTTPResponseCode responseCode, String body, HTTPContentType contentType, HTTPHeader... headers) { + return create(responseCode, body, contentType.asHeaderString, headers); + } + /** + * Creates a HTTPResponse + * @param responseCode {@link HTTPResponseCode} + * @param body The body of the request + * @param contentType {@link HTTPContentType} + * @param headers {@link HTTPHeader} Headers for the response + * @return The constructed {@link HTTPResponse} + */ + public static HTTPResponse create(HTTPResponseCode responseCode, String body, String contentType, HTTPHeader... headers) { + return create(responseCode, body, new HTTPHeader("Content-Type", contentType), headers); + } + + /** + * Creates a HTTPResponse + * @param responseCode {@link HTTPResponseCode} + * @param body The body of the request + * @param contentType content type + * @param headers {@link HTTPHeader} Headers for the response + * @return The constructed {@link HTTPResponse} + */ + public static HTTPResponse create(HTTPResponseCode responseCode, String body, HTTPHeader contentType, HTTPHeader... headers) { + StringBuilder builder = new StringBuilder(); + + builder.append("HTTP/1.1 ").append(responseCode.asInt).append(" ").append(responseCode.name()).append("\r\n"); + builder.append(new HTTPHeader("Content-Type", contentType.value(), contentType.parameter())).append("\r\n"); + builder.append(new HTTPHeader("Content-Length", body.getBytes(StandardCharsets.ISO_8859_1).length + "")).append("\r\n"); + for (HTTPHeader header : headers) { + builder.append(header.toString()).append("\r\n"); + } + builder.append("\r\n"); + builder.append(body); + + return new HTTPResponse(builder.toString(), true); + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPServer.java b/src/main/java/de/tudbut/net/http/HTTPServer.java new file mode 100644 index 0000000..befeb4d --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPServer.java @@ -0,0 +1,213 @@ +package de.tudbut.net.http; + +import de.tudbut.io.RawLineReader; +import de.tudbut.io.StreamWriter; +import de.tudbut.type.Stoppable; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.security.*; +import java.util.ArrayList; +import java.util.concurrent.Executor; + +// TODO: this has a broken thing, please dont use for HTTPS until later. + +/** + * A HTTP server + */ +public class HTTPServer implements Stoppable { + private final int port; + private final ServerSocket serverSocket; + private final HTTPResponse serverError; + private final ArrayList handlers = new ArrayList<>(); + private final Executor executor; + + /** + * Constructs a HTTPServer without HTTPS + * @param portIn Port to listen on + * @throws IOException Inherited + */ + public HTTPServer(int portIn) throws IOException { + port = portIn; + serverError = HTTPResponseFactory.create(HTTPResponseCode.InternalServerError, "500 InternalServerError", HTTPContentType.TXT); + serverSocket = new ServerSocket(port); + executor = Runnable::run; + } + + /** + * Constructs a HTTPServer without HTTPS + * @param portIn Port to listen on + * @param serverErrorIn Response to send on error + * @param executorIn Executor + * @throws IOException Inherited + */ + public HTTPServer(int portIn, HTTPResponse serverErrorIn, Executor executorIn) throws IOException { + port = portIn; + serverError = serverErrorIn; + serverSocket = new ServerSocket(port); + executor = executorIn; + } + + /** + * Constructs a HTTPServer with HTTPS + * @param portIn Port to listen on + * @param serverErrorIn Response to send on error + * @param executorIn Executor + * @param keyStore {@link KeyStore} The PRIVATE KEY KeyStore + * @param keyStorePass Password for the KeyStore + * @throws IOException Inherited + */ + public HTTPServer(int portIn, HTTPResponse serverErrorIn, Executor executorIn, KeyStore keyStore, String keyStorePass) throws IOException { + port = portIn; + serverError = serverErrorIn; + try { + SSLContext context = SSLContext.getInstance("TLS"); + KeyManagerFactory managerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + managerFactory.init(keyStore, keyStorePass.toCharArray()); + context.init(managerFactory.getKeyManagers(), null, null); + serverSocket = context.getServerSocketFactory().createServerSocket(portIn); + } + catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException | KeyManagementException e) { + throw new IllegalArgumentException(e); + } + + executor = executorIn; + } + + /** + * Start listening for requests + */ + public void listen() { + new Thread(() -> { + Socket socket; + while (!isStopped()) { + try { + socket = serverSocket.accept(); + socket.setSoTimeout(10000); + Socket finalSocket = socket; + boolean b = true; + for (int i = 0 ; i < handlers.size() ; i++) { + if(!handlers.get(i).accept(socket.getRemoteSocketAddress()) || !handlers.get(i).accept(socket, socket.getRemoteSocketAddress())) + b = false; + } + if(b) { + executor.execute(() -> { + HTTPServerRequest serverRequest = new HTTPServerRequest("", finalSocket); + try { + HTTPHandler[] handlers = this.handlers.toArray(new HTTPHandler[0]); + + String s; + ArrayList headers = new ArrayList<>(); + String fullRequest = ""; + RawLineReader reader = new RawLineReader(finalSocket.getInputStream()); + int line = 0; + while ((s = reader.readLine()) != null) { + fullRequest += s + "\r\n"; + if (s.equals("")) { + break; + } + if (line != 0) { + headers.add(new HTTPHeader(s)); + } + line++; + } + int contentLength = 0; + for (HTTPHeader header : headers) { + if (header.key().equalsIgnoreCase("Content-Length")) { + contentLength = Integer.parseInt(header.value()); + } + } + if(contentLength != 0) { + for (int i = 0 ; i < contentLength ; i++) { + fullRequest += (char) reader.read(); + } + } + HTTPServerRequest request = new HTTPServerRequest(fullRequest, finalSocket); + serverRequest = request; + for (HTTPHandler handler : handlers) { + handler.handle(request); + } + } + catch (Throwable e) { + try { + new StreamWriter(finalSocket.getOutputStream()).writeChars(handlers.get(0).onError(serverRequest, serverError, e).value.toCharArray(), "ISO-8859-1"); + } + catch (Throwable ignore) { + } + } + }); + } + else { + executor.execute(() -> { + for (int i = 0 ; i < handlers.size() ; i++) { + try { + handlers.get(i).handleDeny(new HTTPServerRequest("", finalSocket)); + } + catch (Exception ignored) { } + } + }); + } + } catch (IOException ignore) { } + } + }).start(); + } + + /** + * Add a handler to handle requests + * @param handler The handler to add + */ + public void addHandler(HTTPHandler handler) { + handlers.add(handler); + } + + /** + * Handler for incoming HTTP request + */ + public interface HTTPHandler { + /** + * Handle a request to the server + * @param request The request to handle + * @throws Exception To make handling easier (less catching required) + */ + void handle(HTTPServerRequest request) throws Exception; + + /** + * Should the connection be accepted? (Useful for rate-limiting) + * @param address The address the connection is coming from + * @return If the connection should be accepted + */ + default boolean accept(SocketAddress address) { + return true; + } + /** + * Should the connection be accepted? (Useful for rate-limiting) + * @param socket The socket + * @param address The address the connection is coming from + * @return If the connection should be accepted + */ + default boolean accept(Socket socket, SocketAddress address) { + return true; + } + + /** + * + * @param request An empty request to hold the response methods + * @throws Exception To make handling easier (less catching required) + */ + default void handleDeny(HTTPServerRequest request) throws Exception { } + + /** + * @param request The request as constructed so far + * @param defaultResponse The server's default response + * @param theError The error + * @throws Exception To make handling easier (less catching required) + */ + default HTTPResponse onError(HTTPServerRequest request, HTTPResponse defaultResponse, Throwable theError) throws Exception { + return defaultResponse; + } + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPServerRequest.java b/src/main/java/de/tudbut/net/http/HTTPServerRequest.java new file mode 100644 index 0000000..aaaff6c --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPServerRequest.java @@ -0,0 +1,221 @@ +package de.tudbut.net.http; + +import de.tudbut.io.StreamWriter; +import de.tudbut.obj.Closable; +import de.tudbut.obj.ClosedClosableException; +import de.tudbut.obj.NotSupportedException; +import de.tudbut.tools.Value; + +import javax.net.ssl.SSLSocket; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Used by the server to handle incoming HTTP requests + */ +public class HTTPServerRequest extends Value implements Closable { + /** + * The socket of the request + */ + public final Socket socket; + + public HTTPServerRequest(String value, Socket s) { + super(spl(value)); + socket = s; + } + + private static String spl(String s) { + String[] splitString = s.split("\r\n\r\n", 2); + if(splitString.length == 1) { + return s.replaceAll("\r", ""); + } + else { + return splitString[0].replaceAll("\r", "") + "\n\n" + splitString[1]; + } + } + + /** + * Close the streams + */ + @Override + public void close() throws IOException { + Closable.super.close(); + socket.shutdownOutput(); + if(socket instanceof SSLSocket) { + socket.setSoLinger(true, 100); + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + try { + SSLSocket.class.getMethod("close").invoke(socket); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + System.err.println("!!! ERROR: OBFUSCATED OR INVALID JRE"); + socket.close(); + } + } + else + socket.close(); + } + + /** + * Not supported. + * @throws NotSupportedException [Always thrown] + */ + @Override + public void open() throws NotSupportedException { + throw new NotSupportedException(); + } + + /** + * Responds to the request. Returns if the request was already responded to. + * @param response The response to send + * @throws IOException Inherited + */ + public void respond(HTTPResponse response) throws IOException { + if(isClosed()) + return; + StreamWriter writer = new StreamWriter(socket.getOutputStream()); + writer.writeChars(response.value.toCharArray(), "ISO_8859_1"); + close(); + } + + /** + * Responds to the request. Throws if the request was already responded to. + * @param response The response to send + * @throws IOException Inherited + * @throws ClosedClosableException If the request has already been responded to. + */ + public void forceRespond(HTTPResponse response) throws IOException, ClosedClosableException { + if(isClosed()) + throw new ClosedClosableException(); + StreamWriter writer = new StreamWriter(socket.getOutputStream()); + writer.writeChars(response.value.toCharArray(), "ISO_8859_1"); + close(); + } + + /** + * Parses the request + * @return The {@link ParsedHTTPValue} of the request + */ + public ParsedHTTPValue parse() { + String[] splitValue = value.split("\n")[0].split(" "); + + String httpVersion = splitValue[2]; + String statusCode = splitValue[0]; + String actualPath = splitValue[1]; + String path = actualPath.split("\\?")[0]; + Map map = new HashMap<>(); + try { + String query = actualPath.split("\\?")[1]; + + String[] splitByAnd = query.split("&"); + for (int i = 0; i < splitByAnd.length; i++) { + try { + String[] v = splitByAnd[i].split("="); + map.put(HTTPUtils.decodeUTF8(v[0]), HTTPUtils.decodeUTF8(v[1])); + } catch (Exception ignore) { } + } + } catch (Exception ignore) { } + HTTPRequestType code = null; + for (int i = 0; i < HTTPRequestType.values().length; i++) { + HTTPRequestType requestType = HTTPRequestType.values()[i]; + if(requestType.name().equals(statusCode)) { + code = requestType; + } + } + HTTPRequestType finalCode = code; + ArrayList headersList = new ArrayList<>(); + String s = value; + s = s.substring(s.split("\n")[0].length() + 1); + for (String line : s.split("\n")) { + if (line.equals("")) + break; + headersList.add(new HTTPHeader(line.split(": ")[0], line.split(": ")[1])); + } + HTTPHeader[] headers = headersList.toArray(new HTTPHeader[0]); + String body = ""; + try { + int start = value.indexOf("\n\n") + 2; + HTTPHeader header = null; + for (int i = 0; i < headers.length; i++) { + if(headers[i].key().equalsIgnoreCase("Content-Length")) + header = headers[i]; + } + if(header != null) { + int end = value.length(); + body = value.substring(start, end); + } + else { + /* + * INCREDIBLY hacky way to make chunk transfer work, will make better later + */ + ByteArrayInputStream b = new ByteArrayInputStream(value.substring(start).getBytes(StandardCharsets.ISO_8859_1)); + + for (int chunk = 0, i = -1 ; i != 0 ; chunk++) { + StringBuilder sbuf = new StringBuilder(); + int c; + while (!sbuf.toString().endsWith("\n") && (c = b.read()) != -1) { + sbuf.append((char) c); + } + i = Integer.parseInt(sbuf.toString().replaceAll("\r", "").split("\n")[0], 16); + byte[] buf = new byte[i]; + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + b.read(buf); + stream.write(buf); + body += stream.toString(); + } + } + } catch (Exception ignored) { + } + String finalBody = body; + return new ParsedHTTPValue() { + @Override + public String getHTTPVersion() { + return httpVersion; + } + + @Override + public int getStatusCode() { + return 0; + } + + @Override + public String getStatusCodeAsString() { + return statusCode; + } + + @Override + public String getPath() { + return path; + } + + @Override + public Map getQuery() { + return map; + } + + @Override + public HTTPRequestType getStatusCodeAsEnum() { + return finalCode; + } + + @Override + public String getBodyRaw() { + return finalBody; + } + + @Override + public HTTPHeader[] getHeaders() { + return headers; + } + }; + } +} diff --git a/src/main/java/de/tudbut/net/http/HTTPUtils.java b/src/main/java/de/tudbut/net/http/HTTPUtils.java new file mode 100644 index 0000000..fb52261 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/HTTPUtils.java @@ -0,0 +1,80 @@ +package de.tudbut.net.http; + +import de.tudbut.obj.DoubleTypedObject; +import de.tudbut.obj.TLMap; +import de.tudbut.tools.Lock; + +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * Utils for sending/receiving with HTTP + */ +public class HTTPUtils { + public static String encodeUTF8(String s) { + try { + return URLEncoder.encode(s, String.valueOf(StandardCharsets.UTF_8)).replace("+", "%20"); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException("Too old OS"); + } + } + public static String decodeUTF8(String s) { + try { + return URLDecoder.decode(s.replace("+", "%20"), String.valueOf(StandardCharsets.UTF_8)); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException("Too old OS"); + } + } + + public static String rawToUtf8(String raw) { + return new String(raw.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); + } + + public static String utf8ToRaw(String utf8) { + return new String(utf8.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); + } + + public static HTTPServer.HTTPHandler createRateLimitingHandler(int rps, HTTPServer.HTTPHandler requestHandler) { + TLMap> map = new TLMap<>(); + return new HTTPServer.HTTPHandler() { + @Override + public void handle(HTTPServerRequest request) throws Exception { + requestHandler.handle(request); + } + + @Override + public boolean accept(SocketAddress address) { + String ip = ((InetSocketAddress) address).getHostString(); + DoubleTypedObject user = map.get(ip); + if(user == null) + map.set(ip, user = new DoubleTypedObject<>(0, new Lock())); + user.o++; + if (user.t.isLocked()) { + if (user.o > rps) + return false; + } + else { + user.t.lock(1000); + user.o = 1; + } + return requestHandler.accept(address); + } + + @Override + public void handleDeny(HTTPServerRequest request) throws Exception { + requestHandler.handleDeny(request); + } + + @Override + public HTTPResponse onError(HTTPServerRequest request, HTTPResponse defaultResponse, Throwable theError) throws Exception { + return requestHandler.onError(request, defaultResponse, theError); + } + }; + } +} diff --git a/src/main/java/de/tudbut/net/http/ParsedHTTPValue.java b/src/main/java/de/tudbut/net/http/ParsedHTTPValue.java new file mode 100644 index 0000000..abf868f --- /dev/null +++ b/src/main/java/de/tudbut/net/http/ParsedHTTPValue.java @@ -0,0 +1,65 @@ +package de.tudbut.net.http; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +/** + * {@link HTTPResponse#parse} + * {@link HTTPServerRequest#parse} + */ +public interface ParsedHTTPValue { + + /** + * @return The version used to send the request/response + */ + String getHTTPVersion(); + + /** + * @return The status code + */ + int getStatusCode(); + + /** + * @return The status code + */ + String getStatusCodeAsString(); + + /** + * @return The path. Empty if a response + */ + String getPath(); + + /** + * @return The query. Empty if a response + */ + Map getQuery(); + + /** + * @return The status code + */ + Object getStatusCodeAsEnum(); + + /** + * @return The body of the request + */ + String getBodyRaw(); + + /** + * @return The body of the request as bytes (raw) + */ + default byte[] getBodyBytes() { + return getBodyRaw().getBytes(StandardCharsets.ISO_8859_1); + } + + /** + * @return The body of the request as UTF-8 + */ + default String getBody() { + return HTTPUtils.rawToUtf8(getBodyRaw()); + } + + /** + * @return The headers of the request + */ + HTTPHeader[] getHeaders(); +} \ No newline at end of file diff --git a/src/main/java/de/tudbut/net/http/serverimpl/ContentType.java b/src/main/java/de/tudbut/net/http/serverimpl/ContentType.java new file mode 100644 index 0000000..77b5bb5 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/ContentType.java @@ -0,0 +1,14 @@ +package de.tudbut.net.http.serverimpl; + +import de.tudbut.net.http.HTTPContentType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ContentType { + HTTPContentType value(); +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/HTTPServerImpl.java b/src/main/java/de/tudbut/net/http/serverimpl/HTTPServerImpl.java new file mode 100644 index 0000000..e64421e --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/HTTPServerImpl.java @@ -0,0 +1,292 @@ +package de.tudbut.net.http.serverimpl; + +import de.tudbut.net.http.*; +import de.tudbut.obj.TLMap; +import de.tudbut.tools.ReflectUtil; +import de.tudbut.tools.Tools; +import de.tudbut.net.http.*; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Objects; + +import static de.tudbut.net.http.serverimpl.Method.from; + +public class HTTPServerImpl implements HTTPServer.HTTPHandler { + + ArrayList listeners = new ArrayList<>(); + TLMap> methods = new TLMap<>(); + TLMap methodObjects = new TLMap<>(); + + public void addListener(Object listener) { + if(!checkValidity(listener)) + throw new IllegalArgumentException("The listener contains a method that is annotated with @Serve " + + "but doesn't have matching arguments! Required: " + + "de.tudbut.net.http.HTTPServerRequest, de.tudbut.net.http.ParsedHTTPValue, [etc]" + ); + listeners.add(listener); + reindex(); + } + public void removeListener(Object listener) { + listeners.remove(listener); + reindex(); + } + + private void reindex() { + methods = new TLMap<>(); + for (int i = 0; i < listeners.size(); i++) { + Object listener = listeners.get(i); + Class clazz = listener.getClass(); + Method[] clazzMethods = clazz.getDeclaredMethods(); + for (int j = 0; j < clazzMethods.length; j++) { + Method method = clazzMethods[j]; + if(method.isAnnotationPresent(Serve.class)) { + Serve annotation = method.getAnnotation(Serve.class); + Class[] params = method.getParameterTypes(); + if( + params[0] != HTTPServerRequest.class || + params[1] != ParsedHTTPValue.class || + checkParams(method, params) + ) + continue; + ReflectUtil.forceAccessible(method); + ArrayList list = methods.get(annotation.type().toString().charAt(0) + annotation.value(), ArrayList::new); + list.add(method); + if(list.size() == 1) + methods.set(annotation.type().toString().charAt(0) + annotation.value(), list); + methodObjects.set(method, listener); + } + } + } + } + + private boolean checkParams(Method method, Class[] params) { + Annotation[][] paramAnnotations = method.getParameterAnnotations(); + boolean gvalid = true; + for (int k = 2; k < params.length; k++) { + boolean valid = false; + for (int l = 0; l < paramAnnotations[k].length; l++) { + if( + paramAnnotations[k][l].annotationType() == Parameter.class || + paramAnnotations[k][l].annotationType() == Path.class || + paramAnnotations[k][l].annotationType() == Header.class + ) { + valid = true; + break; + } + } + gvalid = gvalid && valid; + } + return !gvalid; + } + + private boolean checkValidity(Object listener) { + Class clazz = listener.getClass(); + Method[] clazzMethods = clazz.getDeclaredMethods(); + for (int j = 0; j < clazzMethods.length; j++) { + Method method = clazzMethods[j]; + if(method.isAnnotationPresent(Serve.class)) { + Class[] params = method.getParameterTypes(); + if(params[0] != HTTPServerRequest.class) + return false; + if(params[1] != ParsedHTTPValue.class) + return false; + if(checkParams(method, params)) + return false; + } + } + return true; + } + + private static boolean checkRequestMethods(Method method, HTTPRequestType requestType) { + HTTPRequestType[] values = HTTPRequestType.values(); + ArrayList allowed = new ArrayList<>(); + for (int i = 0, valuesLength = values.length ; i < valuesLength ; i++) { + HTTPRequestType value = values[i]; + if(method.getDeclaredAnnotation(from(value)) != null) { + allowed.add(value); + } + } + return allowed.size() == 0 || allowed.contains(requestType); + } + + private Method find(ArrayList methods, ParsedHTTPValue httpValue) { + for (int j = 0 ; j < methods.size() ; j++) { + Method val = methods.get(j); + if(!checkRequestMethods(val, (HTTPRequestType) httpValue.getStatusCodeAsEnum())) { + continue; + } + return val; + } + return null; + } + + @Override + public void handle(HTTPServerRequest request) throws Exception { + ParsedHTTPValue httpValue = request.parse(); + Method method = null; + String[] keys = methods.keys().toArray(new String[0]); + ArrayList[] vals = ((ArrayList[]) methods.values().toArray(new ArrayList[0])); + String path = httpValue.getPath(); + while (!path.equals("/") && path.endsWith("/")) + path = path.substring(0, path.length() - 1); + for (int i = 0; i < keys.length; i++) { + String key = keys[i].substring(1); + String[] splitKey = key.split("/"); + String[] splitPath = path.split("/"); + if(!key.endsWith("/*") && splitKey.length != splitPath.length) { + continue; + } + boolean valid = true; + for (int k = 0 ; k < splitKey.length && k < splitPath.length ; k++) { + String s = splitKey[k]; + switch (keys[i].charAt(0)) { + case 'W': + if (!splitPath[k].matches(Tools.wildcardToRegex(splitKey[k]))) + valid = false; + break; + case 'P': + if (!splitPath[k].equals(splitKey[k])) + valid = false; + break; + case 'R': + if (!splitPath[k].matches(splitKey[k])) + valid = false; + break; + } + } + if(valid) { + Method m = find(vals[i], httpValue); + if(m != null) + method = m; + } + } + if(method == null) { + method = find(methods.get("404"), httpValue); + } + Object[] params = createParams(method, httpValue, request); + if(params == null) { + method = find(methods.get("400"), httpValue); + params = new Object[] {request, httpValue}; + } + try { + if (Objects.requireNonNull(method).getReturnType() == HTTPResponse.class) { + request.respond((HTTPResponse) method.invoke(methodObjects.get(method), params)); + return; + } + String s = method.invoke(methodObjects.get(method), params).toString(); + ContentType contentType = method.getDeclaredAnnotation(ContentType.class); + if(contentType == null) { + contentType = new ContentType() { + @Override + public Class annotationType() { + return ContentType.class; + } + + @Override + public HTTPContentType value() { + return HTTPContentType.HTML; + } + }; + } + request.respond(HTTPResponseFactory.create(HTTPResponseCode.OK, s, contentType.value())); + } catch (InvocationTargetException exception) { + if(exception.getCause() instanceof Response) { + request.respond(((Response) exception.getCause()).response); + } + else + throw exception; + } + } + + private Parameter getParameterAnnotation(Annotation[] annotations) { + for (int i = 0; i < annotations.length; i++) { + if(annotations[i].annotationType() == Parameter.class) + return (Parameter) annotations[i]; + } + return null; + } + + private Path getPathAnnotation(Annotation[] annotations) { + for (int i = 0; i < annotations.length; i++) { + if(annotations[i].annotationType() == Path.class) + return (Path) annotations[i]; + } + return null; + } + + private Header getHeaderAnnotation(Annotation[] annotations) { + for (int i = 0; i < annotations.length; i++) { + if(annotations[i].annotationType() == Header.class) + return (Header) annotations[i]; + } + return null; + } + + private Object[] createParams(Method method, ParsedHTTPValue v, HTTPServerRequest r) { + try { + ArrayList list = new ArrayList<>(); + list.add(r); + list.add(v); + Class[] parameterTypes = method.getParameterTypes(); + Annotation[][] parameters = method.getParameterAnnotations(); + String[] splitPath = v.getPath().split("/"); + for (int i = 2; i < parameterTypes.length; i++) { + Parameter param = getParameterAnnotation(parameters[i]); + Path path = getPathAnnotation(parameters[i]); + Header header = getHeaderAnnotation(parameters[i]); + String s = null; + if (header != null) { + HTTPHeader[] headers = v.getHeaders(); + for (int j = 0, headersLength = headers.length ; j < headersLength ; j++) { + HTTPHeader httpHeader = headers[j]; + if (httpHeader.key().equalsIgnoreCase(header.value())) { + s = httpHeader.value(); + } + } + } + if (s == null && param != null) { + s = v.getQuery().get(param.value()); + } + if (s == null && path != null) { + if (splitPath.length > path.value()) { + s = splitPath[path.value()]; + } + } + if(s == null) { + list.add(null); + continue; + } + if(parameterTypes[i] == String.class) { + list.add(s); + } + else if(parameterTypes[i] == Integer.class) { + list.add(Integer.parseInt(s)); + } + else if(parameterTypes[i] == Boolean.class) { + list.add(!s.equals("0") && !s.equals("false") && !s.equals("null")); + } + else if(parameterTypes[i] == Long.class) { + list.add(Long.parseLong(s)); + } + else if(parameterTypes[i] == Float.class) { + list.add(Float.parseFloat(s)); + } + else if(parameterTypes[i] == Double.class) { + list.add(Double.parseDouble(s)); + } + else if(parameterTypes[i] == Short.class) { + list.add(Short.parseShort(s)); + } + else { + return null; + } + } + return list.toArray(new Object[0]); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Header.java b/src/main/java/de/tudbut/net/http/serverimpl/Header.java new file mode 100644 index 0000000..a4f58ce --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Header.java @@ -0,0 +1,12 @@ +package de.tudbut.net.http.serverimpl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Header { + String value(); +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Method.java b/src/main/java/de/tudbut/net/http/serverimpl/Method.java new file mode 100644 index 0000000..efb2e20 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Method.java @@ -0,0 +1,77 @@ +package de.tudbut.net.http.serverimpl; + +import de.tudbut.net.http.HTTPRequestType; + +import java.lang.annotation.*; + +public class Method { + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Get { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Post { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Head { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Put { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Patch { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Delete { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Trace { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Options { + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Connect { + + } + + public static Class from(HTTPRequestType type) { + switch (type) { + case GET: return Get.class; + case PUT: return Put.class; + case HEAD: return Head.class; + case POST: return Post.class; + case PATCH: return Patch.class; + case TRACE: return Trace.class; + case DELETE: return Delete.class; + case CONNECT: return Connect.class; + case OPTIONS: return Options.class; + default: return null; + } + } +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Parameter.java b/src/main/java/de/tudbut/net/http/serverimpl/Parameter.java new file mode 100644 index 0000000..17d80ea --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Parameter.java @@ -0,0 +1,12 @@ +package de.tudbut.net.http.serverimpl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Parameter { + String value(); +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Path.java b/src/main/java/de/tudbut/net/http/serverimpl/Path.java new file mode 100644 index 0000000..cc3e258 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Path.java @@ -0,0 +1,13 @@ +package de.tudbut.net.http.serverimpl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Path { + + int value(); +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Response.java b/src/main/java/de/tudbut/net/http/serverimpl/Response.java new file mode 100644 index 0000000..a08a1e1 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Response.java @@ -0,0 +1,15 @@ +package de.tudbut.net.http.serverimpl; + +import de.tudbut.net.http.HTTPContentType; +import de.tudbut.net.http.HTTPResponse; +import de.tudbut.net.http.HTTPResponseCode; +import de.tudbut.net.http.HTTPResponseFactory; + +public class Response extends RuntimeException { + + HTTPResponse response; + + public Response(HTTPResponseCode code, String body, HTTPContentType contentType) { + response = HTTPResponseFactory.create(code, body, contentType); + } +} diff --git a/src/main/java/de/tudbut/net/http/serverimpl/Serve.java b/src/main/java/de/tudbut/net/http/serverimpl/Serve.java new file mode 100644 index 0000000..c693460 --- /dev/null +++ b/src/main/java/de/tudbut/net/http/serverimpl/Serve.java @@ -0,0 +1,21 @@ +package de.tudbut.net.http.serverimpl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Serve { + + String value(); + Type type() default Type.WILDCARD; + + enum Type { + PLAIN, + REGEX, + WILDCARD, + ; + } +} diff --git a/src/main/java/de/tudbut/net/ic/Bus.java b/src/main/java/de/tudbut/net/ic/Bus.java new file mode 100644 index 0000000..b538f09 --- /dev/null +++ b/src/main/java/de/tudbut/net/ic/Bus.java @@ -0,0 +1,82 @@ +package de.tudbut.net.ic; + +import de.tudbut.tools.Lock; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.ByteBuffer; + +public class Bus implements Closeable { // Work in progress, will comment later! + + final InputStream i; + final OutputStream o; + final Lock syncI = new Lock(), syncO = new Lock(); + boolean isClosed; + + public Bus(InputStream i, OutputStream o) { + this.i = i; + this.o = o; + } + + public Bus(Socket socket) throws IOException { + this(socket.getInputStream(), socket.getOutputStream()); + } + + public void write(ByteBuffer buffer) throws IOException { + StringBuilder r = new StringBuilder("Written: "); + syncO.waitHere(); + syncO.lock(); + try { + byte[] bytes = buffer.array(); + for (int j = 0; j < bytes.length; j++) { + o.write(bytes[j]); + r.append((char) Byte.toUnsignedInt(bytes[j])); + } + o.flush(); + syncO.unlock(); + } catch (IOException e) { + syncO.unlock(); + throw new IOException(r.toString(), e); + } + } + + public void read(ByteBuffer buffer) throws IOException { + StringBuilder r = new StringBuilder("Read: "); + syncI.waitHere(); + syncI.lock(); + try { + byte[] bytes = buffer.array(); + for (int j = 0; j < bytes.length; j++) { + bytes[j] = (byte) i.read(); + r.append((char) Byte.toUnsignedInt(bytes[j])); + } + syncI.unlock(); + } catch (IOException e) { + syncI.unlock(); + throw new IOException(r.toString(), e); + } + } + + public void close() throws IOException { + i.close(); + o.close(); + syncI.unlock(); + syncO.unlock(); + isClosed = true; + } + + public boolean isClosed() { + if(isClosed) + return true; + try { + i.available(); + } + catch (IOException e) { + isClosed = true; + } + return isClosed; + } +} diff --git a/src/main/java/de/tudbut/net/ic/PBIC.java b/src/main/java/de/tudbut/net/ic/PBIC.java new file mode 100644 index 0000000..fa6baff --- /dev/null +++ b/src/main/java/de/tudbut/net/ic/PBIC.java @@ -0,0 +1,272 @@ +package de.tudbut.net.ic; + +import de.tudbut.tools.BetterJ; +import de.tudbut.tools.Tools; + +import java.io.Closeable; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class PBIC { + + public static int getInt(byte[] bytes) { + if(bytes.length == 4) { + int i = 0; + int[] ints = Tools.byteArrayToIntArray(bytes); + i += ints[0] << 3 * 8; + i += ints[1] << 2 * 8; + i += ints[2] << 1 * 8; + i += ints[3] << 0 * 8; + return i; + } + else + throw new NumberFormatException(); + } + + public static byte[] putInt(int i) { + byte[] bytes = new byte[4]; + bytes[0] = (byte) ((i >> 3 * 8) & 0xff); + bytes[1] = (byte) ((i >> 2 * 8) & 0xff); + bytes[2] = (byte) ((i >> 1 * 8) & 0xff); + bytes[3] = (byte) ((i >> 0 * 8) & 0xff); + return bytes; + } + + public static class Server implements Closeable { + private final int port; + protected final ServerSocket server; + public final ArrayList sockets = new ArrayList<>(); + public final ArrayList busses = new ArrayList<>(); + public final ArrayList connections = new ArrayList<>(); + public final ArrayList onJoin = new ArrayList<>(); + public Connection lastConnection; + + public Server(int port) throws IOException { + this.port = port; + server = new ServerSocket(); + } + + protected void listen() { + while (server.isBound() && !server.isClosed()) { + try { + Socket socket = server.accept(); + + sockets.add(socket); + Bus bus = new Bus(socket); + busses.add(bus); + lastConnection = onJoin(socket, bus); + Runnable[] array = onJoin.toArray(new Runnable[0]); + for (int i = 0; i < array.length; i++) { + BetterJ.t(array[i]); + } + Thread.sleep(10); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private Connection onJoin(Socket socket, Bus bus) { + Connection connection = new Connection() { + public Bus getBus() { + return bus; + } + + public Socket getSocket() { + return socket; + } + }; + this.connections.add(connection); + return connection; + } + + public void start() throws IOException { + server.bind(new InetSocketAddress(port)); + BetterJ.t(this::listen); + } + + public int getPort() { + return port; + } + + @Override + public void close() throws IOException { + int i; + for(i = 0; i < this.busses.size(); ++i) { + this.busses.get(i).close(); + } + + for(i = 0; i < this.sockets.size(); ++i) { + this.sockets.get(i).close(); + } + + this.server.close(); + } + + } + + public static class PBICException extends Exception { + public PBICException(String message) { + super(message); + } + public PBICException(String message, Exception cause) { + super(message, cause); + } + + public PBICException(Exception cause) { + super(cause); + } + + public static class PBICReadException extends PBICException { + + public PBICReadException(Exception cause) { + super(cause); + } + } + public static class PBICWriteException extends PBICException { + + public PBICWriteException(Exception cause) { + super(cause); + } + } + } + + public interface Connection { + Map oSync = new HashMap<>(); + Map iSync = new HashMap<>(); + + Bus getBus(); + + Socket getSocket(); + + + default boolean isClosed() { + return getBus().isClosed() || getSocket().isClosed(); + } + + default void writePacket(Packet packet) throws PBICException.PBICWriteException { + + while (oSync.getOrDefault(this, false)); + + oSync.put(this, true); + + ByteBuffer buffer; + + int length = packet.getLength(); + byte[] content = packet.getContentBytes(); + + try { + // Send length + buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.put(putInt(length)); + getBus().write(buffer); + + // Send content + buffer = ByteBuffer.allocate(length); + for (int i = 0; i < content.length; i++) { + buffer.put(content[i]); + } + getBus().write(buffer); + } catch (Exception e) { + oSync.put(this, false); + throw new PBICException.PBICWriteException(e); + } + + oSync.put(this, false); + } + + default Packet readPacket() throws PBICException.PBICReadException { + + while (iSync.getOrDefault(this, false)); + + iSync.put(this, true); + + ByteBuffer buffer; + + int length; + byte[] content; + + String strContent; + try { + // Read length + buffer = ByteBuffer.allocate(Integer.BYTES); + getBus().read(buffer); + length = getInt(buffer.array()); + + // Read content + buffer = ByteBuffer.allocate(length); + getBus().read(buffer); + content = buffer.array(); + + // Parse content + strContent = new String(content); + } catch (Exception e) { + iSync.put(this, false); + throw new PBICException.PBICReadException(e); + } + + iSync.put(this, false); + + return new Packet() { + @Override + public int getLength() { + return length; + } + + @Override + public String getContent() { + return strContent; + } + }; + } + } + + public interface Packet { + default int getLength() { + return getContentBytes().length; + } + default byte[] getContentBytes() { + return getContent().getBytes(); + } + + String getContent(); + } + + public static class Client implements Closeable { + private final Socket client; + public Connection connection; + + public Client(String ip, int port) throws IOException { + client = new Socket(ip, port); + + start(new Bus(client)); + } + + private void start(Bus bus) { + connection = new Connection() { + @Override + public Bus getBus() { + return bus; + } + + @Override + public Socket getSocket() { + return client; + } + }; + } + + @Override + public void close() throws IOException { + connection.getBus().close(); + client.close(); + } + } + +} diff --git a/src/main/java/de/tudbut/net/nethandler/Packet.java b/src/main/java/de/tudbut/net/nethandler/Packet.java new file mode 100644 index 0000000..9c2d56a --- /dev/null +++ b/src/main/java/de/tudbut/net/nethandler/Packet.java @@ -0,0 +1,249 @@ +package de.tudbut.net.nethandler; + +import de.tudbut.net.ws.Connection; +import de.tudbut.tools.Tools; +import de.tudbut.type.Vector3d; +import de.tudbut.global.DebugStateManager; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +@Deprecated +public class Packet { + private final AtomicInteger id = new AtomicInteger(-1); + private final AtomicReference data = new AtomicReference<>(); + + public Packet(PacketData data) { + setData(data); + } + + public Packet() { + } + + public static PacketData fromString(String packet) { + try { + return fromString(packet, null); + } + catch (Exception ignore) { + DebugStateManager.getDebugLogger().debug("Packet couldn't be received. Returning null!"); + return null; + } + } + + public static PacketData fromString(String packet, Connection c) throws IOException { + if (packet.startsWith("[BEGIN")) { + StringBuilder d = new StringBuilder(packet); + + if (!d.toString().startsWith("[BEGIN(") && !d.toString().endsWith(")[")) + throw new IllegalStateException("Next data in line is not a splitPacket!"); + + d = new StringBuilder(); + while (!d.toString().endsWith("]END]")) { + d.append(c.receive()); + } + + d = new StringBuilder(d.substring(0, d.length() - "]END]".length())); + + return fromString(d.toString()); + } + + + Map map = Tools.stringToMap(packet); + + return new PacketData() { + public int getID() { + return Integer.parseInt(map.get("id")); + } + + public String getString0() { + return map.get("s0"); + } + + public String getString1() { + return map.get("s1"); + } + + public int getInt0() { + return Integer.parseInt(map.getOrDefault("i0", "0")); + } + + public int getInt1() { + return Integer.parseInt(map.getOrDefault("i1", "0")); + } + + public long getLong0() { + return Long.parseLong(map.getOrDefault("l0", "0")); + } + + public long getLong1() { + return Long.parseLong(map.getOrDefault("l1", "0")); + } + + public Map getMap0() { + return Tools.stringToMap(map.get("m0")); + } + + public Map getMap1() { + return Tools.stringToMap(map.get("m1")); + } + + public double getDouble0() { + return Double.parseDouble(map.getOrDefault("d0", "0")); + } + + public double getDouble1() { + return Double.parseDouble(map.getOrDefault("d1", "0")); + } + + public float getFloat0() { + return Float.parseFloat(map.getOrDefault("f0", "0")); + } + + public float getFloat1() { + return Float.parseFloat(map.getOrDefault("f1", "0")); + } + + public Vector3d getVector0() { + return new Vector3d(Double.parseDouble(Tools.stringToMap(map.get("v0")).get("x")), Double.parseDouble(Tools.stringToMap(map.get("v0")).get("y")), Double.parseDouble(Tools.stringToMap(map.get("v0")).get("z"))); + } + + public Vector3d getVector1() { + return new Vector3d(Double.parseDouble(Tools.stringToMap(map.get("v1")).get("x")), Double.parseDouble(Tools.stringToMap(map.get("v1")).get("y")), Double.parseDouble(Tools.stringToMap(map.get("v1")).get("z"))); + } + }; + } + + public static PacketData receivePieces(Connection c) throws IOException { + try { + StringBuilder d = new StringBuilder(c.receive()); + + if (!d.toString().startsWith("[BEGIN(") && !d.toString().endsWith(")[")) + throw new IllegalStateException("Next data in line is not a splitPacket!"); + + int bytesPerPiece = Integer.parseInt(d.toString().split("\\(")[1].split("\\)")[0]); + + d = new StringBuilder(); + while (!d.toString().endsWith("]END]")) { + d.append(c.receive()); + } + + d = new StringBuilder(d.substring(0, d.length() - "]END]".length())); + + return fromString(d.toString()); + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + throw new IllegalStateException("Next data in line is not a splitPacket!"); + } + } + + public void send(Connection connection) throws IOException { + connection.send(this.toString()); + } + + public int getID() { + return id.get(); + } + + public void setID(int i) { + id.set(i); + } + + public T getDataValue(PacketDataID id) { + switch (id) { + case ID: + return (T) new Integer(getData().getID()); + case INT0: + return (T) new Integer(getData().getInt0()); + case INT1: + return (T) new Integer(getData().getInt1()); + case STRING0: + return (T) getData().getString0(); + case STRING1: + return (T) getData().getString1(); + case LONG0: + return (T) new Long(getData().getLong0()); + case LONG1: + return (T) new Long(getData().getLong1()); + case DOUBLE0: + return (T) new Double(getData().getDouble0()); + case DOUBLE1: + return (T) new Double(getData().getDouble1()); + case FLOAT0: + return (T) new Float(getData().getFloat0()); + case FLOAT1: + return (T) new Float(getData().getFloat1()); + case MAP0: + return (T) getData().getMap0(); + case MAP1: + return (T) getData().getMap1(); + case VECTOR0: + return (T) getData().getVector0(); + case VECTOR1: + return (T) getData().getVector1(); + } + return null; + } + + public PacketData getData() { + return data.get(); + } + + public void setData(PacketData d) { + data.set(d); + setID(data.get().getID()); + } + + @Override + public String toString() { + Map map = new HashMap<>(); + map.put("id", String.valueOf(getID())); + map.put("i0", String.valueOf(((Integer) getDataValue(PacketDataID.INT0)).intValue())); + map.put("i1", String.valueOf(((Integer) getDataValue(PacketDataID.INT1)).intValue())); + map.put("s0", getDataValue(PacketDataID.STRING0)); + map.put("s1", getDataValue(PacketDataID.STRING1)); + map.put("l0", String.valueOf(((Long) getDataValue(PacketDataID.LONG0)).longValue())); + map.put("l1", String.valueOf(((Long) getDataValue(PacketDataID.LONG1)).longValue())); + map.put("d0", String.valueOf(((Double) getDataValue(PacketDataID.DOUBLE0)).doubleValue())); + map.put("d1", String.valueOf(((Double) getDataValue(PacketDataID.DOUBLE1)).doubleValue())); + map.put("f0", String.valueOf(((Float) getDataValue(PacketDataID.FLOAT0)).floatValue())); + map.put("f1", String.valueOf(((Float) getDataValue(PacketDataID.FLOAT1)).floatValue())); + map.put("m0", Tools.mapToString(getDataValue(PacketDataID.MAP0))); + map.put("m1", Tools.mapToString(getDataValue(PacketDataID.MAP1))); + Map v0map = new HashMap<>(); + v0map.put("x", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR0)).getX())); + v0map.put("y", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR0)).getY())); + v0map.put("z", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR0)).getZ())); + map.put("v0", Tools.mapToString(v0map)); + Map v1map = new HashMap<>(); + v1map.put("x", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR1)).getX())); + v1map.put("y", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR1)).getY())); + v1map.put("z", String.valueOf(((Vector3d) getDataValue(PacketDataID.VECTOR1)).getZ())); + map.put("v1", Tools.mapToString(v1map)); + return Tools.mapToString(map); + } + + public void sendAsPieces(int bytesPerPiece, Connection c) throws IOException { + String d = toString(); + c.send("[BEGIN(" + d.length() + ")["); + + for (int i = 0; i < d.length(); i += bytesPerPiece) { + StringBuilder piece = new StringBuilder(); + for (int j = 0; j < bytesPerPiece; j++) { + try { + piece.append(d.toCharArray()[i + j]); + } + catch (ArrayIndexOutOfBoundsException ignore) { + } + } + c.send(piece.toString()); + } + + c.send("]END]"); + } +} diff --git a/src/main/java/de/tudbut/net/nethandler/PacketData.java b/src/main/java/de/tudbut/net/nethandler/PacketData.java new file mode 100644 index 0000000..b9d061c --- /dev/null +++ b/src/main/java/de/tudbut/net/nethandler/PacketData.java @@ -0,0 +1,82 @@ +package de.tudbut.net.nethandler; + +import de.tudbut.net.ws.Connection; +import de.tudbut.type.Vector3d; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@Deprecated +public interface PacketData { + int getID(); + + default String getString0() { + return null; + } + + default String getString1() { + return null; + } + + default int getInt0() { + return 0; + } + + default int getInt1() { + return 0; + } + + default long getLong0() { + return 0; + } + + default long getLong1() { + return 0; + } + + default Map getMap0() { + return new HashMap<>(); + } + + default Map getMap1() { + return new HashMap<>(); + } + + default double getDouble0() { + return 0; + } + + default double getDouble1() { + return 0; + } + + default float getFloat0() { + return 0f; + } + + default float getFloat1() { + return 0f; + } + + default Vector3d getVector0() { + return new Vector3d(0, 0, 0); + } + + default Vector3d getVector1() { + return new Vector3d(0, 0, 0); + } + + + default Packet toPacket() { + return new Packet(this); + } + + default String asString() { + return new Packet(this).toString(); + } + + default void sendInPieces(int bytesPerPiece, Connection connection) throws IOException { + new Packet(this).sendAsPieces(bytesPerPiece, connection); + } +} diff --git a/src/main/java/de/tudbut/net/nethandler/PacketDataID.java b/src/main/java/de/tudbut/net/nethandler/PacketDataID.java new file mode 100644 index 0000000..2802793 --- /dev/null +++ b/src/main/java/de/tudbut/net/nethandler/PacketDataID.java @@ -0,0 +1,6 @@ +package de.tudbut.net.nethandler; + +@Deprecated +public enum PacketDataID { + ID, INT0, INT1, STRING0, STRING1, LONG0, LONG1, DOUBLE0, DOUBLE1, FLOAT0, FLOAT1, MAP0, MAP1, VECTOR0, VECTOR1 +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2.java b/src/main/java/de/tudbut/net/pbic2/PBIC2.java new file mode 100644 index 0000000..0ae2525 --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2.java @@ -0,0 +1,40 @@ +package de.tudbut.net.pbic2; + +import de.tudbut.io.TypedInputStream; +import de.tudbut.io.TypedOutputStream; +import de.tudbut.tools.ReflectUtil; + +import javax.net.ssl.SSLSocket; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.Socket; + +public interface PBIC2 { + + String readMessage() throws IOException; + + String writeMessage(String s) throws IOException; + + String writeMessageWithResponse(String s) throws IOException; + + Socket getSocket(); + TypedInputStream getInput(); + TypedOutputStream getOutput(); + + default boolean isSSL() { + return getSocket() instanceof SSLSocket; + } + default Socket getRealSocket() { + if(!isSSL()) + throw new IllegalStateException("getRealSocket() called despite socket not being SSL"); + try { + Class BaseSSLSocketImpl = Class.forName("sun.security.ssl.BaseSSLSocketImpl"); + Field self = BaseSSLSocketImpl.getDeclaredField("self"); + ReflectUtil.forceAccessible(self); + return (Socket) self.get(getSocket()); + } + catch (Exception e) { + throw new IllegalStateException("cannot call getRealSocket() on this JVM"); + } + } +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2ADisconnect.java b/src/main/java/de/tudbut/net/pbic2/PBIC2ADisconnect.java new file mode 100644 index 0000000..fd87854 --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2ADisconnect.java @@ -0,0 +1,4 @@ +package de.tudbut.net.pbic2; + +public class PBIC2ADisconnect extends Exception { +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2AEventHandler.java b/src/main/java/de/tudbut/net/pbic2/PBIC2AEventHandler.java new file mode 100644 index 0000000..c75f58e --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2AEventHandler.java @@ -0,0 +1,98 @@ +package de.tudbut.net.pbic2; + +import de.tudbut.obj.TLMap; +import de.tudbut.type.Stoppable; +import de.tudbut.io.TypedInputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.util.ArrayList; + +public final class PBIC2AEventHandler implements Stoppable { + + private final ArrayList array = new ArrayList<>(); + private final TLMap map = new TLMap<>(); + private final TLMap listeners = new TLMap<>(); + private final TLMap inputStreams = new TLMap<>(); + + public void start(PBIC2 pbic2, PBIC2AListener listener) throws IOException { + if(array.contains(pbic2)) + return; + array.add(pbic2); + listeners.set(pbic2, listener); + inputStreams.set(pbic2, pbic2.getInput().getStream()); + } + public void remove(PBIC2 pbic2) { + if(!array.contains(pbic2)) + return; + array.remove(pbic2); + map.set(pbic2, null); + listeners.set(pbic2, null); + inputStreams.set(pbic2, null); + } + + public PBIC2AEventHandler() { + new Thread(this::run).start(); + } + + private void runOn(PBIC2 pbic2) throws IOException { + Socket socket = pbic2.getSocket(); + InputStream stream = inputStreams.get(pbic2); + TypedInputStream tis = pbic2.getInput(); + if (!socket.isConnected() || socket.isClosed()) { + listeners.get(pbic2).onError(new PBIC2ADisconnect()); + remove(pbic2); + } + if(pbic2.isSSL()) { + if (pbic2.getRealSocket().getInputStream().available() > 0) { + listeners.get(pbic2).onMessage(tis.readString()); + } + } + else { + if (map.get(pbic2) == null) { + if (stream.available() >= 4) + map.set(pbic2, tis.readInt()); + } + else if (stream.available() >= map.get(pbic2) * 2) { + int i = map.get(pbic2); + StringBuilder builder = new StringBuilder(); + for (int j = 0 ; j < i ; j++) { + builder.append(tis.readChar()); + } + listeners.get(pbic2).onMessage(builder.toString()); + map.set(pbic2, null); + } + } + } + + private void run() { + while (!isStopped()) { + ArrayList array = this.array; + try { + for (int i = 0 ; i < array.size() ; i++) { + try { + runOn(array.get(i)); + } + catch (Throwable e) { + listeners.get(array.get(i)).onError(e); + } + } + } catch (Exception ignored) { } + try { + Thread.sleep(50); + } + catch (InterruptedException ignored) { } + } + } + + private int readWithTimeout(TypedInputStream stream) throws IOException { + try { + return stream.read(); + } catch (SocketTimeoutException e) { + // There is no data available. + return -1; + } + } +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2AListener.java b/src/main/java/de/tudbut/net/pbic2/PBIC2AListener.java new file mode 100644 index 0000000..14a6244 --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2AListener.java @@ -0,0 +1,10 @@ +package de.tudbut.net.pbic2; + +import java.io.IOException; + +public interface PBIC2AListener { + + void onMessage(String message) throws IOException; + + void onError(Throwable throwable); +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2Client.java b/src/main/java/de/tudbut/net/pbic2/PBIC2Client.java new file mode 100644 index 0000000..eb1f871 --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2Client.java @@ -0,0 +1,96 @@ +package de.tudbut.net.pbic2; + +import de.tudbut.io.TypedInputStream; +import de.tudbut.io.TypedOutputStream; +import de.tudbut.net.http.HTTPHeader; +import de.tudbut.net.http.HTTPRequest; +import de.tudbut.net.http.HTTPResponse; +import de.tudbut.net.http.HTTPResponseCode; +import de.tudbut.net.http.*; + +import java.io.*; +import java.net.Socket; + +public final class PBIC2Client implements PBIC2 { + + Socket socket; + public final TypedInputStream in; + public final TypedOutputStream out; + + public PBIC2Client(HTTPRequest request) throws IOException { + this(request, i -> i); + } + + public PBIC2Client(HTTPRequest request, PBIC2Passthrough passthrough) throws IOException { + this(request, passthrough, passthrough); + } + + public PBIC2Client(HTTPRequest request, PBIC2Passthrough passthroughIn, PBIC2Passthrough passthroughOut) throws IOException { + request.headers.removeIf(h -> h.key().equals("Connection")); + request.headers.add(new HTTPHeader("Connection", "Upgrade")); + request.headers.add(new HTTPHeader("Upgrade", "TudbuT/PBIC2")); + socket = request.sendNoRead(); + socket.setSoLinger(false, 0); + StringBuilder s = new StringBuilder(); + int res; + while ((res = socket.getInputStream().read()) != 0) { + s.append((char) res); + if(res == 0x0a) { + try { + if (new HTTPResponse(s.toString()).parse().getStatusCodeAsEnum() != HTTPResponseCode.SwitchingProtocols) { + throw new IOException("Invalid response."); + } + } + catch (Exception ignored) { } + } + } + socket.getOutputStream().write(0); + InputStream adapter = socket.getInputStream(); + + in = new TypedInputStream(new InputStream() { + @Override + public int read() throws IOException { + return passthroughIn.pass(adapter.read()); + } + + @Override + public int available() throws IOException { + return adapter.available(); + } + }); + out = new TypedOutputStream(new OutputStream() { + @Override + public void write(int i) throws IOException { + socket.getOutputStream().write(passthroughOut.pass(i)); + } + }); + } + + public String readMessage() throws IOException { + return in.readString(); + } + + public synchronized String writeMessage(String s) throws IOException { + return out.writeString(s); + } + + public synchronized String writeMessageWithResponse(String s) throws IOException { + out.writeString(s); + return in.readString(); + } + + @Override + public Socket getSocket() { + return socket; + } + + @Override + public TypedInputStream getInput() { + return in; + } + + @Override + public TypedOutputStream getOutput() { + return out; + } +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2Passthrough.java b/src/main/java/de/tudbut/net/pbic2/PBIC2Passthrough.java new file mode 100644 index 0000000..9c4946e --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2Passthrough.java @@ -0,0 +1,6 @@ +package de.tudbut.net.pbic2; + +public interface PBIC2Passthrough { + + int pass(int b); +} diff --git a/src/main/java/de/tudbut/net/pbic2/PBIC2Server.java b/src/main/java/de/tudbut/net/pbic2/PBIC2Server.java new file mode 100644 index 0000000..aa67c1a --- /dev/null +++ b/src/main/java/de/tudbut/net/pbic2/PBIC2Server.java @@ -0,0 +1,89 @@ +package de.tudbut.net.pbic2; + +import de.tudbut.io.TypedInputStream; +import de.tudbut.io.TypedOutputStream; +import de.tudbut.net.http.*; +import de.tudbut.net.http.*; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +public final class PBIC2Server implements PBIC2 { + + Socket socket; + public final TypedInputStream in; + public final TypedOutputStream out; + + public PBIC2Server(HTTPServerRequest request) throws IOException { + this(request, i -> i); + } + + public PBIC2Server(HTTPServerRequest request, PBIC2Passthrough passthrough) throws IOException { + this(request, passthrough, passthrough); + } + + public PBIC2Server(HTTPServerRequest request, PBIC2Passthrough passthroughIn, PBIC2Passthrough passthroughOut) throws IOException { + ParsedHTTPValue p = request.parse(); + if(Arrays.stream(p.getHeaders()).noneMatch(h -> h.toString().equals("Connection: Upgrade"))) { + throw new IOException("Invalid request."); + } + if(Arrays.stream(p.getHeaders()).noneMatch(h -> h.toString().equals("Upgrade: TudbuT/PBIC2"))) { + throw new IOException("Invalid request."); + } + socket = request.socket; + socket.getOutputStream().write(HTTPResponseFactory.create(HTTPResponseCode.SwitchingProtocols, "\u0000", HTTPContentType.ANY).value.getBytes(StandardCharsets.ISO_8859_1)); + while (socket.getInputStream().read() != 0); + + InputStream adapter = socket.getInputStream(); + + in = new TypedInputStream(new InputStream() { + @Override + public int read() throws IOException { + return passthroughIn.pass(adapter.read()); + } + + @Override + public int available() throws IOException { + return adapter.available(); + } + }); + out = new TypedOutputStream(new OutputStream() { + @Override + public void write(int i) throws IOException { + socket.getOutputStream().write(passthroughOut.pass(i)); + } + }); + } + + public String readMessage() throws IOException { + return in.readString(); + } + + public synchronized String writeMessage(String s) throws IOException { + return out.writeString(s); + } + + public synchronized String writeMessageWithResponse(String s) throws IOException { + out.writeString(s); + return in.readString(); + } + + @Override + public Socket getSocket() { + return socket; + } + + @Override + public TypedInputStream getInput() { + return in; + } + + @Override + public TypedOutputStream getOutput() { + return out; + } +} diff --git a/src/main/java/de/tudbut/net/smtp/SMTPDataOutput.java b/src/main/java/de/tudbut/net/smtp/SMTPDataOutput.java new file mode 100644 index 0000000..5510ef2 --- /dev/null +++ b/src/main/java/de/tudbut/net/smtp/SMTPDataOutput.java @@ -0,0 +1,99 @@ +package de.tudbut.net.smtp; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +public class SMTPDataOutput { + private final SMTPSender parent; + final PrintWriter write; + + private boolean headersOpen = true; + private boolean dataOpen = false; + + SMTPDataOutput(SMTPSender sender, PrintWriter writer) { + parent = sender; + this.write = writer; + } + + private void ensureHeadersOpen() { + if(!headersOpen) + throw new HeadersClosedException(); + } + private void ensureDataOpen() { + if(!dataOpen) + throw new DataClosedException(); + } + + public void from(String s) { + ensureHeadersOpen(); + write.println("From: " + s); + } + + public void to(String s) { + ensureHeadersOpen(); + write.println("To: " + s); + } + + public void subject(String s) { + ensureHeadersOpen(); + write.println("Subject: " + s); + } + + public void date(Date date) { + ensureHeadersOpen(); + write.println("Date: " + DateTimeFormatter.ISO_DATE_TIME.format(date.toInstant())); + } + + public void header(String k, String v) { + ensureHeadersOpen(); + write.println(k + ": " + v); + } + + public void closeHeaders() { + ensureHeadersOpen(); + write.println(); + headersOpen = false; + } + + public PrintStream getDataOutputStream() { + if(headersOpen) + closeHeaders(); + + return new PrintStream(new OutputStream() { + int last = 0x0A; + + @Override + public void write(int i) throws IOException { + if(i != '.' && last == 0x0A) + write.write(i); + else + write.write(" ."); + last = i; + } + + @Override + public void close() throws IOException { + super.close(); + closeData(); + } + }); + } + + public void closeData() throws IOException { + ensureDataOpen(); + write.println("."); + dataOpen = false; + parent.expect250(); + } + + public static class HeadersClosedException extends RuntimeException { + + } + public static class DataClosedException extends RuntimeException { + + } +} diff --git a/src/main/java/de/tudbut/net/smtp/SMTPSender.java b/src/main/java/de/tudbut/net/smtp/SMTPSender.java new file mode 100644 index 0000000..75a45e9 --- /dev/null +++ b/src/main/java/de/tudbut/net/smtp/SMTPSender.java @@ -0,0 +1,103 @@ +package de.tudbut.net.smtp; + +import de.tudbut.io.CLSPrintWriter; + +import javax.net.ssl.SSLSocketFactory; +import java.io.*; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +public class SMTPSender { + + InputStreamReader in; + OutputStreamWriter out; + PrintWriter write; + BufferedReader read; + + public BufferedReader getReader() { + return read; + } + + + public SMTPSender(String host, int port, boolean ssl) throws IOException { + this(socket(host, port, ssl)); + } + + private static Socket socket(String host, int port, boolean ssl) throws IOException { + Socket socket; + if(ssl) { + socket = SSLSocketFactory.getDefault().createSocket(host, port); + } + else { + socket = new Socket(host, port); + } + return socket; + } + + public SMTPSender(InputStream inputStream, OutputStream outputStream) { + in = new InputStreamReader(inputStream, StandardCharsets.ISO_8859_1); + out = new OutputStreamWriter(outputStream, StandardCharsets.ISO_8859_1); + write = new CLSPrintWriter(out); + ((CLSPrintWriter)write).customLineSeparator = "\r\n"; + read = new BufferedReader(in); + } + + public SMTPSender(Socket socket) throws IOException { + this(socket.getInputStream(), socket.getOutputStream()); + } + + public void helo(String name) throws IOException { + write.println("HELO " + name); + expect220(); + } + + public void from(String sender) throws IOException { + write.println("MAIL FROM:" + sender); + expect250(); + } + + public void to(String recipient) throws IOException { + write.println("RCPT TO:" + recipient); + expect250(); + } + + public SMTPDataOutput data() throws IOException { + write.println("DATA"); + expect354(); + return new SMTPDataOutput(this, write); + } + + public void quit() throws IOException { + write.println("QUIT"); + expect221(); + } + + void expect220() throws IOException { + String s = read.readLine(); + if(!s.replaceAll("-", " ").split(" ", 2)[0].equals("220")) { + thrUnexpected(s); + } + } + void expect250() throws IOException { + String s = read.readLine(); + if(!s.replaceAll("-", " ").split(" ", 2)[0].equals("250")) { + thrUnexpected(s); + } + } + void expect354() throws IOException { + String s = read.readLine(); + if(!s.replaceAll("-", " ").split(" ", 2)[0].equals("354")) { + thrUnexpected(s); + } + } + void expect221() throws IOException { + String s = read.readLine(); + if(!s.replaceAll("-", " ").split(" ", 2)[0].equals("221")) { + thrUnexpected(s); + } + } + + private void thrUnexpected(String code) { + throw new SMTPServerErrorException(code); + } +} diff --git a/src/main/java/de/tudbut/net/smtp/SMTPServerErrorException.java b/src/main/java/de/tudbut/net/smtp/SMTPServerErrorException.java new file mode 100644 index 0000000..58550f3 --- /dev/null +++ b/src/main/java/de/tudbut/net/smtp/SMTPServerErrorException.java @@ -0,0 +1,7 @@ +package de.tudbut.net.smtp; + +public class SMTPServerErrorException extends RuntimeException { + public SMTPServerErrorException(String code) { + super("Code: " + code); + } +} diff --git a/src/main/java/de/tudbut/net/websocket/Operation.java b/src/main/java/de/tudbut/net/websocket/Operation.java new file mode 100644 index 0000000..f15ebdd --- /dev/null +++ b/src/main/java/de/tudbut/net/websocket/Operation.java @@ -0,0 +1,11 @@ +package de.tudbut.net.websocket; + +class Operation { + final int opcode; + final byte[] data; + + Operation(int opcode, byte[] data) { + this.opcode = opcode; + this.data = data; + } +} diff --git a/src/main/java/de/tudbut/net/websocket/WebSocket.java b/src/main/java/de/tudbut/net/websocket/WebSocket.java new file mode 100644 index 0000000..6158e28 --- /dev/null +++ b/src/main/java/de/tudbut/net/websocket/WebSocket.java @@ -0,0 +1,59 @@ +package de.tudbut.net.websocket; + +import de.tudbut.io.TypedInputStream; + +import java.io.IOException; +import java.io.InputStream; + +public class WebSocket { + + InputStream in; + TypedInputStream tin; + + public Operation readFrame() throws IOException { + int i; + i = in.read(); + boolean fin = (i & 0b10000000) != 0; + if((i & 0b01110000) != 0) { + fail(); + } + int opcode = i & 0xF; + i = in.read(); + boolean mask = (i & 0b10000000) != 0; + long payloadLen = i & 0b01111111; + long tmpLen = payloadLen; + if(payloadLen == 126) { + tmpLen = tin.readChar(); + } + if(payloadLen == 127) { + tmpLen = tin.readLong(); + } + payloadLen = tmpLen; + int maskKey = 0; + if(mask) { + maskKey = tin.readInt(); + } + int[] appData = new int[(int) Math.ceil(payloadLen / 4d)]; + for (int j = 0 ; j < appData.length ; j++) { + i = tin.readInt(); + appData[j] = mask ? i ^ maskKey : i; + } + byte[] appDataBytes = new byte[(int) (appData.length - (4 - (payloadLen % 4)))]; + for (int j = 0 ; j < appData.length ;) { + // TODO check the way this is executed by JVM! (should work tho) + if(j < appDataBytes.length) + appDataBytes[j] = (byte) (appData[j++] >> 8 * 3 & 0xff); + if(j < appDataBytes.length) + appDataBytes[j] = (byte) (appData[j++] >> 8 * 2 & 0xff); + if(j < appDataBytes.length) + appDataBytes[j] = (byte) (appData[j++] >> 8 * 1 & 0xff); + if(j < appDataBytes.length) + appDataBytes[j] = (byte) (appData[j++] >> 8 * 0 & 0xff); + } + return new Operation(opcode, appDataBytes); + } + + private void fail() { + + } +} diff --git a/src/main/java/de/tudbut/net/websocket/WebSocketServer.java b/src/main/java/de/tudbut/net/websocket/WebSocketServer.java new file mode 100644 index 0000000..c16ffda --- /dev/null +++ b/src/main/java/de/tudbut/net/websocket/WebSocketServer.java @@ -0,0 +1,4 @@ +package de.tudbut.net.websocket; + +public class WebSocketServer { +} diff --git a/src/main/java/de/tudbut/net/ws/Client.java b/src/main/java/de/tudbut/net/ws/Client.java new file mode 100644 index 0000000..2c2bf9a --- /dev/null +++ b/src/main/java/de/tudbut/net/ws/Client.java @@ -0,0 +1,9 @@ +package de.tudbut.net.ws; + +import java.io.IOException; + +public class Client extends Connection { + public Client(String ip, int port) throws IOException { + super(ip, port); + } +} diff --git a/src/main/java/de/tudbut/net/ws/Connection.java b/src/main/java/de/tudbut/net/ws/Connection.java new file mode 100644 index 0000000..92f35d7 --- /dev/null +++ b/src/main/java/de/tudbut/net/ws/Connection.java @@ -0,0 +1,113 @@ +package de.tudbut.net.ws; + +import de.tudbut.io.StreamReader; +import de.tudbut.io.StreamWriter; +import de.tudbut.timer.AsyncTask; +import de.tudbut.type.IntArrayList; +import de.tudbut.type.Nothing; +import de.tudbut.type.Stoppable; + +import java.io.*; +import java.net.Socket; + +public class Connection implements Stoppable, Runnable { + private final Socket theSocket; + private final Runnable[] handlers = new Runnable[256]; + private int latestHandler = -1; + + Connection(String ip, int port) throws IOException { + theSocket = new Socket(ip, port); + } + + Connection(Socket socket) { + theSocket = socket; + } + + public Socket getSocket() { + return theSocket; + } + + public void send(String s) throws IOException { + PrintWriter writer = new PrintWriter(new OutputStreamWriter(theSocket.getOutputStream())); + writer.println(s); + writer.flush(); + } + + public void send(int[] bin) throws IOException { + StreamWriter writer = new StreamWriter(theSocket.getOutputStream()); + for (int i = 0; i < bin.length; i++) { + if (bin[i] == 0x00) { + writer.writeUnsignedByte(0x01); + writer.writeUnsignedByte(0x01); + continue; + } + if (bin[i] == 0x01) { + writer.writeUnsignedByte(0x01); + writer.writeUnsignedByte(0x02); + } + writer.writeUnsignedByte(bin[i]); + } + writer.writeUnsignedByte(0x00); + } + + public AsyncTask sendAsync(String s) { + return new AsyncTask<>(() -> { + send(s); + return null; + }); + } + + public AsyncTask sendAsync(int[] s) { + return new AsyncTask<>(() -> { + send(s); + return null; + }); + } + + public int[] receiveBin() throws IOException { + while (!new InputStreamReader(getSocket().getInputStream()).ready()); + StreamReader reader = new StreamReader(theSocket.getInputStream()); + IntArrayList list = new IntArrayList(); + int i; + while ((i = reader.readNextUnsignedByte()) != 0x00) { + if(i == 0x01) { + if(reader.readNextUnsignedByte() == 0x01) + i = 0x00; + if(reader.readNextUnsignedByte() == 0x02) + i = 0x01; + } + + list.add(i); + } + return list.toIntArray(); + } + + public String receive() throws IOException { + while (!new InputStreamReader(getSocket().getInputStream()).ready()); + return new BufferedReader(new InputStreamReader(theSocket.getInputStream())).readLine(); + } + + public void addReceiveHook(Runnable runnable) { + latestHandler++; + handlers[latestHandler] = runnable; + } + + @Override + public void run() { + new Thread(() -> { + while (true) { + try { + if ((!isStopped() && !theSocket.isClosed() && theSocket.getInputStream().available() > 0)) { + for (Runnable handler : handlers) { + if (handler != null) + new Thread(handler).start(); + } + } + } + catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + } +} diff --git a/src/main/java/de/tudbut/net/ws/ConnectionHandler.java b/src/main/java/de/tudbut/net/ws/ConnectionHandler.java new file mode 100644 index 0000000..770e9a2 --- /dev/null +++ b/src/main/java/de/tudbut/net/ws/ConnectionHandler.java @@ -0,0 +1,8 @@ +package de.tudbut.net.ws; + + +import java.io.IOException; + +public interface ConnectionHandler { + void run(Connection connection) throws IOException; +} diff --git a/src/main/java/de/tudbut/net/ws/Server.java b/src/main/java/de/tudbut/net/ws/Server.java new file mode 100644 index 0000000..f4c4f5e --- /dev/null +++ b/src/main/java/de/tudbut/net/ws/Server.java @@ -0,0 +1,48 @@ +package de.tudbut.net.ws; + +import de.tudbut.tools.Tools; +import de.tudbut.type.Stoppable; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +public class Server extends Tools.ObjectMapping implements Runnable, Stoppable { + private final ServerSocket serverSocket; + private final ConnectionHandler[] handlers = new ConnectionHandler[256]; + private int latestHandler = -1; + + public Server(int port) throws IOException { + serverSocket = new ServerSocket(port); + } + + public void addHandler(ConnectionHandler connectionHandler) { + latestHandler++; + handlers[latestHandler] = connectionHandler; + } + + public void run() { + new Thread(() -> { + while (!isStopped()) { + try { + Socket s = serverSocket.accept(); + s.setKeepAlive(true); + for (ConnectionHandler handler : handlers) { + if (handler != null) + new Thread(() -> { + try { + handler.run(new Connection(s)); + } + catch (IOException e) { + e.printStackTrace(); + } + }).start(); + } + } + catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + } +} diff --git a/src/main/java/de/tudbut/obj/Atomic.java b/src/main/java/de/tudbut/obj/Atomic.java new file mode 100644 index 0000000..19cce90 --- /dev/null +++ b/src/main/java/de/tudbut/obj/Atomic.java @@ -0,0 +1,15 @@ +package de.tudbut.obj; + +public class Atomic implements AtomicSink { + private T object; + + public Atomic() {} + public Atomic(T object) { this.object = object; } + + public void set(T object) { this.object = object; } + + public T get() { return object; } + + @Override + public String toString() { return object.toString(); } +} diff --git a/src/main/java/de/tudbut/obj/AtomicSink.java b/src/main/java/de/tudbut/obj/AtomicSink.java new file mode 100644 index 0000000..468cd56 --- /dev/null +++ b/src/main/java/de/tudbut/obj/AtomicSink.java @@ -0,0 +1,7 @@ +package de.tudbut.obj; + +public interface AtomicSink { + + void set(T object); + T get(); +} \ No newline at end of file diff --git a/src/main/java/de/tudbut/obj/AtomicUtils.java b/src/main/java/de/tudbut/obj/AtomicUtils.java new file mode 100644 index 0000000..33290ed --- /dev/null +++ b/src/main/java/de/tudbut/obj/AtomicUtils.java @@ -0,0 +1,4 @@ +package de.tudbut.obj; + +public class AtomicUtils { +} diff --git a/src/main/java/de/tudbut/obj/CarrierException.java b/src/main/java/de/tudbut/obj/CarrierException.java new file mode 100644 index 0000000..c1ec136 --- /dev/null +++ b/src/main/java/de/tudbut/obj/CarrierException.java @@ -0,0 +1,11 @@ +package de.tudbut.obj; + +public class CarrierException extends RuntimeException { + + public final Object carried; + + public CarrierException(Exception e, Object o) { + super(e); + carried = o; + } +} diff --git a/src/main/java/de/tudbut/obj/Closable.java b/src/main/java/de/tudbut/obj/Closable.java new file mode 100644 index 0000000..74102a2 --- /dev/null +++ b/src/main/java/de/tudbut/obj/Closable.java @@ -0,0 +1,29 @@ +package de.tudbut.obj; + +import java.io.IOException; + +public interface Closable { + InstanceBoundMap vars = new InstanceBoundMap<>(); + + default void close() throws IOException { + vars.set(this, C.CLOSED, true); + } + + default void open() throws NotSupportedException { + vars.set(this, C.CLOSED, false); + } + + default boolean isClosed() { + vars.setIfNull(this, C.CLOSED, false); + return vars.get(this, C.CLOSED); + } + + default boolean isOpen() { + vars.setIfNull(this, C.CLOSED, false); + return !vars.get(this, C.CLOSED); + } + + class C { + public static final String CLOSED = "closed"; + } +} diff --git a/src/main/java/de/tudbut/obj/ClosedClosableException.java b/src/main/java/de/tudbut/obj/ClosedClosableException.java new file mode 100644 index 0000000..21a5d9f --- /dev/null +++ b/src/main/java/de/tudbut/obj/ClosedClosableException.java @@ -0,0 +1,7 @@ +package de.tudbut.obj; + +public class ClosedClosableException extends Exception { + + public ClosedClosableException() { } + public ClosedClosableException(String message) { super(message); } +} diff --git a/src/main/java/de/tudbut/obj/DoubleObject.java b/src/main/java/de/tudbut/obj/DoubleObject.java new file mode 100644 index 0000000..8cdd53b --- /dev/null +++ b/src/main/java/de/tudbut/obj/DoubleObject.java @@ -0,0 +1,33 @@ +package de.tudbut.obj; + +import de.tudbut.tools.ArrayGetter; + +public class DoubleObject { + private final T t1; + private final T t2; + + public DoubleObject(T in1, T in2) { + t1 = in1; + t2 = in2; + } + + @SafeVarargs + public final T[] get(T... ignore) { + T[] ts = ArrayGetter.newGenericArray(2, ignore); + ts[0] = t1; + ts[1] = t2; + return ts; + } + + public T get1() { + return t1; + } + + public T get2() { + return t2; + } + + public DoubleObject clone() { + return new DoubleObject<>(t1, t2); + } +} diff --git a/src/main/java/de/tudbut/obj/DoubleTypedObject.java b/src/main/java/de/tudbut/obj/DoubleTypedObject.java new file mode 100644 index 0000000..a6d76b1 --- /dev/null +++ b/src/main/java/de/tudbut/obj/DoubleTypedObject.java @@ -0,0 +1,32 @@ +package de.tudbut.obj; + +import de.tudbut.tools.ReflectUtil; + +import java.util.Objects; + +public class DoubleTypedObject implements Cloneable { + + public O o; + public T t; + + public DoubleTypedObject(O o, T t) { + this.o = o; + this.t = t; + } + public DoubleTypedObject() { + } + + @Override + public boolean equals(Object o1) { + if (this == o1) return true; + if (!(o1 instanceof DoubleTypedObject)) return false; + DoubleTypedObject that = (DoubleTypedObject) o1; + return Objects.equals(o, that.o) && Objects.equals(t, that.t); + } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public DoubleTypedObject clone() { + return new DoubleTypedObject<>(ReflectUtil.forceClone(o), ReflectUtil.forceClone(t)); + } +} diff --git a/src/main/java/de/tudbut/obj/IgnoreThrowRunnable.java b/src/main/java/de/tudbut/obj/IgnoreThrowRunnable.java new file mode 100644 index 0000000..5714393 --- /dev/null +++ b/src/main/java/de/tudbut/obj/IgnoreThrowRunnable.java @@ -0,0 +1,12 @@ +package de.tudbut.obj; + +public interface IgnoreThrowRunnable extends Runnable { + + default void run() { + try { + this.doRun(); + } catch (Throwable ignore) { } + } + + void doRun() throws Throwable; +} diff --git a/src/main/java/de/tudbut/obj/InstanceBoundMap.java b/src/main/java/de/tudbut/obj/InstanceBoundMap.java new file mode 100644 index 0000000..acc1c5c --- /dev/null +++ b/src/main/java/de/tudbut/obj/InstanceBoundMap.java @@ -0,0 +1,99 @@ +package de.tudbut.obj; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class InstanceBoundMap { + protected final HashMap> bindings = new HashMap<>(); + + public synchronized void createBindingIfNonExistent(Object bindingObject) { + if(!bindings.containsKey(bindingObject)) + bindings.put(bindingObject, new Binding<>()); + } + + public synchronized void set(Object bindingObject, K key, V value) { + createBindingIfNonExistent(bindingObject); + bindings.get(bindingObject).set(key, value); + } + + public synchronized void setIfNull(Object bindingObject, K key, V value) { + createBindingIfNonExistent(bindingObject); + if(bindings.get(bindingObject).get(key) == null) { + bindings.get(bindingObject).set(key, value); + } + } + + public V get(Object bindingObject, K key) { + return bindings.get(bindingObject).get(key); + } + + public K[] keys(Object bindingObject) { + return bindings.get(bindingObject).keys(); + } + + public V[] values(Object bindingObject) { + return bindings.get(bindingObject).values(); + } + + + protected static class Binding { + private final ArrayList entries = new ArrayList<>(); + + private void set(K key, V value) { + boolean exists = false; + for (int i = 0; i < entries.size(); i++) { + Entry entry = entries.get(i); + if (key == entry.key || entry.key.equals(key)) { + entry.val = value; + exists = true; + } + } + if(!exists) { + this.entries.add(new Entry(key, value)); + } + } + + private V get(K key) { + ArrayList entries = (ArrayList) this.entries.clone(); + for (Entry entry : entries) { + if (key == entry.key || entry.key.equals(key)) + return entry.val; + } + return null; + } + + private K[] keys() { + HashSet keys = new HashSet<>(); + for (int i = 0; i < entries.size(); i++) { + keys.add(entries.get(i).key); + } + if(keys.size() == 0) { + return (K[]) new Object[0]; + } + return keys.toArray((K[]) Array.newInstance(entries.get(0).key.getClass(), keys.size())); + } + + private V[] values() { + HashSet vals = new HashSet<>(); + for (int i = 0; i < entries.size(); i++) { + vals.add(entries.get(i).val); + } + if(vals.size() == 0) { + return (V[]) new Object[0]; + } + return vals.toArray((V[]) Array.newInstance(entries.get(0).val.getClass(), vals.size())); + } + + private class Entry { + private final K key; + private V val; + + private Entry(K key, V val) { + this.key = key; + this.val = val; + } + } + } +} diff --git a/src/main/java/de/tudbut/obj/Mappable.java b/src/main/java/de/tudbut/obj/Mappable.java new file mode 100644 index 0000000..4cb8e3d --- /dev/null +++ b/src/main/java/de/tudbut/obj/Mappable.java @@ -0,0 +1,8 @@ +package de.tudbut.obj; + +import java.util.Map; + +public interface Mappable { + + Map map(); +} diff --git a/src/main/java/de/tudbut/obj/NotSupportedException.java b/src/main/java/de/tudbut/obj/NotSupportedException.java new file mode 100644 index 0000000..577038e --- /dev/null +++ b/src/main/java/de/tudbut/obj/NotSupportedException.java @@ -0,0 +1,11 @@ +package de.tudbut.obj; + +public class NotSupportedException extends RuntimeException { + public NotSupportedException() { + super(); + } + + public NotSupportedException(String s) { + super(s); + } +} diff --git a/src/main/java/de/tudbut/obj/PAtomic.java b/src/main/java/de/tudbut/obj/PAtomic.java new file mode 100644 index 0000000..bcaed67 --- /dev/null +++ b/src/main/java/de/tudbut/obj/PAtomic.java @@ -0,0 +1,15 @@ +package de.tudbut.obj; + +public class PAtomic implements AtomicSink { + public T object; + + public PAtomic() {} + public PAtomic(T object) { this.object = object; } + + public void set(T object) { this.object = object; } + + public T get() { return object; } + + @Override + public String toString() { return object.toString(); } +} diff --git a/src/main/java/de/tudbut/obj/Partial.java b/src/main/java/de/tudbut/obj/Partial.java new file mode 100644 index 0000000..fba30a2 --- /dev/null +++ b/src/main/java/de/tudbut/obj/Partial.java @@ -0,0 +1,60 @@ +package de.tudbut.obj; + +import de.tudbut.tools.ArrayGetter; + +import java.util.ArrayList; + +public class Partial { + private T current; + private final ArrayList> listeners = new ArrayList<>(); + private boolean completed = false; + + public Partial(T original) { + current = original; + } + + @SafeVarargs + public final T change(T changed, Listener... ignore) { + if(completed) + return null; + + Listener[] listeners = ArrayGetter.newGenericArray(this.listeners.size(), ignore); + for (int i = 0; i < listeners.length; i++) { + listeners[i] = this.listeners.get(i); + } + for (Listener listener : listeners) { + listener.onChange(current, changed); + } + current = changed; + return changed; + } + + @SafeVarargs + public final void complete(T completedValue, Listener... ignore) { + completed = true; + current = completedValue; + Listener[] listeners = ArrayGetter.newGenericArray(this.listeners.size(), ignore); + for (int i = 0; i < listeners.length; i++) { + listeners[i] = this.listeners.get(i); + } + for (Listener listener : listeners) { + listener.onComplete(current); + } + } + + public T get() { + return current; + } + + public void addChangeListener(Listener listener) { + listeners.add(listener); + } + + public interface Listener { + default void onChange(T original, T changed) { + + } + + void onComplete(T completed); + } +} diff --git a/src/main/java/de/tudbut/obj/PrintThrowRunnable.java b/src/main/java/de/tudbut/obj/PrintThrowRunnable.java new file mode 100644 index 0000000..357063c --- /dev/null +++ b/src/main/java/de/tudbut/obj/PrintThrowRunnable.java @@ -0,0 +1,14 @@ +package de.tudbut.obj; + +public interface PrintThrowRunnable extends Runnable { + + default void run() { + try { + this.doRun(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } + + void doRun() throws Throwable; +} diff --git a/src/main/java/de/tudbut/obj/RayCast.java b/src/main/java/de/tudbut/obj/RayCast.java new file mode 100644 index 0000000..f9dc79b --- /dev/null +++ b/src/main/java/de/tudbut/obj/RayCast.java @@ -0,0 +1,39 @@ +package de.tudbut.obj; + +import de.tudbut.rendering.Maths3D; +import de.tudbut.rendering.Rectangle3D; +import de.tudbut.type.Vector3d; + +public class RayCast { + + private final Vector3d step = new Vector3d(0, 0, -1); + private final Vector3d origin; + + public RayCast(Vector3d origin, Vector3d step) { + this.origin = origin; + this.step.set(step); + } + + public boolean hits(Rectangle3D rectangle, int maxSteps) { + Vector3d pos = origin.clone(); + for (int i = 0; i < maxSteps; i++) { + if(intersects(rectangle, new Rectangle3D(origin, pos.clone().negate().add(origin).negate()))) + return true; + pos.add(step); + } + return intersects(rectangle, new Rectangle3D(origin, pos.clone().negate().add(origin).negate())); + } + + private boolean intersects(Rectangle3D rectangle, Rectangle3D castRectangle) { + rectangle.sort(); + castRectangle.sort(); + boolean[] relation = Maths3D.getRelation(rectangle, castRectangle); + return + !relation[0] && + !relation[1] && + !relation[2] && + !relation[3] && + !relation[4] && + !relation[5] ; + } +} diff --git a/src/main/java/de/tudbut/obj/RelativeVector2d.java b/src/main/java/de/tudbut/obj/RelativeVector2d.java new file mode 100644 index 0000000..22c30a3 --- /dev/null +++ b/src/main/java/de/tudbut/obj/RelativeVector2d.java @@ -0,0 +1,48 @@ +package de.tudbut.obj; + +import de.tudbut.type.Vector2d; + +public class RelativeVector2d extends Vector2d { + private final Vector2d relativeTo = new Vector2d(0,0); + + public RelativeVector2d(Vector2d relativeTo, double x, double y) { + super(relativeTo.getX() + x, relativeTo.getY() + y); + this.relativeTo.set(relativeTo); + } + + public RelativeVector2d(Vector2d relativeTo, Vector2d relative) { + super(relativeTo.getX() + relative.getX(), relativeTo.getY() + relative.getY()); + this.relativeTo.set(relativeTo); + } + + @Override + public Vector2d set(double x, double y) { + Vector2d rpos = getRelativePos(); + relativeTo.set(x - rpos.getX(), y - rpos.getY()); + return super.set(x, y); + } + + public Vector2d setSize(double x, double y) { + return super.set(relativeTo.getX() + x, relativeTo.getY() + y); + } + + public Vector2d getRelativeTo() { + return relativeTo.clone(); + } + + public Vector2d getRelativePos() { + return new Vector2d(getX() - relativeTo.getX(), getY() - relativeTo.getY()); + } + + public double getRX() { + return getRelativePos().getX(); + } + + public double getRY() { + return getRelativePos().getY(); + } + + public RelativeVector2d clone() { + return new RelativeVector2d(relativeTo, getRelativePos()); + } +} diff --git a/src/main/java/de/tudbut/obj/RelativeVector3d.java b/src/main/java/de/tudbut/obj/RelativeVector3d.java new file mode 100644 index 0000000..8b3d624 --- /dev/null +++ b/src/main/java/de/tudbut/obj/RelativeVector3d.java @@ -0,0 +1,53 @@ +package de.tudbut.obj; + +import de.tudbut.type.Vector3d; + +public class RelativeVector3d extends Vector3d { + private final Vector3d relativeTo = new Vector3d(0,0, 0); + + public RelativeVector3d(Vector3d relativeTo, double x, double y, double z) { + super(relativeTo.getX() + x, relativeTo.getY() + y, relativeTo.getZ() + z); + this.relativeTo.set(relativeTo); + } + + public RelativeVector3d(Vector3d relativeTo, Vector3d relative) { + super(relativeTo.getX() + relative.getX(), relativeTo.getY() + relative.getY(), relativeTo.getZ() + relative.getZ()); + this.relativeTo.set(relativeTo); + } + + @Override + public Vector3d set(double x, double y, double z) { + Vector3d rpos = getRelativePos(); + relativeTo.set(x - rpos.getX(), y - rpos.getY(), z - rpos.getZ()); + return super.set(x, y, z); + } + + public Vector3d setSize(double x, double y, double z) { + super.set(relativeTo.getX() + x, relativeTo.getY() + y, relativeTo.getZ()); + return this; + } + + public Vector3d getRelativeTo() { + return relativeTo.clone(); + } + + public Vector3d getRelativePos() { + return new Vector3d(getX() - relativeTo.getX(), getY() - relativeTo.getY(), getZ() - relativeTo.getZ()); + } + + public double getRX() { + return getRelativePos().getX(); + } + + public double getRY() { + return getRelativePos().getY(); + } + + public double getRZ() { + return getRelativePos().getZ(); + } + + public RelativeVector3d clone() { + return new RelativeVector3d(relativeTo, getRelativePos()); + } +} diff --git a/src/main/java/de/tudbut/obj/Save.java b/src/main/java/de/tudbut/obj/Save.java new file mode 100644 index 0000000..b6f571c --- /dev/null +++ b/src/main/java/de/tudbut/obj/Save.java @@ -0,0 +1,11 @@ +package de.tudbut.obj; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Save { +} diff --git a/src/main/java/de/tudbut/obj/TLClassLoader.java b/src/main/java/de/tudbut/obj/TLClassLoader.java new file mode 100644 index 0000000..0683826 --- /dev/null +++ b/src/main/java/de/tudbut/obj/TLClassLoader.java @@ -0,0 +1,9 @@ +package de.tudbut.obj; + +public interface TLClassLoader { + Class lc(String s) throws ClassNotFoundException; + + Class get(String s) throws ClassNotFoundException; + + Class friendlyGet(String s); +} diff --git a/src/main/java/de/tudbut/obj/TLMap.java b/src/main/java/de/tudbut/obj/TLMap.java new file mode 100644 index 0000000..5b80fb7 --- /dev/null +++ b/src/main/java/de/tudbut/obj/TLMap.java @@ -0,0 +1,199 @@ +package de.tudbut.obj; + +import de.tudbut.tools.Retriever; + +import java.util.*; + +public class TLMap { + protected Binding binding = new Binding<>(); + + public static TLMap stringToMap(String mapStringParsable) { + TLMap map = new TLMap<>(); + + String[] splitTiles = mapStringParsable.split(";"); + for (int i = 0; i < splitTiles.length; i++) { + String tile = splitTiles[i]; + String[] splitTile = tile.split(":"); + if (tile.contains(":")) { + if (splitTile.length == 2) + map.set( + splitTile[0].replaceAll("%I", ":").replaceAll("%B", ";").replaceAll("%P", "%"), + splitTile[1].equals("%N") ? null : splitTile[1].replaceAll("%I", ":").replaceAll("%B", ";").replaceAll("%P", "%") + ); + else + map.set(splitTile[0].replaceAll("%I", ":").replaceAll("%B", ";").replaceAll("%P", "%"), ""); + } + } + + return map; + } + + public static String mapToString(TLMap map) { + StringBuilder r = new StringBuilder(); + + for (String key : map.keys().toArray(new String[0])) { + + r + .append(key.replaceAll("%", "%P").replaceAll(";", "%B").replaceAll(":", "%I")) + .append(":") + .append(map.get(key) == null ? "%N" : map.get(key).replaceAll("%", "%P").replaceAll(";", "%B").replaceAll(":", "%I")) + .append(";") + ; + } + + return r.toString(); + } + + public void set(K key, V value) { + binding.set(key, value); + } + + public void setIfNull(K key, V value) { + if(binding.get(key) == null) { + binding.set(key, value); + } + } + + public V get(K key) { + return binding.get(key); + } + + public V get(K key, V def) { + V v = binding.get(key); + return v == null ? def : v; + } + public V get(K key, Retriever def) { + V v = binding.get(key); + return v == null ? def.retrieve() : v; + } + + public Set keys() { + return binding.keys(); + } + + public int size() { + return binding.size(); + } + + public Set values() { + return binding.values(); + } + + public ArrayList> entries() { + return binding.entries(); + } + + public TLMap flip() { + TLMap map = new TLMap<>(); + map.binding = binding.flip(); + return map; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TLMap tlMap = (TLMap) o; + return tlMap.binding.entries.equals(binding.entries); + } + + protected static class Binding { + protected ArrayList> entries = new ArrayList<>(); + + protected void set(K key, V value) { + boolean exists = false; + for (int i = 0; i < entries.size(); i++) { + Entry entry = entries.get(i); + if (key == entry.key || entry.key.equals(key)) { + exists = true; + if(value == null) { + entries.remove(i); + break; + } + entry.val = value; + } + } + if(!exists && value != null && key != null) { + this.entries.add(new Entry<>(key, value)); + } + } + + protected V get(K key) { + ArrayList> entries = (ArrayList>) this.entries.clone(); + for (Entry entry : entries) { + if (key == entry.key || entry.key.equals(key)) + return entry.val; + } + return null; + } + + protected Set keys() { + Set keys = new LinkedHashSet<>(); + for (int i = 0; i < entries.size(); i++) { + keys.add(entries.get(i).key); + } + return keys; + } + + protected Set values() { + Set vals = new LinkedHashSet<>(); + for (int i = 0; i < entries.size(); i++) { + vals.add(entries.get(i).val); + } + return vals; + } + + protected ArrayList> entries() { + ArrayList> vals = new ArrayList<>(); + vals.addAll(entries); + return vals; + } + + protected Binding flip() { + Binding binding = new Binding<>(); + for (int i = 0 ; i < entries.size() ; i++) { + Entry entry = entries.get(i); + binding.entries.add(new Entry<>(entry.val, entry.key)); + } + return binding; + } + + public int size() { + return entries.size(); + } + } + + public static class Entry { + public K key; + public V val; + + protected Entry() { + } + + protected Entry(K key, V val) { + this.key = key; + this.val = val; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Entry entry = (Entry) o; + if (!Objects.equals(key, entry.key)) return false; + return Objects.equals(val, entry.val); + } + } + + @Override + public TLMap clone() { + TLMap n = new TLMap<>(); + Object[] keys = keys().toArray(); + Object[] vals = values().toArray(); + for (int i = 0, arrayLength = keys.length ; i < arrayLength ; i++) { + Object key = keys[i]; + n.set((K)key, (V)vals[i]); + } + return n; + } +} diff --git a/src/main/java/de/tudbut/obj/Transient.java b/src/main/java/de/tudbut/obj/Transient.java new file mode 100644 index 0000000..bdacfbf --- /dev/null +++ b/src/main/java/de/tudbut/obj/Transient.java @@ -0,0 +1,11 @@ +package de.tudbut.obj; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Transient { +} diff --git a/src/main/java/de/tudbut/obj/TypedArray.java b/src/main/java/de/tudbut/obj/TypedArray.java new file mode 100644 index 0000000..353f189 --- /dev/null +++ b/src/main/java/de/tudbut/obj/TypedArray.java @@ -0,0 +1,118 @@ +package de.tudbut.obj; + +import de.tudbut.tools.Tools; + +import java.lang.reflect.Array; +import java.util.Arrays; + +public class TypedArray { + + private Class tClass = null; + private boolean locked = false; + protected final Object synchronizer = new Object(); + + protected T[] ts; + + public TypedArray(int length) { + ts = (T[]) new Object[length]; + } + + public TypedArray(T[] ts) { + tClass = ts.getClass().getComponentType(); + if(ts.length != 0) + tClass = ts[0].getClass(); + this.ts = ts; + } + + public TypedArray(int length, T[] ts) { + T[] oTs = ts; + ts = (T[]) new Object[length]; + Tools.copyArray(oTs, ts, Math.min(ts.length, oTs.length)); + tClass = ts.getClass().getComponentType(); + if(ts.length != 0 && ts[0] != null) + tClass = ts[0].getClass(); + this.ts = ts; + + } + + public T get(int i) throws ArrayIndexOutOfBoundsException { + synchronized (synchronizer) { + return ((T[]) ts)[i]; + } + } + + public T set(int i, T t) throws ArrayIndexOutOfBoundsException { + synchronized (synchronizer) { + checkLocked(); + return ts[i] = t; + } + } + + public String toString() { + synchronized (synchronizer) { + return Arrays.toString(ts); + } + } + + public T[] toArray(T... type) { + synchronized (synchronizer) { + tClass = type.getClass().getComponentType(); + T[] nts = (T[]) Array.newInstance(tClass, ts.length); + Tools.copyArray(ts, nts, nts.length); + ts = nts; + return ts; + } + } + + public int length() { + synchronized (synchronizer) { + return ts.length; + } + } + + public TypedArray lock() { + locked = true; + return this; + } + + public boolean isLocked() { + return locked; + } + + protected void checkLocked() throws IllegalStateException { + if(isLocked()) + throw new IllegalStateException("TypedArray is locked"); + } + + @Override + public TypedArray clone() { + return new TypedArray<>(ts); + } + + public boolean equals(Object o) { + return o instanceof TypedArray && (Arrays.equals(((TypedArray) o).ts, ts)); + } + + + @SafeVarargs + public static T[] convertToArray(T... ts) { + return new TypedArray<>(ts).toArray(); + } + + public static Object[] convertToObjectArray(Object... objects) { + return new TypedArray<>(objects).toArray(); + } + + @SafeVarargs + public static TypedArray convert(T... ts) { + return new TypedArray<>(ts); + } + + public static TypedArray convertObjects(Object... objects) { + return new TypedArray<>(objects); + } + + public static String string(Object... objects) { + return Arrays.toString(objects); + } +} diff --git a/src/main/java/de/tudbut/obj/TypedList.java b/src/main/java/de/tudbut/obj/TypedList.java new file mode 100644 index 0000000..269d5bd --- /dev/null +++ b/src/main/java/de/tudbut/obj/TypedList.java @@ -0,0 +1,138 @@ +package de.tudbut.obj; + +import de.tudbut.tools.Tools; + +import java.util.List; + +public class TypedList extends TypedArray { + public TypedList() { + super(0); + } + + public TypedList(T[] ts) { + super(ts); + } + + public static TypedList get(TypedArray typedArray) { + TypedList list = new TypedList<>(typedArray.toArray()); + if(typedArray.isLocked()) + list.lock(); + return list; + } + + public synchronized T[] add(T[] t) { + synchronized (synchronizer) { + checkLocked(); + T[] n = new TypedArray<>(length() + t.length, ts).toArray(); + for (int i = ts.length, j = 0; j < t.length; i++, j++) { + n[i] = t[j]; + } + ts = n; + return t; + } + } + + public synchronized TypedArray add(TypedArray t) { + synchronized (synchronizer) { + add(t.ts); + return t; + } + } + + public synchronized List add(List t) { + synchronized (synchronizer) { + add(t.toArray(ts)); + return t; + } + } + + public synchronized T add(T t) { + synchronized (synchronizer) { + checkLocked(); + T[] n = new TypedArray<>(length() + 1, ts).toArray(); + n[n.length - 1] = t; + ts = n; + return t; + } + } + + public synchronized T add(int i, T t) { + synchronized (synchronizer) { + checkLocked(); + if (i == ts.length - 1) + return add(t); + + T[] n = new TypedArray<>(length() + 1, ts).toArray(); + int tPos = 0; + for (int j = 0; j < n.length; j++) { + if (j == i) { + n[j] = t; + j++; + continue; + } + n[j] = ts[tPos]; + tPos++; + } + ts = n; + return t; + } + } + + public synchronized T pop(int i) { + synchronized (synchronizer) { + checkLocked(); + int aP = 0; + boolean removed = false; + T t = null; + for (int j = 0; j < ts.length; j++) { + if (j == i && !removed) { + t = ts[j]; + removed = true; + continue; + } + ts[aP] = ts[j]; + aP++; + } + decrementSize(); + return t; + } + } + + public synchronized T pop() { + synchronized (synchronizer) { + checkLocked(); + T t = ts[ts.length - 1]; + decrementSize(); + return t; + } + } + + public synchronized void remove(int i) { + synchronized (synchronizer) { + checkLocked(); + pop(i); + } + } + + public synchronized void removeLast() { + synchronized (synchronizer) { + checkLocked(); + pop(); + } + } + + private synchronized void decrementSize() { + checkLocked(); + ts = new TypedArray<>(length() - 1, ts).toArray(); + } + + public T random() { + synchronized (synchronizer) { + return Tools.randomOutOfArray(this); + } + } + + public TypedList clone() { + return new TypedList<>(ts); + } +} diff --git a/src/main/java/de/tudbut/obj/Vector2i.java b/src/main/java/de/tudbut/obj/Vector2i.java new file mode 100644 index 0000000..c28b049 --- /dev/null +++ b/src/main/java/de/tudbut/obj/Vector2i.java @@ -0,0 +1,104 @@ +package de.tudbut.obj; + +import de.tudbut.tools.Tools; +import de.tudbut.type.Vector2d; + +import java.util.Map; +import java.util.Objects; + +public class Vector2i implements Mappable { + private int x, y; + + public Vector2i(int x, int y) { + this.x = x; + this.y = y; + } + + public void set(int x, int y) { + this.x = x; + this.y = y; + } + + public void set(Vector2i vec) { + this.x = vec.x; + this.y = vec.y; + } + + public Vector2i add(Vector2i vec) { + set(x+vec.x, y+vec.y); + return this; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } + + @Override + public Vector2i clone() { + return new Vector2i(x, y); + } + + public Vector2i multiply(double m) { + set((int) (x * m), (int) (y * m)); + return this; + } + + public Vector2i multiply(double mx, double my) { + set((int) (x * mx), (int) (y * my)); + return this; + } + + public Vector2i multiply(Vector2d vec) { + set((int) (x * vec.getX()), (int) (y * vec.getY())); + return this; + } + + + public Vector2i negate() { + set(-x, -y); + return this; + } + + public String toString() { + return "x:" + x + ";y:" + y; + } + + @Override + public Map map() { + return Tools.stringToMap("x:" + x + ";y:" + y); + } + + public static Vector2i fromMap(Map map) { + return + new Vector2i( + Integer.parseInt(map.get("x")), + Integer.parseInt(map.get("y")) + ) + ; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Vector2i)) return false; + Vector2i vector2i = (Vector2i) o; + return x == vector2i.x && y == vector2i.y; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } +} diff --git a/src/main/java/de/tudbut/parsing/AddressedTCN.java b/src/main/java/de/tudbut/parsing/AddressedTCN.java new file mode 100644 index 0000000..6be94c2 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/AddressedTCN.java @@ -0,0 +1,88 @@ +package de.tudbut.parsing; + +import de.tudbut.obj.TLMap; + +import java.util.ArrayList; + +public class AddressedTCN { + + public static TCN addressedToNormal(TCN db) { + TCN tcn = new TCN(); + ArrayList keys = new ArrayList<>(db.map.keys()); + TLMap objects = new TLMap<>(); + for (int i = 0 ; i < keys.size() ; i++) { + if(keys.get(i).startsWith("_")) { + Object o = db.get(keys.get(i)); + objects.set(keys.get(i).substring(1), o); + } + } + reverse(objects); + tcn.map = db.getSub("_" + db.getInteger("main")).map; + return tcn; + } + + private static void reverse(TLMap objects) { + for (Object v : objects.values()) { + if(v instanceof TCN) { + TCN tcn = (TCN) v; + for (String key : tcn.map.keys()) { + int i = tcn.getInteger(key); + tcn.set(key, objects.get(String.valueOf(i))); + } + } + if(v instanceof TCNArray) { + TCNArray tcn = (TCNArray) v; + for (int l = 0 ; l < tcn.size() ; l++) { + int i = tcn.getInteger(l); + tcn.set(l, objects.get(String.valueOf(i))); + } + } + } + } + + public static TCN normalToAddressed(TCN tcn) { + TCN db = new TCN(); + ArrayList allObjects = new ArrayList<>(); + TCN theTCN = new TCN(); + theTCN.set("main", tcn); + recursiveScan(allObjects, db, theTCN); + db.set("main", 0); + for (String key : db.map.keys()) { + TCN.deepConvert(key, db.get(key), db); + } + return db; + } + + private static void recursiveScan(ArrayList objects, TCN main, TCN tcn) { + for (String key : tcn.map.keys()) { + if(tcn.get(key) instanceof TCNArray) + add(objects, main, key, ((TCNArray) tcn.get(key)).toTCN()); + else + add(objects, main, key, tcn.get(key)); + } + } + + private static Integer add(ArrayList objects, TCN main, String key, Object o) { + if(o instanceof TCN) { + if(!objects.contains(o)) { + int i = objects.size(); + objects.add(o); + TCN tcn = ((TCN) o).isArray ? new TCNArray().toTCN() : new TCN(); + recursiveScan(objects, main, (TCN) o); + for (String theKey : ((TCN) o).map.keys()) { + tcn.set(theKey, add(objects, main, theKey, ((TCN) o).get(theKey))); + } + main.set("_" + i, tcn); + return i; + } + return objects.indexOf(o); + } + if(!objects.contains(o)) { + int i = objects.size(); + objects.add(o); + main.set("_" + i, o); + return i; + } + return objects.indexOf(o); + } +} diff --git a/src/main/java/de/tudbut/parsing/ArgumentParser.java b/src/main/java/de/tudbut/parsing/ArgumentParser.java new file mode 100644 index 0000000..e5ad7d9 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/ArgumentParser.java @@ -0,0 +1,74 @@ +package de.tudbut.parsing; + +import java.util.HashMap; +import java.util.Map; + +public class ArgumentParser { + public static Map parseDefault(String[] args) { + Map map = new HashMap<>(); + for (int i = 0; i < args.length; i++) { + try { + // -a | -abc &! -a bc + if (args[i].startsWith("-") && args[i].charAt(1) != '-' && (args.length == i + 1 || args[i + 1].startsWith("-"))) { + for (int j = 1; j < args[i].length(); j++) { + map.put(String.valueOf(args[i].toCharArray()[j]), "true"); + } + } + + // -a bc + if (args[i].startsWith("-") && args[i].charAt(1) != '-' && args.length != i + 1 && !args[i + 1].startsWith("-")) { + map.put(String.valueOf(args[i].charAt(1)), args[i + 1]); + i++; + } + + // --abc &! -abc de + if (args[i].startsWith("-") && args[i].charAt(1) == '-' && (args.length == i + 1 || args[i + 1].startsWith("-"))) { + map.put(args[i].substring(2), "true"); + } + + // --abc de + if (args[i].startsWith("-") && args[i].charAt(1) == '-' && args.length != i + 1 && !args[i + 1].startsWith("-")) { + map.put(args[i].substring(2), args[i + 1]); + i++; + } + } + catch (Exception ignore) { + } + } + return map; + } + + public static Map parseSlash(String[] args) { + Map map = new HashMap<>(); + for (int i = 0; i < args.length; i++) { + try { + // -a | -abc &! -a bc + if (args[i].startsWith("/") && args[i].charAt(1) != '/' && (args.length == i + 1 || args[i + 1].startsWith("/"))) { + for (int j = 1; j < args[i].length(); j++) { + map.put(String.valueOf(args[i].toCharArray()[j]), "true"); + } + } + + // -a bc + if (args[i].startsWith("/") && args[i].charAt(1) != '/' && args.length != i + 1 && !args[i + 1].startsWith("/")) { + map.put(String.valueOf(args[i].charAt(1)), args[i + 1]); + i++; + } + + // --abc &! --abc de + if (args[i].startsWith("/") && args[i].charAt(1) == '/' && (args.length == i + 1 || args[i + 1].startsWith("/"))) { + map.put(args[i].substring(2), "true"); + } + + // --abc de + if (args[i].startsWith("/") && args[i].charAt(1) == '/' && args.length != i + 1 && !args[i + 1].startsWith("/")) { + map.put(args[i].substring(2), args[i + 1]); + i++; + } + } + catch (Exception ignore) { + } + } + return map; + } +} diff --git a/src/main/java/de/tudbut/parsing/AsyncJSON.java b/src/main/java/de/tudbut/parsing/AsyncJSON.java new file mode 100644 index 0000000..a36fd65 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/AsyncJSON.java @@ -0,0 +1,437 @@ +package de.tudbut.parsing; + +import de.tudbut.async.Task; +import de.tudbut.tools.Stack; +import de.tudbut.tools.StringTools; + +import java.util.ArrayList; + +/** + * Interconverting JSON and TCN + */ +public class AsyncJSON { + + /** + * Converts a JSON string to a TCN object + * @param str The JSON string, supports most compact and readable formats + * @return The parsed TCN object + */ + public static Task read(String str) { + return new Task<>((gres, grej) -> { + String string = str; + + while (string.startsWith(" ")) { + string = string.substring(1); + } + if (!string.startsWith("{") && !string.startsWith("[")) { + grej.call(new JSON.JSONFormatException("Expected: { or [ at 0 (String is '" + string + "')")); + return; + } + boolean array = string.startsWith("["); + TCN tcn = new TCN("AJSON", array); + final boolean[] escape = { false }; + final int[] pos = { 1 }; + char[] a = string.toCharArray(); + final char[] c = { a[pos[0]] }; + final int[] arrayPos = { 0 }; + final boolean[] inString = { false }; + final boolean[] startString = { false }; + final StringBuilder[] theString = { new StringBuilder() }; + final boolean[] kv = { false }; + final String[] key = { "" }; + final boolean[] inStringKV = { false }; + final boolean[] inObjectKV = { false }; + final TCN[] sub = { null }; + + try { + while (inString[0] || (c[0] != '}' && c[0] != ']')) { + new Task((res, rej) -> { + if (array) { + kv[0] = true; + } + + if (startString[0]) { + inString[0] = true; + startString[0] = false; + } + if (c[0] == '\\') { + escape[0] = !escape[0]; + } + if (!escape[0] && c[0] == '"') { + startString[0] = !inString[0]; + if (startString[0]) { + if (kv[0]) + inStringKV[0] = true; + theString[0] = new StringBuilder(); + } + else { + inString[0] = false; + if (!kv[0]) { // Key + key[0] = theString[0].toString(); + } + } + } + if (inString[0]) { + if (!escape[0]) + theString[0].append(c[0]); + else { + a: + { + // Make escapes work + if (c[0] == 'n') + theString[0].append('\n'); + if (c[0] == 'r') + theString[0].append('\r'); + if (c[0] == 'u') { + String e = ""; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 16)); + break a; + } + if (c[0] == '0') { + String e = ""; + e += c[0] = a[pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 8)); + break a; + } + if (c[0] == '1') { + String e = ""; + e += c[0] = a[pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 8)); + break a; + } + if (c[0] == '2') { + String e = ""; + e += c[0] = a[pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 8)); + break a; + } + if (c[0] == '3') { + String e = ""; + e += c[0] = a[pos[0]]; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 8)); + break a; + } + if (c[0] == 'x') { + String e = ""; + e += c[0] = a[++pos[0]]; + e += c[0] = a[++pos[0]]; + theString[0].append((char) Integer.parseInt(e, 16)); + break a; + } + if (c[0] == '"') { + theString[0].append("\""); + } + } + } + } + + // Booleans, ints, etc + else if (kv[0] && !startString[0] && !inStringKV[0] && c[0] != ',' && (Character.isLetterOrDigit(c[0]) || c[0] == '.' || c[0] == '-')) { + theString[0].append(c[0]); + } + + // SubObjects + if (!inString[0] && c[0] == '{') { + inObjectKV[0] = true; + escape[0] = false; + theString[0] = new StringBuilder("{"); + int layer = 1; + while (layer > 0) { + c[0] = a[++pos[0]]; + theString[0].append(c[0]); + if (c[0] == '{' && !inString[0]) { + layer++; + } + if (c[0] == '}' && !inString[0]) { + layer--; + } + + if (c[0] == '\\') { + escape[0] = !escape[0]; + } + if (c[0] == '\"' && !escape[0]) { + inString[0] = !inString[0]; + } + if (c[0] != '\\') { + escape[0] = false; + } + } + theString[0].append("}"); + sub[0] = read(theString[0].toString()).err(rej).ok().await(); + theString[0] = new StringBuilder(); + } + // Arrays + if (!inString[0] && c[0] == '[') { + inObjectKV[0] = true; + escape[0] = false; + theString[0] = new StringBuilder("["); + int layer = 1; + while (layer != 0) { + c[0] = a[++pos[0]]; + theString[0].append(c[0]); + if (c[0] == '[' && !inString[0]) { + layer++; + } + if (c[0] == ']' && !inString[0]) { + layer--; + } + + if (c[0] == '\\') { + escape[0] = !escape[0]; + } + if (c[0] == '\"' && !escape[0]) { + inString[0] = !inString[0]; + } + if (c[0] != '\\') { + escape[0] = false; + } + } + theString[0].append(']'); + sub[0] = read(theString[0].toString()).err(rej).ok().await(); + theString[0] = new StringBuilder(); + } + + + if (array) { + kv[0] = true; + } + // Key vs Value parsing + if (!inString[0] && c[0] == ':') { + theString[0] = new StringBuilder(); + if (!kv[0]) + kv[0] = true; + else { + rej.call(new JSON.JSONFormatException("Unexpected: '" + c[0] + "' at " + pos[0] + " - Should be ','")); + return; + } + } + if (!inString[0] && c[0] == ',') { + if (array) + key[0] = String.valueOf(arrayPos[0]++); + + if (inObjectKV[0]) { + tcn.set(key[0], sub[0]); + } + else if (inStringKV[0] || !theString[0].toString().equals("null")) { + tcn.set(key[0], theString[0].toString()); + } + inObjectKV[0] = false; + inStringKV[0] = false; + theString[0] = new StringBuilder(); + if (kv[0]) + kv[0] = false; + else { + rej.call(new JSON.JSONFormatException("Unexpected: '" + c[0] + "' at " + pos[0] + " - Should be ':'")); + return; + } + + } + + if (c[0] != '\\') { + escape[0] = false; + } + + c[0] = a[++pos[0]]; + res.call(null); + }).err(grej).ok().await(); + } + if (kv[0]) { + if (array) + key[0] = String.valueOf(arrayPos[0]); + if (inObjectKV[0]) + tcn.set(key[0], sub[0]); + else if (inStringKV[0] || !theString[0].toString().equals("null")) + tcn.set(key[0], theString[0].toString()); + } + + + for (String theKey : tcn.map.keys()) { + TCN.deepConvert(theKey, tcn.get(theKey), tcn); + } + } + catch (Throwable e) { + grej.call(new JSON.JSONFormatException("At " + pos[0] + " in " + string + " (Debug: " + inString[0] + " " + kv[0] + " " + theString[0] + " " + key[0] + " " + array + ")", e)); + } + gres.call(tcn); + }); + } + + private static String indent(boolean b, int i, int il) { + if(b) + return StringTools.multiply(StringTools.multiply(" ", il), i); + else + return ""; + } + + /** + * Converts a TCN object to a JSON string, uses the most compact form + * @param tcn The TCN to write to JSON + * @return The JSON string + */ + public static Task write(TCN tcn) { + return write(tcn, false, false, 0); + } + + /** + * Converts a TCN object to a JSON string + * @param tcn The TCN to write to JSON + * @param spaces If the JSON string should have spaces after : and , + * @return The JSON string + */ + public static Task write(TCN tcn, boolean spaces) { + return write(tcn, false, spaces, 0); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with selected indent and without spaces + * @param tcn The TCN to write to JSON + * @param indent The indent to use + * @return The JSON string + */ + public static Task write(TCN tcn, int indent) { + return write(tcn, true, false, indent); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with an indent of 2 and spaces + * @param tcn The TCN to write to JSON + * @return The JSON string + */ + public static Task writeReadable(TCN tcn) { + return write(tcn, true, true, 2); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with selected indent and spaces + * @param tcn The TCN to write to JSON + * @param indent The indent to use + * @return The JSON string + */ + public static Task writeReadable(TCN tcn, int indent) { + return write(tcn, true, true, indent); + } + + /** + * Converts a TCN object to a JSON string + * @param tcn The TCN to write to JSON + * @param newlines If the JSON string should have newlines and indents + * @param spaces If the JSON string should have spaces after : and , + * @param indentLength The indent to use + * @return The JSON string + */ + public static Task write(TCN tcn, boolean newlines, boolean spaces, int indentLength) { + return new Task<>((gres, grej) -> { + StringBuilder s = new StringBuilder(); + s.append(tcn.isArray ? "[" : "{").append(newlines ? "\n" : ""); + final int[] i = { 1 }; + + ArrayList> paths = new ArrayList<>(); + Stack tcnStack = new Stack<>(); + Stack path = new Stack<>(); + tcnStack.add(tcn); + path.add(""); + while (tcnStack.size() > 0) { + final boolean[] b = { false }; + for (String key : tcnStack.peek().map.keys()) { + new Task((res, rej) -> { + Object o = tcnStack.peek().map.get(key); + + if (o == null) { + res.call(null); + return; + } + + String k = key.replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\"", "\\\\\""); + if (o.getClass() == TCN.class) { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + TCN theTCN = tcnStack.peek(); + tcnStack.add((TCN) o); + if (theTCN.isArray) { + s.append(indent(newlines, i[0], indentLength)).append("{").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i[0], indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append("{").append(newlines ? "\n" : ""); + b[0] = true; + i[0]++; + } + else + path.next(); + } + else if (o.getClass() == TCNArray.class) { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + TCN theTCN = tcnStack.peek(); + tcnStack.add(((TCNArray) o).toTCN()); + if (theTCN.isArray) { + s.append(indent(newlines, i[0], indentLength)).append("[").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i[0], indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append("[").append(newlines ? "\n" : ""); + i[0]++; + b[0] = true; + } + else + path.next(); + } + else if (o instanceof String) { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + String val = o.toString().replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\r", "\\\\r").replaceAll("\"", "\\\\\""); + if (tcnStack.peek().isArray) { + s.append(indent(newlines, i[0], indentLength)).append("\"").append(val).append("\",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i[0], indentLength)).append("\"").append(k).append("\":").append(spaces ? " \"" : "\"").append(val).append("\",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + b[0] = true; + } + path.next(); + } + else { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + String val = o.toString(); + if (tcnStack.peek().isArray) { + s.append(indent(newlines, i[0], indentLength)).append(val).append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i[0], indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append(val).append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + b[0] = true; + } + path.next(); + } + res.call(null); + }).err(grej).ok().await(); + } + if (!b[0]) { + TCN theTCN = tcnStack.next(); + path.next(); + i[0]--; + if (theTCN.map.keys().isEmpty()) + s.append(",").append(spaces ? " " : ""); + s.delete(s.length() - ((newlines ? 2 : 1) + (spaces ? 1 : 0)), s.length()); + s.append(newlines ? "\n" : "").append(indent(newlines, i[0], indentLength)).append(theTCN.isArray ? "]" : "}").append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + } + s.delete(s.length() - ((newlines ? 2 : 1) + (spaces ? 1 : 0)), s.length()); + gres.call(s.toString()); + }); + } + +} diff --git a/src/main/java/de/tudbut/parsing/JSON.java b/src/main/java/de/tudbut/parsing/JSON.java new file mode 100644 index 0000000..e0f5e48 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/JSON.java @@ -0,0 +1,424 @@ +package de.tudbut.parsing; + +import de.tudbut.tools.Stack; +import de.tudbut.tools.StringTools; + +import java.util.ArrayList; + +/** + * Interconverting JSON and TCN + */ +public class JSON { + + /** + * Converts a JSON string to a TCN object + * @param string The JSON string, supports most compact and readable formats + * @return The parsed TCN object + * @throws JSONFormatException If a format error is found + */ + public static TCN read(String string) throws JSONFormatException { + while (string.startsWith(" ")) { + string = string.substring(1); + } + if(!string.startsWith("{") && !string.startsWith("[")) { + throw new JSONFormatException("Expected: { or [ at 0 (String is '" + string + "')"); + } + boolean array = string.startsWith("["); + TCN tcn = new TCN("JSON", array); + boolean escape = false; + int pos = 1; + char[] a = string.toCharArray(); + char c = a[pos]; + int arrayPos = 0; + boolean inString = false; + boolean startString = false; + StringBuilder theString = new StringBuilder(); + boolean kv = false; + String key = ""; + boolean inStringKV = false; + boolean inObjectKV = false; + TCN sub = null; + + try { + while (inString || (c != '}' && c != ']')) { + if(array) { + kv = true; + } + + if (startString) { + inString = true; + startString = false; + } + if (c == '\\') { + escape = !escape; + } + if (!escape && c == '"') { + startString = !inString; + if (startString) { + if (kv) + inStringKV = true; + theString = new StringBuilder(); + } + else { + inString = false; + if (!kv) { // Key + key = theString.toString(); + } + } + } + if (inString) { + if (!escape) + theString.append(c); + else { + a: + { + // Make escapes work + if (c == 'n') + theString.append('\n'); + if (c == 'r') + theString.append('\r'); + if (c == 'u') { + String e = ""; + e += c = a[++pos]; + e += c = a[++pos]; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 16)); + break a; + } + if (c == '0') { + String e = ""; + e += c = a[pos]; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 8)); + break a; + } + if (c == '1') { + String e = ""; + e += c = a[pos]; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 8)); + break a; + } + if (c == '2') { + String e = ""; + e += c = a[pos]; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 8)); + break a; + } + if (c == '3') { + String e = ""; + e += c = a[pos]; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 8)); + break a; + } + if (c == 'x') { + String e = ""; + e += c = a[++pos]; + e += c = a[++pos]; + theString.append((char) Integer.parseInt(e, 16)); + break a; + } + if (c == '"') { + theString.append("\""); + } + } + } + } + + // Booleans, ints, etc + else if (kv && !startString && !inStringKV && c != ',' && (Character.isLetterOrDigit(c) || c == '.' || c == '-')) { + theString.append(c); + } + + // SubObjects + if (!inString && c == '{') { + inObjectKV = true; + escape = false; + theString = new StringBuilder("{"); + int layer = 1; + while (layer > 0) { + c = a[++pos]; + theString.append(c); + if(c == '{' && !inString) { + layer++; + } + if(c == '}' && !inString) { + layer--; + } + + if (c == '\\') { + escape = !escape; + } + if (c == '\"' && !escape) { + inString = !inString; + } + if (c != '\\') { + escape = false; + } + } + theString.append("}"); + sub = read(theString.toString()); + theString = new StringBuilder(); + } + // Arrays + if (!inString && c == '[') { + inObjectKV = true; + escape = false; + theString = new StringBuilder("["); + int layer = 1; + while (layer != 0) { + c = a[++pos]; + theString.append(c); + if(c == '[' && !inString) { + layer++; + } + if(c == ']' && !inString) { + layer--; + } + + if (c == '\\') { + escape = !escape; + } + if (c == '\"' && !escape) { + inString = !inString; + } + if (c != '\\') { + escape = false; + } + } + theString.append(']'); + sub = read(theString.toString()); + theString = new StringBuilder(); + } + + + if(array) { + kv = true; + } + // Key vs Value parsing + if (!inString && c == ':') { + theString = new StringBuilder(); + if (!kv) + kv = true; + else + throw new JSONFormatException("Unexpected: '" + c + "' at " + pos + " - Should be ','"); + } + if (!inString && c == ',') { + if(array) + key = String.valueOf(arrayPos++); + + if (inObjectKV) { + tcn.set(key, sub); + } else if(inStringKV || !theString.toString().equals("null")) { + tcn.set(key, theString.toString()); + } + inObjectKV = false; + inStringKV = false; + theString = new StringBuilder(); + if (kv) + kv = false; + else + throw new JSONFormatException("Unexpected: '" + c + "' at " + pos + " - Should be ':'"); + + } + + if (c != '\\') { + escape = false; + } + + c = a[++pos]; + } + if(kv) { + if (array) + key = String.valueOf(arrayPos); + if (inObjectKV) + tcn.set(key, sub); + else if(inStringKV || !theString.toString().equals("null")) + tcn.set(key, theString.toString()); + } + + + for (String theKey : tcn.map.keys()) { + TCN.deepConvert(theKey, tcn.get(theKey), tcn); + } + return tcn; + } catch (JSONFormatException e) { + throw e; + } catch (Throwable e) { + throw new JSONFormatException("At " + pos + " in " + string + " (Debug: " + inString + " " + kv + " " + theString + " " + key + " " + array + ")", e); + } + } + + private static String indent(boolean b, int i, int il) { + if(b) + return StringTools.multiply(StringTools.multiply(" ", il), i); + else + return ""; + } + + /** + * Converts a TCN object to a JSON string, uses the most compact form + * @param tcn The TCN to write to JSON + * @return The JSON string + */ + public static String write(TCN tcn) { + return write(tcn, false, false, 0); + } + + /** + * Converts a TCN object to a JSON string + * @param tcn The TCN to write to JSON + * @param spaces If the JSON string should have spaces after : and , + * @return The JSON string + */ + public static String write(TCN tcn, boolean spaces) { + return write(tcn, false, spaces, 0); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with selected indent and without spaces + * @param tcn The TCN to write to JSON + * @param indent The indent to use + * @return The JSON string + */ + public static String write(TCN tcn, int indent) { + return write(tcn, true, false, indent); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with an indent of 2 and spaces + * @param tcn The TCN to write to JSON + * @return The JSON string + */ + public static String writeReadable(TCN tcn) { + return write(tcn, true, true, 2); + } + + /** + * Converts a TCN object to a JSON string, uses newlines with selected indent and spaces + * @param tcn The TCN to write to JSON + * @param indent The indent to use + * @return The JSON string + */ + public static String writeReadable(TCN tcn, int indent) { + return write(tcn, true, true, indent); + } + + /** + * Converts a TCN object to a JSON string + * @param tcn The TCN to write to JSON + * @param newlines If the JSON string should have newlines and indents + * @param spaces If the JSON string should have spaces after : and , + * @param indentLength The indent to use + * @return The JSON string + */ + public static String write(TCN tcn, boolean newlines, boolean spaces, int indentLength) { + + StringBuilder s = new StringBuilder(); + s.append(tcn.isArray ? "[" : "{").append(newlines ? "\n" : ""); + int i = 1; + + ArrayList> paths = new ArrayList<>(); + Stack tcnStack = new Stack<>(); + Stack path = new Stack<>(); + tcnStack.add(tcn); + path.add(""); + while (tcnStack.size() > 0) { + boolean b = false; + for(String key : tcnStack.peek().map.keys()) { + Object o = tcnStack.peek().map.get(key); + + if(o == null) + continue; + + String k = key.replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\"", "\\\\\""); + if(o.getClass() == TCN.class) { + path.add(key); + if(!paths.contains(path)) { + paths.add(path.clone()); + TCN theTCN = tcnStack.peek(); + tcnStack.add((TCN) o); + if(theTCN.isArray) { + s.append(indent(newlines, i, indentLength)).append("{").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i, indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append("{").append(newlines ? "\n" : ""); + b = true; + i++; + } else + path.next(); + } else if(o.getClass() == TCNArray.class) { + path.add(key); + if(!paths.contains(path)) { + paths.add(path.clone()); + TCN theTCN = tcnStack.peek(); + tcnStack.add(((TCNArray) o).toTCN()); + if(theTCN.isArray) { + s.append(indent(newlines, i, indentLength)).append("[").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i, indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append("[").append(newlines ? "\n" : ""); + i++; + b = true; + } else + path.next(); + } else if (o instanceof String) { + path.add(key); + if(!paths.contains(path)) { + paths.add(path.clone()); + String val = o.toString().replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\r", "\\\\r").replaceAll("\"", "\\\\\""); + if(tcnStack.peek().isArray) { + s.append(indent(newlines, i, indentLength)).append("\"").append(val).append("\",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i, indentLength)).append("\"").append(k).append("\":").append(spaces ? " \"" : "\"").append(val).append("\",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + b = true; + } + path.next(); + } + else { + path.add(key); + if(!paths.contains(path)) { + paths.add(path.clone()); + String val = o.toString(); + if(tcnStack.peek().isArray) { + s.append(indent(newlines, i, indentLength)).append(val).append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + else + s.append(indent(newlines, i, indentLength)).append("\"").append(k).append("\":").append(spaces ? " " : "").append(val).append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + b = true; + } + path.next(); + } + } + if(!b) { + TCN theTCN = tcnStack.next(); + path.next(); + i--; + if(theTCN.map.keys().isEmpty()) { + s.append(",").append(spaces ? " " : ""); + } + s.delete(s.length() - ((newlines ? 2 : 1) + (spaces ? 1 : 0)), s.length()); + s.append(newlines ? "\n" : "").append(indent(newlines, i, indentLength)).append(theTCN.isArray ? "]" : "}").append(",").append(spaces ? " " : "").append(newlines ? "\n" : ""); + } + } + s.delete(s.length() - ((newlines ? 2 : 1) + (spaces ? 1 : 0)), s.length()); + return s.toString(); + } + + public static class JSONFormatException extends Exception { + public JSONFormatException(String s, Throwable e) { + super(s, e); + } + public JSONFormatException(String s) { + super(s); + } + } +} diff --git a/src/main/java/de/tudbut/parsing/Lang.java b/src/main/java/de/tudbut/parsing/Lang.java new file mode 100644 index 0000000..7d7e2b2 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/Lang.java @@ -0,0 +1,68 @@ +package de.tudbut.parsing; + +import de.tudbut.tools.Tools; +import de.tudbut.obj.InstanceBoundMap; + +import java.util.ArrayList; +import java.util.Map; + +public class Lang extends InstanceBoundMap { + + private String language = "default"; + private static final ArrayList factories = new ArrayList<>(); + + public String getLanguage() { + return language; + } + + public void setLanguage(String languageIn) { + language = languageIn; + } + + public String get(String key) { + return get(language, key); + } + + private Lang() { + } + + public static Factory factory() { + for (int i = 0; i < factories.size(); i++) { + if(!factories.get(i).inUse) { + for (int j = i+1; j < factories.size(); j++) { + factories.remove(i); + i--; + } + factories.get(i).theLang = new Lang(); + return factories.get(i); + } + } + Factory f = new Factory(); + factories.add(f); + return f; + } + + public static class Factory { + private boolean inUse = true; + private Lang theLang = new Lang(); + + private Factory() {} + + public Factory addLanguage(String name, Map map) { + for (String key : map.keySet()) { + theLang.set(name, key, map.get(key)); + } + return this; + } + + public Factory addLanguage(String name, String langFile) { + return addLanguage(name, Tools.stringToMap(langFile.replaceAll("\n", ";"))); + } + + public Lang build(String defaultLanguage) { + theLang.setLanguage(defaultLanguage); + inUse = false; + return theLang; + } + } +} diff --git a/src/main/java/de/tudbut/parsing/StringMapParser.java b/src/main/java/de/tudbut/parsing/StringMapParser.java new file mode 100644 index 0000000..58597fc --- /dev/null +++ b/src/main/java/de/tudbut/parsing/StringMapParser.java @@ -0,0 +1,32 @@ +package de.tudbut.parsing; + +import java.util.Map; + +public class StringMapParser { + public static boolean getBoolean(Map map, String key) { + try { + return Boolean.parseBoolean(map.getOrDefault(key, "false")); + } + catch (Exception e) { + return false; + } + } + + public static String get(Map map, String key) { + try { + return map.getOrDefault(key, ""); + } + catch (Exception e) { + return ""; + } + } + + public static int getInt(Map map, String key) { + try { + return Integer.parseInt(map.getOrDefault(key, "0")); + } + catch (Exception e) { + return 0; + } + } +} diff --git a/src/main/java/de/tudbut/parsing/TCN.java b/src/main/java/de/tudbut/parsing/TCN.java new file mode 100644 index 0000000..67e8a23 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/TCN.java @@ -0,0 +1,516 @@ +package de.tudbut.parsing; + + +import de.tudbut.tools.Tools; +import de.tudbut.obj.DoubleTypedObject; +import de.tudbut.obj.TLMap; +import de.tudbut.tools.Stack; +import de.tudbut.tools.StringTools; + +import java.util.*; + +/** + * T udbuT
+ * C onfig
+ * N otation
+ */ +public class TCN { + + /** + * The map + */ + public TLMap map = new TLMap<>(); + public final boolean isArray; + public String type; + + /** + * Creates a new, empty TCN + */ + public TCN() { + isArray = false; + this.type = "TCN"; + } + + TCN(boolean array) { + isArray = array; + this.type = "TCN"; + } + + public TCN(String type) { + isArray = false; + this.type = type; + } + + TCN(String type, boolean array) { + isArray = array; + this.type = type; + } + + + /** + * Sets something in the map + * @param key Key + * @param o Object, can be a native type, string, or another TCN (or TCNArray) + */ + public void set(String key, Object o) { + /*TLMap map = this.map; + ArrayList path = new ArrayList<>(Arrays.asList(key.split("#"))); + + while (path.size() > 1) { + map = ((TCN) map.get(path.remove(0))).map; + } + */ + map.set(key, o); + } + + public String getString(String key) { + Object o = map.get(key); + if(o != null) + return get(key).toString(); + else + return null; + } + + public Short getShort(String key) { + Object o = get(key); + if(o != null) + return Short.valueOf(String.valueOf(o)); + else + return null; + } + + public Integer getInteger(String key) { + Object o = get(key); + if(o != null) + return Integer.valueOf(String.valueOf(o)); + else + return null; + } + + public Boolean getBoolean(String key) { + Object o = get(key); + if(o != null) + return Boolean.valueOf(String.valueOf(o)); + else + return null; + } + + public Float getFloat(String key) { + Object o = get(key); + if(o != null) + return Float.valueOf(String.valueOf(o)); + else + return null; + } + + public Long getLong(String key) { + Object o = get(key); + if(o != null) + return Long.valueOf(String.valueOf(o)); + else + return null; + } + + public Double getDouble(String key) { + Object o = get(key); + if(o != null) + return Double.valueOf(String.valueOf(o)); + else + return null; + } + + public TCN getSub(String key) { + Object o = get(key); + if(o != null && o.getClass() == TCN.class) + return (TCN) map.get(key); + else + return null; + } + + public TCNArray getArray(String key) { + Object o = get(key); + if(o != null && o.getClass() == TCNArray.class) + return (TCNArray) map.get(key); + else + return null; + } + + public Object get(String key) { + TLMap map = this.map; + ArrayList path = new ArrayList<>(Collections.singletonList(key)); + + while (path.size() > 1) { + map = ((TCN) map.get(path.remove(0))).map; + } + + return map.get(path.get(0)); + } + + /** + * Converts a Map to a TCN + * @param map The map to convert + * @return The converted TCN + */ + public static TCN readMap(Map map) { + TCN tcn = new TCN(map.containsKey("TCN%isArray") && Boolean.parseBoolean(map.get("TCN%isArray"))); + + map.remove("TCN%isArray"); + + String[] array = map.keySet().toArray(new String[0]); + for (int i = 0, arrayLength = array.length; i < arrayLength; i++) { + String key = array[i]; + String s = map.get(key); + + if(s.contains(":")) { + tcn.map.set(key, TCN.readMap(Tools.stringToMap(s))); + } + else { + tcn.map.set(key, s.replaceAll("%C", ":").replaceAll("%P", "%")); + } + } + + for (String key : tcn.map.keys()) { + deepConvert(key, tcn.get(key), tcn); + } + + return tcn; + } + + /** + * Converts this TCN object to a Map + * {@link #readMap} + * @return The converted Map + */ + public Map toMap() { + Map r = new LinkedHashMap<>(); + + if(isArray) + r.put("TCN%isArray", "true"); + + String[] array = map.keys().toArray(new String[0]); + for (int i = 0, arrayLength = array.length; i < arrayLength; i++) { + String key = array[i]; + Object o = map.get(key); + + if(o == null) + continue; + + if(o.getClass() == TCN.class) { + r.put(key, Tools.mapToString(((TCN) o).toMap())); + } + else if(o.getClass() == TCNArray.class) { + r.put(key, Tools.mapToString(((TCNArray) o).toMap())); + } + else + r.put(key, o.toString().replaceAll("%", "%P").replaceAll(":", "%C")); + } + + return r; + } + + /** + * Converts this TCN to a reversible string + * @return The converted string + */ + public String toString() { + if(type.equalsIgnoreCase("TCN")) { + StringBuilder s = new StringBuilder(); + int i = 0; + + ArrayList> paths = new ArrayList<>(); + Stack tcnStack = new Stack<>(); + Stack path = new Stack<>(); + tcnStack.add(this); + path.add(""); + while (tcnStack.size() > 0) { + boolean b = false; + for (String key : tcnStack.peek().map.keys()) { + Object o = tcnStack.peek().map.get(key); + + if (o == null) + continue; + + String k = key.replaceAll("%", "%P").replaceAll(":", "%C").replaceAll("\n", "%N"); + if (k.startsWith(" ")) { + if (!k.equals(" ")) { + k = "%S" + k.substring(1); + } else + k = "%S"; + } + if (k.startsWith("#")) { + if (!k.equals("#")) { + k = "%H" + k.substring(1); + } else + k = "%H"; + } + if (o.getClass() == TCN.class) { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + TCN tcn = tcnStack.peek(); + tcnStack.add((TCN) o); + String indent = StringTools.multiply(" ", i); + if (tcn.isArray) { + s.append("\n").append(indent).append(";").append(((TCN) o).isArray ? " [\n" : " {\n"); + } else + s.append("\n").append(indent).append(k).append(((TCN) o).isArray ? " [\n" : " {\n"); + i++; + b = true; + break; + } else + path.next(); + } else if (o.getClass() == TCNArray.class) { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + TCN tcn = tcnStack.peek(); + tcnStack.add(((TCNArray) o).toTCN()); + String indent = StringTools.multiply(" ", i); + if (tcn.isArray) { + s.append("\n").append(indent).append(";").append(" [\n"); + } else + s.append("\n").append(indent).append(k).append(" [\n"); + i++; + b = true; + break; + } else + path.next(); + } else { + path.add(key); + if (!paths.contains(path)) { + paths.add(path.clone()); + String indent = StringTools.multiply(" ", i); + String val = o.toString().replaceAll("%", "%P").replaceAll("\n", "%N"); + if (tcnStack.peek().isArray) { + s.append(indent).append("; ").append(val).append("\n"); + } else + s.append(indent).append(k).append(": ").append(val).append("\n"); + b = true; + } + path.next(); + } + } + if (!b) { + TCN tcn = tcnStack.next(); + path.next(); + i--; + String indent = StringTools.multiply(" ", i); + s.append(indent).append(tcn.isArray ? "]\n\n" : "}\n\n"); + } + } + try { + s.setLength(s.length() - "\n#\n\n".length()); + s.trimToSize(); + } catch (Exception ignored) { + } + + return s.toString(); + } + else if(type.equalsIgnoreCase("JSON")) { + return JSON.write(this); + } + else if(type.equalsIgnoreCase("AJSON")) { + return AsyncJSON.write(this).ok().await(); + } + return ""; + } + + /** + * Converts a string {@link #toString()} to a TCN object + * @param s The string + * @return The converted TCN + * @throws TCNException If a format error occurs + */ + public static TCN read(String s) throws TCNException { + TCN tcn = new TCN(); + + Map, Stack>, String> scanned = deepScan(s); + Set, Stack>> keys = scanned.keySet(); + for (DoubleTypedObject, Stack> path : keys) { + deepPut(path.clone(), tcn, scanned.get(path)); + } + for (String key : tcn.map.keys()) { + deepConvert(key, tcn.get(key), tcn); + } + + return tcn; + } + + /** + * Converts stray arrays to TCNArray objects, recursive.
+ *
Example:{@code
+     *     for (String key : tcn.map.keys()) {
+     *         deepConvert(key, tcn.get(key), tcn);
+     *     }
+     * }
+ * @param key The key of o + * @param o Any value in a TCN + * @param top The TCN that o is embedded in + */ + public static void deepConvert(String key, Object o, TCN top) { + if(!(o instanceof TCN) && !(o instanceof TCNArray)) + return; + TCN tcn; + if(o instanceof TCN) { + tcn = (TCN) o; + } + else + tcn = ((TCNArray) o).toTCN(); + + for (String theKey : tcn.map.keys()) { + deepConvert(theKey, tcn.get(theKey), tcn); + } + + if (tcn.isArray) { + top.set(key, TCNArray.fromTCN(tcn)); + } + } + + private static void deepPut(DoubleTypedObject, Stack> path, TCN tcn, String value) throws TCNException { + try { + if (path.o.size() == 1) { + tcn.map.set(path.o.next(), value); + } + else { + int arrayPos = path.t.popBottom(); + TCN toPut = (TCN) tcn.map.get(path.o.getBottom(), () -> new TCN(arrayPos != -1)); + tcn.map.set(path.o.popBottom(), toPut); + deepPut(path, toPut, value); + } + } catch (Exception e) { + throw new TCNException(null, pathToString(path.o) + ": " + value, e); + } + } + + private static Map, Stack>, String> deepScan(String s) throws TCNException { + String[] lines = s.split("\n"); + Map, Stack>, String> map = new LinkedHashMap<>(); + + Stack array = new Stack<>(); + array.add(false); + Stack arrayPos = new Stack<>(); + Stack path = new Stack<>(); + for (int i = 0; i < lines.length; i++) { + try { + String line = removePrefixSpaces(lines[i]); + if (!line.isEmpty() && !line.startsWith("#")) { + if (line.equals("}") || line.equals("]")) { + path.next(); + array.next(); + arrayPos.next(); + } + else if (line.endsWith(" {") && !line.contains(": ")) { + String k = line.split(" \\{")[0].replaceAll("%C", ":").replaceAll("%N", "\n"); + if (k.startsWith("%S")) { + if (!k.equals("%S")) + k = " " + k.substring(2); + else + k = " "; + } + if (k.startsWith("%H")) { + if (!k.equals("%H")) + k = "#" + k.substring(2); + else + k = "#"; + } + if(!array.peek()) + path.add(k.replaceAll("%P", "%")); + else { + path.add(arrayPos.peek() + ""); + arrayPos.add(arrayPos.next() + 1); + } + array.add(false); + arrayPos.add(-1); + } + else if (line.endsWith(" [") && !line.contains(": ")) { + String k = line.split(" \\[")[0].replaceAll("%C", ":").replaceAll("%N", "\n"); + if (k.startsWith("%S")) { + if (!k.equals("%S")) + k = " " + k.substring(2); + else + k = " "; + } + if (k.startsWith("%H")) { + if (!k.equals("%H")) + k = "#" + k.substring(2); + else + k = "#"; + } + if(!array.peek()) + path.add(k.replaceAll("%P", "%")); + else { + path.add(arrayPos.peek() + ""); + arrayPos.add(arrayPos.next() + 1); + } + array.add(true); + arrayPos.add(0); + } + else { + Stack p = path.clone(); + if(array.peek()) { + p.add(String.valueOf(arrayPos.peek())); + if(line.equals(";")) + line = "; "; + map.put(new DoubleTypedObject<>(p, arrayPos.clone()), line.substring(2).replaceAll("%N", "\n").replaceAll("%P", "%")); + arrayPos.add(arrayPos.next() + 1); + } + else { + String rawk = line.split(": ")[0].replaceAll("%C", ":").replaceAll("%N", "\n"); + String k = rawk; + if (k.startsWith("%S")) { + if (!k.equals("%S")) + k = " " + k.substring(2); + else + k = " "; + } + if (k.startsWith("%H")) { + if (!k.equals("%H")) + k = "#" + k.substring(2); + else + k = "#"; + } + p.add(k.replaceAll("%P", "%")); + map.put(new DoubleTypedObject<>(p, arrayPos.clone()), line.substring(rawk.length() + 2).replaceAll("%N", "\n").replaceAll("%P", "%")); + } + } + } + } catch (Exception e) { + throw new TCNException(i, lines[i], e); + } + } + + return map; + } + + static String pathToString(Stack path) { + StringBuilder s = new StringBuilder("/"); + path = path.clone(); + while (path.hasNext()) { + s.append(path.popBottom()).append("/"); + } + return s.toString(); + } + + private static String removePrefixSpaces(String s) { + while (s.startsWith(" ")) + s = s.substring(1); + return s; + } + + /** + * Creates a new, empty TCN object + * @deprecated Use {@code new} {@link #TCN()} + * @return The new TCN + */ + @Deprecated + public static TCN getEmpty() { + return new TCN(); + } + + public static class TCNException extends Exception { + + public TCNException(Integer line, String lineString, Exception e) { + super("Error in line " + line + " (" + lineString + ")", e); + } + } +} diff --git a/src/main/java/de/tudbut/parsing/TCNArray.java b/src/main/java/de/tudbut/parsing/TCNArray.java new file mode 100644 index 0000000..555250e --- /dev/null +++ b/src/main/java/de/tudbut/parsing/TCNArray.java @@ -0,0 +1,127 @@ +package de.tudbut.parsing; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +/** + * TCN-Compatible Arrays + */ +public class TCNArray extends ArrayList { + + /** + * Creates a new, empty TCNArray + */ + public TCNArray() { } + /** + * Creates a new TCNArray from a collection + */ + public TCNArray(Collection collection) { + addAll(collection); + } + + public String getString(int key) { + Object o = get(key); + if(o != null) + return o.toString(); + else + return null; + } + + public Short getShort(int key) { + Object o = get(key); + if(o != null) + return Short.valueOf(String.valueOf(o)); + else + return null; + } + + public Integer getInteger(int key) { + Object o = get(key); + if(o != null) + return Integer.valueOf(String.valueOf(o)); + else + return null; + } + + public Boolean getBoolean(int key) { + Object o = get(key); + if(o != null) + return Boolean.valueOf(String.valueOf(o)); + else + return null; + } + + public Float getFloat(int key) { + Object o = get(key); + if(o != null) + return Float.valueOf(String.valueOf(o)); + else + return null; + } + + public Long getLong(int key) { + Object o = get(key); + if(o != null) + return Long.valueOf(String.valueOf(o)); + else + return null; + } + + public Double getDouble(int key) { + Object o = get(key); + if(o != null) + return Double.valueOf(String.valueOf(o)); + else + return null; + } + + public TCN getSub(int key) { + Object o = get(key); + if(o != null && o.getClass() == TCN.class) + return (TCN) o; + else + return null; + } + + public TCNArray getArray(int key) { + Object o = get(key); + if(o != null && o.getClass() == TCNArray.class) + return (TCNArray) o; + else + return null; + } + + /** + * + * @return a TCN object from this TCNArray, this is the only way to get a TCN with {@link TCN#isArray} true + */ + public TCN toTCN() { + TCN tcn = new TCN(true); + for (int i = 0 ; i < this.size() ; i++) { + tcn.set(String.valueOf(i), get(i)); + } + return tcn; + } + + /** + * Converts a TCN to a TCNArray + * @param tcn The TCN to convert from + * @return The created TCNArray + */ + public static TCNArray fromTCN(TCN tcn) { + TCNArray array = new TCNArray(); + for (String key : tcn.map.keys()) { + array.add(tcn.get(key)); + } + return array; + } + + /** + * Converts the TCNArray to a TCN, then maps it + * @return the created map + */ + public Map toMap() { + return toTCN().toMap(); + } +} diff --git a/src/main/java/de/tudbut/parsing/TudSort.java b/src/main/java/de/tudbut/parsing/TudSort.java new file mode 100644 index 0000000..80eb008 --- /dev/null +++ b/src/main/java/de/tudbut/parsing/TudSort.java @@ -0,0 +1,102 @@ +package de.tudbut.parsing; + +import de.tudbut.tools.Tools; +import de.tudbut.tools.ArrayGetter; +import de.tudbut.tools.ArrayTools; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class TudSort { + + public static T[] sort(T[] toSort, Sorter sorter) { + return sort(toSort, sorter, false); + } + + public static T[] sort(T[] toSort, Sorter sorter, boolean reverse) { + if(toSort.length == 0) + return toSort; + T[] nt = (T[]) ArrayGetter.newArray(toSort.length, toSort.getClass().getComponentType()); + + boolean[] indexes = new boolean[toSort.length]; + for (int i = 0; i < toSort.length; i++) { + long smallest = Long.MAX_VALUE; + int smallestIndex = 0; + for (int j = 0; j < toSort.length; j++) { + long id = sorter.get(toSort[j]); + if(id <= smallest && !indexes[j]) { + smallest = id; + smallestIndex = j; + } + } + nt[i] = toSort[smallestIndex]; + indexes[smallestIndex] = true; + } + + if(reverse) { + List list = Arrays.asList(nt); + Collections.reverse(list); + return list.toArray(toSort); + } + + return nt; + } + + public static T[] sortDouble(T[] toSort, SorterDouble sorter) { + if(toSort.length == 0) + return toSort; + T[] nt = (T[]) ArrayGetter.newArray(toSort.length, toSort.getClass().getComponentType()); + + boolean[] indexes = new boolean[toSort.length]; + for (int i = 0; i < toSort.length; i++) { + double smallest = Integer.MAX_VALUE; + int smallestIndex = 0; + for (int j = 0; j < toSort.length; j++) { + double id = sorter.get(toSort[j]); + if(id <= smallest && !indexes[j]) { + smallest = id; + smallestIndex = j; + } + } + nt[i] = toSort[smallestIndex]; + indexes[smallestIndex] = true; + } + + return nt; + } + + public static void sortSet(T[] toSort, Sorter sorter) { + T[] sorted = sort(toSort, sorter); + Tools.copyArray(sorted, toSort, toSort.length); + } + + public static T find(T[] toFindIn, ArrayTools.Getter getter, Object o) { + for (T t : toFindIn) { + try { + if (getter.get(t).equals(o)) + return t; + } + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + return null; + } + + public interface Sorter { + long sort(T t); + + default long get(T t) { + return sort(t); + } + } + + public interface SorterDouble { + double sort(T t); + + default double get(T t) { + return sort(t); + } + } +} diff --git a/src/main/java/de/tudbut/pluginapi/Plugin.java b/src/main/java/de/tudbut/pluginapi/Plugin.java new file mode 100644 index 0000000..3be94bb --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/Plugin.java @@ -0,0 +1,114 @@ +package de.tudbut.pluginapi; + + +import de.tudbut.tools.BetterClassLoader; +import de.tudbut.logger.DetailedLogger; +import de.tudbut.logger.Logger; +import de.tudbut.logger.LoggerSink; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public abstract class Plugin { + static void loadClass() { + + } + + private LoggerSink logger; + private Logger nLogger; + private DetailedLogger dLogger; + private BetterClassLoader cl; + private String jar; + + public abstract void onLoad(); + + public abstract void onUnload(); + + public abstract void onEvent(PluginEvent event); + + public BetterClassLoader getCL() { + return cl; + } + + public LoggerSink getLogger() { + return logger; + } + + public LoggerSink getLogger(String sub) { + return logger.subChannel(sub); + } + + public void setLoggerType(boolean detailed) { + logger = detailed ? dLogger : nLogger; + } + + public void setLogger(String name) { + this.nLogger = new Logger(name); + this.dLogger = new DetailedLogger(name); + setLoggerType(false); + } + + public void setCL(BetterClassLoader cl, String jar) throws PluginException { + if (this.cl == null) { + this.cl = cl; + this.jar = jar; + } + else + throw new PluginException("Redefining classLoader"); + } + + public String getName() { + return jar; + } + + public Object useCustomMethod(String methodName, Object... args) { + for (Method method : getClass().getDeclaredMethods()) { + try { + if(method.getName().equals(methodName)) + return method.invoke(this, args); + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + } + return null; + } + + public Object useStaticCustomMethod(String methodName, Object... args) { + for (Method method : getClass().getDeclaredMethods()) { + try { + if(method.getName().equals(methodName)) + return method.invoke(null, args); + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + } + return null; + } + + public Object useStaticCustomMethodInCustomClass(String className, String methodName, Object... args) { + for (Method method : getCustomClass(className).getDeclaredMethods()) { + try { + if(method.getName().equals(methodName)) + return method.invoke(null, args); + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + } + return null; + } + + public Object useCustomMethodInCustomClass(String methodName, Object instance, Object... args) { + for (Method method : instance.getClass().getDeclaredMethods()) { + try { + if(method.getName().equals(methodName)) + return method.invoke(instance, args); + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + } + return null; + } + + public Class getCustomClass(String className) { + try { + return getCL().friendlyGet(className); + } catch (NullPointerException ignore) { } + return null; + } +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginEvent.java b/src/main/java/de/tudbut/pluginapi/PluginEvent.java new file mode 100644 index 0000000..4a4fe8d --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginEvent.java @@ -0,0 +1,4 @@ +package de.tudbut.pluginapi; + +public class PluginEvent { +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginException.java b/src/main/java/de/tudbut/pluginapi/PluginException.java new file mode 100644 index 0000000..dc9ece4 --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginException.java @@ -0,0 +1,7 @@ +package de.tudbut.pluginapi; + +public class PluginException extends Exception { + public PluginException(String msg) { + System.out.println(msg); + } +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginGetEvent.java b/src/main/java/de/tudbut/pluginapi/PluginGetEvent.java new file mode 100644 index 0000000..3b2fbb3 --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginGetEvent.java @@ -0,0 +1,10 @@ +package de.tudbut.pluginapi; + +public class PluginGetEvent extends PluginEvent { + public String id = ""; + + public String backS = ""; + public Object backO = new Object(); + public String[] backAS = new String[0]; + public Object[] backAO = new Object[0]; +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginLoadEvent.java b/src/main/java/de/tudbut/pluginapi/PluginLoadEvent.java new file mode 100644 index 0000000..fe7a0bb --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginLoadEvent.java @@ -0,0 +1,4 @@ +package de.tudbut.pluginapi; + +public class PluginLoadEvent extends PluginEvent { +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginManager.java b/src/main/java/de/tudbut/pluginapi/PluginManager.java new file mode 100644 index 0000000..f91ad71 --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginManager.java @@ -0,0 +1,149 @@ +package de.tudbut.pluginapi; + +import de.tudbut.tools.BetterClassLoader; +import de.tudbut.type.FileFormatException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +public class PluginManager { + + public static Plugin loadPlugin(String jarPath) + throws IOException { + String main = ""; + + JarFile file = new JarFile(new File(jarPath)); + + if(file.getEntry("main.tplv") != null) + main = new BufferedReader(new InputStreamReader(file.getInputStream(file.getEntry("main.tplv")))).readLine(); + + return loadPlugin(jarPath, main); + } + + public static Plugin loadPlugin(String jarPath, boolean autoLoad) + throws IOException { + String main = ""; + + JarFile file = new JarFile(new File(jarPath)); + + if(file.getEntry("main.tplv") != null) + main = new BufferedReader(new InputStreamReader(file.getInputStream(file.getEntry("main.tplv")))).readLine(); + + + return loadPlugin(jarPath, main, autoLoad); + } + + public static Plugin loadPlugin(String jarPath, String main) { + return loadPlugin(jarPath, main, true); + } + + public static Plugin loadPlugin(String jarPath, String main, boolean autoLoad) { + + System.out.println("Loading plugin " + jarPath); + Plugin.loadClass(); + URL[] urls; + JarFile file; + try { + file = new JarFile(jarPath); + } + catch (IOException e) { + System.out.println("Couldn't load plugin " + jarPath); + e.printStackTrace(); + return null; + } + try { + urls = new URL[]{new File(jarPath).toURI().toURL()}; + } catch (MalformedURLException e) { + System.out.println("Couldn't load plugin " + jarPath); + e.printStackTrace(); + return null; + } + BetterClassLoader cl = new BetterClassLoader(urls); + + Plugin r = null; + try { + if(!main.equals("")) { + Class lc = cl.lc(main); + if(Plugin.class.isAssignableFrom(lc.getSuperclass())) + r = (Plugin) lc.newInstance(); + else { + System.out.println(lc.getSuperclass().hashCode()); + System.out.println(Plugin.class.hashCode()); + } + } else { + ArrayList> classes = new ArrayList<>(); + Enumeration enumerator = file.entries(); + while (enumerator.hasMoreElements()) { + ZipEntry entry = enumerator.nextElement(); + if(entry.getName().endsWith(".class")) { + String s = entry.getName().split("\\.")[0].replaceAll("/", "."); + System.out.println("Loading class " + s); + Class clazz = cl.lc(s); + if(clazz != null) + classes.add(clazz); + } + } + + for (int i = 0; i < classes.size(); i++) { + if(classes.get(i).getSuperclass().getName().equals(Plugin.class.getName())) { + try { + r = classes.get(i).asSubclass(Plugin.class).newInstance(); + } catch (InstantiationException ignore) { } + } + } + } + if (r != null) { + r.setLogger(jarPath.split(File.separator)[jarPath.split(File.separator).length - 1]); + r.setCL(cl, jarPath.split(File.separator)[jarPath.split(File.separator).length - 1]); + if (autoLoad) { + r.onLoad(); + r.onEvent(new PluginLoadEvent()); + } + } + else { + System.out.println("Couldn't load plugin " + jarPath); + return null; + } + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | PluginException e) { + System.out.println("Couldn't load plugin " + jarPath); + e.printStackTrace(); + return null; + } + + + return r; + } + + public static Plugin[] loadPlugins(File dir) throws FileFormatException { + if (!dir.isDirectory()) { + throw new FileFormatException(); + } + + Plugin[] plugins = new Plugin[dir.listFiles(file -> file.getAbsolutePath().endsWith(".jar")).length]; + for (int i = 0; i < dir.listFiles(file -> file.getAbsolutePath().endsWith(".jar")).length; i++) { + try { + plugins[i] = loadPlugin(dir.listFiles(file -> file.getAbsolutePath().endsWith(".jar"))[i].getAbsolutePath()); + } catch (IOException ignore) { } + } + return plugins; + } + + public static void unloadPlugin(Plugin plugin) throws IOException { + plugin.onUnload(); + plugin.onEvent(new PluginUnloadEvent()); + plugin.getCL().close(); + } + + public static void unloadPlugins(Plugin[] plugins) throws IOException { + for (Plugin plugin : plugins) + unloadPlugin(plugin); + } +} diff --git a/src/main/java/de/tudbut/pluginapi/PluginUnloadEvent.java b/src/main/java/de/tudbut/pluginapi/PluginUnloadEvent.java new file mode 100644 index 0000000..573e37e --- /dev/null +++ b/src/main/java/de/tudbut/pluginapi/PluginUnloadEvent.java @@ -0,0 +1,4 @@ +package de.tudbut.pluginapi; + +public class PluginUnloadEvent extends PluginEvent { +} diff --git a/src/main/java/de/tudbut/rendering/GIFEncoder.java b/src/main/java/de/tudbut/rendering/GIFEncoder.java new file mode 100644 index 0000000..3bfd7b7 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/GIFEncoder.java @@ -0,0 +1,72 @@ +package de.tudbut.rendering; + +import javax.imageio.*; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageOutputStream; +import java.awt.image.RenderedImage; +import java.io.IOException; + +public class GIFEncoder { + + protected final ImageWriter writer; + protected final ImageWriteParam params; + protected final IIOMetadata metadata; + protected ImageOutputStream stream; + + public GIFEncoder(ImageOutputStream out, int imageType, int delay, boolean loop) throws IOException { + writer = ImageIO.getImageWritersBySuffix("gif").next(); + params = writer.getDefaultWriteParam(); + + ImageTypeSpecifier imageTypeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(imageType); + metadata = writer.getDefaultImageMetadata(imageTypeSpecifier, params); + + configureRootMetadata(delay, loop); + + writer.setOutput(out); + writer.prepareWriteSequence(null); + } + + private void configureRootMetadata(int delay, boolean loop) throws IIOInvalidTreeException { + String metaFormatName = metadata.getNativeMetadataFormatName(); + IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(metaFormatName); + + IIOMetadataNode meta = getNode(root, "GraphicControlExtension"); + meta.setAttribute("disposalMethod", "none"); + meta.setAttribute("userInputFlag", "FALSE"); + meta.setAttribute("transparentColorFlag", "FALSE"); + meta.setAttribute("delayTime", Integer.toString(delay / 10)); + meta.setAttribute("transparentColorIndex", "0"); + + meta = getNode(root, "ApplicationExtensions"); + + int loopContinuously = loop ? 0 : 1; + IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension"); + child.setAttribute("applicationID", "TuddyLIB"); + child.setAttribute("authenticationCode", "1.0"); + child.setUserObject(new byte[]{ 0x1, (byte) (loopContinuously & 0xFF), 0}); + meta.appendChild(child); + metadata.setFromTree(metaFormatName, root); + } + + private static IIOMetadataNode getNode(IIOMetadataNode rootNode, String nodeName){ + int nNodes = rootNode.getLength(); + for (int i = 0; i < nNodes; i++){ + if (rootNode.item(i).getNodeName().equalsIgnoreCase(nodeName)){ + return (IIOMetadataNode) rootNode.item(i); + } + } + IIOMetadataNode node = new IIOMetadataNode(nodeName); + rootNode.appendChild(node); + return(node); + } + + public void addFrame(RenderedImage img) throws IOException { + writer.writeToSequence(new IIOImage(img, null, metadata), params); + } + + public void close() throws IOException { + writer.endWriteSequence(); + } +} diff --git a/src/main/java/de/tudbut/rendering/Graph.java b/src/main/java/de/tudbut/rendering/Graph.java new file mode 100644 index 0000000..42ca1ef --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Graph.java @@ -0,0 +1,6 @@ +package de.tudbut.rendering; + +public interface Graph { + + double getAsFunction(double x); +} diff --git a/src/main/java/de/tudbut/rendering/GraphRenderer.java b/src/main/java/de/tudbut/rendering/GraphRenderer.java new file mode 100644 index 0000000..f77d10f --- /dev/null +++ b/src/main/java/de/tudbut/rendering/GraphRenderer.java @@ -0,0 +1,62 @@ +package de.tudbut.rendering; + +import java.awt.*; +import java.awt.image.BufferedImage; + +public class GraphRenderer { + + double scale = 1; + int offsetX = 0; + int offsetY = 0; + double scaleY; + + public GraphRenderer() { + + } + + public void setScaleX(double scale) { + if(scale <= 0) + throw new IllegalArgumentException(); + + this.scale = 1 / scale; + } + + public void setScaleY(double scale) { + if(scale <= 0) + throw new IllegalArgumentException(); + + this.scaleY = scale; + } + + public void setOffsetX(int offset) { + this.offsetX = offset; + } + + public void setOffsetY(int offset) { + this.offsetY = offset; + } + + public BufferedImage render(Graph graph, int pxX, int pxY, boolean markZero) { + BufferedImage image = new BufferedImage(pxX, pxY, BufferedImage.TYPE_INT_RGB); + + for (int y = 0; y < pxY; y++) { + for (int x = 0; x < pxX; x++) { + image.setRGB(x,y, 0xffffff); + } + } + + Graphics graphics = image.getGraphics().create(); + graphics.setColor(new Color(0x000000)); + + int lastPixelY = Maths2D.center(Maths2D.camera((int) Math.round(-graph.getAsFunction(-Maths2D.center(Maths2D.camera(0, offsetX), pxX) * scale) * scaleY), offsetY), pxY); + if(markZero) + graphics.drawRect(Maths2D.center(Maths2D.camera(0, offsetX), pxX) - 1, Maths2D.center(Maths2D.camera(0, offsetY), pxY) - 1, 2, 2); + for (int i = -Maths2D.center(Maths2D.camera(0, offsetX), pxX) ; i < Maths2D.center(Maths2D.camera(0, offsetX), pxX) * 2; i++) { + int y = Maths2D.center(Maths2D.camera((int) Math.round(-graph.getAsFunction(i * scale) * scaleY), offsetY), pxY); + graphics.drawLine(Maths2D.center(Maths2D.camera(i, offsetX), pxX) - 1, lastPixelY, Maths2D.center(Maths2D.camera(i, offsetX), pxX), y); + lastPixelY = y; + } + + return image; + } +} diff --git a/src/main/java/de/tudbut/rendering/Maths2D.java b/src/main/java/de/tudbut/rendering/Maths2D.java new file mode 100644 index 0000000..d484fd9 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Maths2D.java @@ -0,0 +1,218 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector2d; +import de.tudbut.tools.NoiseGenerator; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Random; + +public class Maths2D { + public static int camera(int i, int camera) { + return i - camera; + } + + public static int center(int i, int size) { + return i + size / 2; + } + + public static boolean[] getRelation(Rectangle2D measuringObject, Rectangle2D toMeasure) { + boolean[] b = new boolean[4]; + Vector2d a0 = toMeasure.getPos(); + Vector2d a1 = toMeasure.getEndPoint(); + Vector2d b0 = measuringObject.getPos(); + Vector2d b1 = measuringObject.getEndPoint(); + + b[0] = a0.getX() < b1.getX(); + b[1] = a0.getY() < b1.getY(); + b[2] = a1.getX() > b0.getX(); + b[3] = a1.getY() > b0.getY(); + + for (int i = 0; i < b.length; i++) { + b[i] = !b[i]; + } + + return b; + } + + public static BufferedImage createNoiseImage(BufferedImage base, int smoothness, Random random, int color) { + int x = base.getWidth(), y = base.getHeight(); + BufferedImage image = Maths2D.distortImage(base, x, y, 1); + //noinspection SuspiciousNameCombination + float[][] floats = NoiseGenerator.generateRandom(1, x, y, smoothness, 1, random)[0]; + + Graphics graphics = image.getGraphics(); + for (int i = 0 ; i < y ; i++) { + for (int j = 0 ; j < x ; j++) { + Color c = new Color(color, true); + c = new Color(c.getRed() * floats[j][i] / 255, c.getGreen() * floats[j][i] / 255, c.getBlue() * floats[j][i] / 255, 1); + graphics.setColor(c); + graphics.drawRect(j, i, 1, 1); + } + } + + return image; + } + + public static BufferedImage createMultiNoiseImage(BufferedImage base, int smoothness, Random random, int color) { + int x = base.getWidth(), y = base.getHeight(); + BufferedImage image = Maths2D.distortImage(base, x, y, 1); + //noinspection SuspiciousNameCombination + float[][][] floats = new float[][][] { + NoiseGenerator.generateRandom(1, x, y, smoothness, 1, random)[0], + NoiseGenerator.generateRandom(1, x, y, smoothness, 1, random)[0], + NoiseGenerator.generateRandom(1, x, y, smoothness, 1, random)[0], + NoiseGenerator.generateRandom(1, x, y, smoothness, 1, random)[0] + }; + + Graphics graphics = image.getGraphics(); + for (int i = 0 ; i < y ; i++) { + for (int j = 0 ; j < x ; j++) { + Color c = new Color(color, true); + c = new Color((int) (c.getRed() * floats[1][j][i]), (int) (c.getGreen() * floats[2][j][i]), (int) (c.getBlue() * floats[3][j][i]), (int) (c.getAlpha() * floats[0][j][i])); + graphics.setColor(c); + graphics.drawRect(j, i, 1, 1); + } + } + + return image; + } + + public static boolean collides(Rectangle2D rectangle0, Rectangle2D rectangle1) { + boolean b = true; + boolean[] rel = getRelation(rectangle0, rectangle1); + for (int i = 0; i < rel.length; i++) { + if (rel[i]) { + b = false; + break; + } + } + return b; + } + + public static int[][] getFillingCoordinatesForRectangle(RenderObject2D o) { + int[][] r = new int[2][4]; + + r[0][0] = (int) o.vectors[0].getX(); + r[0][1] = (int) o.vectors[1].getX(); + r[0][2] = (int) o.vectors[2].getX(); + r[0][3] = (int) o.vectors[3].getX(); + + r[1][0] = (int) o.vectors[0].getY(); + r[1][1] = (int) o.vectors[1].getY(); + r[1][2] = (int) o.vectors[2].getY(); + r[1][3] = (int) o.vectors[3].getY(); + + return r; + } + + public static int[][] getFillingCoordinatesForTriangle(RenderObject2D o) { + int[][] r = new int[2][3]; + + r[0][0] = (int) o.vectors[0].getX(); + r[0][1] = (int) o.vectors[1].getX(); + r[0][2] = (int) o.vectors[2].getX(); + + r[1][0] = (int) o.vectors[0].getY(); + r[1][1] = (int) o.vectors[1].getY(); + r[1][2] = (int) o.vectors[2].getY(); + + return r; + } + + public static BufferedImage distortImage(Image image, int newResolutionX, int newResolutionY, double multiplier) { + newResolutionX = (int) (newResolutionX * multiplier); + newResolutionY = (int) (newResolutionY * multiplier); + + BufferedImage r = new BufferedImage(newResolutionX, newResolutionY, BufferedImage.TYPE_INT_ARGB); + BufferedImage img = (BufferedImage) image; + + for (int x = 0; x < newResolutionX; x++) { + for (int y = 0; y < newResolutionY; y++) { + r.setRGB(x, y, img.getRGB((int) ((double) x / ((double) newResolutionX / (double) img.getWidth())), (int) ((double) y / ((double) newResolutionY / (double) img.getHeight())))); + } + } + + return r; + } + + public static BufferedImage prepareImage(BufferedImage image, Vector2d pos, Vector2d endPos) { + endPos = endPos.clone().add(pos.clone().negate()); + return distortImage(image, (int) endPos.getX(), (int) endPos.getY(), 1); + } + + public static BufferedImage rotateImage(Image image, double rot) { + rot = Math.PI * (rot / 360 * 2); + BufferedImage img = (BufferedImage) image; + BufferedImage r = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB); + Vector2d p = new Vector2d(img.getWidth() / 2d, img.getHeight() / 2d); + + for (int x = 0; x < img.getWidth(); x++) { + for (int y = 0; y < img.getHeight(); y++) { + Vector2d rotated = rotate(new Vector2d(x,y), p, rot); + try { + r.setRGB(x, y, img.getRGB((int) rotated.getX(), (int) rotated.getY())); + } catch (Exception ignore) { } + } + } + + return r; + } + + public static BufferedImage rotateImage(Image image, double rot, boolean clockwise) { + return clockwise ? rotateImage(image, -rot) : rotateImage(image, rot); + } + + public static BufferedImage cropImage(Image image, Vector2d start, Vector2d end) { + BufferedImage img = (BufferedImage) image; + BufferedImage r = new BufferedImage((int) (end.getY() - start.getY()), (int) (end.getY() - start.getY()), BufferedImage.TYPE_INT_ARGB); + + for (int x = 0; x < r.getWidth(); x++) { + for (int y = 0; y < r.getHeight(); y++) { + r.setRGB(x, y, img.getRGB((int) (x + start.getX()), (int) (y + start.getY()))); + } + } + + return r; + } + + public static BufferedImage addImageBorder(Image image, Vector2d posOnResult, Vector2d resultSize, int color) { + BufferedImage img = (BufferedImage) image; + BufferedImage r = new BufferedImage((int) resultSize.getX(), (int) resultSize.getY(), BufferedImage.TYPE_INT_ARGB); + Graphics g = r.getGraphics().create(); + + if(color >= 0 && color <= 0xffffff) + g.setColor(new Color(color, false)); + else + g.setColor(new Color(color, true)); + + g.fillRect(0,(int) posOnResult.getY(), (int) posOnResult.getX(), (int) resultSize.getY()); + + g.fillRect(0,0, (int) resultSize.getX(), (int) posOnResult.getY()); + + g.fillRect((int) posOnResult.getX() + img.getWidth(), (int) (posOnResult.getY()), (int) (resultSize.getX() - (posOnResult.getX() + img.getWidth())), (int) (resultSize.getY() - (resultSize.getY() - img.getHeight()))); + + g.fillRect((int) posOnResult.getX(), (int) posOnResult.getY() + img.getHeight(), (int) resultSize.getX(), (int) (resultSize.getY() - (posOnResult.getY() + img.getHeight()))); + + + g.drawImage(img, (int) posOnResult.getX(), (int) posOnResult.getY(), null); + + g.dispose(); + return r; + } + + public static Vector2d rotate(Vector2d vec, Vector2d point, double rotation) { + point.negate(); + vec.add(point); + double sinRot = Math.sin(rotation); + double cosRot = Math.cos(rotation); + vec.set( + vec.getX() * cosRot + vec.getY() * -sinRot, + vec.getX() * sinRot + vec.getY() * cosRot + ); + point.negate(); + vec.add(point); + return vec; + } +} + diff --git a/src/main/java/de/tudbut/rendering/Maths3D.java b/src/main/java/de/tudbut/rendering/Maths3D.java new file mode 100644 index 0000000..80f415a --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Maths3D.java @@ -0,0 +1,113 @@ +package de.tudbut.rendering; + +import de.tudbut.tools.ExtendedMath; +import de.tudbut.type.Vector3d; + +public class Maths3D { + public static boolean[] getRelation(Rectangle3D measuringObject, Rectangle3D toMeasure) { + boolean[] b = new boolean[6]; + Vector3d a0 = toMeasure.getPos(); + Vector3d a1 = toMeasure.getEndPoint(); + Vector3d b0 = measuringObject.getPos(); + Vector3d b1 = measuringObject.getEndPoint(); + + b[0] = a0.getX() < b1.getX(); + b[1] = a0.getY() < b1.getY(); + b[2] = a0.getZ() < b1.getZ(); + b[3] = a1.getX() > b0.getX(); + b[4] = a1.getY() > b0.getY(); + b[5] = a1.getZ() > b0.getZ(); + + for (int i = 0; i < b.length; i++) { + b[i] = !b[i]; + } + + return b; + } + + public static boolean collides(Rectangle3D rectangle0, Rectangle3D rectangle1) { + boolean b = true; + boolean[] rel = getRelation(rectangle0, rectangle1); + for (int i = 0; i < rel.length; i++) { + if (rel[i]) { + b = false; + break; + } + } + return b; + } + + public static void prepareVectorsForRectangle(RenderObject3D o, int renderOutputX, int renderOutputY, double var0, double fovMod) { + Vector3d pos1 = o.vectors[0]; + Vector3d pos2 = o.vectors[1]; + Vector3d pos3 = o.vectors[2]; + Vector3d pos4 = o.vectors[3]; + + if ((1 / fovMod) / (ExtendedMath.min(pos1.getZ(), pos2.getZ(), pos3.getZ(), pos4.getZ()) / 15) <= 0) + o.isClipped = true; + + includeZDirection(pos1, fovMod); + includeZDirection(pos2, fovMod); + includeZDirection(pos3, fovMod); + includeZDirection(pos4, fovMod); + + for (int i = 0; i < o.vectors.length; i++) { + Vector3d vec = o.vectors[i]; + vec.multiply(var0); + vec.set(vec.getX() + (double) renderOutputX / 2, vec.getY() + (double) renderOutputY / 2, vec.getZ()); + } + } + + public static void prepareVectorsForTriangle(RenderObject3D o, int renderOutputX, int renderOutputY, double var0, double fovMod) { + Vector3d pos1 = o.vectors[0]; + Vector3d pos2 = o.vectors[1]; + Vector3d pos3 = o.vectors[2]; + + if ((1 / fovMod) / (ExtendedMath.min(pos1.getZ(), pos2.getZ(), pos3.getZ()) / 15) <= 0) + o.isClipped = true; + + includeZDirection(pos1, fovMod); + includeZDirection(pos2, fovMod); + includeZDirection(pos3, fovMod); + + for (int i = 0; i < o.vectors.length; i++) { + Vector3d vec = o.vectors[i]; + vec.multiply(var0); + vec.set(vec.getX() + (double) renderOutputX / 2, vec.getY() + (double) renderOutputY / 2, vec.getZ()); + } + } + + public static void includeZDirection(Vector3d vec, double fovMod) { + vec.multiply((1 / fovMod) / (vec.getZ() / 15)); + } + + public static int[][] getFillingCoordinatesForRectangle(RenderObject3D o) { + int[][] r = new int[2][4]; + + r[0][0] = (int) o.vectors[0].getX(); + r[0][1] = (int) o.vectors[1].getX(); + r[0][2] = (int) o.vectors[2].getX(); + r[0][3] = (int) o.vectors[3].getX(); + + r[1][0] = (int) o.vectors[0].getY(); + r[1][1] = (int) o.vectors[1].getY(); + r[1][2] = (int) o.vectors[2].getY(); + r[1][3] = (int) o.vectors[3].getY(); + + return r; + } + + public static int[][] getFillingCoordinatesForTriangle(RenderObject3D o) { + int[][] r = new int[2][3]; + + r[0][0] = (int) o.vectors[0].getX(); + r[0][1] = (int) o.vectors[1].getX(); + r[0][2] = (int) o.vectors[2].getX(); + + r[1][0] = (int) o.vectors[0].getY(); + r[1][1] = (int) o.vectors[1].getY(); + r[1][2] = (int) o.vectors[2].getY(); + + return r; + } +} diff --git a/src/main/java/de/tudbut/rendering/Projection2D.java b/src/main/java/de/tudbut/rendering/Projection2D.java new file mode 100644 index 0000000..0c5d45b --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Projection2D.java @@ -0,0 +1,247 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector2d; +import de.tudbut.ui.windowgui.AdaptedGraphics; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +public class Projection2D { + final AtomicInteger x; + final AtomicInteger y; + final RenderOutputType type; + final ArrayList objects = new ArrayList<>(); + final ArrayList imageObjects = new ArrayList<>(); + int fgColor = 0x000000; + final int bgColor; + double multiplier; + Vector2d offset = new Vector2d(0,0); + + public Projection2D(AtomicInteger xSize, AtomicInteger ySize, RenderOutputType type, int backgroundColor) { + x = xSize; + y = ySize; + this.type = type; + bgColor = backgroundColor; + multiplier = 1; + } + + public Projection2D(AtomicInteger xSize, AtomicInteger ySize, RenderOutputType type, int backgroundColor, double multiplier) { + x = xSize; + y = ySize; + this.type = type; + bgColor = backgroundColor; + this.multiplier = multiplier + 1; + } + + public void setMultiplier(double multiplier) { + if (multiplier == 0) + throw new IllegalArgumentException("Multiplier may not be 0"); + + this.multiplier = multiplier; + } + + public void setOffset(Vector2d vector) { + this.offset = vector; + } + + public void drawTriangle(Vector2d pos1, Vector2d pos2, Vector2d pos3) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject2D(pos1, pos2, pos3, false, multiplier, fgColor)); + } + + public void drawImage(Vector2d pos, Image image, int newResolutionX, int newResolutionY) { + if (pos == null) + throw new IllegalArgumentException(); + + imageObjects.add(new Object[]{pos, image, newResolutionX, newResolutionY, true}); + } + + public void drawImage(Vector2d pos, Image image, int newResolutionX, int newResolutionY, boolean applyMultiplier) { + if (pos == null) + throw new IllegalArgumentException(); + + imageObjects.add(new Object[]{pos, image, newResolutionX, newResolutionY, applyMultiplier}); + } + + public void drawRectangle(Vector2d pos1, Vector2d pos2, Vector2d pos3, Vector2d pos4) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + if (pos4 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject2D(pos1, pos2, pos3, pos4, false, multiplier, fgColor)); + } + + public void fillTriangle(Vector2d pos1, Vector2d pos2, Vector2d pos3) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject2D(pos1, pos2, pos3, true, multiplier, fgColor)); + } + + public void fillRectangle(Vector2d pos1, Vector2d pos2, Vector2d pos3, Vector2d pos4) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + if (pos4 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject2D(pos1, pos2, pos3, pos4, true, multiplier, fgColor)); + } + + public void draw(RenderObject2D object) { + if (object == null) + throw new IllegalArgumentException(); + objects.add(object); + } + + public void draw(BufferedImage image, Vector2d pos, Vector2d endPos) { + if (image == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject2D(image,pos,endPos,multiplier)); + } + + public void draw(Vector2d[] vectors) { + if (vectors == null) + throw new IllegalArgumentException(); + if (vectors.length == 3) { + objects.add(new RenderObject2D(vectors[0], vectors[1], vectors[2], false, multiplier, fgColor)); + } + else if (vectors.length == 4) { + objects.add(new RenderObject2D(vectors[0], vectors[1], vectors[2], vectors[3], false, multiplier, fgColor)); + } + else + throw new IllegalArgumentException(); + } + + public void setColor(int hexColor) { + fgColor = hexColor; + } + + public Object render() { + BufferedImage image = new BufferedImage(x.get(), y.get(), BufferedImage.TYPE_INT_ARGB); + Graphics g = image.createGraphics(); + g.setColor(new Color(bgColor, true)); + g.fillRect(0, 0, x.get(), y.get()); + + render(g); + + g.dispose(); + + objects.clear(); + imageObjects.clear(); + + if (type == RenderOutputType.ARRAY2D) { + int[][] ints = new int[x.get()][y.get()]; + + for (int i = 0; i < x.get(); i++) { + for (int j = 0; j < y.get(); j++) { + ints[i][j] = image.getRGB(i, j); + } + } + + return ints; + } + if (type == RenderOutputType.ARRAY) { + int[] ints = new int[x.get() * y.get()]; + + int c = 0; + for (int i = 0; i < x.get(); i++) { + for (int j = 0; j < y.get(); j++) { + ints[c] = image.getRGB(i, j); + + c++; // YEET + } + } + + return ints; + } + if (type == RenderOutputType.BUFFEREDIMAGE) { + return image; + } + return null; + } + + void render(Graphics g) { + + for (int j = 0, objectsSize = objects.size(); j < objectsSize; j++) { + RenderObject2D o = objects.get(j); + o = o.clone(); + for (int i = 0; i < o.vectors.length; i++) { + o.vectors[i].add(offset); + } + g.setColor(new Color(o.color)); + + if (o.type == RenderObjectType.RECTANGLE) { + Vector2d pos1 = o.vectors[0]; + Vector2d pos2 = o.vectors[1]; + Vector2d pos3 = o.vectors[2]; + Vector2d pos4 = o.vectors[3]; + + g.drawLine((int) pos1.getX(), (int) pos1.getY(), (int) pos2.getX(), (int) pos2.getY()); + g.drawLine((int) pos2.getX(), (int) pos2.getY(), (int) pos3.getX(), (int) pos3.getY()); + g.drawLine((int) pos3.getX(), (int) pos3.getY(), (int) pos4.getX(), (int) pos4.getY()); + g.drawLine((int) pos4.getX(), (int) pos4.getY(), (int) pos1.getX(), (int) pos1.getY()); + } + + if (o.type == RenderObjectType.TRIANGLE) { + Vector2d pos1 = o.vectors[0]; + Vector2d pos2 = o.vectors[1]; + Vector2d pos3 = o.vectors[2]; + + g.drawLine((int) pos1.getX(), (int) pos1.getY(), (int) pos2.getX(), (int) pos2.getY()); + g.drawLine((int) pos2.getX(), (int) pos2.getY(), (int) pos3.getX(), (int) pos3.getY()); + g.drawLine((int) pos3.getX(), (int) pos3.getY(), (int) pos1.getX(), (int) pos1.getY()); + } + + if (o.type == RenderObjectType.FULL_RECTANGLE) { + int[][] rpos = Maths2D.getFillingCoordinatesForRectangle(o); + + g.fillPolygon(rpos[0], rpos[1], 4); + } + + if (o.type == RenderObjectType.FULL_TRIANGLE) { + int[][] rpos = Maths2D.getFillingCoordinatesForTriangle(o); + + g.fillPolygon(rpos[0], rpos[1], 3); + } + + if (o.type == RenderObjectType.IMAGE) { + g.drawImage(Maths2D.prepareImage( + o.image, + o.vectors[0], + o.vectors[1] + ), (int) o.vectors[0].getX(), (int) o.vectors[0].getY(), null); + } + } + + for (Object[] object : imageObjects) { + Vector2d pos = (Vector2d) object[0]; + Image image = (Image) object[1]; + int nx = (Integer) object[2]; + int ny = (Integer) object[3]; + boolean applyMultiplier = (Boolean) object[4]; + pos.add(offset); + + + new AdaptedGraphics(g).drawImage((int) (pos.getX() * (applyMultiplier ? multiplier : 1)), (int) (pos.getY() * (applyMultiplier ? multiplier : 1)), Maths2D.distortImage(image, nx, ny, applyMultiplier ? multiplier : 1)); + } + } + + +} diff --git a/src/main/java/de/tudbut/rendering/Projection3D.java b/src/main/java/de/tudbut/rendering/Projection3D.java new file mode 100644 index 0000000..5bcd643 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Projection3D.java @@ -0,0 +1,256 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector3d; +import de.tudbut.parsing.TudSort; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +public class Projection3D { + public final Vector3d offset = new Vector3d(0, 0, 0); + final AtomicInteger x; + final AtomicInteger y; + final RenderOutputType type; + final ArrayList objects = new ArrayList<>(); + int fgColor = 0x000000; + final int bgColor; + final double var0 = 20; + int zClip = -1; + double fovMod = 1; + + public Projection3D(AtomicInteger xSize, AtomicInteger ySize, RenderOutputType type, int backgroundColor) { + x = xSize; + y = ySize; + this.type = type; + bgColor = backgroundColor; + } + + public Projection3D(AtomicInteger xSize, AtomicInteger ySize, RenderOutputType type, int backgroundColor, int zClip) { + x = xSize; + y = ySize; + this.type = type; + bgColor = backgroundColor; + this.zClip = zClip; + } + + public void setFOVMod(double fovMod) { + this.fovMod = fovMod; + } + + public void drawTriangle(Vector3d pos1, Vector3d pos2, Vector3d pos3) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject3D(pos1, pos2, pos3, false, var0, fgColor)); + } + + public void drawRectangle(Vector3d pos1, Vector3d pos2, Vector3d pos3, Vector3d pos4) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + if (pos4 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject3D(pos1, pos2, pos3, pos4, false, var0, fgColor)); + } + + public void fillTriangle(Vector3d pos1, Vector3d pos2, Vector3d pos3) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject3D(pos1, pos2, pos3, true, var0, fgColor)); + } + + public void fillRectangle(Vector3d pos1, Vector3d pos2, Vector3d pos3, Vector3d pos4) { + if (pos1 == null) + throw new IllegalArgumentException(); + if (pos2 == null) + throw new IllegalArgumentException(); + if (pos3 == null) + throw new IllegalArgumentException(); + if (pos4 == null) + throw new IllegalArgumentException(); + objects.add(new RenderObject3D(pos1, pos2, pos3, pos4, true, var0, fgColor)); + } + + public void draw(RenderObject3D object) { + if (object == null) + throw new IllegalArgumentException(); + objects.add(object); + } + + public void draw(Vector3d[] vectors) { + if (vectors == null) + throw new IllegalArgumentException(); + if (vectors.length == 3) { + objects.add(new RenderObject3D(vectors[0], vectors[1], vectors[2], false, var0, fgColor)); + } + else if (vectors.length == 4) { + objects.add(new RenderObject3D(vectors[0], vectors[1], vectors[2], vectors[3], false, var0, fgColor)); + } + else + throw new IllegalArgumentException(); + } + + public void setColor(int hexColor) { + fgColor = hexColor; + } + + public Object render() { + BufferedImage image = new BufferedImage(x.get(), y.get(), BufferedImage.TYPE_INT_RGB); + Graphics g = image.createGraphics(); + g.setColor(new Color(bgColor)); + g.fillRect(0, 0, x.get(), y.get()); + + render(g); + + g.dispose(); + + objects.clear(); + + if (type == RenderOutputType.ARRAY2D) { + int[][] ints = new int[x.get()][y.get()]; + + for (int i = 0; i < x.get(); i++) { + for (int j = 0; j < y.get(); j++) { + ints[i][j] = image.getRGB(i, j); + } + } + + return ints; + } + if (type == RenderOutputType.ARRAY) { + int[] ints = new int[x.get() * y.get()]; + + int c = 0; + for (int i = 0; i < x.get(); i++) { + for (int j = 0; j < y.get(); j++) { + ints[c] = image.getRGB(i, j); + + c++; // YEET + } + } + + return ints; + } + if (type == RenderOutputType.BUFFEREDIMAGE) { + return image; + } + return null; + } + + void render(Graphics g) { + + RenderObject3D[] objects = this.objects.toArray(new RenderObject3D[0]); + + try { + objects = TudSort.sortDouble(objects, t -> { + double i = 0; + int j = 0; + + for (Vector3d vector : t.vectors) { + i += Math.max(vector.getX(), -vector.getX()); + j++; + } + + for (Vector3d vector : t.vectors) { + i += Math.max(vector.getY(), -vector.getY()); + j++; + } + + for (Vector3d vector : t.vectors) { + i += vector.getZ() * 5; + j++; + } + + i = i / j; + + return -i; + }); + } catch (Throwable ignored) { } + + for (int i = 0, objectsLength = objects.length; i < objectsLength; i++) { + RenderObject3D o = objects[i]; + if (o == null) + continue; + o = o.clone(); + for (Vector3d vector : o.vectors) { + vector.add(offset); + } + if (o.color >= 0 && o.color <= 0xffffff) { + g.setColor(new Color(o.color, false)); + } else + g.setColor(new Color(o.color, true)); + + if (o.type == RenderObjectType.RECTANGLE) { + Maths3D.prepareVectorsForRectangle(o, x.get(), y.get(), var0, fovMod); + + if (o.isClipped) { + continue; + } + + Vector3d pos1 = o.vectors[0]; + Vector3d pos2 = o.vectors[1]; + Vector3d pos3 = o.vectors[2]; + Vector3d pos4 = o.vectors[3]; + + g.drawLine((int) (pos1.getX()), (int) (pos1.getY()), (int) (pos2.getX()), (int) (pos2.getY())); + g.drawLine((int) (pos2.getX()), (int) (pos2.getY()), (int) (pos3.getX()), (int) (pos3.getY())); + g.drawLine((int) (pos3.getX()), (int) (pos3.getY()), (int) (pos4.getX()), (int) (pos4.getY())); + g.drawLine((int) (pos4.getX()), (int) (pos4.getY()), (int) (pos1.getX()), (int) (pos1.getY())); + } + + if (o.type == RenderObjectType.TRIANGLE) { + Maths3D.prepareVectorsForTriangle(o, x.get(), y.get(), var0, fovMod); + + if (o.isClipped) { + continue; + } + + Vector3d pos1 = o.vectors[0]; + Vector3d pos2 = o.vectors[1]; + Vector3d pos3 = o.vectors[2]; + + g.drawLine((int) (pos1.getX()), (int) (pos1.getY()), (int) (pos2.getX()), (int) (pos2.getY())); + g.drawLine((int) (pos2.getX()), (int) (pos2.getY()), (int) (pos3.getX()), (int) (pos3.getY())); + g.drawLine((int) (pos3.getX()), (int) (pos3.getY()), (int) (pos1.getX()), (int) (pos1.getY())); + } + + if (o.type == RenderObjectType.FULL_RECTANGLE) { + Maths3D.prepareVectorsForRectangle(o, x.get(), y.get(), var0, fovMod); + + if (o.isClipped) { + continue; + } + + int[][] rpos = Maths3D.getFillingCoordinatesForRectangle(o); + + g.fillPolygon(rpos[0], rpos[1], 4); + } + + if (o.type == RenderObjectType.FULL_TRIANGLE) { + Maths3D.prepareVectorsForTriangle(o, x.get(), y.get(), var0, fovMod); + + if (o.isClipped) { + continue; + } + + int[][] rpos = Maths3D.getFillingCoordinatesForTriangle(o); + + g.fillPolygon(rpos[0], rpos[1], 3); + } + } + } + + +} diff --git a/src/main/java/de/tudbut/rendering/Rectangle2D.java b/src/main/java/de/tudbut/rendering/Rectangle2D.java new file mode 100644 index 0000000..eb1087b --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Rectangle2D.java @@ -0,0 +1,30 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector2d; +import de.tudbut.obj.RelativeVector2d; + +public class Rectangle2D { + Vector2d pos; + RelativeVector2d end; + + public Rectangle2D(Vector2d pos, Vector2d size) { + construct(pos, new RelativeVector2d(pos, size)); + } + + private void construct(Vector2d pos, RelativeVector2d size) { + this.pos = pos.clone(); + this.end = size.clone(); + } + + public Vector2d getPos() { + return pos.clone(); + } + + public Vector2d getSize() { + return end.getRelativePos().clone(); + } + + public RelativeVector2d getEndPoint() { + return end.clone(); + } +} diff --git a/src/main/java/de/tudbut/rendering/Rectangle3D.java b/src/main/java/de/tudbut/rendering/Rectangle3D.java new file mode 100644 index 0000000..07cdfc4 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/Rectangle3D.java @@ -0,0 +1,44 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector3d; + +public class Rectangle3D { + Vector3d pos; + Vector3d size; + + public Rectangle3D(Vector3d pos, Vector3d size) { + construct(pos, size); + } + + private void construct(Vector3d pos, Vector3d size) { + this.pos = pos.clone(); + this.size = size.clone(); + } + + public void sort() { + if(size.getX() <= 0) { + pos.setX(pos.getX() - size.getX()); + size.setX(-size.getX()); + } + if(size.getY() <= 0) { + pos.setY(pos.getY() - size.getY()); + size.setY(-size.getY()); + } + if(size.getZ() <= 0) { + pos.setZ(pos.getZ() - size.getZ()); + size.setZ(-size.getZ()); + } + } + + public Vector3d getPos() { + return pos; + } + + public Vector3d getSize() { + return size; + } + + public Vector3d getEndPoint() { + return pos.clone().add(size); + } +} diff --git a/src/main/java/de/tudbut/rendering/RenderAssistant.java b/src/main/java/de/tudbut/rendering/RenderAssistant.java new file mode 100644 index 0000000..0e7ecdb --- /dev/null +++ b/src/main/java/de/tudbut/rendering/RenderAssistant.java @@ -0,0 +1,183 @@ +package de.tudbut.rendering; + +import de.tudbut.type.Vector2d; +import de.tudbut.type.Vector3d; + +import java.awt.*; + +public class RenderAssistant { + @Deprecated + public static void renderToGraphics(Graphics g, Projection3D projection) { + if (g == null) + throw new IllegalArgumentException(); + + projection.render(g); + } + + public static void renderToGraphics(Graphics g, Projection2D projection) { + if (g == null) + throw new IllegalArgumentException(); + + projection.render(g); + } + + @Deprecated + public static void drawRectangle(Projection3D projection, double[] doubles) { + if (doubles.length != 4 * 3) + throw new IllegalArgumentException("Length must be 4 * 3!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.drawRectangle( + new Vector3d(doubles[0], doubles[1], doubles[2]), + new Vector3d(doubles[3], doubles[4], doubles[5]), + new Vector3d(doubles[6], doubles[7], doubles[8]), + new Vector3d(doubles[9], doubles[10], doubles[11]) + ); + } + + @Deprecated + public static void fillRectangle(Projection3D projection, double[] doubles) { + if (doubles.length != 4 * 3) + throw new IllegalArgumentException("Length must be 4 * 3!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.fillRectangle( + new Vector3d(doubles[0], doubles[1], doubles[2]), + new Vector3d(doubles[3], doubles[4], doubles[5]), + new Vector3d(doubles[6], doubles[7], doubles[8]), + new Vector3d(doubles[9], doubles[10], doubles[11]) + ); + } + + public static void drawRectangle(Projection2D projection, double[] doubles) { + if (doubles.length != 4 * 2) + throw new IllegalArgumentException("Length must be 4 * 2!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.drawRectangle( + new Vector2d(doubles[0], doubles[1]), + new Vector2d(doubles[2], doubles[3]), + new Vector2d(doubles[4], doubles[5]), + new Vector2d(doubles[6], doubles[7]) + ); + } + + public static void fillRectangle(Projection2D projection, double[] doubles) { + if (doubles.length != 4 * 2) + throw new IllegalArgumentException("Length must be 4 * 2!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.fillRectangle( + new Vector2d(doubles[0], doubles[1]), + new Vector2d(doubles[2], doubles[3]), + new Vector2d(doubles[4], doubles[5]), + new Vector2d(doubles[6], doubles[7]) + ); + } + + public static void drawTriangle(Projection3D projection, double[] doubles) { + if (doubles.length != 3 * 3) + throw new IllegalArgumentException("Length must be 3 * 3!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.drawTriangle( + new Vector3d(doubles[0], doubles[1], doubles[2]), + new Vector3d(doubles[3], doubles[4], doubles[5]), + new Vector3d(doubles[6], doubles[7], doubles[8]) + ); + } + + public static void fillTriangle(Projection3D projection, double[] doubles) { + if (doubles.length != 3 * 3) + throw new IllegalArgumentException("Length must be 3 * 3!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.fillTriangle( + new Vector3d(doubles[0], doubles[1], doubles[2]), + new Vector3d(doubles[3], doubles[4], doubles[5]), + new Vector3d(doubles[6], doubles[7], doubles[8]) + ); + } + + public static void drawTriangle(Projection2D projection, double[] doubles) { + if (doubles.length != 3 * 2) + throw new IllegalArgumentException("Length must be 3 * 2!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.drawTriangle( + new Vector2d(doubles[0], doubles[1]), + new Vector2d(doubles[2], doubles[3]), + new Vector2d(doubles[4], doubles[5]) + ); + } + + public static void fillTriangle(Projection2D projection, double[] doubles) { + if (doubles.length != 3 * 2) + throw new IllegalArgumentException("Length must be 3 * 2!"); + if (projection == null) + throw new IllegalArgumentException("NULL"); + + projection.fillTriangle( + new Vector2d(doubles[0], doubles[1]), + new Vector2d(doubles[2], doubles[3]), + new Vector2d(doubles[4], doubles[5]) + ); + } + + public static double[] addOffset(double[] doubles, double xOffset, double yOffset, double zOffset) { + byte currentCoord = 0; + + for (int i = 0; i < doubles.length; i++) { + if (currentCoord == 0) + doubles[i] = doubles[i] + xOffset; + if (currentCoord == 1) + doubles[i] = doubles[i] + yOffset; + if (currentCoord == 2) { + doubles[i] = doubles[i] + zOffset; + currentCoord = -1; + } + currentCoord++; + } + + return doubles; + } + + public static double[] addOffset(double[] doubles, double xOffset, double yOffset) { + byte currentCoord = 0; + + for (int i = 0; i < doubles.length; i++) { + if (currentCoord == 0) + doubles[i] = doubles[i] + xOffset; + if (currentCoord == 1) { + doubles[i] = doubles[i] + yOffset; + currentCoord = -1; + } + currentCoord++; + } + + return doubles; + } + + public static Vector3d[] addOffset(Vector3d[] vectors, double xOffset, double yOffset, double zOffset) { + for (Vector3d vector : vectors) { + vector.set(vector.getX() + xOffset, vector.getY() + yOffset, vector.getZ() + zOffset); + } + + return vectors; + } + + public static Vector2d[] addOffset(Vector2d[] vectors, double xOffset, double yOffset) { + for (Vector2d vector : vectors) { + vector.set(vector.getX() + xOffset, vector.getY() + yOffset); + } + + return vectors; + } +} diff --git a/src/main/java/de/tudbut/rendering/RenderObject2D.java b/src/main/java/de/tudbut/rendering/RenderObject2D.java new file mode 100644 index 0000000..c20f4ad --- /dev/null +++ b/src/main/java/de/tudbut/rendering/RenderObject2D.java @@ -0,0 +1,67 @@ +package de.tudbut.rendering; + +import de.tudbut.tools.Tools; +import de.tudbut.type.Vector2d; + +import java.awt.image.BufferedImage; + +public class RenderObject2D { + private final double multiplier; + final Vector2d[] vectors; + final RenderObjectType type; + int color; + BufferedImage image; + + public RenderObject2D(Vector2d pos1, Vector2d pos2, Vector2d pos3, boolean fill, double multiplier, int color) { + type = fill ? RenderObjectType.FULL_TRIANGLE : RenderObjectType.TRIANGLE; + vectors = new Vector2d[]{pos1, pos2, pos3}; + this.multiplier = multiplier; + this.color = color; + + updateMultiplier(); + } + + public RenderObject2D(Vector2d pos1, Vector2d pos2, Vector2d pos3, Vector2d pos4, boolean fill, double multiplier, int color) { + type = fill ? RenderObjectType.FULL_RECTANGLE : RenderObjectType.RECTANGLE; + vectors = new Vector2d[]{pos1, pos2, pos3, pos4}; + this.multiplier = multiplier; + this.color = color; + + updateMultiplier(); + } + + public RenderObject2D(BufferedImage image, Vector2d pos, Vector2d size, double multiplier) { + type = RenderObjectType.IMAGE; + vectors = new Vector2d[]{pos, size}; + this.multiplier = multiplier; + this.image = image; + + updateMultiplier(); + } + + private RenderObject2D(Vector2d[] vectors, RenderObjectType type, double multiplier, int color, BufferedImage image) { + this.vectors = vectors; + this.type = type; + this.multiplier = multiplier; + this.color = color; + this.image = image; + } + + void updateMultiplier() { + for (Vector2d vec : vectors) { + vec.multiply(multiplier); + } + } + + @Override + public RenderObject2D clone() { + Vector2d[] lvectors = new Vector2d[vectors.length]; + Tools.copyArray(vectors, lvectors, vectors.length); + + for (int i = 0; i < lvectors.length; i++) { + lvectors[i] = lvectors[i].clone(); + } + + return new RenderObject2D(lvectors, type, multiplier, color, image); + } +} diff --git a/src/main/java/de/tudbut/rendering/RenderObject3D.java b/src/main/java/de/tudbut/rendering/RenderObject3D.java new file mode 100644 index 0000000..6f2145c --- /dev/null +++ b/src/main/java/de/tudbut/rendering/RenderObject3D.java @@ -0,0 +1,54 @@ +package de.tudbut.rendering; + +import de.tudbut.tools.Tools; +import de.tudbut.type.Vector3d; + +public class RenderObject3D { + final double multiplier; + final Vector3d[] vectors; + final RenderObjectType type; + final int color; + boolean isClipped; + + public RenderObject3D(Vector3d pos1, Vector3d pos2, Vector3d pos3, boolean fill, double multiplier, int color) { + type = fill ? RenderObjectType.FULL_TRIANGLE : RenderObjectType.TRIANGLE; + vectors = new Vector3d[]{pos1, pos2, pos3}; + this.multiplier = multiplier; + this.color = color; + + updateMultiplier(); + } + + public RenderObject3D(Vector3d pos1, Vector3d pos2, Vector3d pos3, Vector3d pos4, boolean fill, double multiplier, int color) { + type = fill ? RenderObjectType.FULL_RECTANGLE : RenderObjectType.RECTANGLE; + vectors = new Vector3d[]{pos1, pos2, pos3, pos4}; + this.multiplier = multiplier; + this.color = color; + + updateMultiplier(); + } + + private RenderObject3D(Vector3d[] vectors, RenderObjectType type, double multiplier, int color) { + this.vectors = vectors; + this.type = type; + this.multiplier = multiplier; + this.color = color; + } + + @Deprecated + private void updateMultiplier() { + + } + + @Override + public RenderObject3D clone() { + Vector3d[] lvectors = new Vector3d[vectors.length]; + Tools.copyArray(vectors, lvectors, vectors.length); + + for (int i = 0; i < lvectors.length; i++) { + lvectors[i] = lvectors[i].clone(); + } + + return new RenderObject3D(lvectors, type, multiplier, color); + } +} diff --git a/src/main/java/de/tudbut/rendering/RenderObjectType.java b/src/main/java/de/tudbut/rendering/RenderObjectType.java new file mode 100644 index 0000000..3d1801b --- /dev/null +++ b/src/main/java/de/tudbut/rendering/RenderObjectType.java @@ -0,0 +1,9 @@ +package de.tudbut.rendering; + +public enum RenderObjectType { + TRIANGLE, + RECTANGLE, + FULL_TRIANGLE, + FULL_RECTANGLE, + IMAGE +} diff --git a/src/main/java/de/tudbut/rendering/RenderOutputType.java b/src/main/java/de/tudbut/rendering/RenderOutputType.java new file mode 100644 index 0000000..fe59744 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/RenderOutputType.java @@ -0,0 +1,7 @@ +package de.tudbut.rendering; + +public enum RenderOutputType { + BUFFEREDIMAGE, + ARRAY2D, + ARRAY +} diff --git a/src/main/java/de/tudbut/rendering/tph/TPH300.java b/src/main/java/de/tudbut/rendering/tph/TPH300.java new file mode 100644 index 0000000..cd3c311 --- /dev/null +++ b/src/main/java/de/tudbut/rendering/tph/TPH300.java @@ -0,0 +1,63 @@ +package de.tudbut.rendering.tph; + +import de.tudbut.type.FInfo; +import de.tudbut.type.Vector3d; + +@FInfo(s = "TuddyProjection Helper 3D 00: Rotation") +public class TPH300 { + public static final int ID = 302; + public static final int X = ID + 1; + public static final int Y = ID + 2; + public static final int Z = ID + 3; + + public static Vector3d applyRotation(Vector3d vector, Vector3d rotPoint, int type, double angle) { + angle = (angle / 360) * Math.PI * 2; + type = type - ID; + switch (type) { + case 1: + Vector3d p1 = vector.clone(); + Vector3d c1 = rotPoint.clone(); + c1.negate(); + p1.add(c1); + p1.set( + p1.getX(), + p1.getY() * Math.cos(angle) + p1.getZ() * -Math.sin(angle), + p1.getY() * Math.sin(angle) + p1.getZ() * Math.cos(angle) + ); + c1.negate(); + p1.add(c1); + vector.set(p1); + return vector; + case 2: + Vector3d p2 = vector.clone(); + Vector3d c2 = rotPoint.clone(); + c2.negate(); + p2.add(c2); + p2.set( + p2.getX() * Math.cos(angle) + p2.getZ() * Math.sin(angle), + p2.getY(), + p2.getX() * -Math.sin(angle) + p2.getZ() * Math.cos(angle) + ); + c2.negate(); + p2.add(c2); + vector.set(p2); + return vector; + case 3: + Vector3d p3 = vector.clone(); + Vector3d c3 = rotPoint.clone(); + c3.negate(); + p3.add(c3); + p3.set( + p3.getX() * Math.cos(angle) + p3.getY() * -Math.sin(angle), + p3.getX() * Math.sin(angle) + p3.getY() * Math.cos(angle), + p3.getZ() + ); + c3.negate(); + p3.add(c3); + vector.set(p3); + return vector; + + } + return null; + } +} diff --git a/src/main/java/de/tudbut/rendering/tph/TPH301.java b/src/main/java/de/tudbut/rendering/tph/TPH301.java new file mode 100644 index 0000000..573366f --- /dev/null +++ b/src/main/java/de/tudbut/rendering/tph/TPH301.java @@ -0,0 +1,96 @@ +package de.tudbut.rendering.tph; + +import de.tudbut.type.FInfo; +import de.tudbut.type.Vector2d; +import de.tudbut.type.Vector3d; +import de.tudbut.io.DoubleBuffer; + +import static de.tudbut.tools.BufferManager.createResource; +import static de.tudbut.tools.BufferManager.getBufferFromID; +import static de.tudbut.rendering.tph.TPH300.*; + +@FInfo(s = "TuddyProjection Helper 3D 01: Cameras") +public class TPH301 { + public static final int ID = 301; + + public static long createCamera(Vector3d position, int rotX, int rotY) { + DoubleBuffer res = DoubleBuffer.create(6 * 8); + double[] doubles = (double[]) res.get(); + doubles[0] = position.getX(); + doubles[1] = position.getY(); + doubles[2] = position.getZ(); + doubles[3] = rotX; + doubles[4] = rotY; + return createResource(res); + } + + public static long createCamera(Vector3d position, int rotX, int rotY, int rotZ) { + DoubleBuffer res = DoubleBuffer.create(6 * 8); + double[] doubles = (double[]) res.get(); + doubles[0] = position.getX(); + doubles[1] = position.getY(); + doubles[2] = position.getZ(); + doubles[3] = rotX; + doubles[4] = rotY; + doubles[5] = rotZ; + return createResource(res); + } + + public static long createCamera(Vector3d position, Vector2d rot) { + DoubleBuffer res = DoubleBuffer.create(6 * 8); + double[] doubles = (double[]) res.get(); + doubles[0] = position.getX(); + doubles[1] = position.getY(); + doubles[2] = position.getZ(); + doubles[3] = rot.getX(); + doubles[4] = rot.getY(); + return createResource(res); + } + + public static long createCamera(Vector3d position, Vector3d rot) { + DoubleBuffer res = DoubleBuffer.create(6 * 8); + double[] doubles = (double[]) res.get(); + doubles[0] = position.getX(); + doubles[1] = position.getY(); + doubles[2] = position.getZ(); + doubles[3] = rot.getX(); + doubles[4] = rot.getY(); + doubles[5] = rot.getZ(); + return createResource(res); + } + + public static void setCameraPosition(Vector3d pos, long cam) { + double[] d = (double[]) getBufferFromID(cam).get(); + d[0] = pos.getX(); + d[1] = pos.getY(); + d[2] = pos.getZ(); + } + + public static void setCameraRotation(Vector2d rot, long cam) { + double[] d = (double[]) getBufferFromID(cam).get(); + d[3] = rot.getX(); + d[4] = rot.getY(); + } + + public static void setCameraRotation(Vector3d rot, long cam) { + double[] d = (double[]) getBufferFromID(cam).get(); + d[3] = rot.getX(); + d[4] = rot.getY(); + d[5] = rot.getZ(); + } + + public static Vector3d translate(Vector3d vec, long camera) { + DoubleBuffer cam = (DoubleBuffer) getBufferFromID(camera); + double[] d = (double[]) cam.get(); + Vector3d pos = new Vector3d(-d[0], d[1], d[2]); + double rotX = d[3]; + double rotY = d[4]; + double rotZ = d[5]; + + applyRotation(vec, pos.clone().negate(), Y, -rotX); + applyRotation(vec, pos.clone().negate(), X, rotY); + applyRotation(vec, pos.clone().negate(), Z, rotZ); + vec.add(pos); + return vec; + } +} diff --git a/src/main/java/de/tudbut/rendering/tph/TPH302.java b/src/main/java/de/tudbut/rendering/tph/TPH302.java new file mode 100644 index 0000000..4a8ed0e --- /dev/null +++ b/src/main/java/de/tudbut/rendering/tph/TPH302.java @@ -0,0 +1,37 @@ +package de.tudbut.rendering.tph; + +import de.tudbut.rendering.Projection3D; +import de.tudbut.type.Vector2d; +import de.tudbut.type.FInfo; +import de.tudbut.type.Vector3d; + +import java.awt.*; +import java.awt.image.BufferedImage; + +import static de.tudbut.rendering.tph.TPH301.*; +import static de.tudbut.rendering.tph.TPH300.*; + +@FInfo(s = "TuddyProjection Helper 3D 02: Textures") +public class TPH302 { + public static final int ID = 303; + + public static void renderTexturedRectangle(Vector3d offset, Vector2d size, Vector3d rot, Vector3d rotPoint, long camera, Projection3D projection3D, Image image) { + BufferedImage img = (BufferedImage) image; + + for (int y = 0; y < img.getHeight(); y++) { + for (int x = 0; x < img.getWidth(); x++) { + projection3D.setColor(img.getRGB(x,y)); + projection3D.fillRectangle( + translate(applyRotations(new Vector3d(offset.getX() + (size.getX() / img.getWidth()) * x, offset.getY() + (size.getY() / img.getHeight()) * y, offset.getZ()), rot, rotPoint), camera), + translate(applyRotations(new Vector3d(offset.getX() + (size.getX() / img.getWidth()) * (x + 1), offset.getY() + (size.getY() / img.getHeight()) * (y), offset.getZ()), rot, rotPoint), camera), + translate(applyRotations(new Vector3d(offset.getX() + (size.getX() / img.getWidth()) * (x + 1), offset.getY() + (size.getY() / img.getHeight()) * (y + 1), offset.getZ()), rot, rotPoint), camera), + translate(applyRotations(new Vector3d(offset.getX() + (size.getX() / img.getWidth()) * (x), offset.getY() + (size.getY() / img.getHeight()) * (y + 1), offset.getZ()), rot, rotPoint), camera) + ); + } + } + } + + private static Vector3d applyRotations(Vector3d vec, Vector3d rot, Vector3d rotPoint) { + return applyRotation(applyRotation(applyRotation(vec, rotPoint, Z, rot.getZ()), rotPoint, Y, rot.getY()), rotPoint, X, rot.getX()); + } +} diff --git a/src/main/java/de/tudbut/security/AccessKiller.java b/src/main/java/de/tudbut/security/AccessKiller.java new file mode 100644 index 0000000..e645802 --- /dev/null +++ b/src/main/java/de/tudbut/security/AccessKiller.java @@ -0,0 +1,133 @@ +package de.tudbut.security; + +import de.tudbut.tools.ReflectUtil; + +import java.lang.ref.SoftReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class AccessKiller { + + private static final Field reflectionData; + + static { + try { + reflectionData = getField(Class.class.getDeclaredField("reflectionData")); + } catch (NoSuchFieldException e) { + throw new InternalError(e); + } + } + + private static Field getField(Field f) { + ReflectUtil.forceAccessible(f); + return f; + } + + private static Object getReflectionData(Class clazz) throws ReflectiveOperationException { + // make sure ReflectionData is populated + clazz.getDeclaredMethods(); + clazz.getDeclaredFields(); + clazz.getDeclaredConstructors(); + clazz.getInterfaces(); + + SoftReference data = (SoftReference) reflectionData.get(clazz); + Object reflectionData = data.get(); + assert reflectionData != null; + return reflectionData; + } + + /** + * WARNING!!! Can only erase private fields!! + * @param clazz Class to kill fields of + * @param fieldsToKill Field names to kill, or empty to kill all. + */ + public static void killFieldAccess(Class clazz, String... fieldsToKill) { + List toKill = Arrays.asList(fieldsToKill); + try { + Object reflectionData = getReflectionData(clazz); + Field data = getField(reflectionData.getClass().getDeclaredField("declaredFields")); + List fields = new ArrayList<>(Arrays.asList((Field[]) data.get(reflectionData))); + if(!toKill.isEmpty()) { + for (int i = 0; i < fields.size(); i++) { + if (toKill.contains(fields.get(i).getName())) + fields.remove(i--); + } + } + else { + fields.clear(); + } + data.set(reflectionData, fields.toArray(new Field[0])); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * WARNING!!! Can only erase private methods!! + * @param clazz Class to kill methods of + * @param methodsToKill Method names to kill, or empty to kill all. + */ + public static void killMethodAccess(Class clazz, String... methodsToKill) { + List toKill = Arrays.asList(methodsToKill); + try { + Object reflectionData = getReflectionData(clazz); + Field data = getField(reflectionData.getClass().getDeclaredField("declaredMethods")); + List methods = new ArrayList<>(Arrays.asList((Method[]) data.get(reflectionData))); + if(!toKill.isEmpty()) { + for (int i = 0; i < methods.size(); i++) { + if (toKill.contains(methods.get(i).getName())) + methods.remove(i--); + } + } + else { + methods.clear(); + } + data.set(reflectionData, methods.toArray(new Method[0])); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * WARNING!!! Can only erase private constructors!! + * @param clazz Class to kill constructors of + */ + public static void killConstructorAccess(Class clazz) { + try { + Object reflectionData = getReflectionData(clazz); + Field data = getField(reflectionData.getClass().getDeclaredField("declaredConstructors")); + data.set(reflectionData, new Constructor[0]); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void killReflectionFor(Class... classes) { + for (Class clazz : classes) { + killConstructorAccess(clazz); + killMethodAccess(clazz); + killFieldAccess(clazz); + } + } + + /** + * Stops any code from making further changes to reflectionData. + * This also stops any further AccessKiller calls.
+ * Use with EXTREME caution!! + */ + public static void killClassReflection() { + killReflectionFor(Class.class); + } + + /** + * Kills access to possible ways to restore reflective access after it has been removed. + * This should prevent all other ways of accessing fields, but other ways may exist. + */ + public static void ensureKills() { + killMethodAccess(Class.class, "getDeclaredFields0", "getDeclaredMethods0", "getDeclaredConstructors0"); + } +} diff --git a/src/main/java/de/tudbut/security/DataKeeper.java b/src/main/java/de/tudbut/security/DataKeeper.java new file mode 100644 index 0000000..d44d9d1 --- /dev/null +++ b/src/main/java/de/tudbut/security/DataKeeper.java @@ -0,0 +1,137 @@ +package de.tudbut.security; + +import de.tudbut.obj.DoubleTypedObject; +import de.tudbut.tools.Lock; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Supplier; + +// Keeps some data as safe as possible, unable to be accessed even by reflection +public class DataKeeper { + public static boolean forgetAll = false; + + + public Lock forget = new Lock(true); + private final PermissionManager permissionManager; + private Supplier dataInsertion; + private final Strictness strictness; + private final Lock lock = new Lock(true); + private final Queue>, Lock>> nextFunctionToRun = new LinkedList<>(); + private final Thread keeper = new Thread(this::keep, "DataKeeper"); { keeper.start(); } + + static { initSecurity(); } + + private static void initSecurity() { + // this should prevent any reflection, but is not a 100% guarantee! + AccessKiller.killReflectionFor(DataKeeper.class, Accessor.class); + } + + public DataKeeper(PermissionManager permissionManager, Strictness strictness, T toKeep) { + // make sure reflection is killed for it + permissionManager.killReflection(); + + this.permissionManager = permissionManager; + dataInsertion = () -> toKeep; + this.strictness = strictness; + lock.unlock(); + } + + public Strictness getStrictness() { + return strictness.clone(); + } + + public void access(Consumer> accessor) { + if(!forget.isLocked()) { + throw new IllegalStateException("This DataKeeper has already forgotten its value."); + } + if(!permissionManager.checkCaller(strictness)) { + if(permissionManager.showErrors()) + throw new IllegalAccessError("The active PermissionManager does not allow you to access this DataKeeper."); + else + return; + } + Lock waitLock = new Lock(true); + nextFunctionToRun.add(new DoubleTypedObject<>(accessor, waitLock)); + lock.unlock(); + waitLock.waitHere(500); + } + + public void forget() { + forget.unlock(); + } + + public DataKeeper forgetIn(int ms) { + if(forget.timeLeft() > ms) + return this; + forget.lock(ms); + return this; + } + + private void keep() { + lock.waitHere(); + lock.lock(); + PermissionManager permissionManager = this.permissionManager.clone(); + AtomicReference data = new AtomicReference<>(dataInsertion.get()); + Strictness strictness = this.strictness.clone(); + dataInsertion = null; + while(!forgetAll && forget.isLocked()) { + lock.waitHere(); + lock.lock(500); + + DoubleTypedObject>, Lock> itm = nextFunctionToRun.poll(); + if(itm == null) + continue; + Consumer> toRun = itm.o; + Lock lock = itm.t; + // second layer of protection, crashes this time. + if(!permissionManager.checkLambda(strictness, toRun)) + permissionManager.crash(strictness); + + toRun.accept(new Accessor<>(permissionManager, strictness, data)); + lock.unlock(); + } + } + + // A very last, third layer of protection, not actually that necessary. + public static class Accessor { + // The accessor will only ever be in local variables, so it does + // not have to be reflection-safe. But it is anyway due to AccessKiller. + private final PermissionManager permissionManager; + private final AtomicReference value; + private final Strictness strictness; + + public Accessor(PermissionManager permissionManager, Strictness strictness, AtomicReference data) { + this.permissionManager = permissionManager; + this.strictness = strictness; + value = data; + } + + public T getValue() { + if(permissionManager.checkCaller(strictness)) + return value.get(); + else { + // crash soon + new Thread(() -> { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + permissionManager.crash(strictness); + }).start(); + // generate a weird error + return (T) value.get().getClass().cast(new Object()); + } + } + + public T setValue(T newValue) { + // check is in getValue + T old = getValue(); + value.set(newValue); + return old; + } + } +} diff --git a/src/main/java/de/tudbut/security/ExtendedStrictness.java b/src/main/java/de/tudbut/security/ExtendedStrictness.java new file mode 100644 index 0000000..65ab896 --- /dev/null +++ b/src/main/java/de/tudbut/security/ExtendedStrictness.java @@ -0,0 +1,25 @@ +package de.tudbut.security; + +public class ExtendedStrictness implements Strictness { + static { + AccessKiller.killReflectionFor(ExtendedStrictness.class); + } + private final Strictness primary, secondary; + + public ExtendedStrictness(Strictness primary, Strictness secondary) { + this.primary = primary; + this.secondary = secondary; + } + + @Override + public Object getRawProperty(String name) { + if(primary.hasProperty(name)) + return primary.getRawProperty(name); + return secondary.getRawProperty(name); + } + + @Override + public Strictness clone() { + return new ExtendedStrictness(primary.clone(), secondary.clone()); + } +} diff --git a/src/main/java/de/tudbut/security/PermissionManager.java b/src/main/java/de/tudbut/security/PermissionManager.java new file mode 100644 index 0000000..f058604 --- /dev/null +++ b/src/main/java/de/tudbut/security/PermissionManager.java @@ -0,0 +1,71 @@ +package de.tudbut.security; + +import de.tudbut.tools.ReflectUtil; + +import java.lang.reflect.Method; +import java.lang.reflect.Field; +import java.util.Arrays; + +public interface PermissionManager extends Cloneable { + boolean checkCaller(Strictness strictnessLevel); + + boolean checkLambda(Strictness strictnessLevel, T lambda); + + default void crash(Strictness strictnessLevel) { + DataKeeper.forgetAll = true; + try { + Class shutdownClass = Class.forName("java.lang.Shutdown"); + Method exitMethod = shutdownClass.getDeclaredMethod("exit", int.class); + ReflectUtil.forceAccessible(exitMethod); + exitMethod.invoke(null, 1); + } catch (Exception ignored) {} + System.exit(1); + throw new Error(); + } + + default boolean showErrors() { + return true; + } + + default void killReflection() { + Class clazz = getClass(); + while(Arrays.stream(clazz.getInterfaces()).anyMatch(x -> x == PermissionManager.class)) { + AccessKiller.killReflectionFor(clazz); + clazz = clazz.getSuperclass(); + } + } + + PermissionManager clone(); + + default String getClassName(Class clazz) { + return getClassName(clazz, null, 0); + } + default String getClassName(Class clazz, boolean[] cache, int idx) { + if(cache != null && cache[0]) + return clazz.getName(); + try { + // Reset the name field so that it must be cached again + Field nameField = clazz.getClass().getDeclaredField("name"); + ReflectUtil.forceAccessible(nameField); + nameField.set(clazz, null); + // name is clean, getName can now be used. + if(cache != null) + cache[idx] = true; + return clazz.getName(); + } + catch(Exception e) { + // name can't be cleaned, getName can't be used. + if(cache != null) + cache[idx] = false; + try { + // Unable to reset the name field, invoking the native that gets the name directly + Method initClassName = clazz.getClass().getDeclaredMethod("initClassName"); + ReflectUtil.forceAccessible(initClassName); + return (String) initClassName.invoke(clazz); + } + catch(Exception e1) { + return null; + } + } + } +} diff --git a/src/main/java/de/tudbut/security/Strictness.java b/src/main/java/de/tudbut/security/Strictness.java new file mode 100644 index 0000000..46a898b --- /dev/null +++ b/src/main/java/de/tudbut/security/Strictness.java @@ -0,0 +1,30 @@ +package de.tudbut.security; + +public interface Strictness extends Cloneable { + + Object getRawProperty(String name); + + default T getProperty(String name) { + return (T) getRawProperty(name); + } + default boolean getBoolProperty(String name) { + Boolean b = getProperty(name); + if(b == null) + return false; + return b; + } + default String getStringProperty(String name) { + return getProperty(name); + } + default int getIntProperty(String name) { + return getProperty(name); + } + default boolean hasProperty(String name) { + return getRawProperty(name) != null; + } + default Strictness extend(Strictness newPrimary) { + return new ExtendedStrictness(newPrimary, this); + } + + Strictness clone(); +} diff --git a/src/main/java/de/tudbut/security/StrictnessBuilder.java b/src/main/java/de/tudbut/security/StrictnessBuilder.java new file mode 100644 index 0000000..f5b772e --- /dev/null +++ b/src/main/java/de/tudbut/security/StrictnessBuilder.java @@ -0,0 +1,68 @@ +package de.tudbut.security; + +import de.tudbut.parsing.TCN; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class StrictnessBuilder { + static { + AccessKiller.killReflectionFor(StrictnessBuilder.class); + } + + private final HashMap properties = new HashMap<>(); + + public static StrictnessBuilder create() { + return new StrictnessBuilder(); + } + + public static Strictness empty() { + return new StrictnessBuilder().build(); + } + + public StrictnessBuilder property(String s, Object o) { + properties.put(s, o); + return this; + } + + public StrictnessBuilder fromTCN(TCN tcn) { + properties.putAll(tcn.toMap()); + return this; + } + + public StrictnessBuilder tryFromStrictness(Strictness strictness) { + if(strictness instanceof StrictnessImpl) { + properties.putAll((((StrictnessImpl) strictness).properties)); + return this; + } + // error + return null; + } + + public Strictness build() { + return new StrictnessImpl((HashMap) properties.clone()); + } + + private static class StrictnessImpl implements Strictness { + static { + AccessKiller.killReflectionFor(StrictnessImpl.class); + } + + private final HashMap properties; + + public StrictnessImpl(HashMap properties) { + this.properties = properties; + } + + @Override + public Object getRawProperty(String name) { + return properties.get(name); + } + + @Override + public Strictness clone() { + return new StrictnessImpl((HashMap) properties.clone()); + } + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/AllowAllRestriction.java b/src/main/java/de/tudbut/security/permissionmanager/AllowAllRestriction.java new file mode 100644 index 0000000..5409dc4 --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/AllowAllRestriction.java @@ -0,0 +1,21 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +public class AllowAllRestriction implements PermissionManager { + @Override + public boolean checkCaller(Strictness strictnessLevel) { + return true; + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + return true; + } + + @Override + public PermissionManager clone() { + return this; // stateless + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/CallClassRestriction.java b/src/main/java/de/tudbut/security/permissionmanager/CallClassRestriction.java new file mode 100644 index 0000000..7e9049c --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/CallClassRestriction.java @@ -0,0 +1,65 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Supported strictness properties: + * - Restriction.CallClass.MaxDistance (int): How far down the stack trace should the restriction look until it fails + * - Restriction.CallClass.RestrictLambda (bool): If the restriction should apply to lambdas. If true, ONLY classes in the + * allowlist pass, instead of allowing the allowed classes to call "through" others. + */ +public class CallClassRestriction extends Restriction { + + private final Set allow; + + public CallClassRestriction(PermissionManager parent, Class... allowFromClasses) { + super(parent); + allow = Collections.unmodifiableSet(Arrays.stream(allowFromClasses).map(this::getClassName).collect(Collectors.toSet())); + } + public CallClassRestriction(Class... allowFromClasses) { + this(null, allowFromClasses); + } + + @Override + public boolean checkCaller(Strictness strictnessLevel) { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + + if(strictnessLevel.hasProperty("Restriction.CallClass.MaxDistance")) { + int maxDist = strictnessLevel.getIntProperty("Restriction.CallClass.MaxDistance"); + if(st.length > maxDist) { + StackTraceElement[] elements = new StackTraceElement[maxDist]; + System.arraycopy(st, 0, elements, 0, maxDist); + st = elements; + } + } + + boolean isCalledByAllowed = false; + for (StackTraceElement element : st) { + if (allow.contains(element.getClassName())) { + isCalledByAllowed = true; + break; + } + } + return isCalledByAllowed && super.checkCaller(strictnessLevel); + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + boolean b = true; + if(strictnessLevel.getBoolProperty("Restriction.CallClass.RestrictLambda")) { + // might get more complex soon. + // is class, inner class of it, loaded by it, or lambda in it? + Class enclosingClass = lambda.getClass().getEnclosingClass(); + boolean[] cache = new boolean[2]; + b = allow.contains(getClassName(lambda.getClass(), cache, 0)) + || allow.contains(getClassName(lambda.getClass(), cache, 0).replaceAll("\\$\\$Lambda.*$", "")); + if (enclosingClass != null) + b = b || allow.contains(getClassName(enclosingClass)); + } + return b && super.checkLambda(strictnessLevel, lambda); + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/ClassLoaderRestriction.java b/src/main/java/de/tudbut/security/permissionmanager/ClassLoaderRestriction.java new file mode 100644 index 0000000..edb1feb --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/ClassLoaderRestriction.java @@ -0,0 +1,108 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; +import de.tudbut.tools.ReflectUtil; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Only allows classes loaded by a certain class loader, and the classloader itself. + * + * Supported strictness properties: + * - Restriction.ClassLoader.MaxDistance (int): How far down the stack trace should the restriction look until it fails + * - Restriction.ClassLoader.RestrictLambda (bool): If the restriction should apply to lambdas. If true, ONLY classes in the + * allowlist pass, instead of allowing the allowed classes to call "through" others. + */ +public class ClassLoaderRestriction extends Restriction { + private final Set allow; + + public ClassLoaderRestriction(PermissionManager parent, ClassLoader... allowFromClassLoaders) { + super(parent); + this.allow = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(allowFromClassLoaders))); + } + + public ClassLoaderRestriction(ClassLoader... allowFromClassLoaders) { + this(null, allowFromClassLoaders); + } + + @Override + public boolean checkCaller(Strictness strictnessLevel) { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + + if(strictnessLevel.hasProperty("Restriction.ClassLoader.MaxDistance")) { + int maxDist = strictnessLevel.getIntProperty("Restriction.ClassLoader.MaxDistance"); + if(st.length > maxDist) { + StackTraceElement[] elements = new StackTraceElement[maxDist]; + System.arraycopy(st, 0, elements, 0, maxDist); + st = elements; + } + } + + boolean isCalledByAllowed = false; + for (StackTraceElement element : st) { + try { + Class cls = getClassObject(element.getClassName()); + // is the classloader or loaded by it? + if(allow.stream().anyMatch(x -> x.getClass() == cls) || allow.contains(cls.getClassLoader())) { + isCalledByAllowed = true; + break; + } + } catch (Exception e) { + // it'll just stay false + } + } + return isCalledByAllowed && super.checkCaller(strictnessLevel); + } + + private Class getClassObject(String className) throws ClassNotFoundException { + try { + Method findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); + ReflectUtil.forceAccessible(findLoadedClass); + for (ClassLoader allowed : allow) { + Class clazz = (Class) findLoadedClass.invoke(allowed, className); + if(clazz != null) { + return clazz; + } + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + return Class.forName(className); + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + boolean b = true; + if(strictnessLevel.getBoolProperty("Restriction.ClassLoader.RestrictLambda")) { + // might get more complex soon. + // is classloader, inner class of it, or loaded by it? + + + //noinspection SuspiciousMethodCalls + b = allow.contains(lambda) + || allow.contains(lambda.getClass().getClassLoader()); + + // is enclosed class (e.g. anonymous class) + Class enclosingClass = lambda.getClass().getEnclosingClass(); + if (enclosingClass != null) + b = b || allow.stream().anyMatch(x -> x.getClass() == enclosingClass); + + // is lambda in allowed class? + boolean[] cache = new boolean[1]; + String name = getClassName(lambda.getClass(), cache, 0).replaceAll("\\$\\$Lambda.*$", ""); + b = b || allow.stream().anyMatch(x -> getClassName(x.getClass(), cache, 0).equals(name)); // is lambda in classloader + try { + b = b || allow.contains(getClassObject(name).getClassLoader()); // is lambda in classloader-loaded class + } catch (Exception e) { + // it'll just stay false + } + } + return b && super.checkLambda(strictnessLevel, lambda); + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/DenyAllRestriction.java b/src/main/java/de/tudbut/security/permissionmanager/DenyAllRestriction.java new file mode 100644 index 0000000..f0c013a --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/DenyAllRestriction.java @@ -0,0 +1,21 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +public class DenyAllRestriction implements PermissionManager { + @Override + public boolean checkCaller(Strictness strictnessLevel) { + return false; + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + return false; + } + + @Override + public PermissionManager clone() { + return this; // stateless + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/HideErrorRestriction.java b/src/main/java/de/tudbut/security/permissionmanager/HideErrorRestriction.java new file mode 100644 index 0000000..88ef947 --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/HideErrorRestriction.java @@ -0,0 +1,14 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; + +public class HideErrorRestriction extends Restriction { + public HideErrorRestriction(PermissionManager parent) { + super(parent); + } + + @Override + public boolean showErrors() { + return false; // stateless + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/PermissionAND.java b/src/main/java/de/tudbut/security/permissionmanager/PermissionAND.java new file mode 100644 index 0000000..0a020a3 --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/PermissionAND.java @@ -0,0 +1,50 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +/** + * Equivalent to setting primary's parent to secondary. + */ +public class PermissionAND implements PermissionManager { + + private final PermissionManager primary, secondary; + + public PermissionAND(PermissionManager primary, PermissionManager secondary) { + this.primary = primary; + this.secondary = secondary; + } + + @Override + public boolean checkCaller(Strictness strictnessLevel) { + return primary.checkCaller(strictnessLevel) && secondary.checkCaller(strictnessLevel); + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + return primary.checkLambda(strictnessLevel, lambda) && secondary.checkLambda(strictnessLevel, lambda); + } + + @Override + public void crash(Strictness strictnessLevel) { + primary.crash(strictnessLevel); + secondary.crash(strictnessLevel); + } + + @Override + public boolean showErrors() { + return primary.showErrors() && secondary.showErrors(); + } + + @Override + public void killReflection() { + PermissionManager.super.killReflection(); + primary.killReflection(); + secondary.killReflection(); + } + + @Override + public PermissionManager clone() { + return new PermissionAND(primary.clone(), secondary.clone()); + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/PermissionOR.java b/src/main/java/de/tudbut/security/permissionmanager/PermissionOR.java new file mode 100644 index 0000000..36bf983 --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/PermissionOR.java @@ -0,0 +1,47 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +public class PermissionOR implements PermissionManager { + + private final PermissionManager primary, secondary; + + public PermissionOR(PermissionManager primary, PermissionManager secondary) { + this.primary = primary; + this.secondary = secondary; + } + + @Override + public boolean checkCaller(Strictness strictnessLevel) { + return primary.checkCaller(strictnessLevel) || secondary.checkCaller(strictnessLevel); + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + return primary.checkLambda(strictnessLevel, lambda) || secondary.checkLambda(strictnessLevel, lambda); + } + + @Override + public void crash(Strictness strictnessLevel) { + primary.crash(strictnessLevel); + secondary.crash(strictnessLevel); + } + + @Override + public boolean showErrors() { + return primary.showErrors() || secondary.showErrors(); + } + + @Override + public void killReflection() { + PermissionManager.super.killReflection(); + primary.killReflection(); + secondary.killReflection(); + } + + @Override + public PermissionManager clone() { + return new PermissionOR(primary.clone(), secondary.clone()); + } +} diff --git a/src/main/java/de/tudbut/security/permissionmanager/Restriction.java b/src/main/java/de/tudbut/security/permissionmanager/Restriction.java new file mode 100644 index 0000000..7d2ee26 --- /dev/null +++ b/src/main/java/de/tudbut/security/permissionmanager/Restriction.java @@ -0,0 +1,47 @@ +package de.tudbut.security.permissionmanager; + +import de.tudbut.security.PermissionManager; +import de.tudbut.security.Strictness; + +public abstract class Restriction implements PermissionManager { + + protected PermissionManager parent; + + public Restriction(PermissionManager parent) { + if(parent == null) + parent = new AllowAllRestriction(); + this.parent = parent; + } + + @Override + public boolean checkCaller(Strictness strictnessLevel) { + return parent.checkCaller(strictnessLevel); + } + + @Override + public boolean checkLambda(Strictness strictnessLevel, T lambda) { + return parent.checkLambda(strictnessLevel, lambda); + } + + @Override + public void crash(Strictness strictnessLevel) { + parent.crash(strictnessLevel); + } + + @Override + public void killReflection() { + parent.killReflection(); + PermissionManager.super.killReflection(); + } + + @Override + public PermissionManager clone() { + try { + Restriction cloned = (Restriction) super.clone(); + cloned.parent = parent.clone(); + return cloned; + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/de/tudbut/timer/AsyncCatcher.java b/src/main/java/de/tudbut/timer/AsyncCatcher.java new file mode 100644 index 0000000..a286bad --- /dev/null +++ b/src/main/java/de/tudbut/timer/AsyncCatcher.java @@ -0,0 +1,5 @@ +package de.tudbut.timer; + +public interface AsyncCatcher { + void run(Exception theException) throws Exception; +} diff --git a/src/main/java/de/tudbut/timer/AsyncRunnable.java b/src/main/java/de/tudbut/timer/AsyncRunnable.java new file mode 100644 index 0000000..d2865a0 --- /dev/null +++ b/src/main/java/de/tudbut/timer/AsyncRunnable.java @@ -0,0 +1,5 @@ +package de.tudbut.timer; + +public interface AsyncRunnable { + T run() throws Exception; +} diff --git a/src/main/java/de/tudbut/timer/AsyncTask.java b/src/main/java/de/tudbut/timer/AsyncTask.java new file mode 100644 index 0000000..4d92cd9 --- /dev/null +++ b/src/main/java/de/tudbut/timer/AsyncTask.java @@ -0,0 +1,111 @@ +package de.tudbut.timer; + +import de.tudbut.tools.Lock; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicReference; + +import static de.tudbut.tools.BetterJ.t; + +public class AsyncTask { + private final AtomicReference backValue = new AtomicReference<>(); + private Exception exception = null; + private boolean isCatched = false; + private AsyncCatcher catcher; + private AsyncThenRunnable thenRunnable = null; + + // Has the program finished? (Useful to know if it hit the timeout or exited.) + private volatile boolean done = false; + // We want efficiency, don't use a while loop to wait! + private final Lock threadLock = new Lock(); + + // When to force termination + private long timeout = -1; + // Start of the execution + private long startTime = new Date().getTime(); + + public AsyncTask(AsyncRunnable runnable) { + threadLock.lock(); + Lock stopperLock = new Lock(true); + Lock sLock = new Lock(true); + Thread runner = t(() -> { + startTime = new Date().getTime(); + stopperLock.waitHere(); + sLock.unlock(); + + startTime = new Date().getTime(); + try { + backValue.set(runnable.run()); + } + catch (Exception e) { + exception = e; + if (!isCatched) + exception.printStackTrace(); + else { + try { + catcher.run(exception); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + done = true; + done(); + }); + Thread stopper = t(() -> { + while (sLock.isLocked()) + stopperLock.unlock(); + while ((new Date().getTime() < startTime + timeout || timeout == -1) && !done) { + threadLock.waitHere(1); + } + if(!done) { + runner.stop(); + done(); + } + }); + } + + private void done() { + if (thenRunnable != null) { + try { + thenRunnable.run(backValue.get()); + } + catch (Exception e) { + exception = e; + if (!isCatched) + exception.printStackTrace(); + else { + try { + catcher.run(exception); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + } + } + done = true; + threadLock.unlock(); + } + + public void catchExceptions(AsyncCatcher catcher) { + this.catcher = catcher; + isCatched = true; + } + + public void then(AsyncThenRunnable runnable) { + this.thenRunnable = runnable; + } + + public T waitForFinish(int waitTimeout) { + threadLock.waitHere(waitTimeout); + + return backValue.get(); + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } +} diff --git a/src/main/java/de/tudbut/timer/AsyncThenRunnable.java b/src/main/java/de/tudbut/timer/AsyncThenRunnable.java new file mode 100644 index 0000000..a4fd20f --- /dev/null +++ b/src/main/java/de/tudbut/timer/AsyncThenRunnable.java @@ -0,0 +1,5 @@ +package de.tudbut.timer; + +public interface AsyncThenRunnable { + void run(T theValue) throws Exception; +} diff --git a/src/main/java/de/tudbut/timer/SyncQueue.java b/src/main/java/de/tudbut/timer/SyncQueue.java new file mode 100644 index 0000000..8bee814 --- /dev/null +++ b/src/main/java/de/tudbut/timer/SyncQueue.java @@ -0,0 +1,112 @@ +package de.tudbut.timer; + +import de.tudbut.tools.ThrowingRunnable; +import de.tudbut.tools.Queue; + +import java.util.ArrayList; +import java.util.Date; +import java.util.concurrent.atomic.AtomicBoolean; + +public class SyncQueue { + private final Queue queue = new Queue<>(); + private final Queue immediateQueue = new Queue<>(); + private final ArrayList timers = new ArrayList<>(); + + { + initAndRunAsynchronously(); + } + + public void add(ThrowingRunnable runnable) { + if(runnable == null) + throw new IllegalArgumentException(); + queue.add(runnable); + } + + public void addImmediate(ThrowingRunnable runnable) { + if(runnable == null) + throw new IllegalArgumentException(); + immediateQueue.add(runnable); + } + + public void addTimer(Ticker ticker, AtomicBoolean stop, int delay) { + addTimer(new Ticker() { + public int getDelay() { + return delay; + } + + public void run() { + ticker.run(); + } + + public boolean doRun() { + return !stop.get(); + } + }); + } + + public void addTimer(Ticker ticker, int delay) { + addTimer(new Ticker() { + public int getDelay() { + return delay; + } + + public void run() { + ticker.run(); + } + }); + } + + public void addTimer(Ticker ticker) { + timers.add(new Object[]{ticker, 0L}); + } + + private void initAndRun() { + while (true) { + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } catch (Exception exception) { + exception.printStackTrace(); + } + continue; + } + if (queue.hasNext()) { + try { + queue.next().run(); + } catch (Exception exception) { + exception.printStackTrace(); + } + } + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } catch (Exception exception) { + exception.printStackTrace(); + } + continue; + } + // An enhanced loop will throw an exception, fuck you intellij for this shit suggestion + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < timers.size(); i++) { + Object[] timer = timers.get(i); + if (new Date().getTime() - ((Long) timer[1]) >= ((Ticker) timer[0]).getDelay()) { + if (((Ticker) timer[0]).doRun()) { + ((Ticker) timer[0]).run(); + timer[1] = new Date().getTime(); + } + } + } + if (immediateQueue.hasNext()) { + try { + immediateQueue.next().run(); + } catch (Exception exception) { + exception.printStackTrace(); + } + } + } + } + + public void initAndRunAsynchronously() { + new Thread(this::initAndRun, "SyncQueue").start(); + } +} diff --git a/src/main/java/de/tudbut/timer/Ticker.java b/src/main/java/de/tudbut/timer/Ticker.java new file mode 100644 index 0000000..542ac53 --- /dev/null +++ b/src/main/java/de/tudbut/timer/Ticker.java @@ -0,0 +1,13 @@ +package de.tudbut.timer; + +public interface Ticker { + default int getDelay() { + return 0; + } + + void run(); + + default boolean doRun() { + return true; + } +} diff --git a/src/main/java/de/tudbut/timer/Ticks.java b/src/main/java/de/tudbut/timer/Ticks.java new file mode 100644 index 0000000..a079ae3 --- /dev/null +++ b/src/main/java/de/tudbut/timer/Ticks.java @@ -0,0 +1,58 @@ +package de.tudbut.timer; + +import de.tudbut.logger.Logger; +import de.tudbut.global.GlobalSyncQueue; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicBoolean; + +public class Ticks { + private static final Logger logger = new Logger("Ticker"); + + public static long perSecound(int count) { + return 1000 / count; + } + + public static void onTick(int TPS, Ticker ticker) { + try { + int waitTicks = 1000 / TPS; + long ticksPassed = 0; + + while (true) { + long sAt = new Date().getTime(); + ticker.run(); + ticksPassed++; + try { + Thread.sleep(waitTicks - (new Date().getTime() - sAt)); + } + catch (Exception ignore) { + } + } + } + catch (Exception e) { + logger.error(e); + } + } + + public static void inQueue(int TPS, Ticker ticker, AtomicBoolean stop, SyncQueue queue) { + try { + int waitTicks = 1000 / TPS; + + queue.addTimer(ticker, stop, waitTicks); + } + catch (Exception e) { + logger.error(e); + } + } + + public static void inQueue(int TPS, Ticker ticker, AtomicBoolean stop) { + try { + int waitTicks = 1000 / TPS; + + GlobalSyncQueue.addTimer(ticker, stop, waitTicks); + } + catch (Exception e) { + logger.error(e); + } + } +} diff --git a/src/main/java/de/tudbut/tools/Application.java b/src/main/java/de/tudbut/tools/Application.java new file mode 100644 index 0000000..6d95cef --- /dev/null +++ b/src/main/java/de/tudbut/tools/Application.java @@ -0,0 +1,144 @@ +package de.tudbut.tools; + +import de.tudbut.logger.Logger; +import de.tudbut.window.Window; + +import javax.swing.*; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.util.concurrent.atomic.AtomicBoolean; + + +public class Application { + protected Window window = null; + private boolean hasConfig = false; + private Config config; + private boolean main = false; + private String label = ""; + private Logger logger = null; + + + protected void activate(String name, boolean hasCfg, boolean hasWindow, boolean isMain) throws Exception { + this.hasConfig = hasCfg; + this.label = name; + this.main = isMain; + this.logger = new Logger(name); + + if (hasWindow) this.window = new Window(name, name, isMain).setSize(100, 100).open(); + + if (hasCfg) this.config = new Config(name.replaceAll(" ", "_") + ".config"); + } + + public String toString() { + return label; + } + + protected Config getConfig() { + if (this.hasConfig) return this.config; + else return null; + } + + protected void setConfig(Config c) { + this.config = c; + this.hasConfig = true; + } + + public Logger getLogger() { + return this.logger; + } + + protected String input(String name) { + return JOptionPane.showInputDialog(name); + } + + protected void output(String text) { + JOptionPane.showMessageDialog(null, text); + } + + @Deprecated + protected boolean askD(String question) { + this.logger.info("Showing question dialog"); + + AtomicBoolean received = new AtomicBoolean(false); + AtomicBoolean yes = new AtomicBoolean(false); + + Window askWindow = new Window(this.label, this.label + " - " + question, false).setText(question).setSize(400, 150).open(); + + + JButton askButton_yes = new JButton("Yes"); + JButton askButton_no = new JButton("No"); + askWindow.label.add(askButton_yes); + askWindow.label.add(askButton_no); + + askButton_yes.setLocation(50, 70); + askButton_no.setLocation(250, 70); + askButton_yes.setSize(100, 25); + askButton_no.setSize(100, 25); + + askWindow.frame.addWindowListener(new WindowListener() { + @Override + public void windowOpened(WindowEvent windowEvent) { + } + + @Override + public void windowClosing(WindowEvent windowEvent) { + } + + @Override + public void windowClosed(WindowEvent windowEvent) { + received.set(true); + yes.set(false); + } + + @Override + public void windowIconified(WindowEvent windowEvent) { + } + + @Override + public void windowDeiconified(WindowEvent windowEvent) { + } + + @Override + public void windowActivated(WindowEvent windowEvent) { + } + + @Override + public void windowDeactivated(WindowEvent windowEvent) { + } + }); + + askButton_yes.addActionListener((a) -> { + yes.set(true); + received.set(true); + }); + + askButton_no.addActionListener((a) -> { + yes.set(false); + received.set(true); + }); + + askWindow.label.updateUI(); + + while (true) { + if (received.get()) break; + } + + this.logger.info("Answer received"); + + if (yes.get()) { + askWindow.delete(); + return true; + } + else { + askWindow.delete(); + return false; + } + } + + protected boolean ask(String question) { + return JOptionPane.showConfirmDialog(null, + question, label, JOptionPane.YES_NO_OPTION + ) == + JOptionPane.YES_OPTION; + } +} diff --git a/src/main/java/de/tudbut/tools/ArrayGetter.java b/src/main/java/de/tudbut/tools/ArrayGetter.java new file mode 100644 index 0000000..3e32f0e --- /dev/null +++ b/src/main/java/de/tudbut/tools/ArrayGetter.java @@ -0,0 +1,14 @@ +package de.tudbut.tools; + +import java.lang.reflect.Array; + +public class ArrayGetter { + @SafeVarargs + public static T[] newGenericArray(int length, T... ts) { + return newArray(length, (Class) ts.getClass().getComponentType()); + } + + public static T[] newArray(int length, Class type) { + return (T[]) Array.newInstance(type, length); + } +} diff --git a/src/main/java/de/tudbut/tools/ArrayTools.java b/src/main/java/de/tudbut/tools/ArrayTools.java new file mode 100644 index 0000000..dd58026 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ArrayTools.java @@ -0,0 +1,115 @@ +package de.tudbut.tools; + +import de.tudbut.obj.Mappable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ArrayTools { + public static T[] replace(T[] array, T repl, T with) { array=array.clone();for (int i=0;i T[] arrayFromList(List list, T... empty) { + return list.toArray(empty); + } + + @SafeVarargs + public static O[] getFromArray(T[] array, Getter getter, O... ignore) throws RuntimeException { + O[] os = (O[]) ArrayGetter.newArray(array.length, ignore.getClass().getComponentType()); + for (int i = 0; i < array.length; i++) { + try { + os[i] = getter.get(array[i]); + } + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + return os; + } + + public interface Getter { + O get(T t) throws Throwable; + } + + public static double[] convertToNative(Double[] doubles) { + double[] d = new double[doubles.length]; + for (int i = 0; i < d.length; i++) { + d[i] = doubles[i]; + } + return d; + } + + public static int[] convertToNative(Integer[] ints) { + int[] d = new int[ints.length]; + for (int i = 0; i < d.length; i++) { + d[i] = ints[i]; + } + return d; + } + + public static byte[] convertToNative(Byte[] bytes) { + byte[] d = new byte[bytes.length]; + for (int i = 0; i < d.length; i++) { + d[i] = bytes[i]; + } + return d; + } + + public static Map mapFrom(Mappable[] mappables) { + Map map = new HashMap<>(); + + for (int i = 0; i < mappables.length; i++) { + map.put(String.valueOf(i), Tools.mapToString(mappables[i].map())); + } + + map.put("len", String.valueOf(mappables.length)); + + return map; + } + + public static Map mapFrom(T[] objects, Getter getter) { + Map map = new HashMap<>(); + + for (int i = 0; i < objects.length; i++) { + try { + map.put(String.valueOf(i), getter.get(objects[i])); + } + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + map.put("len", String.valueOf(objects.length)); + + return map; + } + + public static T[] fromMap(Map map, Getter getter, T... ignore) { + int len = Integer.parseInt(map.get("len")); + + T[] t = ArrayGetter.newGenericArray(len, ignore); + + for (int i = 0; i < len; i++) { + try { + t[i] = getter.get(map.get(String.valueOf(i))); + } + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + return t; + } + + public static T[] fromValueArray(ValueArray valueArray, T... ignore) { + T[] t; + t = ArrayGetter.newGenericArray(valueArray.length, ignore); + + for (int i = 0; i < valueArray.length; i++) { + t[i] = valueArray.get(i); + } + + return t; + } +} diff --git a/src/main/java/de/tudbut/tools/AudioPlayer.java b/src/main/java/de/tudbut/tools/AudioPlayer.java new file mode 100644 index 0000000..e1a0710 --- /dev/null +++ b/src/main/java/de/tudbut/tools/AudioPlayer.java @@ -0,0 +1,65 @@ +package de.tudbut.tools; + +import de.tudbut.io.StreamReader; + +import javax.sound.sampled.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class AudioPlayer { + + public static AudioTrackPlayer startPlaying(InputStream stream) throws IOException { + AudioTrackPlayer r = new AudioTrackPlayer(); + + try { + AudioInputStream as = AudioSystem.getAudioInputStream(new ByteArrayInputStream(new StreamReader(stream).readAllAsBytes())); + DataLine.Info info = new DataLine.Info(Clip.class, as.getFormat()); + Clip clip = (Clip) AudioSystem.getLine(info); + clip.open(as); + clip.start(); + r.player = clip; + } + catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) { + throw new AudioException(e); + } + + return r; + } + + public static class AudioException extends IOException { + + public AudioException(Throwable cause) { + super(cause); + } + } + + public static class AudioTrackPlayer { + private Clip player; + + public void setVolume(float vol) { + ((FloatControl) player.getControl(FloatControl.Type.VOLUME)).setValue(vol); + } + public void setMS(int ms) { + player.setFramePosition(ms); + } + public boolean isPlaying() { + return player.isActive(); + } + public boolean isPausedOrPlaying() { + return player.isOpen(); + } + public void start() { + if(!isPausedOrPlaying()) + setMS(0); + player.start(); + } + public void pause() { + player.stop(); + } + public void stop() { + player.stop(); + player.close(); + } + } +} diff --git a/src/main/java/de/tudbut/tools/BYMLFile.java b/src/main/java/de/tudbut/tools/BYMLFile.java new file mode 100644 index 0000000..76732a6 --- /dev/null +++ b/src/main/java/de/tudbut/tools/BYMLFile.java @@ -0,0 +1,36 @@ +package de.tudbut.tools; + +import de.tudbut.type.FileFormatException; + +import java.util.HashMap; +import java.util.Map; + +public class BYMLFile extends FileRW { + protected Map map = new HashMap<>(); + + public BYMLFile(String path) throws Exception { + super(path); + if (!path.endsWith(".byml") && !path.endsWith(".byaml")) { + throw new FileFormatException(); + } + else { + this.remap(); + } + } + + public void remap() throws Exception { + map = Tools.stringToMap(getContent().join(";").replaceAll(": ", ":").replaceAll("\\$\\$\\$", "\u0000").replaceAll("\"", "")); + } + + public void mapToObj(Object object) throws IllegalAccessException { + Tools.ObjectMapping.mapToObject(object, map); + } + + public void mapToSObj(Class clazz) throws IllegalAccessException { + Tools.ObjectMapping.mapToStaticObject(clazz, map); + } + + public String getValue(String key) { + return this.map.get(key); + } +} diff --git a/src/main/java/de/tudbut/tools/Bash.java b/src/main/java/de/tudbut/tools/Bash.java new file mode 100644 index 0000000..ba70bb7 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Bash.java @@ -0,0 +1,129 @@ +package de.tudbut.tools; + +import de.tudbut.type.StringArray; + +import java.io.*; + +public class Bash { + private final File file; + private final FileRW frw; + private BufferedReader stdO; + private BufferedReader stdE; + private BufferedWriter stdI; + private Process p; + private final StringArray output = new StringArray(); + private final StringArray error = new StringArray(); + + public Bash(String tempFile) throws Exception { + this.file = new File(tempFile); + frw = new FileRW(this.file.getAbsolutePath()); + } + + public void run(String command, boolean enableSTDINPUT) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec("bash " + file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + if (!enableSTDINPUT) { + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + } + + public void run(StringArray command, boolean enableSTDINPUT) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec("bash " + file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + if (!enableSTDINPUT) { + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + } + + public void run(String command) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec("bash " + file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + + public void run(StringArray command) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec("bash " + file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + + public StringArray getOutput() { + return this.output; + } + + public StringArray getError() { + return this.error; + } + + public String getNextOutput() throws Exception { + return this.stdO.readLine(); + } + + public String getNextError() throws Exception { + return this.stdE.readLine(); + } + + public BufferedWriter getInputWriter() { + return this.stdI; + } +} diff --git a/src/main/java/de/tudbut/tools/BetterClassLoader.java b/src/main/java/de/tudbut/tools/BetterClassLoader.java new file mode 100644 index 0000000..9410a22 --- /dev/null +++ b/src/main/java/de/tudbut/tools/BetterClassLoader.java @@ -0,0 +1,51 @@ +package de.tudbut.tools; + +import de.tudbut.obj.TLClassLoader; + +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLStreamHandlerFactory; + +public class BetterClassLoader extends URLClassLoader implements TLClassLoader { + public BetterClassLoader(URL[] urls, ClassLoader classLoader) { + super(urls, classLoader); + } + + public BetterClassLoader(URL[] urls) { + super(urls); + } + + public BetterClassLoader(URL[] urls, ClassLoader classLoader, URLStreamHandlerFactory urlStreamHandlerFactory) { + super(urls, classLoader, urlStreamHandlerFactory); + } + + public BetterClassLoader(URLClassLoader loader) { + super(loader.getURLs()); + } + + public Class lc(String s) throws ClassNotFoundException { + setClassAssertionStatus(s, true); + return loadClass(s); + } + + public Class get(String s) throws ClassNotFoundException { + Class c; + if((c = findLoadedClass(s)) != null) { + return c; + } + else + return findClass(s); + } + + public Class friendlyGet(String s) { + try { + Class c; + if ((c = findLoadedClass(s)) != null) { + return c; + } else + return findClass(s); + } catch (ClassNotFoundException e) { + return null; + } + } +} diff --git a/src/main/java/de/tudbut/tools/BetterJ.java b/src/main/java/de/tudbut/tools/BetterJ.java new file mode 100644 index 0000000..3021f8f --- /dev/null +++ b/src/main/java/de/tudbut/tools/BetterJ.java @@ -0,0 +1,138 @@ +package de.tudbut.tools; + +import java.util.HashMap; +import java.util.Map; + +public class BetterJ { + public static ForLoop f() { + return new ForLoop(); + } + + public static Thread t(Runnable runnable) { + Thread thread = new Thread(runnable); + thread.start(); + return thread; + } + + public static void p(String t) { + System.out.println(t); + } + + public static void pn(String t) { + System.out.print(t); + } + + public static Map newMap() { + return new HashMap<>(); + } + + public static final class ForLoop { + private final int step = 1; + private final int to = 1; + private int from = 0; + + ForLoop() { + } + + public ForLoop1 from(int from) { + this.from = from; + return new ForLoop1(this); + } + + public interface ForLoopRunnable { + void run(int i); + } + + public static final class ForLoop1 { + protected int step; + protected int from; + protected int to; + + ForLoop1(ForLoop forLoop) { + this.from = forLoop.from; + this.to = forLoop.to; + this.step = forLoop.step; + } + + public ForLoop2 to(int to) { + this.to = to; + return new ForLoop2(this); + } + } + + public static final class ForLoop2 { + protected int step; + protected int from; + protected int to; + + ForLoop2(ForLoop1 forLoop) { + this.from = forLoop.from; + this.to = forLoop.to; + this.step = forLoop.step; + } + + public ForLoop3 inclusively() { + to += step; + return new ForLoop3(this); + } + + public ForLoop4 step(int step) { + this.step = step; + return new ForLoop4(this); + } + + public void r(ForLoopRunnable r) { + for (int i = from; i < to; i += step) { + r.run(i); + } + } + } + + public static final class ForLoop3 { + protected int step; + protected int from; + protected int to; + + ForLoop3(ForLoop2 forLoop) { + this.from = forLoop.from; + this.to = forLoop.to; + this.step = forLoop.step; + } + + public ForLoop4 step(int step) { + this.step = step; + return new ForLoop4(this); + } + + public void r(ForLoopRunnable r) { + for (int i = from; i < to; i += step) { + r.run(i); + } + } + } + + public static final class ForLoop4 { + protected int step; + protected int from; + protected int to; + + ForLoop4(ForLoop2 forLoop) { + this.from = forLoop.from; + this.to = forLoop.to; + this.step = forLoop.step; + } + + ForLoop4(ForLoop3 forLoop) { + this.from = forLoop.from; + this.to = forLoop.to; + this.step = forLoop.step; + } + + public void r(ForLoopRunnable r) { + for (int i = from; i < to; i += step) { + r.run(i); + } + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/BufferManager.java b/src/main/java/de/tudbut/tools/BufferManager.java new file mode 100644 index 0000000..088b4fd --- /dev/null +++ b/src/main/java/de/tudbut/tools/BufferManager.java @@ -0,0 +1,33 @@ +package de.tudbut.tools; + +import de.tudbut.io.Buffer; + +import java.util.HashMap; +import java.util.Map; + +public class BufferManager { + private static final Map buffers = new HashMap<>(); + + public static long createResource(Buffer buffer) { + long s = newResourceID(); + buffers.put(s, buffer); + return s; + } + + public static Buffer getBufferFromID(long id) { + return buffers.get(id); + } + + private static long newResourceID() { + long s = ExtendedMath.randomLong(0, Long.MAX_VALUE - 1); + + while (buffers.containsKey(s)) + s = ExtendedMath.randomLong(0, Long.MAX_VALUE - 1); + + return s; + } + + public static void removeBuffer(long id) { + buffers.remove(id); + } +} diff --git a/src/main/java/de/tudbut/tools/CMD.java b/src/main/java/de/tudbut/tools/CMD.java new file mode 100644 index 0000000..d833627 --- /dev/null +++ b/src/main/java/de/tudbut/tools/CMD.java @@ -0,0 +1,139 @@ +package de.tudbut.tools; + +import de.tudbut.type.StringArray; + +import java.io.*; + +public class CMD extends Bash { + private final File file; + private final FileRW frw; + private BufferedReader stdO; + private BufferedReader stdE; + private BufferedWriter stdI; + private Process p; + private final StringArray output = new StringArray(); + private final StringArray error = new StringArray(); + + public CMD(String tempFile) throws Exception { + super(tempFile); + this.file = new File(tempFile); + frw = new FileRW(this.file.getAbsolutePath()); + } + + @Override + public void run(String command, boolean enableSTDINPUT) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec(file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + if (!enableSTDINPUT) { + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + } + + @Override + public void run(StringArray command, boolean enableSTDINPUT) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec(file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + if (!enableSTDINPUT) { + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + } + + @Override + public void run(String command) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec(file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + + @Override + public void run(StringArray command) throws Exception { + frw.setContent(command); + + this.p = Runtime.getRuntime().exec(file.getAbsolutePath()); + this.stdO = new BufferedReader(new + InputStreamReader( + p.getInputStream())); + this.stdE = new BufferedReader(new + InputStreamReader( + p.getErrorStream())); + this.stdI = new BufferedWriter(new + OutputStreamWriter( + p.getOutputStream())); + + String s; + while ((s = this.stdO.readLine()) != null) + this.output.add(s); + while ((s = this.stdE.readLine()) != null) + this.error.add(s); + } + + @Override + public StringArray getOutput() { + return this.output; + } + + @Override + public StringArray getError() { + return this.error; + } + + @Override + public String getNextOutput() throws Exception { + return this.stdO.readLine(); + } + + @Override + public String getNextError() throws Exception { + return this.stdE.readLine(); + } + + @Override + public BufferedWriter getInputWriter() { + return this.stdI; + } +} diff --git a/src/main/java/de/tudbut/tools/Cache.java b/src/main/java/de/tudbut/tools/Cache.java new file mode 100644 index 0000000..bfdd110 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Cache.java @@ -0,0 +1,187 @@ +package de.tudbut.tools; + +import de.tudbut.parsing.TudSort; + +import java.util.*; + +/** + * Cache (Map with passively expiring values) + * @param + * @param + */ +public class Cache { + protected final ArrayList>[] entries = (ArrayList>[]) new ArrayList[50]; + { + for (int i = 0 ; i < entries.length ; i++) { + entries[i] = new ArrayList<>(); + } + } + + /** + * Add a key + * @param key Key + * @param ttl Time to live + * @param retriever Retriever + */ + public void add(K key, long ttl, CacheRetriever retriever) { + add(key, retriever.doRetrieve(null, key), ttl, retriever); + } + /** + * Add/Set a value to a key + * @param key Key + * @param val The value to set + * @param ttl Time to live + * @param retriever Re-Retriever + */ + public void add(K key, V val, long ttl, CacheRetriever retriever) { + boolean exists = false; + ArrayList> entries = this.entries[Math.abs(key.hashCode() % this.entries.length)]; + for (int i = 0; i < entries.size(); i++) { + Entry entry = entries.get(i); + if (key == entry.key || entry.key.equals(key)) { + exists = true; + if(val == null || retriever == null) { + entries.remove(i); + return; + } + entry.val = val; + entry.timer.lock((int) (entry.ttl = ttl)); + } + } + if(!exists) { + entries.add(new Entry<>(key, val, ttl, retriever)); + } + } + + /** + * Get the value associated to a key + * @param key Key + * @return The value + */ + public V get(K key) { + ArrayList> entries = this.entries[Math.abs(key.hashCode() % this.entries.length)]; + V v = null; + for (int i = 0 ; i < entries.size() ; i++) { + Entry entry = entries.get(i); + v = entry.get(); + if(v == null) + entries.remove(entry); + else if (key == entry.key || entry.key.equals(key)) + break; + else + v = null; + } + return v; + } + + /** + * + * @return All keys + */ + public Set keys() { + ArrayList keys = new ArrayList<>(); + for (int i = 0 ; i < entries.length ; i++) { + for (int j = 0 ; j < entries[i].size() ; j++) { + keys.add(entries[i].get(j).key); + } + } + return new LinkedHashSet(keys); + } + + /** + * + * @return All values + */ + public Set values() { + LinkedHashSet vals = new LinkedHashSet<>(); + for (int i = 0 ; i < entries.length ; i++) { + for (int j = 0 ; j < entries[i].size() ; j++) { + vals.add(entries[i].get(j).val); + } + } + return vals; + } + + public LinkedHashMap linkedHashMap() { + ArrayList> allEntries = new ArrayList<>(); + for (int i = 0 ; i < entries.length ; i++) { + allEntries.addAll(entries[i]); + } + Entry[] entries = TudSort.sort(allEntries.toArray(new Entry[0]), entry -> entry.i); + LinkedHashMap map = new LinkedHashMap<>(); + for (int i = 0 ; i < entries.length ; i++) { + map.put(entries[i].key, entries[i].val); + } + return map; + } + + /** + * Flip keys and values + * @return The flipped cache + */ + public Cache flip() { + Cache cache = new Cache<>(); + ArrayList> allEntries = new ArrayList<>(); + for (int i = 0 ; i < entries.length ; i++) { + for (int j = 0 ; j < entries[i].size() ; j++) { + allEntries.add(entries[i].get(j).flip()); + } + } + Entry[] entries = TudSort.sort(allEntries.toArray(new Entry[0]), entry -> entry.i); + for (int i = 0 ; i < entries.length ; i++) { + cache.add(entries[i].key, entries[i].val, entries[i].ttl, entries[i].retriever); + } + return cache; + } + + protected static class Entry { + protected K key; + protected V val; + protected Lock timer; + protected long ttl; + protected CacheRetriever retriever; + protected static int ni = 0; + protected int i = ni++; + + protected Entry() { + } + + protected V get() { + if(!timer.isLocked()) { + V v = retriever.doRetrieve(val, key); + timer.lock((int) ttl); + val = v; + } + return val; + } + + protected Entry(K key, V val, long ttl, CacheRetriever retriever) { + this.key = key; + this.val = val; + this.timer = new Lock(); + this.ttl = ttl; + this.timer.lock((int) ttl); + this.retriever = retriever; + } + + protected Entry flip() { + return new Entry<>(val, key, 0, new CacheRetriever() { }); + } + } + + public interface CacheRetriever { + default V retrieveFromOld(V old) { + return old; + } + default V retrieveFromKey(K key) { + return null; + } + default V doRetrieve(V old, K key) { + V v; + if((v = retrieveFromKey(key)) == null) { + return retrieveFromOld(old); + } + return v; + } + } +} diff --git a/src/main/java/de/tudbut/tools/CalibratingInterpolator.java b/src/main/java/de/tudbut/tools/CalibratingInterpolator.java new file mode 100644 index 0000000..f4ca669 --- /dev/null +++ b/src/main/java/de/tudbut/tools/CalibratingInterpolator.java @@ -0,0 +1,27 @@ +package de.tudbut.tools; + +public class CalibratingInterpolator extends FloatInterpolator { + + private float sensitivity = 1; + + @Override + public float get(float f) { + if(f < upper && f > lower) + setBounds( + ((lower * sensitivity) + f) / (sensitivity + 1), + ((upper * sensitivity) + f) / (sensitivity + 1) + ); + else { + setBounds(Math.min(lower, f), Math.max(upper, f)); + } + return super.get(f); + } + + public float getSensitivity() { + return 1 / sensitivity; + } + + public void setSensitivity(float sensitivity) { + this.sensitivity = 1 / sensitivity; + } +} diff --git a/src/main/java/de/tudbut/tools/Catcher.java b/src/main/java/de/tudbut/tools/Catcher.java new file mode 100644 index 0000000..7c67181 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Catcher.java @@ -0,0 +1,38 @@ +package de.tudbut.tools; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static de.tudbut.tools.BetterJ.t; + +public class Catcher { + public Exception currentException; + + public void run(ThrowingRunnable runnable) { + AtomicBoolean done = new AtomicBoolean(false); + t(() -> { + try { + runnable.run(); + } + catch (Exception e) { + currentException = e; + } + done.set(true); + }); + while (!done.get()) { + try { + Thread.sleep(0); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public void throwNextHere() throws Exception { + while (true) { + if (currentException != null) { + throw currentException; + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/ClassLoaderAdapter.java b/src/main/java/de/tudbut/tools/ClassLoaderAdapter.java new file mode 100644 index 0000000..29eaee0 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ClassLoaderAdapter.java @@ -0,0 +1,28 @@ +package de.tudbut.tools; + +import de.tudbut.obj.TLClassLoader; + +public class ClassLoaderAdapter implements TLClassLoader { + private final ClassLoader cl; + + public ClassLoaderAdapter(ClassLoader classLoader) { + cl = classLoader; + } + + public Class lc(String s) throws ClassNotFoundException { + cl.setClassAssertionStatus(s, true); + return cl.loadClass(s); + } + + public Class get(String s) throws ClassNotFoundException { + return cl.loadClass(s); + } + + public Class friendlyGet(String s) { + try { + return cl.loadClass(s); + } catch (ClassNotFoundException e) { + return null; + } + } +} diff --git a/src/main/java/de/tudbut/tools/Config.java b/src/main/java/de/tudbut/tools/Config.java new file mode 100644 index 0000000..61b8daf --- /dev/null +++ b/src/main/java/de/tudbut/tools/Config.java @@ -0,0 +1,85 @@ +package de.tudbut.tools; + + +import de.tudbut.type.StringArray; + +import java.io.*; + +public class Config { + private final File configFile; + + public Config(String file) throws Exception { + this.configFile = new File(file); + if (!this.configFile.exists()) { + this.configFile.createNewFile(); + new BufferedWriter(new FileWriter(this.configFile)).write(" : ;"); + } + + + } + + + public String get(String cfgPath) throws Exception { + + FileReader reader = new FileReader(this.configFile); + BufferedReader breader = new BufferedReader(reader); + + String cfg = breader.readLine(); + + if (cfg == null) { + cfg = " : ;"; + } + + String[] cfgp1 = cfg.split(";"); + for (String x : cfgp1) { + String[] cfgp2 = x.split(":"); + if (cfgp2[0].equals(cfgPath)) { + return cfgp2[1].replaceAll("&a", ";").replaceAll("&b", ":").replaceAll("&d", "\n").replaceAll("&c", "&"); + } + } + return null; + } + + public void set(String cfgPath, String value) throws Exception { + FileReader reader = new FileReader(this.configFile); + BufferedReader breader = new BufferedReader(reader); + String cfg = breader.readLine(); + breader.close(); + reader.close(); + + FileWriter writer = new FileWriter(this.configFile); + BufferedWriter bwriter = new BufferedWriter(writer); + + if (cfg == null) { + cfg = " : ;"; + } + + String[] cfgp1 = cfg.split(";"); + for (String x : cfgp1) { + if (!x.split(":")[0].equals(cfgPath) && !x.equals(" : ")) bwriter.write(x + ";"); + } + + + bwriter.write(cfgPath); + bwriter.write(":"); + bwriter.write(value.replaceAll("&", "&c").replaceAll(":", "&b").replaceAll(";", "&a").replaceAll("\n", "&d")); + bwriter.write(";"); + bwriter.close(); + } + + public StringArray getList(String cfgPath) throws Exception { + StringArray r = new StringArray(); + for (int i = 0; i < Integer.parseInt(get(cfgPath + "[length]")); i++) { + r.add(get(cfgPath + "[" + i + "]")); + } + + return r; + } + + public void setList(String cfgPath, String[] list) throws Exception { + for (int i = 0; i < list.length; i++) { + set(cfgPath + "[" + i + "]", list[i]); + } + set(cfgPath + "[length]", String.valueOf(list.length)); + } +} \ No newline at end of file diff --git a/src/main/java/de/tudbut/tools/ConfigSaverTCN.java b/src/main/java/de/tudbut/tools/ConfigSaverTCN.java new file mode 100644 index 0000000..aca5cc3 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ConfigSaverTCN.java @@ -0,0 +1,78 @@ +package de.tudbut.tools; + +import de.tudbut.parsing.TCN; +import de.tudbut.debug.Debug; +import de.tudbut.debug.DebugProfiler; +import de.tudbut.obj.Save; + +import java.lang.reflect.Field; + +public class ConfigSaverTCN { + + public static TCN saveConfig(Object object) { + DebugProfiler profiler = Debug.getDebugProfiler(ConfigSaverTCN.class, false); + + profiler.next("Create TCN"); + TCN map = new TCN(); + profiler.next("Get class"); + Class clazz = object.getClass(); + while (clazz != Object.class) { + profiler.next("Classes"); + Field[] fields = clazz.getDeclaredFields(); + try { + for (int i = 0; i < fields.length; i++) { + profiler.next("Fields"); + Field field = fields[i]; + + if(shouldSave(field)) { + // I want to access it + ReflectUtil.forceAccessible(field); + Object o = field.get(object); + ObjectSerializerTCN serializer = new ObjectSerializerTCN(o); + o = serializer.convertAll().done(); + map.set(field.getName(), o); + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + clazz = clazz.getSuperclass(); + } + + profiler.endAll(); + return map; + } + + public static void loadConfig(Object object, TCN tcn) { + Class clazz = object.getClass(); + while (clazz != Object.class) { + Field[] fields = clazz.getDeclaredFields(); + try { + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + + if(shouldSave(field)) { + // I want to access it + ReflectUtil.forceAccessible(field); + if (tcn.map.get(field.getName()) != null) { + Object o; + ObjectSerializerTCN serializer = new ObjectSerializerTCN(tcn.getSub(field.getName())); + o = serializer.convertAll().done(); + if (o != null) + field.set(object, o); + } + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + clazz = clazz.getSuperclass(); + } + } + + public static boolean shouldSave(Field field) { + return field.getDeclaredAnnotation(Save.class) != null; + } +} diff --git a/src/main/java/de/tudbut/tools/ConfigSaverTCN2.java b/src/main/java/de/tudbut/tools/ConfigSaverTCN2.java new file mode 100644 index 0000000..602a1de --- /dev/null +++ b/src/main/java/de/tudbut/tools/ConfigSaverTCN2.java @@ -0,0 +1,263 @@ +package de.tudbut.tools; + +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.Arrays; + +import de.tudbut.parsing.TCN; +import de.tudbut.parsing.TCNArray; +import sun.misc.Unsafe; +import de.tudbut.obj.Save; +import de.tudbut.obj.Transient; + +public class ConfigSaverTCN2 { + + static ArrayList> tcnPrimitives = new ArrayList<>(Arrays.asList( + boolean.class, Boolean.class, + byte.class, Byte.class, + short.class, Short.class, + char.class, Character.class, + int.class, Integer.class, + float.class, Float.class, + long.class, Long.class, + double.class, Double.class, + String.class, + TCN.class, + TCNArray.class + )); + + public static Object write(Object object, boolean writeAll, boolean writeStatic) { + return write(object, writeAll, writeStatic, true); + } + + public static Object write(Object object, boolean writeAll, boolean writeStatic, boolean allowPrimitives) { + if(object == null) { + TCN tcn = new TCN(); + tcn.set("$", "null"); + return tcn; + } + Class objectClass = object.getClass(); + if(objectClass == Class.class && writeStatic) { + objectClass = (Class) object; + object = null; + } + if(tcnPrimitives.contains(objectClass) && allowPrimitives) { + if(object instanceof TCN) + ((TCN) object).set("$", "TCN"); + return object; // just write the object without any wrapping lol + } + + TCN tcn = new TCN(); + if(objectClass.isArray()) { + int len = Array.getLength(object); + tcn.set("$", "[]"); + tcn.set("*", objectClass.getComponentType().getName()); + tcn.set("length", len); + TCNArray tcnArray = new TCNArray(); + for(int i = 0; i < len; i++) { + tcnArray.add(write(Array.get(object, i), true, false, objectClass.getComponentType() != Object.class)); + } + tcn.set("items", tcnArray); + } + else { + tcn.set("$", objectClass.getName()); + if(objectClass.isEnum()) { + tcn.set("*", Arrays.asList(objectClass.getEnumConstants()).indexOf(object)); // Return the equivalent of .ordinal() + return tcn; + } + ArrayList fields = new ArrayList<>(); + + // Read fields + ArrayList> supers = getSupers(objectClass); // getSupers because interfaces can't have fields + for(Class superclass : supers) { + fields.addAll(Arrays.asList(superclass.getDeclaredFields())); + } + fields.addAll(Arrays.asList(objectClass.getDeclaredFields())); + + for(Field field : fields) { + boolean isStatic = (field.getModifiers() & Modifier.STATIC) != 0; + // Ignore field if writeAll is not set and it isn't @Save + if(!writeAll && field.getDeclaredAnnotation(Save.class) == null) + continue; + if(!writeStatic && isStatic) + continue; + if(isStatic && (field.getModifiers() & Modifier.FINAL) != 0) + continue; + if(field.getDeclaredAnnotation(Transient.class) != null) + continue; + + ReflectUtil.forceAccessible(field); // lovely java 18 bypass + Object o; + try { + o = field.get(isStatic ? null : object); + } catch (IllegalArgumentException | IllegalAccessException e) { + // These can't happen + System.err.println("ReflectUtil.forceAccessible silently failed. Exiting."); + throw new Error("ConfigSaverTCN2: ReflectUtil.forceAccessible failed"); + } + tcn.set(field.getName(), write(o, true, false, field.getType() != Object.class)); + } + } + + return tcn; + } + + public static Object convertPrimitive(Object object, Class type) { + if(object instanceof String) { + switch(type.getSimpleName()) { + case "boolean": + case "Boolean": + return Boolean.parseBoolean((String) object); + case "byte": + case "Byte": + return (byte) Integer.parseInt((String) object); + case "short": + case "Short": + return (short) Integer.parseInt((String) object); + case "char": + case "Character": + return ((String) object).charAt(0); + case "int": + case "Integer": + return Integer.parseInt((String) object); + case "float": + case "Float": + return Float.parseFloat((String) object); + case "long": + case "Long": + return Long.parseLong((String) object); + case "double": + case "Double": + return Double.parseDouble((String) object); + } + } + return object; + } + + public static Object read(Object object, Object toReadTo) throws ClassNotFoundException { + Class objectClass = object.getClass(); + if(tcnPrimitives.contains(objectClass) && (!(object instanceof TCN) || ((TCN) object).getString("$").equals("TCN"))) { + return object; // just write the object without any wrapping + } + + boolean forceAllow = ((Integer)(-1)).equals(toReadTo); + if(forceAllow) toReadTo = null; + + TCN tcn = (TCN) object; + if(tcn.getString("$") == null && toReadTo != null) tcn.set("$", toReadTo.getClass().getName()); + if(tcn.getString("$").equals("null")) + return null; + if(tcn.getString("$").equals("[]")) { + try { + objectClass = Class.forName(tcn.getString("*")); + } catch (ClassNotFoundException e) { + switch(tcn.getString("*")) { + case "boolean": + objectClass = boolean.class; + break; + case "byte": + objectClass = byte.class; + break; + case "short": + objectClass = short.class; + break; + case "char": + objectClass = char.class; + break; + case "int": + objectClass = int.class; + break; + case "float": + objectClass = float.class; + break; + case "long": + objectClass = long.class; + break; + case "double": + objectClass = double.class; + break; + } + } + TCNArray tcnArray = tcn.getArray("items"); + Object jArray = Array.newInstance(objectClass, tcn.getInteger("length")); + for(int i = 0; i < tcnArray.size(); i++) { + Array.set(jArray, i, convertPrimitive(read(tcnArray.get(i), null), objectClass)); + } + return jArray; + } + else { + objectClass = Class.forName(tcn.getString("$")); + Object instance = toReadTo; + if(objectClass.isEnum()) { + instance = objectClass.getEnumConstants()[tcn.getInteger("*")]; + return instance; + } + if(instance == null) { + try { + instance = ReflectUtil.theSafe.allocateInstance(objectClass); + } catch (InstantiationException e1) { + // This can't happen + throw new Error(e1); + } + } + ArrayList fields = new ArrayList<>(); + + // Read fields + ArrayList> supers = getSupers(objectClass); // getSupers because interfaces can't have fields + for(Class superclass : supers) { + fields.addAll(Arrays.asList(superclass.getDeclaredFields())); + } + fields.addAll(Arrays.asList(objectClass.getDeclaredFields())); + + for(Field field : fields) { + boolean isStatic = (field.getModifiers() & Modifier.STATIC) != 0; + if(field.getDeclaredAnnotation(Transient.class) != null) + continue; + if(isStatic && (field.getModifiers() & Modifier.FINAL) != 0) + continue; + ReflectUtil.forceAccessible(field); // lovely java 18 bypass + ReflectUtil.eraseFinality(field); // other lovely java 18 bypass + Object o = tcn.get(field.getName()); + if(o == null) { + if(toReadTo != null || isStatic || forceAllow) + continue; + else + throw new IllegalArgumentException("TCN is not complete. Try adding a toReadTo parameter."); + } + try { + field.set(isStatic ? null : instance, convertPrimitive(read(o, null), field.getType())); + } catch (IllegalAccessException e) { + e.printStackTrace(); + // These can't happen + System.err.println("ReflectUtil.forceAccessible silently failed. Exiting."); + throw new Error("ConfigSaverTCN2: ReflectUtil.forceAccessible failed"); + } + } + + return instance; + } + + } + + public static ArrayList> getSupers(Class primary) { + ArrayList> supers = new ArrayList<>(); + Class c = primary; + while(c != null) { + c = c.getSuperclass(); + if(c != null) supers.add(c); + } + return supers; + } + + public static ArrayList> getInterfaces(Class primary) { + ArrayList> supers = new ArrayList<>(); + Class c = primary; + while(c != null) { + supers.addAll(Arrays.asList(c.getInterfaces())); + c = c.getSuperclass(); + if(c != null) supers.add(c); + } + return supers; + } + +} diff --git a/src/main/java/de/tudbut/tools/DiscordRPC.java b/src/main/java/de/tudbut/tools/DiscordRPC.java new file mode 100644 index 0000000..d9d3b1c --- /dev/null +++ b/src/main/java/de/tudbut/tools/DiscordRPC.java @@ -0,0 +1,16 @@ +package de.tudbut.tools; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.net.Socket; +import java.net.SocketImpl; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.SocketChannel; +import java.nio.file.Paths; + +@Deprecated +public class DiscordRPC { + +} diff --git a/src/main/java/de/tudbut/tools/DiscoverClasses.java b/src/main/java/de/tudbut/tools/DiscoverClasses.java new file mode 100644 index 0000000..4c840e8 --- /dev/null +++ b/src/main/java/de/tudbut/tools/DiscoverClasses.java @@ -0,0 +1,107 @@ +package de.tudbut.tools; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class DiscoverClasses { + + ClassLoader loader; + String pkg; + Class annotation; + Class superClass; + ArrayList> interfaces = new ArrayList<>(); + + private DiscoverClasses() {} + + public static DiscoverClasses of(ClassLoader loader) { + DiscoverClasses discover = new DiscoverClasses(); + discover.loader = loader; + return discover; + } + + public DiscoverClasses in(String pkg) { + this.pkg = pkg; + return this; + } + + public DiscoverClasses with(Class annotation) { + this.annotation = annotation; + return this; + } + + public DiscoverClasses extending(Class superClass) { + if(superClass.isInterface()) throw new IllegalArgumentException("Interfaces can't be superclasses!"); + this.superClass = superClass; + return this; + } + + public DiscoverClasses implementing(Class iface) { + if(!iface.isInterface()) throw new IllegalArgumentException("Superclasses can't be interfaces!"); + this.interfaces.add(iface); + return this; + } + + public List> run() { + InputStream stream = loader.getResourceAsStream(pkg.replace('.', '/')); + if(stream == null) + return null; + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + List files = reader.lines() + .filter(line -> line.endsWith(".class") || !line.contains(".")) + .map(line -> pkg.replace('.', '/') + "/" + line) + .collect(Collectors.toList()); + for (int i = 0; i < files.size(); i++) { + String file = files.get(i); + if(!file.endsWith(".class")) { + try { + BufferedReader subReader = new BufferedReader(new InputStreamReader(loader.getResourceAsStream(file))); + List subFiles = subReader.lines() + .filter(line -> line.endsWith(".class") || !line.contains(".")) + .map(line -> file + "/" + line) + .collect(Collectors.toList()); + files.addAll(subFiles); + } catch (Exception e) {} + } + } + return files.stream() + .map(line -> { + try { + return loader.loadClass(line.replace('/', '.').substring(0, line.length() - ".class".length())); + } catch (ClassNotFoundException e) { + return null; + } + }) + .filter(Objects::nonNull) + .filter(x -> { + if(annotation != null) { + return x.getDeclaredAnnotation(annotation) != null; + } + return true; + }) + .filter(x -> { + if(superClass != null) { + return x.getSuperclass() == superClass; + } + return true; + }) + .filter(x -> { + if(!interfaces.isEmpty()) { + List> list = Arrays.asList(x.getInterfaces()); + for (Class iface : interfaces) { + if(!list.contains(iface)) + return false; + } + } + return true; + }) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/de/tudbut/tools/ExtendedMath.java b/src/main/java/de/tudbut/tools/ExtendedMath.java new file mode 100644 index 0000000..5d83434 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ExtendedMath.java @@ -0,0 +1,259 @@ +package de.tudbut.tools; + +import de.tudbut.type.FInfo; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +public class ExtendedMath { + public static long[] solveSimple(String eq, char toSolveChar, int maxResults) throws InterruptedException { + eq = eq.replaceAll("=", "==").replaceAll("======", "==").replaceAll("====", "=="); + + + final boolean[] stop = {false}; + final long[] result = new long[maxResults]; + final char[] current = {0}; + + String finalEq = eq; + new Thread(() -> { + ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js"); + for (long i = -1; i > Long.MIN_VALUE; i--) { + if (stop[0]) + break; + try { + if (engine.eval("(" + finalEq.replaceAll(String.valueOf(toSolveChar), "(" + i + ")") + ")").equals(true)) { + if (current[0] == result.length) + stop[0] = true; + else { + result[current[0]] = i; + current[0]++; + if (current[0] == result.length) + stop[0] = true; + } + if (stop[0]) + break; + } + } + catch (ScriptException e) { + e.printStackTrace(); + break; + } + } + }).start(); + String finalEq1 = eq; + new Thread(() -> { + ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js"); + for (long i = 0; i < Long.MAX_VALUE; i++) { + if (stop[0]) + break; + try { + if (engine.eval("(" + finalEq1.replaceAll(String.valueOf(toSolveChar), "(" + i + ")") + ")").equals(true)) { + if (current[0] == result.length) + stop[0] = true; + else { + result[current[0]] = i; + current[0]++; + if (current[0] == result.length) + stop[0] = true; + } + if (stop[0]) + break; + } + } + catch (ScriptException e) { + e.printStackTrace(); + break; + } + } + }).start(); + + while (!stop[0]) { + Thread.sleep(0); + } + + return result; + } + + public static long solveSimple(String eq, char toSolveChar) throws InterruptedException { + return solveSimple(eq, toSolveChar, 1)[0]; + } + + public static double[] solveDouble(String eq, char toSolveChar, int precision, int maxResults) throws InterruptedException { + eq = eq.replaceAll("=", "==").replaceAll("======", "==").replaceAll("====", "=="); + + + final boolean[] stop = {false}; + final double[] result = new double[maxResults]; + final char[] current = {0}; + + String finalEq = eq; + new Thread(() -> { + ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js"); + for (long i = -1; i > Long.MIN_VALUE; i--) { + if (stop[0]) + break; + try { + if (engine.eval(finalEq.replaceAll(String.valueOf(toSolveChar), "(" + (double) i / (double) precision + ")")).equals(true)) { + if (current[0] == result.length) + stop[0] = true; + else { + result[current[0]] = (double) i / (double) precision; + current[0]++; + if (current[0] == result.length) + stop[0] = true; + } + if (stop[0]) + break; + } + } + catch (ScriptException e) { + e.printStackTrace(); + break; + } + } + }).start(); + String finalEq1 = eq; + new Thread(() -> { + ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js"); + for (long i = 0; i < Long.MAX_VALUE; i++) { + if (stop[0]) + break; + try { + if (engine.eval(finalEq1.replaceAll(String.valueOf(toSolveChar), "(" + (double) i / (double) precision + ")")).equals(true)) { + if (current[0] == result.length) + stop[0] = true; + else { + result[current[0]] = (double) i / (double) precision; + current[0]++; + if (current[0] == result.length) + stop[0] = true; + } + if (stop[0]) + break; + } + } + catch (ScriptException e) { + e.printStackTrace(); + break; + } + } + }).start(); + + while (!stop[0]) { + Thread.sleep(0); + } + + return result; + } + + + public static double solveDouble(String eq, char toSolveChar, int precision) throws InterruptedException { + return solveDouble(eq, toSolveChar, precision, 1)[0]; + } + + public static double solveDouble(String eq, char toSolveChar) throws InterruptedException { + return solveDouble(eq, toSolveChar, 100, 1)[0]; + } + + public static double[] solveDouble(String eq, char toSolveChar, long maxResults) throws InterruptedException { + return solveDouble(eq, toSolveChar, 100, (int) maxResults); + } + + public static int random(int lower, int upper) { + return (int) randomLong(lower, upper); + } + + public static long randomLong(long lower, long upper) { + upper ++; + return (long) (Math.floor(Math.random() * (upper - lower)) + lower); + } + + public static float randomFloat(float lower, float upper, int precision) { + return (float) randomLong((int) (lower * (precision)), (int) (upper * (precision))) / (precision); + } + + public static double min(double... doubles) { + double currentMin = doubles[0]; + + for (int i = 1; i < doubles.length; i++) { + currentMin = Math.min(currentMin, doubles[i]); + } + + return currentMin; + } + + public static int min(int... ints) { + int currentMin = ints[0]; + + for (int i = 1; i < ints.length; i++) { + currentMin = Math.min(currentMin, ints[i]); + } + + return currentMin; + } + + public static double max(double... doubles) { + double currentMax = doubles[0]; + + for (int i = 1; i < doubles.length; i++) { + currentMax = Math.max(currentMax, doubles[i]); + } + + return currentMax; + } + + public static int max(int... ints) { + int currentMax = ints[0]; + + for (int i = 1; i < ints.length; i++) { + currentMax = Math.max(currentMax, ints[i]); + } + + return currentMax; + } + + public static double highestMinusLowest(double d1, double d2) { + if (d1 > d2) { + return d1 - d2; + } + if (d2 > d1) { + return d2 - d1; + } + return 0; + } + + public static int highestMinusLowest(int i1, int i2) { + if (i1 > i2) { + return i1 - i2; + } + if (i2 > i1) { + return i2 - i1; + } + return 0; + } + + public static T[] flipArray(T[] array) { + T[] oldArray = array.clone(); + + for (int i = 0; i < array.length; i++) { + array[-i + array.length - 1] = oldArray[i]; + } + + return array; + } + + public static double removeSign(double d) { + return d < 0 ? -d : d; + } + + @FInfo(s = "only requires ~0.5 measurable ticks (-> new Date().toInstant().getNanos()) while Math.round takes ~5") + public static long fastRound(double d) { + return d - (long) d < 0.5 ? (long) d : (long) d + 1; + } + + @FInfo(s = "only requires ~0.5 measurable ticks (-> new Date().toInstant().getNanos()) while Math.round takes ~5") + public static int fastIntRound(double d) { + return d - (int) d < 0.5 ? (int) d : (int) d + 1; + } +} diff --git a/src/main/java/de/tudbut/tools/FileRW.java b/src/main/java/de/tudbut/tools/FileRW.java new file mode 100644 index 0000000..685985e --- /dev/null +++ b/src/main/java/de/tudbut/tools/FileRW.java @@ -0,0 +1,154 @@ +package de.tudbut.tools; + +import de.tudbut.io.StreamReader; +import de.tudbut.io.StreamWriter; +import de.tudbut.logger.LoggerSink; +import de.tudbut.type.StringArray; +import de.tudbut.global.DebugStateManager; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.atomic.AtomicReference; + +public class FileRW { + + private final AtomicReference logger; + private final StringArray lines; + protected final File file; + + public FileRW(String path) throws IOException { + + this.file = new File(path); + logger = DebugStateManager.debugLoggerReference("FileRW " + path); + + logger.get().info("INIT"); + if (!this.file.exists()) { + logger.get().info("Creating file..."); + this.file.createNewFile(); + new BufferedWriter(new FileWriter(this.file)).write("\n"); + logger.get().info("Done."); + } + this.lines = new StringArray(); + logger.get().info("Reading file..."); + rereadFile(); + logger.get().info("Read file."); + } + + public StringArray getContent() { + return this.lines; + } + + public void setContent(String content) throws IOException { + logger.get().info("Writing file..."); + this.lines.clear(); + this.lines.set(content.split("\n")); + FileOutputStream fileWriter = new FileOutputStream(this.file); + new StreamWriter(fileWriter).writeChars(content.toCharArray()); + fileWriter.close(); + logger.get().info("Done."); + } + + public void safeSetContent(String content) throws IOException { + logger.get().info("Writing file safely..."); + this.lines.clear(); + this.lines.set(content.split("\n")); + File file = new File(this.file.getAbsolutePath() + ".tmp"); + FileOutputStream fileWriter = new FileOutputStream(file); + new StreamWriter(fileWriter).writeChars(content.toCharArray()); + fileWriter.close(); + Files.move(file.toPath(), this.file.toPath(), StandardCopyOption.REPLACE_EXISTING); + logger.get().info("Done."); + } + + public void setContent(StringArray content) throws IOException { + setContent(content.join("\n")); + } + + public void rereadFile() throws IOException { + StreamReader reader = new StreamReader(new FileInputStream(file)); + char[] chars = reader.readAllAsChars(); + String[] s = new String(chars).split("\n"); + lines.clear(); + for (String value : s) { + lines.add(value); + } + } +} + +/*import de.tudbut.logger.GlobalLogger; +import de.tudbut.logger.Logger; +import de.tudbut.type.StringArray; + +import java.io.*; + +public class FileRW { + private File file = null; + private Logger logger = null; + private StringArray lines = null; + + public FileRW(String path) throws Exception { + GlobalLogger.info("Initialising FileRW " + path); + this.file = new File(path); + this.logger = new Logger("FileRW " + path); + if(!this.file.exists()) { + this.logger.info("Creating file..."); + this.file.createNewFile(); + new BufferedWriter(new FileWriter(this.file)).write("\n"); + this.logger.info("Done!"); + } + FileReader fileReader = new FileReader(this.file); + BufferedReader reader = new BufferedReader(fileReader); + this.lines = new StringArray(new String[]{""}); + String _line = ""; + while((_line = reader.readLine()) != null) { + this.lines = this.lines.add(_line); + } + this.logger.info("FileRW " + path + " initialized."); + } + + public String getContent() throws Exception { + FileReader fileReader = new FileReader(this.file); + BufferedReader reader = new BufferedReader(fileReader); + this.lines = new StringArray(new String[]{""}); + String _line = ""; + String __lines = ""; + while((_line = reader.readLine()) != null) { + /*this.lines = this.lines.add(_line); + this.logger.info(this.lines.join("\n"));*//* + __lines = __lines + "\n" + _line; + } + return __lines; + } + + public String getLine(int line) throws Exception { + FileReader fileReader = new FileReader(this.file); + BufferedReader reader = new BufferedReader(fileReader); + + reader.close(); + return lines.asArray()[line]; + } + + public void setContent(String content) throws Exception { + FileWriter fileWriter = new FileWriter(this.file); + BufferedWriter writer = new BufferedWriter(fileWriter); + this.lines = new StringArray(content.split("\n")); + for (String line : lines.asArray()) { + writer.write(line); + writer.newLine(); + } + writer.close(); + } + + public void setLine(int line, String content) throws Exception { + FileWriter fileWriter = new FileWriter(this.file); + BufferedWriter writer = new BufferedWriter(fileWriter); + lines.asArray()[line] = content; + for (String line_ : lines.asArray()) { + writer.write(line_); + writer.newLine(); + } + writer.close(); + } +} +*/ \ No newline at end of file diff --git a/src/main/java/de/tudbut/tools/FloatInterpolator.java b/src/main/java/de/tudbut/tools/FloatInterpolator.java new file mode 100644 index 0000000..c0cbc81 --- /dev/null +++ b/src/main/java/de/tudbut/tools/FloatInterpolator.java @@ -0,0 +1,20 @@ +package de.tudbut.tools; + +public class FloatInterpolator { + + float diff, lower, upper; + + public void setBounds(float lower, float upper) { + if(lower > upper) + throw new IllegalArgumentException("Lower bound > Upper bound"); + + this.lower = lower; + this.upper = upper; + this.diff = upper - lower; + } + + public float get(float f) { + f = f - lower; + return f / diff; + } +} diff --git a/src/main/java/de/tudbut/tools/Hasher.java b/src/main/java/de/tudbut/tools/Hasher.java new file mode 100644 index 0000000..4a7380c --- /dev/null +++ b/src/main/java/de/tudbut/tools/Hasher.java @@ -0,0 +1,144 @@ +package de.tudbut.tools; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Hasher { + public static final String TYPE_SHA256HEX = "sha256hex"; + public static final String TYPE_SHA512HEX = "sha512hex"; + public static final String TYPE_INT = "int"; + @SuppressWarnings("CanBeFinal") + public static String LETTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"§$%&/()=?#_-.:,;µ<>|^°{[]}\\ "; + + public static String sha256hex(String toHash) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-256"); + } + catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Impossible condition reached"); + } + return hash(toHash, digest); + } + + public static String sha512hex(String toHash) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-512"); + } + catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Impossible condition reached"); + } + return hash(toHash, digest); + } + + private static String hash(String toHash, MessageDigest digest) { + byte[] hash = digest.digest( + toHash.getBytes(StandardCharsets.UTF_8)); + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + } + + public static String bruteforce(String type, String hash, int beginAt) { + switch (type) { + case TYPE_SHA256HEX: + return bf_sha256hex(hash, beginAt); + case TYPE_SHA512HEX: + return bf_sha512hex(hash, beginAt); + case TYPE_INT: + return bf_int(Integer.parseInt(hash), beginAt); + } + return null; + } + + private static String bf_int(int hash, int beginAt) { + for (int length = beginAt; true; length++) { + String s; + if ((s = bf_int_tryGenChar("", 0, length, hash)) != null) { + if (s.hashCode() == hash) + return s; + } + } + } + + private static String bf_int_tryGenChar(String str, int pos, int length, int hash) { + if (length == 0) { + if (str.hashCode() == hash) + return str; + } + else { + if (pos != 0) { + pos = 0; + } + for (int i = pos; i < LETTERS.toCharArray().length; i++) { + String s; + if ((s = bf_int_tryGenChar(str + LETTERS.charAt(i), i, length - 1, hash)) != null) { + return s; + } + } + } + return null; + } + + private static String bf_sha256hex(String hash, int beginAt) { + for (int length = beginAt; true; length++) { + String s; + if ((s = bf_sha256hex_tryGenChar("", 0, length, hash)) != null) { + return s; + } + } + } + + private static String bf_sha256hex_tryGenChar(String str, int pos, int length, String hash) { + if (length == 0) { + if (sha256hex(str).equals(hash)) + return str; + } + else { + if (pos != 0) { + pos = 0; + } + for (int i = pos; i < LETTERS.toCharArray().length; i++) { + String s; + if ((s = bf_sha256hex_tryGenChar(str + LETTERS.charAt(i), i, length - 1, hash)) != null) { + return s; + } + } + } + return null; + } + + private static String bf_sha512hex(String hash, int beginAt) { + for (int length = beginAt; true; length++) { + String s; + if ((s = bf_sha512hex_tryGenChar("", 0, length, hash)) != null) { + return s; + } + } + } + + private static String bf_sha512hex_tryGenChar(String str, int pos, int length, String hash) { + if (length == 0) { + if (sha512hex(str).equals(hash)) + return str; + } + else { + if (pos != 0) { + pos = 0; + } + for (int i = pos; i < LETTERS.toCharArray().length; i++) { + String s; + if ((s = bf_sha512hex_tryGenChar(str + LETTERS.charAt(i), i, length - 1, hash)) != null) { + return s; + } + } + } + return null; + } +} diff --git a/src/main/java/de/tudbut/tools/ImageUtils.java b/src/main/java/de/tudbut/tools/ImageUtils.java new file mode 100644 index 0000000..de48968 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ImageUtils.java @@ -0,0 +1,442 @@ +package de.tudbut.tools; + +import de.tudbut.rendering.Maths2D; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Arrays; + +import static java.lang.Math.*; + +public class ImageUtils { + + public static BufferedImage antiAlias(BufferedImage image, int amount) { + return antiAlias(image, amount, 20); + } + + public static BufferedImage antiAlias(BufferedImage image, int amount, float m) { + Color color = getAverageColor(image); + int i = 0; + i += color.getRed(); + i += color.getGreen(); + i += color.getBlue(); + i += color.getAlpha(); + if(i > (0xff * 2)) { + image = invert(image); + } + BufferedImage img = smoothen(getContrastColors(image), amount, m); + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + Color c = new Color(image.getRGB(x,y), image.getColorModel().hasAlpha()); + Color sc = new Color(img.getRGB(x,y), true); + out.setRGB(x,y, new Color( + max(c.getRed(), sc.getRed()), + max(c.getGreen(), sc.getGreen()), + max(c.getBlue(), sc.getBlue()), + 255 + ).getRGB()); + } + } + if(i > (0xff * 2)) { + out = invert(out); + } + return out; + } + + public static BufferedImage whiteContrast(BufferedImage image, int amount) { + BufferedImage img = smoothen(getContrast(image), amount, 2); + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + Color c = new Color(image.getRGB(x,y), image.getColorModel().hasAlpha()); + Color sc = new Color(img.getRGB(x,y), image.getColorModel().hasAlpha()); + out.setRGB(x,y, new Color( + min(abs(c.getRed() + sc.getRed()), 255), + min(abs(c.getGreen() + sc.getGreen()), 255), + min(abs(c.getBlue() + sc.getBlue()), 255), + 255 + ).getRGB()); + } + } + return out; + } + + public static BufferedImage whiteContrastColored(BufferedImage image, int amount) { + BufferedImage img = smoothen(getContrastColors(image), amount, 2); + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + Color c = new Color(image.getRGB(x,y), image.getColorModel().hasAlpha()); + Color sc = new Color(img.getRGB(x,y), image.getColorModel().hasAlpha()); + out.setRGB(x,y, new Color( + min(abs(c.getRed() + sc.getRed()), 255), + min(abs(c.getGreen() + sc.getGreen()), 255), + min(abs(c.getBlue() + sc.getBlue()), 255), + 255 + ).getRGB()); + } + } + return out; + } + + public static BufferedImage contrast(BufferedImage image, int amount) { + BufferedImage img = smoothen(getContrast(image), amount); + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + Color c = new Color(image.getRGB(x,y), image.getColorModel().hasAlpha()); + Color sc = new Color(img.getRGB(x,y), image.getColorModel().hasAlpha()); + out.setRGB(x,y, new Color( + abs(c.getRed() - sc.getRed()), + abs(c.getGreen() - sc.getGreen()), + abs(c.getBlue() - sc.getBlue()), + 255 + ).getRGB()); + } + } + return out; + } + + public static BufferedImage contrastColored(BufferedImage image, int smoothness, int amount) { + for (int i = 0 ; i < amount ; i++) { + image = contrastColored(image, smoothness); + } + return image; + } + + public static BufferedImage contrastColored(BufferedImage image, int smoothness) { + BufferedImage img = smoothen(getContrastColors(image), smoothness); + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + Color c = new Color(image.getRGB(x,y), image.getColorModel().hasAlpha()); + Color sc = new Color(img.getRGB(x,y), image.getColorModel().hasAlpha()); + out.setRGB(x,y, new Color( + abs(c.getRed() - sc.getRed()), + abs(c.getGreen() - sc.getGreen()), + abs(c.getBlue() - sc.getBlue()), + 255 + ).getRGB()); + } + } + return out; + } + + public static BufferedImage smoothen(BufferedImage image, int amount, float m) { + float[][][] floats = imageToFloats(image); + float[][][] floats1 = new float[4][floats.length][floats[0].length]; + for (int x = 0 ; x < floats.length ; x++) { + for (int y = 0 ; y < floats[0].length ; y++) { + floats1[0][x][y] = floats[x][y][0]; + floats1[1][x][y] = floats[x][y][1]; + floats1[2][x][y] = floats[x][y][2]; + floats1[3][x][y] = floats[x][y][3]; + } + } + NoiseGenerator.smooth(new float[][][]{floats1[0]}, 0, 0, 0, 1, image.getWidth(), image.getHeight(), amount, m); + NoiseGenerator.smooth(new float[][][]{floats1[1]}, 0, 0, 0, 1, image.getWidth(), image.getHeight(), amount, m); + NoiseGenerator.smooth(new float[][][]{floats1[2]}, 0, 0, 0, 1, image.getWidth(), image.getHeight(), amount, m); + NoiseGenerator.smooth(new float[][][]{floats1[3]}, 0, 0, 0, 1, image.getWidth(), image.getHeight(), amount, m); + for (int x = 0 ; x < floats.length ; x++) { + for (int y = 0 ; y < floats[0].length ; y++) { + floats[x][y][0] = floats1[0][x][y]; + floats[x][y][1] = floats1[1][x][y]; + floats[x][y][2] = floats1[2][x][y]; + floats[x][y][3] = floats1[3][x][y]; + } + } + return floatsToImage(floats); + } + + public static BufferedImage smoothen(BufferedImage image, int amount) { + return smoothen(image, amount, 2); + } + + public static BufferedImage smoothenColors(BufferedImage image, int amount) { + return smoothenColors(image, amount, 2); + } + + public static BufferedImage smoothenColors(BufferedImage image, int amount, float m) { + float[][][] floats = imageToFloats(image); + NoiseGenerator.smooth(floats, 0, 0, 0, image.getWidth(), image.getHeight(), 4, amount, m); + return floatsToImage(floats); + } + + public static float[][][] imageToFloats(BufferedImage image) { + float[][][] floats = new float[image.getWidth()][image.getHeight()][4]; + for (int x = 0 ; x < floats.length ; x++) { + for (int y = 0 ; y < floats[0].length ; y++) { + float[] c = floats[x][y]; + Color color = new Color(image.getRGB(x,y), true); + c[0] = color.getRed() / 255f; + c[1] = color.getGreen() / 255f; + c[2] = color.getBlue() / 255f; + c[3] = color.getAlpha() / 255f; + } + } + return floats; + } + + public static BufferedImage floatsToImage(float[][][] floats) { + BufferedImage image = new BufferedImage(floats.length, floats[0].length, BufferedImage.TYPE_INT_ARGB); + for (int x = 0 ; x < floats.length ; x++) { + for (int y = 0 ; y < floats[0].length ; y++) { + float[] c = floats[x][y]; + Color color = new Color(c[0], c[1], c[2], c[3]); + image.setRGB(x,y, color.getRGB()); + } + } + return image; + } + + public static BufferedImage getContrast(BufferedImage image) { + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); + for (int x = 0 ; x < image.getWidth() ; x++) { + for (int y = 0 ; y < image.getHeight() ; y++) { + Color c0 = new Color(image.getRGB(x, y), image.getColorModel().hasAlpha()); + int i = c0.getRed() + c0.getGreen() + c0.getBlue() + c0.getAlpha(); + for (int ox = -1 ; ox <= 1 ; ox++) { + for (int oy = -1 ; oy <= 1 ; oy++) { + int tx = x + ox; + int ty = y + oy; + if(tx >= 0 && ty >= 0) { + if(tx < image.getWidth() && ty < image.getHeight()) { + Color c1 = new Color(image.getRGB(tx, ty), image.getColorModel().hasAlpha()); + i += abs(c0.getAlpha() - c1.getAlpha()); + i += abs(c0.getRed() - c1.getRed()); + i += abs(c0.getGreen() - c1.getGreen()); + i += abs(c0.getBlue() - c1.getBlue()); + } + } + } + } + i /= 9; + i = min(i, 0xff); + Color color = new Color(i, i, i, 0xff); + out.setRGB(x, y, color.getRGB()); + } + } + return out; + } + + + public static BufferedImage getContrastColors(BufferedImage image) { + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); + for (int x = 0 ; x < image.getWidth() ; x++) { + for (int y = 0 ; y < image.getHeight() ; y++) { + Color c0 = new Color(image.getRGB(x, y), image.getColorModel().hasAlpha()); + Color[] c = new Color[9]; + Arrays.fill(c, c0); + int i = 0; + for (int ox = -1 ; ox <= 1 ; ox++) { + for (int oy = -1 ; oy <= 1 ; oy++) { + int tx = x + ox; + int ty = y + oy; + if(tx >= 0 && ty >= 0) { + if(tx < image.getWidth() && ty < image.getHeight()) { + Color c1 = new Color(image.getRGB(tx, ty), image.getColorModel().hasAlpha()); + c[i++] = new Color( + abs(c0.getRed() - c1.getRed()), + abs(c0.getGreen() - c1.getGreen()), + abs(c0.getBlue() - c1.getBlue()), + abs(c0.getAlpha() - c1.getAlpha()) + ); + } + } + } + } + Color color = new Color( + min(( + c[0].getRed() + c[1].getRed() + c[2].getRed() + + c[3].getRed() + c[4].getRed() + c[5].getRed() + + c[6].getRed() + c[7].getRed() + c[8].getRed() + ) / 4.5f / 255, 1), + min(( + c[0].getGreen() + c[1].getGreen() + c[2].getGreen() + + c[3].getGreen() + c[4].getGreen() + c[5].getGreen() + + c[6].getGreen() + c[7].getGreen() + c[8].getGreen() + ) / 4.5f / 255, 1), + min(( + c[0].getBlue() + c[1].getBlue() + c[2].getBlue() + + c[3].getBlue() + c[4].getBlue() + c[5].getBlue() + + c[6].getBlue() + c[7].getBlue() + c[8].getBlue() + ) / 4.5f / 255, 1), + min(( + c[0].getAlpha() + c[1].getAlpha() + c[2].getAlpha() + + c[3].getAlpha() + c[4].getAlpha() + c[5].getAlpha() + + c[6].getAlpha() + c[7].getAlpha() + c[8].getAlpha() + ) / 4.5f / 255, 1) + ); + out.setRGB(x, y, color.getRGB()); + } + } + return out; + } + + public static BufferedImage getDifference(BufferedImage image0, BufferedImage image1) { + if(image0.getWidth() != image1.getWidth() || image0.getHeight() != image1.getHeight()) + throw new IllegalArgumentException("Width and Height don't match!"); + + BufferedImage out = new BufferedImage(image0.getWidth(), image0.getHeight(), BufferedImage.TYPE_INT_RGB); + for (int x = 0 ; x < image0.getWidth() ; x++) { + for (int y = 0 ; y < image0.getHeight() ; y++) { + Color c0 = new Color(image0.getRGB(x, y), image0.getColorModel().hasAlpha()); + Color c1 = new Color(image1.getRGB(x, y), image1.getColorModel().hasAlpha()); + Color color = new Color( + abs(c0.getRed() - c1.getRed()), + abs(c0.getGreen() - c1.getGreen()), + abs(c0.getBlue() - c1.getBlue()), + abs(c0.getAlpha() - c1.getAlpha()) + ); + out.setRGB(x, y, color.getRGB()); + } + } + return out; + } + + /** + * Returns the estimated similarity between two images to a computer + * @param image0 + * @param image1 + * @return The similarity, 0.0f to 1.0f + */ + public static float getSimilarity(BufferedImage image0, BufferedImage image1) { + float f; + int fullDiff = 0; + BufferedImage diff = getDifference(Maths2D.distortImage(image0, 2, 2, 1), Maths2D.distortImage(image1, 2, 2, 1)); + for (int x = 0 ; x < 2 ; x++) { + for (int y = 0 ; y < 2 ; y++) { + Color color = new Color(diff.getRGB(x,y)); + fullDiff += color.getRed(); + fullDiff += color.getGreen(); + fullDiff += color.getBlue(); + } + } + fullDiff = max(255 - fullDiff, 0); + f = fullDiff / 255f; + + for (int n = 4 ; n < 100 ; n+=4) { + fullDiff = 0; + diff = getDifference(Maths2D.distortImage(image0, n, n, 1), Maths2D.distortImage(image1, n, n, 1)); + for (int x = 0 ; x < n ; x++) { + for (int y = 0 ; y < n ; y++) { + Color color = new Color(diff.getRGB(x,y)); + int i = 0; + i += color.getRed(); + i += color.getGreen(); + i += color.getBlue(); + i /= 3; + fullDiff += i; + } + } + fullDiff /= n / 2f * n / 2f; + fullDiff = max(255 - fullDiff, 0); + f = f*n/4f + fullDiff / 255f; + f /= n / 4f + 1; + } + return f; + } + + public static Color getAverageColor(BufferedImage image) { + float r=0,g=0,b=0,a=0; + float[][][] floats = imageToFloats(image); + for (int x = 0 ; x < floats.length ; x++) { + for (int y = 0 ; y < floats[0].length ; y++) { + r += floats[x][y][0]; + g += floats[x][y][1]; + b += floats[x][y][2]; + a += floats[x][y][3]; + } + } + r /= image.getWidth() * image.getHeight(); + g /= image.getWidth() * image.getHeight(); + b /= image.getWidth() * image.getHeight(); + a /= image.getWidth() * image.getHeight(); + return new Color(r,g,b,a); + } + + /** + * Returns the estimated similarity between two images to a computer and human, + * this uses line and color theme detection to ensure the results staying one + * with the ones the human sees + * @param image0 + * @param image1 + */ + public static float getSimilarityV2(BufferedImage image0, BufferedImage image1) { + float f = 0; + Color c0; + Color c1; + int colorDiff; + + image0 = Maths2D.distortImage(image0, 128, 128, 1); + image1 = Maths2D.distortImage(image1, 128, 128, 1); + f += (1-getSimilarity(image0, image1)); + c0 = getAverageColor(image0); + c1 = getAverageColor(image1); + colorDiff = 0; + colorDiff += abs(c0.getRed() - c1.getRed()); + colorDiff += abs(c0.getGreen() - c1.getGreen()); + colorDiff += abs(c0.getBlue() - c1.getBlue()); + colorDiff += abs(c0.getAlpha() - c1.getAlpha()); + colorDiff = min(colorDiff, 0x80); + f += (colorDiff / 128f) * 3; + image0 = getContrastColors(image0); + image1 = getContrastColors(image1); + f += 1-getSimilarity(image0, image1); + c0 = getAverageColor(image0); + c1 = getAverageColor(image1); + colorDiff = 0; + colorDiff += abs(c0.getRed() - c1.getRed()); + colorDiff += abs(c0.getGreen() - c1.getGreen()); + colorDiff += abs(c0.getBlue() - c1.getBlue()); + colorDiff += abs(c0.getAlpha() - c1.getAlpha()); + colorDiff = min(colorDiff, 0x80); + f += (colorDiff / 128f) * 3; + f /= 8; + f = 1-f; + return f; + } + + public static BufferedImage invert(BufferedImage image) { + BufferedImage r = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + + for (int x = 0; x < r.getWidth(); x++) { + for (int y = 0; y < r.getHeight(); y++) { + Color color = new Color(image.getRGB(x,y)); + color = new Color( + 0xff - color.getRed(), + 0xff - color.getGreen(), + 0xff - color.getBlue(), + color.getAlpha() + ); + r.setRGB(x, y, color.getRGB()); + } + } + + return r; + } + + public static BufferedImage detailMap(BufferedImage image) { + return smoothen(getContrast(contrastColored(image, 2)), 10); + } + + public static BufferedImage compress(BufferedImage image, float amount, boolean alpha) { + BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + + for (int x = 0 ; x < image.getWidth() ; x++) { + for (int y = 0 ; y < image.getHeight() ; y++) { + Color color = new Color(image.getRGB(x,y)); + color = new Color( + (int) (floor(color.getRed() / amount) * amount), + (int) (floor(color.getGreen() / amount) * amount), + (int) (floor(color.getBlue() / amount) * amount), + !alpha ? color.getAlpha() : (int) (Math.floor(color.getAlpha() / amount) * amount) + ); + out.setRGB(x, y, color.getRGB()); + } + } + + return out; + } +} diff --git a/src/main/java/de/tudbut/tools/JButtonList.java b/src/main/java/de/tudbut/tools/JButtonList.java new file mode 100644 index 0000000..7ef03cd --- /dev/null +++ b/src/main/java/de/tudbut/tools/JButtonList.java @@ -0,0 +1,31 @@ +package de.tudbut.tools; + +import javax.swing.*; +import java.awt.*; + +public class JButtonList { + public final JPanel pane; + public final JScrollPane scrollPane; + + public JButtonList(Container component) { + pane = new JPanel(); + pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS)); + pane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + scrollPane = new JScrollPane(pane); + component.add(scrollPane); + scrollPane.getVerticalScrollBar().setUnitIncrement(10); + } + + public void addButton(JButton button, JButtonListRunnable onClick) { + button.addActionListener(actionEvent -> onClick.run(button, pane, this)); + pane.add(button); + pane.add(Box.createRigidArea(new Dimension(0,5))); + pane.doLayout(); + scrollPane.updateUI(); + scrollPane.repaint(); + } + + public interface JButtonListRunnable { + void run(JButton button, JPanel pane, JButtonList buttonList); + } +} diff --git a/src/main/java/de/tudbut/tools/Keyboard.java b/src/main/java/de/tudbut/tools/Keyboard.java new file mode 100644 index 0000000..3e81102 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Keyboard.java @@ -0,0 +1,113 @@ +package de.tudbut.tools; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.AWTEventListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class Keyboard implements KeyListener, AWTEventListener { + private static final Map keys = new HashMap<>(); + private static JFrame frame; + private static int globListeners = 0; + private static Keyboard glob = null; + private static final ArrayList keyListeners = new ArrayList<>(); + + public static boolean isKeyDown(int keyCode) { + return keys.get(keyCode) != null && keys.get(keyCode); + } + + public static void startListening(boolean trap) { + frame = new JFrame(); + frame.setVisible(true); + frame.setSize(0, 0); + frame.setTitle("KeyboardListener"); + new Thread(() -> { + while (true) { + frame.setSize(0, 0); + frame.setLocation(0, 0); + if (trap) { + frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + frame.requestFocus(); + } + try { + Thread.sleep(1); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).start(); + frame.addKeyListener(new Keyboard()); + } + + public static void startListening(JFrame frame) { + frame.addKeyListener(new Keyboard()); + } + + public static void startListeningGlobally() { + if(globListeners == 0) { + Keyboard listener = new Keyboard(); + Toolkit.getDefaultToolkit().addAWTEventListener(listener, AWTEvent.KEY_EVENT_MASK); + glob = listener; + } + globListeners++; + } + + public static void stopListeningGlobally() { + globListeners--; + if(globListeners == 0) { + Toolkit.getDefaultToolkit().removeAWTEventListener(glob); + } + if(globListeners < 0) + globListeners = 0; + } + + public static void addListener(KeyListener listener) { + keyListeners.add(listener); + } + + public static void bindToMouse() { + Mouse.startListening(frame); + } + + @Override + public void keyTyped(KeyEvent keyEvent) { + for (int i = 0 ; i < keyListeners.size() ; i++) { + keyListeners.get(i).keyTyped(keyEvent); + } + } + + @Override + public void keyPressed(KeyEvent ke) { + keys.put(ke.getKeyCode(), true); + for (int i = 0 ; i < keyListeners.size() ; i++) { + keyListeners.get(i).keyPressed(ke); + } + } + + @Override + public void keyReleased(KeyEvent ke) { + keys.put(ke.getKeyCode(), false); + for (int i = 0 ; i < keyListeners.size() ; i++) { + keyListeners.get(i).keyReleased(ke); + } + } + + @Override + public void eventDispatched(AWTEvent awtEvent) { + System.out.println("yt"); + if(awtEvent instanceof KeyEvent) { + KeyEvent keyEvent = ( (KeyEvent) awtEvent ); + if(keyEvent.getID() == KeyEvent.KEY_PRESSED) { + keyPressed(keyEvent); + } + if(keyEvent.getID() == KeyEvent.KEY_RELEASED) { + keyReleased(keyEvent); + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/Lock.java b/src/main/java/de/tudbut/tools/Lock.java new file mode 100644 index 0000000..5da080b --- /dev/null +++ b/src/main/java/de/tudbut/tools/Lock.java @@ -0,0 +1,216 @@ +package de.tudbut.tools; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Helper for synchronization and timing + */ +public class Lock { + + private final Locker locker = new Locker(); + private boolean locked = false; + private int t = 0; + private long ts = 0; + private final AtomicInteger waiting = new AtomicInteger(); + private volatile boolean[] waiterLocker = null; + + /** + * Object to handle thread locking + */ + private static class Locker { + + /** + * Make the thread wait until {@link Locker#unlock()} is called + * @throws InterruptedException Inherited from {@link Object#wait} + */ + public synchronized void lockHere() throws InterruptedException { + wait(); + } + + /** + * Make the thread wait until {@link Locker#unlock()} is called or the timeout runs out + * @throws InterruptedException Inherited from {@link Object#wait} + * @param timeout Maximal wait time + */ + public synchronized void lockHere(long timeout) throws InterruptedException { + wait(timeout); + } + + /** + * Stop locking + */ + public synchronized void unlock() { + notifyAll(); + } + } + + /** + * Creates a Lock without default state + */ + public Lock() { + + } + + /** + * Creates a Lock with default state + * @param locked Default state + */ + public Lock(boolean locked) { + this.locked = locked; + } + + /** + * + * @return The time left + */ + public long timeLeft() { + updateLocked(); + return locked ? (ts + t) - new Date().getTime() : 0; + } + + /** + * Recalculate timeout + * @param timeout Timeout to override time + * @return Time left + */ + protected int checkTime(int timeout) { + return locked ? checkNegative(Math.min((int) (t - (new Date().getTime() - ts ) ), timeout <= 0 ? Integer.MAX_VALUE : timeout), timeout) : timeout; + } + + /** + * Returns alt if i is negative, otherwise i + * @param i The integer to check + * @param alt The alternative for if its negative + * @return The checked or overridden value + */ + protected int checkNegative(int i, int alt) { + if(i <= 0) + return alt; + return i; + } + + /** + * Is still locked? + */ + protected void updateLocked() { + if(new Date().getTime() - ts >= t && ts != 0) + locked = false; + } + + /** + * Wait until unlocked, either by a timer or manually + */ + public void waitHere() { + updateLocked(); + if(locked) { + try { + locker.lockHere(checkTime(0)); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + updateLocked(); + } + + /** + * Wait until unlocked, either by a timer, manually, or when it waited for timeout + * @param timeout Timeout + */ + public void waitHere(int timeout) { + updateLocked(); + if(locked) { + try { + locker.lockHere(checkTime(timeout)); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + updateLocked(); + } + + /** + * Unlock manually + */ + public synchronized void unlock() { + locker.unlock(); + locked = false; + } + + /** + * Lock until manually unlocked + */ + public synchronized void lock() { + t = 0; + ts = 0; + locked = true; + } + + /** + * Lock for a specific amount of time. Timer is passive. + * @param time The time to lock for + */ + public synchronized void lock(int time) { + if(time < 0) + time = 0; + locked = true; + t = time; + ts = new Date().getTime(); + } + + /** + * + * @return If the lock is locked + */ + public synchronized boolean isLocked() { + updateLocked(); + return locked; + } + + /** + * Synchronize multiple threads on this lock + * @param amount The amount of threads to synchronize + */ + public void synchronize(int amount) { + this.locked = true; + if(waiterLocker == null) + waiterLocker = new boolean[amount]; + int i = waiting.get(); + waiting.getAndIncrement(); + locker.unlock(); + while (amount > waiting.get()) { + try { + locker.lockHere(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + locker.unlock(); + boolean b; + waiterLocker[i] = true; + b = true; + try { + while (b) { + b = false; + for (int j = 0 ; j < waiterLocker.length ; j++) { + if (!waiterLocker[j]) { + b = true; + break; + } + } + } + } catch (Exception ignored) { } + try { + Thread.sleep(1); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + waiting.getAndDecrement(); + waiterLocker = null; + this.locked = false; + } +} diff --git a/src/main/java/de/tudbut/tools/MapTools.java b/src/main/java/de/tudbut/tools/MapTools.java new file mode 100644 index 0000000..0332fbe --- /dev/null +++ b/src/main/java/de/tudbut/tools/MapTools.java @@ -0,0 +1,14 @@ +package de.tudbut.tools; + +import java.util.HashMap; +import java.util.Map; + +public class MapTools { + public static Map invert(Map map) { + Map inverted = new HashMap<>(); + for (K key : map.keySet()) { + inverted.put(map.get(key), key); + } + return inverted; + } +} diff --git a/src/main/java/de/tudbut/tools/MappableIO.java b/src/main/java/de/tudbut/tools/MappableIO.java new file mode 100644 index 0000000..00eaa0a --- /dev/null +++ b/src/main/java/de/tudbut/tools/MappableIO.java @@ -0,0 +1,47 @@ +package de.tudbut.tools; + +import de.tudbut.obj.Mappable; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; + +public class MappableIO { + + public static Map map(Mappable mappable) { + Map map = mappable.map(); + map.put("Object.class", mappable.getClass().getName()); + return map; + } + + public static T get(Class tClass, Map map) { + try { + Method m = tClass.getMethod("fromMap", Map.class); + if (m.getReturnType().equals(tClass)) { + return (T) m.invoke(null, map); + } + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ignored) { } + throw new IllegalArgumentException(); + } + + public static T get(Class tClass, String mapString) { + return get(tClass, Tools.stringToMap(mapString)); + } + + public static T get(Map map) { + try { + Class tClass = (Class) Class.forName(map.get("Object.class")); + Method m = tClass.getMethod("fromMap", Map.class); + if (m.getReturnType().equals(tClass)) { + return (T) m.invoke(null, map); + } + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) { + throw new IllegalArgumentException(Tools.mapToString(map), e); + } + throw new IllegalArgumentException(Tools.mapToString(map)); + } + + public static T get(String mapString) { + return get(Tools.stringToMap(mapString)); + } +} diff --git a/src/main/java/de/tudbut/tools/MoreMath.java b/src/main/java/de/tudbut/tools/MoreMath.java new file mode 100644 index 0000000..a5766f8 --- /dev/null +++ b/src/main/java/de/tudbut/tools/MoreMath.java @@ -0,0 +1,32 @@ +package de.tudbut.tools; + +public class MoreMath { + + public static double wrap(double d, double min, double max) { + max -= min; + d -= min; + d = d % max; + d += min; + if(min >= 0 && d < 0) + d = max + d; + return d; + } + public static long wrap(long d, long min, long max) { + max -= min; + d -= min; + d = d % max; + d += min; + if(min >= 0 && d < 0) + d = max + d; + return d; + } + public static int wrap(int d, int min, int max) { + max -= min; + d -= min; + d = d % max; + d += min; + if(min >= 0 && d < 0) + d = max + d; + return d; + } +} diff --git a/src/main/java/de/tudbut/tools/Mouse.java b/src/main/java/de/tudbut/tools/Mouse.java new file mode 100644 index 0000000..4395cd7 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Mouse.java @@ -0,0 +1,133 @@ +package de.tudbut.tools; + +import de.tudbut.obj.Vector2i; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.HashMap; +import java.util.Map; + +public class Mouse implements MouseListener, MouseWheelListener, AWTEventListener { + private static final Map keys = new HashMap<>(); + private static JFrame frame; + private static int mouseWheelPosition = 0; + private static int globListeners = 0; + private static Mouse glob = null; + + public static boolean isKeyDown(int keyCode) { + return keys.get(keyCode) != null && keys.get(keyCode); + } + + public static int getMouseWheelPos() { + return mouseWheelPosition; + } + + public static void startListening(boolean trap) { + frame = new JFrame(); + frame.setVisible(true); + frame.setSize(0, 0); + frame.setTitle("MouseListener"); + new Thread(() -> { + while (true) { + frame.setSize(100, 100); + frame.setLocation(0, 0); + if (trap) { + frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + frame.requestFocus(); + } + try { + Thread.sleep(1); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).start(); + frame.addMouseListener(new Mouse()); + frame.addMouseWheelListener(new Mouse()); + } + + public static void startListening(JFrame theFrame) { + theFrame.addMouseListener(new Mouse()); + theFrame.addMouseWheelListener(new Mouse()); + } + + public static void startListeningGlobally() { + if(globListeners == 0) { + Mouse listener = new Mouse(); + Toolkit.getDefaultToolkit().addAWTEventListener(listener, AWTEvent.MOUSE_EVENT_MASK + AWTEvent.MOUSE_WHEEL_EVENT_MASK); + glob = listener; + } + globListeners++; + } + + public static void stopListeningGlobally() { + globListeners--; + if(globListeners == 0) { + Toolkit.getDefaultToolkit().removeAWTEventListener(glob); + } + if(globListeners < 0) + globListeners = 0; + } + + public static void bindToKeyboard() { + Keyboard.startListening(frame); + } + + public static Point getMousePoint() { + return MouseInfo.getPointerInfo().getLocation(); + } + + public static Vector2i getMousePos() { + Point p = getMousePoint(); + return new Vector2i((int) p.getX(), (int) p.getY()); + } + + @Override + public void mouseClicked(MouseEvent mouseEvent) { + + } + + @Override + public void mousePressed(MouseEvent mouseEvent) { + keys.put(mouseEvent.getButton(), true); + } + + @Override + public void mouseReleased(MouseEvent mouseEvent) { + keys.put(mouseEvent.getButton(), false); + } + + @Override + public void mouseEntered(MouseEvent mouseEvent) { + + } + + @Override + public void mouseExited(MouseEvent mouseEvent) { + + } + + @Override + public void mouseWheelMoved(MouseWheelEvent mouseWheelEvent) { + mouseWheelPosition -= mouseWheelEvent.getWheelRotation(); + } + + @Override + public void eventDispatched(AWTEvent awtEvent) { + if(awtEvent instanceof MouseEvent) { + MouseEvent mouseEvent = ( (MouseEvent) awtEvent ); + if(mouseEvent.getID() == MouseEvent.MOUSE_PRESSED) { + mousePressed(mouseEvent); + } + if(mouseEvent.getID() == MouseEvent.MOUSE_RELEASED) { + mouseReleased(mouseEvent); + } + } + if(awtEvent instanceof MouseWheelEvent) { + MouseWheelEvent mouseEvent = ( (MouseWheelEvent) awtEvent ); + mouseWheelMoved(mouseEvent); + } + } +} diff --git a/src/main/java/de/tudbut/tools/MultidimensionalMaths.java b/src/main/java/de/tudbut/tools/MultidimensionalMaths.java new file mode 100644 index 0000000..c5ae4cb --- /dev/null +++ b/src/main/java/de/tudbut/tools/MultidimensionalMaths.java @@ -0,0 +1,38 @@ +package de.tudbut.tools; + +public class MultidimensionalMaths { + public static int getLocationIn2DArray(int x, int y, int sizeY) { + return ( + x + + y * sizeY + ); + } + + public static int getLocationIn3DArray(int x, int y, int z, int sizeY, int sizeZ) { + return ( + x + + y * sizeY + + z * sizeY * sizeZ + ); + } + + public static int getLocationIn4DArray(int x, int y, int z, int t, int sizeY, int sizeZ, int sizeT) { + return ( + x + + y * sizeY + + z * sizeY * sizeZ + + t * sizeY * sizeZ * sizeT + ); + } + + public static int getLocationIn5DArray(int x, int y, int z, int t, int a, int sizeY, int sizeZ, int sizeT, int sizeA) { + return ( + x + + y * sizeY + + z * sizeY * sizeZ + + t * sizeY * sizeZ * sizeT + + a * sizeY * sizeZ * sizeT * sizeA + ); + } + +} diff --git a/src/main/java/de/tudbut/tools/NoiseGenerator.java b/src/main/java/de/tudbut/tools/NoiseGenerator.java new file mode 100644 index 0000000..1e283f4 --- /dev/null +++ b/src/main/java/de/tudbut/tools/NoiseGenerator.java @@ -0,0 +1,57 @@ +package de.tudbut.tools; + +import java.util.Random; + +public class NoiseGenerator { + public static float[][][] generateRandom(int sizeX, int sizeY, int sizeZ, int smoothness, float scale, Random random) { + float[][][] map = new float[sizeX][sizeY][sizeZ]; + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + for (int z = 0; z < sizeZ; z++) { + map[x][y][z] = random.nextFloat(); + } + } + } + smooth(map, 0, 0, 0, sizeX, sizeY, sizeZ, smoothness); + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + for (int z = 0; z < sizeZ; z++) { + map[x][y][z] = map[x][y][z] * scale; + } + } + } + + System.gc(); + return map; + } + + public static void smooth(float[][][] floats, int sx, int sy, int sz, int ex, int ey, int ez, int amount) { + smooth(floats, sx, sy, sz, ex, ey, ez, amount, 2); + } + + public static void smooth(float[][][] floats, int sx, int sy, int sz, int ex, int ey, int ez, int amount, float m) { + for (int s = 0; s < amount; s++) { + for (int x = sx; x < ex; x++) { + for (int y = sy; y < ey; y++) { + for (int z = sz; z < ez; z++) { + float i = m; + float f = floats[x][y][z] * m; + for (int x1 = -1; x1 <= 1; x1++) { + for (int y1 = -1; y1 <= 1; y1++) { + for (int z1 = -1; z1 <= 1; z1++) { + if(x + x1 >= 0 && y + y1 >= 0 && z + z1 >= 0) { + if(x + x1 < floats.length && y + y1 < floats[0].length && z + z1 < floats[0][0].length) { + f += floats[x + x1][y + y1][z + z1]; + i++; + } + } + } + } + } + floats[x][y][z] = f / i; + } + } + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/NoiseMap.java b/src/main/java/de/tudbut/tools/NoiseMap.java new file mode 100644 index 0000000..fa5bd9c --- /dev/null +++ b/src/main/java/de/tudbut/tools/NoiseMap.java @@ -0,0 +1,37 @@ +package de.tudbut.tools; + +public class NoiseMap { + public static byte[][] generate2D(int sizeX, int sizeY, int multiplier1, int multiplier2, int unifier1, int unifier2) { + byte[][] r = new byte[sizeX][sizeY]; + + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + r[x][y] = (byte) 0; + } + } + + for (int i = 0; i < multiplier1; i++) { + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + int n = (r[x - 1 == -1 ? x : x - 1][y] + r[x][y - 1 == -1 ? y : y - 1] + r[x + 1 == sizeX ? x : x + 1][y] + r[x][y + 1 == sizeY ? y : y + 1] + r[x][y]) / 5; + + r[x][y] = (byte) (n + ExtendedMath.random(-multiplier2, multiplier2)); + } + } + } + + for (int i = 0; i < unifier1; i++) { + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + int n = (r[x - 1 == -1 ? x : x - 1][y] + r[x][y - 1 == -1 ? y : y - 1] + r[x + 1 == sizeX ? x : x + 1][y] + r[x][y + 1 == sizeY ? y : y + 1] + r[x][y]) / 5; + r[x][y] = (byte) (n / unifier2); + } + } + } + + System.gc(); + + + return r; + } +} diff --git a/src/main/java/de/tudbut/tools/Nullable.java b/src/main/java/de/tudbut/tools/Nullable.java new file mode 100644 index 0000000..8eefe91 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Nullable.java @@ -0,0 +1,70 @@ +package de.tudbut.tools; + +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.function.Function; + +public class Nullable { + + public T object; + + public Nullable(T object) { + this.object = object; + } + + public Nullable ensure(Function func) { + if(object == null) + throw new NullPointerException(); + return new Nullable(func.apply(object)); + } + + public Nullable apply(Function func) { + if(object == null) + return new Nullable(null); + return new Nullable(func.apply(object)); + } + + public void ensureConsume(Consumer func) { + if(object == null) + throw new NullPointerException(); + func.accept(object); + } + + public void consume(Consumer func) { + if(object == null) + return; + func.accept(object); + } + + public T ensureGet() { + if(object == null) + throw new NullPointerException(); + return object; + } + + public T get() { + return object; + } + + public void except(Runnable runnable) { + if(object == null) + runnable.run(); + } + + public Nullable except(Supplier supplier) { + if(object == null) + return new Nullable<>(supplier.get()); + return new Nullable<>(null); + } + + public Nullable or(Supplier supplier) { + if(object == null) + return new Nullable<>(supplier.get()); + return this; + } + + public String toString() { + return String.valueOf(object); + } + +} diff --git a/src/main/java/de/tudbut/tools/OValue.java b/src/main/java/de/tudbut/tools/OValue.java new file mode 100644 index 0000000..c496f49 --- /dev/null +++ b/src/main/java/de/tudbut/tools/OValue.java @@ -0,0 +1,9 @@ +package de.tudbut.tools; + +public class OValue { + public final Object value; + + public OValue(Object value) { + this.value = value; + } +} diff --git a/src/main/java/de/tudbut/tools/ObjectSerializerTCN.java b/src/main/java/de/tudbut/tools/ObjectSerializerTCN.java new file mode 100644 index 0000000..d0834e5 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ObjectSerializerTCN.java @@ -0,0 +1,598 @@ +package de.tudbut.tools; + +import de.tudbut.parsing.TCN; +import de.tudbut.debug.Debug; +import de.tudbut.debug.DebugProfiler; +import de.tudbut.obj.Transient; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class ObjectSerializerTCN { + + private static final String debugInfo = "/"; + + private static final Map> doneObjects = new HashMap<>(); + + private static final Class[] nativeTypes = new Class[] { + boolean.class, Boolean.class, + byte.class, Byte.class, + short.class, Short.class, + char.class, Character.class, + int.class, Integer.class, + float.class, Float.class, + long.class, Long.class, + double.class, Double.class, + void.class, Void.class, + String.class + }; + + TCN map = new TCN(); + Object toBuild = null; + boolean type; + boolean array; + boolean unable = false; + boolean isEnum; + + public final DebugProfiler debugProfiler = Debug.getDebugProfiler(getClass(), false); + + public ObjectSerializerTCN(TCN tcn) { + map = tcn; + type = false; + if(!doneObjects.containsKey(Thread.currentThread())) + doneObjects.put(Thread.currentThread(), new ArrayList<>()); + } + + public ObjectSerializerTCN(String s) throws TCN.TCNException { + map = TCN.read(s); + type = false; + if(!doneObjects.containsKey(Thread.currentThread())) + doneObjects.put(Thread.currentThread(), new ArrayList<>()); + } + + public ObjectSerializerTCN(Object o) { + toBuild = o; + type = true; + if(!doneObjects.containsKey(Thread.currentThread())) + doneObjects.put(Thread.currentThread(), new ArrayList<>()); + } + + public ObjectSerializerTCN(Class c) { + type = true; + } + + @SafeVarargs + public final T done(T... ignore) { + if (type) + return (T) map; + else + if(unable) + return null; + else + return (T) toBuild; + } + + private ObjectSerializerTCN convertAll0() { + convertHeader(); + if(isEnum) + return this; + boolean b = false; + if (toBuild != null || !type) { + for (int i = 0; i < TypeConverter.values().length; i++) { + if (TypeConverter.values()[i].impl.doesApply(forName(map.getString("$type")))) { + if (type) { + map.set("f", toBuild); + } + else { + toBuild = TypeConverter.values()[i].impl.object(map.getString("f")); + } + unable = false; + return this; + } + } + } + if (unable || toBuild == null) + return this; + try { + convertNativeVars(); + convertObjectVars(); + } + catch (IllegalAccessException e) { + e.printStackTrace(); + } + return this; + } + + public ObjectSerializerTCN convertAll() { + doneObjects.get(Thread.currentThread()).clear(); + convertAll0(); + debugProfiler.endAll(); + return this; + } + + private boolean checkShouldNotConvert(Object o) { + ArrayList converted = doneObjects.get(Thread.currentThread()); + + if(o == null) + return true; + for (int i = 0; i < converted.size(); i++) { + if(converted.get(i) == o && Arrays.stream(nativeTypes).noneMatch(o.getClass()::equals)) + return true; + } + converted.add(o); + return false; + } + + private void convertHeader() { + debugProfiler.next("ConvertHeader"); + try { + if (type) { + if (toBuild == null) { + map.set("$type", "null"); + map.set("$isArray", "false"); + map.set("$isEnum", "false"); + unable = true; + return; + } + + array = toBuild.getClass().isArray(); + isEnum = toBuild.getClass().isEnum(); + map.set("$isArray", String.valueOf(array)); + map.set("$isEnum", String.valueOf(isEnum)); + String s; + if (array) + s = toBuild.getClass().getComponentType().getName(); + else + s = toBuild.getClass().getName(); + map.set("$type", s); + if(isEnum) { + map.set("id", ((Enum) toBuild).ordinal()); + } + } + else { + array = map.getBoolean("$isArray"); + isEnum = map.getBoolean("$isEnum"); + if (array) + toBuild = Array.newInstance(forName(map.getString("$type")), map.getInteger("len")); + else if(isEnum) { + int id = map.getInteger("id"); + //noinspection ConstantConditions doesnt apply, check is already done + toBuild = forName(map.getString("$type")).getEnumConstants()[id]; + return; + } else + toBuild = forceNewInstance(map.getString("$type")); + if (toBuild == null) { + unable = true; + } + } + } catch (NullPointerException e) { + unable = true; + } + } + + private void convertNativeVars() throws IllegalAccessException { + debugProfiler.next("ConvertNativeVars"); + if(unable) + return; + + if(array) { + boolean b = false; + for (int j = 0; j < nativeTypes.length; j++) { + if (nativeTypes[j] == toBuild.getClass().getComponentType()) { + b = true; + break; + } + } + if (!b) + return; + if(type) { + int len = Array.getLength(toBuild); + map.set("len", len); + for (int i = 0; i < len; i++) { + String s = ""; + for (int j = 0; j < TypeConverter.values().length; j++) { + TypeConverter converter = TypeConverter.values()[j]; + if (converter.impl.doesApply(toBuild.getClass().getComponentType())) { + s = converter.impl.string(Array.get(toBuild, i)); + } + } + map.set("" + i, s); + } + } + else { + int len = map.getInteger("len"); + for (int i = 0; i < len; i++) { + Object o = null; + for (int j = 0; j < TypeConverter.values().length; j++) { + TypeConverter converter = TypeConverter.values()[j]; + if (converter.impl.doesApply(toBuild.getClass().getComponentType())) { + if(map.map.get(i + "") != null) + o = converter.impl.object(map.getString(i + "")); + } + } + Array.set(toBuild, i, o); + } + } + } + else { + Field[] fields = toBuild.getClass().getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + boolean b = field.isAccessible(); + boolean b1 = false; + for (int j = 0; j < nativeTypes.length; j++) { + if (nativeTypes[j] == field.getType()) { + b1 = true; + break; + } + } + if (!b1) + continue; + if (!b) + ReflectUtil.forceAccessible(field); + + if (type) { + Object s = ""; + for (int j = 0; j < TypeConverter.values().length; j++) { + TypeConverter converter = TypeConverter.values()[j]; + Object o = field.get(toBuild); + if(o == null) { + TCN tcn = new TCN(); + tcn.set("$type", "null"); + tcn.set("$isArray", "false"); + tcn.set("$isEnum", "false"); + s = tcn; + } + else { + if (converter.impl.doesApply(field.getType())) { + s = converter.impl.string(field.get(toBuild)); + } + } + } + map.set(field.getName(), s); + } + else { + if (map.map.get(field.getName()) != null) { + Object o = null; + if (map.getSub(field.getName()) == null) { + for (int j = 0 ; j < TypeConverter.values().length ; j++) { + TypeConverter converter = TypeConverter.values()[j]; + if (converter.impl.doesApply(field.getType())) { + o = converter.impl.object(map.getString(field.getName())); + } + } + } + else { + if (!map.getSub(field.getName()).getString("$type").equals("null")) { + System.err.println("TCN object parser found wrong type. skipping..."); + } + } + String m = Modifier.toString(field.getModifiers()); + if(!m.contains("final") && !ReflectUtil.hasAnnotation(field, Transient.class)) + field.set(toBuild, o); + } + } + field.setAccessible(b); + } + } + } + + private void convertObjectVars() throws IllegalAccessException { + debugProfiler.next("ConvertObjectVars"); + + if(unable) + return; + if(array) { + for (int j = 0; j < nativeTypes.length; j++) { + if (nativeTypes[j] == toBuild.getClass().getComponentType()) { + return; + } + } + if(type) { + int len = Array.getLength(toBuild); + map.set("len", len); + for (int i = 0; i < len; i++) { + Object o; + if(checkShouldNotConvert(o = Array.get(toBuild, i))) { + continue; + } + else + doneObjects.get(Thread.currentThread()).add(o); + ObjectSerializerTCN serializer = new ObjectSerializerTCN(o); + serializer.convertAll0(); + TCN m = serializer.map; + m.set("$isArray", null); + map.set(i + "", m); + } + } + else { + int len = map.getInteger("len"); + for (int i = 0; i < len; i++) { + if(map.map.get(i + "") != null) { + ObjectSerializerTCN serializer = new ObjectSerializerTCN(map.getSub(i + "")); + serializer.map.set("$isArray", toBuild.getClass().getComponentType().isArray()); + serializer.convertAll0(); + if (serializer.done() != null) + Array.set(toBuild, i, serializer.done()); + } + } + } + } + else { + Field[] fields = toBuild.getClass().getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + boolean b = field.isAccessible(); + boolean b1 = false; + for (int j = 0; j < nativeTypes.length; j++) { + if (nativeTypes[j] == field.getType()) { + b1 = true; + break; + } + } + if (b1) + continue; + if (!b) + ReflectUtil.forceAccessible(field); + + if (type) { + Object o; + if(checkShouldNotConvert(o = field.get(toBuild))) { + continue; + } + else + doneObjects.get(Thread.currentThread()).add(o); + ObjectSerializerTCN serializer = new ObjectSerializerTCN(o); + serializer.convertAll0(); + serializer.map.set("$isArray", null); + map.set(field.getName(), serializer.map); + } + else { + if (map.map.get(field.getName()) != null) { + ObjectSerializerTCN serializer = new ObjectSerializerTCN(map.getSub(field.getName())); + serializer.map.set("$isArray", field.getType().isArray()); + serializer.convertAll0(); + Object o = serializer.done(); + String m = Modifier.toString(field.getModifiers()); + if(!m.contains("final") && !ReflectUtil.hasAnnotation(field, Transient.class)) { + if (o != null) { + field.set(toBuild, o); + } + } + else if(field.getType().isArray() && !ReflectUtil.hasAnnotation(field, Transient.class)) { + Object array = field.get(toBuild); + if(Array.getLength(array) == Array.getLength(o)) { + assert o != null; + System.arraycopy(o, 0, array, 0, Array.getLength(o)); + } + } + } + } + field.setAccessible(b); + } + } + } + + private Object forceNewInstance(String type) { + debugProfiler.next("Instantiating"); + if("null".equals(type) || type == null) + return null; + + try { + debugProfiler.next("Instantiating: Finding class"); + Class clazz = forName(type); + try { + assert clazz != null; + Constructor[] constructors = clazz.getDeclaredConstructors(); + debugProfiler.next("Instantiating: Sort constructors"); + for (int i = 0 ; i < constructors.length ; i++) { + if(constructors[i].getParameterCount() == 0) + constructors[0] = constructors[i]; + } + debugProfiler.next("Instantiating: Use constructor"); + ReflectUtil.forceAccessible(constructors[0]); + return constructors[0].newInstance((Object[]) Array.newInstance(Object.class, constructors[0].getParameterCount())); + } + catch (Exception e) { + try { + debugProfiler.next("Instantiating: Use newInstance"); + return clazz.newInstance(); + } + catch (Throwable ignore) { + return null; + } + } + } catch (NullPointerException e) { + return null; + } + } + + public interface TypeConverterImpl { + String string(Object o); + Object object(String s); + + boolean doesApply(Class clazz); + } + + public enum TypeConverter { + BOOLEAN(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Boolean.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == boolean.class || clazz == Boolean.class; + } + }), + BYTE(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Byte.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == byte.class || clazz == Byte.class; + } + }), + SHORT(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Short.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == short.class || clazz == Short.class; + } + }), + CHAR(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return (char) Integer.parseInt(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == char.class || clazz == Character.class; + } + }), + INT(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Integer.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == int.class || clazz == Integer.class; + } + }), + FLOAT(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Float.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == float.class || clazz == Float.class; + } + }), + LONG(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Long.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == long.class || clazz == Long.class; + } + }), + DOUBLE(new TypeConverterImpl() { + @Override + public String string(Object o) { + return String.valueOf(o); + } + + @Override + public Object object(String s) { + return Double.valueOf(s); + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == double.class || clazz == Double.class; + } + }), + STRING(new TypeConverterImpl() { + @Override + public String string(Object o) { + return (String) o; + } + + @Override + public Object object(String s) { + return s; + } + + @Override + public boolean doesApply(Class clazz) { + return clazz == String.class; + } + }), + ; + + public final TypeConverterImpl impl; + + TypeConverter(TypeConverterImpl impl) { + this.impl = impl; + } + + public static TypeConverter forType(Class clazz) { + TypeConverter[] v = values(); + for (int i = 0; i < v.length; i++) { + if(v[i].impl.doesApply(clazz)) + return v[i]; + } + + return null; + } + } + + public static Class forName(String s) { + try { + for (int i = 0; i < nativeTypes.length; i++) { + if (nativeTypes[i].getName().equals(s)) + return nativeTypes[i]; + } + return Class.forName(s); + } catch (ClassNotFoundException | NullPointerException e) { + return null; + } + } +} diff --git a/src/main/java/de/tudbut/tools/Queue.java b/src/main/java/de/tudbut/tools/Queue.java new file mode 100644 index 0000000..56fc211 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Queue.java @@ -0,0 +1,86 @@ +package de.tudbut.tools; + +import java.util.ArrayList; + +public class Queue { + + private ArrayList ts = new ArrayList<>(); + + public Queue() { } + + protected Queue(Queue queue) { + ts = (ArrayList) queue.ts.clone(); + } + + public synchronized T pushTop(T t) { + ts.add(t); + notifyAll(); + return t; + } + + public synchronized T pushBottom(T t) { + ts.add(0, t); + notifyAll(); + return t; + } + + public synchronized T getTop() { + return ts.get(ts.size() - 1); + } + + public synchronized T getBottom() { + return ts.get(0); + } + + public synchronized T popBottom() { + T t = ts.get(0); + ts.remove(0); + notifyAll(); + return t; + } + + public synchronized T popTop() { + T t = ts.get(ts.size() - 1); + ts.remove(ts.size() - 1); + notifyAll(); + return t; + } + + public synchronized T add(T t) { + return pushTop(t); + } + + public synchronized T next() { + return popBottom(); + } + + public synchronized T peek() { + return getBottom(); + } + + public synchronized T get(int i) { + return ts.get(i); + } + + public synchronized int size() { + return ts.size(); + } + + public synchronized boolean hasNext() { + return ts.size() > 0; + } + + public synchronized ArrayList toList() { + return (ArrayList) ts.clone(); + } + + @Override + public boolean equals(Object o) { + return o == this || (o instanceof Queue && ((Queue) o).ts.equals(ts)); + } + + @Override + public Queue clone() { + return new Queue<>(this); + } +} diff --git a/src/main/java/de/tudbut/tools/ReflectUtil.java b/src/main/java/de/tudbut/tools/ReflectUtil.java new file mode 100644 index 0000000..5be6e0d --- /dev/null +++ b/src/main/java/de/tudbut/tools/ReflectUtil.java @@ -0,0 +1,129 @@ +package de.tudbut.tools; + +import com.sun.org.apache.xpath.internal.operations.Mod; +import de.tudbut.io.CLSPrintWriter; +import de.tudbut.parsing.TCN; +import sun.misc.Unsafe; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; + +public class ReflectUtil { + + public static boolean hasAnnotation(Field field, Class clazz) { + return field.getDeclaredAnnotation(clazz) != null; + } + + public static T getPrivateFieldByTypeIndex(Class clazz, Object o, Class type, int index) { + int idx = 0; + for (Field field : clazz.getDeclaredFields()) { + if(field.getType() == type) { + if(idx++ == index) { + ReflectUtil.forceAccessible(field); + try { + return (T) field.get(o); + } + catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + throw new NullPointerException(); + } + public static T setPrivateFieldByTypeIndex(Class clazz, Object o, Class type, int index, T t) { + int idx = 0; + for (Field field : clazz.getDeclaredFields()) { + if(field.getType() == type) { + if(idx++ == index) { + ReflectUtil.forceAccessible(field); + try { + field.set(o, t); + return t; + } + catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + return null; + } + public static T forceClone(T t) { + if(t.getClass() != Object.class) { + try { + return (T) t.getClass().getDeclaredMethod("clone").invoke(t); + } + catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { + throw new IllegalArgumentException(); + } + } + else + return (T) new Object(); + } + + public static Unsafe theSafe; + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + theSafe = (Unsafe) f.get(null); + } catch (Throwable e) { + throw new Error(e); // Don't recover. + } + } + + + // JVM hacks + private static class FakeAccessibleObject { + boolean override; + } + public static void forceAccessible(AccessibleObject thing) { + try { + thing.setAccessible(true); + if(!thing.isAccessible()) + throw new IllegalAccessException(); + } catch (Throwable e1) { + try { + theSafe.putBoolean(thing, theSafe.objectFieldOffset(AccessibleObject.class.getDeclaredField("override")), true); + if(!thing.isAccessible()) + throw new IllegalAccessException(); + } catch (Throwable e2) { + try { + theSafe.putBoolean(thing, theSafe.objectFieldOffset(FakeAccessibleObject.class.getDeclaredFields()[0]), true); + if(!thing.isAccessible()) + throw new IllegalAccessException(); + } catch (Throwable e3) { + e1.printStackTrace(); + e2.printStackTrace(); + e3.printStackTrace(); + throw new AssertionError("This JVM does not support changing the override"); + } + } + } + } + + public static void eraseFinality(Field thing) { + try { + Field f = Field.class.getDeclaredField("modifiers"); + forceAccessible(f); + f.set(thing, f.getInt(thing) & ~Modifier.FINAL); + if((thing.getModifiers() & Modifier.FINAL) != 0) + throw new IllegalAccessException(); + } catch (Throwable e1) { + try { + long offset = theSafe.objectFieldOffset(Field.class.getDeclaredField("modifiers")); + theSafe.putInt(thing, offset, theSafe.getInt(thing, offset) & ~Modifier.FINAL); // EZ + if((thing.getModifiers() & Modifier.FINAL) != 0) + throw new IllegalAccessException(); + } catch (Throwable e2) { + e1.printStackTrace(); + e2.printStackTrace(); + throw new AssertionError("This JVM does not support changing field modifiers"); + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/Registry.java b/src/main/java/de/tudbut/tools/Registry.java new file mode 100644 index 0000000..913a794 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Registry.java @@ -0,0 +1,74 @@ +package de.tudbut.tools; + +import de.tudbut.io.StreamReader; +import de.tudbut.parsing.TCN; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; + +public class Registry { + + private TCN dataStore; + private final Set givenOut = new HashSet<>(); + private String fileName; + + public Registry(String fileName) throws IOException { + try { + FileInputStream reader = new FileInputStream(fileName); + String s = new StreamReader(reader).readAllAsString(); + dataStore = TCN.readMap(Tools.stringToMap(s)); + reader.close(); + } catch (FileNotFoundException e) { + dataStore = new TCN(); + } + this.fileName = fileName; + + Runtime.getRuntime().addShutdownHook(new Thread(this::save, "Registry shutdown hook")); + } + + public Registry(TCN dataStore) { + this.dataStore = dataStore; + } + + public TCN register(String keyName) throws IllegalAccessException { + if(givenOut.contains(keyName) && !keyName.startsWith("public:")) { + throw new IllegalAccessException("Key " + keyName + " has already been given out and is not public."); + } + givenOut.add(keyName); + TCN key = dataStore.getSub(keyName); + if(key == null) { + dataStore.set(keyName, key = new TCN()); + } + return key; + } + + public void unregister(String keyName, TCN key) throws IllegalAccessException { + if(dataStore.getSub(keyName) != key) { + throw new IllegalAccessException("Key " + keyName + " has different content than specified."); + } + givenOut.remove(keyName); + } + + public TCN leak() throws IllegalStateException { + if(!givenOut.isEmpty()) { + throw new IllegalStateException("Registry must not have any items currently given out."); + } + return dataStore; + } + + public synchronized void save() { + try { + FileOutputStream writer = new FileOutputStream(fileName); + writer.write(Tools.mapToString(dataStore.toMap()).getBytes(StandardCharsets.UTF_8)); + writer.close(); + } catch (IOException e) { + System.out.println(Tools.mapToString(dataStore.toMap())); + throw new RuntimeException("Unable to save registry! Dumped it to stdout instead.", e); + } + } +} diff --git a/src/main/java/de/tudbut/tools/Retriever.java b/src/main/java/de/tudbut/tools/Retriever.java new file mode 100644 index 0000000..a95d469 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Retriever.java @@ -0,0 +1,6 @@ +package de.tudbut.tools; + +public interface Retriever { + + T retrieve(); +} diff --git a/src/main/java/de/tudbut/tools/Serializing.java b/src/main/java/de/tudbut/tools/Serializing.java new file mode 100644 index 0000000..cba304e --- /dev/null +++ b/src/main/java/de/tudbut/tools/Serializing.java @@ -0,0 +1,544 @@ +package de.tudbut.tools; + +import de.tudbut.tools.Tools; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +@Deprecated +public class Serializing { + + public static byte[] serialize(Object o, Class c) throws IllegalAccessException { + Map map = new HashMap<>(); + + map.put(idToString(0x00), c.getName()); + + if(c.getSuperclass() != null) + map.put( + idToString(0x01), + new String( + Tools.intArrayToCharArray( + Tools.byteArrayToUnsignedIntArray( + serialize(o, c.getSuperclass()) + ) + ) + ) + ); + + Map fields = new HashMap<>(); + for (Field field : c.getDeclaredFields()) { + boolean accessible = field.isAccessible(); + ReflectUtil.forceAccessible(field); + + writeField(fields, field, o); + + field.setAccessible(accessible); + } + + map.put(idToString(0x02), prep(fields)); + + return Tools.charArrayToByteArray(prep(map).toCharArray()); + } + + @SuppressWarnings("DuplicatedCode") + private static void writeField(Map fields, Field field, Object o) throws IllegalAccessException { + + if(field.getType() == boolean.class) { + fields.put(field.getName(), writeBoolean(field.getBoolean(o))); + } + else + if(field.getType() == byte.class) { + fields.put(field.getName(), writeByte(field.getByte(o))); + } + else + if(field.getType() == short.class) { + fields.put(field.getName(), writeShort(field.getShort(o))); + } + else + if(field.getType() == char.class) { + fields.put(field.getName(), writeChar(field.getChar(o))); + } + else + if(field.getType() == int.class) { + fields.put(field.getName(), writeInt(field.getInt(o))); + } + else + if(field.getType() == long.class) { + fields.put(field.getName(), writeLong(field.getLong(o))); + } + else + if(field.getType() == double.class) { + fields.put(field.getName(), writeDouble(field.getDouble(o))); + } + else + if(field.getType() == float.class) { + fields.put(field.getName(), writeFloat(field.getFloat(o))); + } + else + if(field.getType().isArray()) { + Map arrayMap = new HashMap<>(); + writeArray(arrayMap, field.get(o)); + fields.put(field.getName(), prep(arrayMap)); + } + else + { + fields.put(field.getName(), writeObject(field.get(o))); + } + } + + private static void writeArray(Map map, Object o) throws IllegalAccessException { + Class c = o.getClass(); + + if(c.getComponentType() == boolean.class) { + boolean[] a = (boolean[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeBoolean(a[i])); + } + } + else + if(c.getComponentType() == byte.class) { + byte[] a = (byte[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeByte(a[i])); + } + } + else + if(c.getComponentType() == short.class) { + short[] a = (short[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeShort(a[i])); + } + } + else + if(c.getComponentType() == char.class) { + char[] a = (char[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeChar(a[i])); + } + } + else + if(c.getComponentType() == int.class) { + int[] a = (int[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeInt(a[i])); + } + } + else + if(c.getComponentType() == long.class) { + long[] a = (long[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeLong(a[i])); + } + } + else + if(c.getComponentType() == double.class) { + double[] a = (double[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeDouble(a[i])); + } + } + else + if(c.getComponentType() == float.class) { + float[] a = (float[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeFloat(a[i])); + } + } + else + if(c.getComponentType().isArray()) { + int len = Array.getLength(o); + map.put(idToString(0x00), writeInt(len)); + for (int i = 0; i < len; i++) { + Map arrayMap = new HashMap<>(); + writeArray(arrayMap, Array.get(o, i)); + map.put(writeInt(i), prep(arrayMap)); + } + } + else + { + Object[] a = (Object[]) o; + map.put(idToString(0x00), writeInt(a.length)); + for (int i = 0; i < a.length; i++) { + map.put(writeInt(i), writeObject(a[i])); + } + } + } + + private static String writeBoolean(boolean o) { + return idToString(o ? 0x01 : 0x00); + } + + public static String writeByte(byte o) { + return idToString(o); + } + + public static String writeShort(short o) { + return idToString( + o >> 8 * 1 & 0xff, + o >> 8 * 0 & 0xff + ); + } + + public static String writeChar(char o) { + return idToString( + o >> 8 * 1 & 0xff, + o >> 8 * 0 & 0xff + ); + } + + public static String writeInt(int o) { + return idToString( + o >> 8 * 3 & 0xff, + o >> 8 * 2 & 0xff, + o >> 8 * 1 & 0xff, + o >> 8 * 0 & 0xff + ); + } + + public static String writeLong(long o) { + return idToString( + o >> 8 * 7 & 0xff, + o >> 8 * 6 & 0xff, + o >> 8 * 5 & 0xff, + o >> 8 * 4 & 0xff, + o >> 8 * 3 & 0xff, + o >> 8 * 2 & 0xff, + o >> 8 * 1 & 0xff, + o >> 8 * 0 & 0xff + ); + } + + public static String writeDouble(double o) { + return writeLong(Double.doubleToLongBits(o)); + } + + public static String writeFloat(float o) { + return writeInt(Float.floatToIntBits(o)); + } + + private static String writeObject(Object o) throws IllegalAccessException { + return idToString(Tools.byteArrayToUnsignedIntArray(serialize(o, o.getClass()))); + } + + + public static Object deserialize(byte[] bytes) throws IllegalAccessException, InstantiationException, ClassNotFoundException { + return deserialize(new String(Tools.intArrayToCharArray(Tools.byteArrayToUnsignedIntArray(bytes))), null); + } + + private static Object deserialize(String s, Object o) throws ClassNotFoundException, IllegalAccessException, InstantiationException { + + Map map = prep(s); + + + + Class c = Class.forName(map.get(idToString(0x00))); + + if(o == null) + o = c.newInstance(); + + if(c.getSuperclass() != null) + deserialize(map.get(idToString(0x01)), o); + + Map fields = new HashMap<>(); + for (Field field : c.getDeclaredFields()) { + boolean accessible = field.isAccessible(); + ReflectUtil.forceAccessible(field); + + readField(fields, field, o); + + field.setAccessible(accessible); + } + + return o; + } + + private static void readField(Map fields, Field field, Object o) throws IllegalAccessException, ClassNotFoundException, InstantiationException { + + if(field.getType() == boolean.class) { + field.setBoolean(o, readBoolean(fields.get(field.getName()))); + } + else + if(field.getType() == byte.class) { + field.setByte(o, readByte(fields.get(field.getName()))); + } + else + if(field.getType() == short.class) { + field.setShort(o, readShort(fields.get(field.getName()))); + } + else + if(field.getType() == char.class) { + field.setChar(o, readChar(fields.get(field.getName()))); + } + else + if(field.getType() == int.class) { + field.setInt(o, readInt(fields.get(field.getName()))); + } + else + if(field.getType() == long.class) { + field.setLong(o, readLong(fields.get(field.getName()))); + } + else + if(field.getType() == double.class) { + field.setDouble(o, readDouble(fields.get(field.getName()))); + } + else + if(field.getType() == float.class) { + field.setFloat(o, readFloat(fields.get(field.getName()))); + } + else + if(field.getType().isArray()) { + field.set(o, readArray(field.getType(), prep(fields.get(field.getName())))); + } + else + { + field.set(o, deserialize(fields.get(field.getName()), null)); + } + } + + private static Object readArray(Class c, Map data) + throws IllegalAccessException, InstantiationException, ClassNotFoundException { + + Object r; + + if(c.getComponentType() == boolean.class) { + boolean[] a = new boolean[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readBoolean(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == byte.class) { + byte[] a = new byte[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readByte(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == short.class) { + short[] a = new short[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readShort(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == char.class) { + char[] a = new char[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readChar(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == int.class) { + int[] a = new int[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readInt(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == long.class) { + long[] a = new long[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readLong(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == double.class) { + double[] a = new double[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readDouble(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType() == float.class) { + float[] a = new float[readInt(data.get(idToString(0x00)))]; + for (int i = 0; i < a.length; i++) { + a[i] = readFloat(data.get(writeInt(i))); + } + r = a; + } + else + if(c.getComponentType().isArray()) { + Object[] a = (Object[]) Array.newInstance(c.getComponentType(), readInt(data.get(idToString(0x00)))); + for (int i = 0; i < a.length; i++) { + a[i] = readArray(c, prep(data.get(writeInt(i)))); + } + r = a; + } + else + { + Object[] a = (Object[]) Array.newInstance(c.getComponentType(), readInt(data.get(idToString(0x00)))); + for (int i = 0; i < a.length; i++) { + a[i] = deserialize(data.get(writeInt(i)), null); + } + r = a; + } + + return r; + } + + private static boolean readBoolean(String s) { + return s.equals(idToString(0x01)); + } + + private static byte readByte(String s) { + return (byte) stringToID(s); + } + + private static short readShort(String s) { + int[] o = stringToIDs(s); + return + (short) ( + (o[0] << 8 * 1) + + (o[1] << 8 * 0) + ) + ; + } + + private static char readChar(String s) { + int[] o = stringToIDs(s); + return + (char) ( + (o[0] << 8 * 1) + + (o[1] << 8 * 0) + ) + ; + } + + private static int readInt(String s) { + int[] o = stringToIDs(s); + return + (o[0] << 8 * 3) + + (o[1] << 8 * 2) + + (o[2] << 8 * 1) + + (o[3] << 8 * 0) + ; + } + + private static long readLong(String s) { + int[] o = stringToIDs(s); + return + ((long) o[0] << 8 * 7) + + ((long) o[1] << 8 * 6) + + ((long) o[2] << 8 * 5) + + ((long) o[3] << 8 * 4) + + ((long) o[4] << 8 * 3) + + ((long) o[5] << 8 * 2) + + ((long) o[6] << 8 * 1) + + ((long) o[7] << 8 * 0); + } + + private static double readDouble(String s) { + return Double.longBitsToDouble(readLong(s)); + } + + private static float readFloat(String s) { + return Float.intBitsToFloat(readInt(s)); + } + + + + private static int[] stringToIDs(String id) { + int[] ints = new int[id.length()]; + for (int i = 0; i < id.length(); i++) { + ints[i] = id.charAt(i) & 0xff; + } + return ints; + } + private static int stringToID(String id) { + return (int) id.charAt(0) & 0xff; + } + + private static String idToString(int... id) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < id.length; i++) { + builder.append((char) id[i]); + } + return builder.toString(); + } + + private static String idToString(long... id) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < id.length; i++) { + builder.append((char) id[i]); + } + return builder.toString(); + } + + private static String prep(Map map) { + StringBuilder r = new StringBuilder(); + + for (String key : map.keySet().toArray(new String[0])) { + + r + .append( + key + .replaceAll(idToString(0xfd), idToString(0xfd, 0x0d)) + .replaceAll(idToString(0xff), idToString(0xfd, 0x0f)) + .replaceAll(idToString(0xfe), idToString(0xfd, 0x0e)) + ) + .append(idToString(0xfe)) + .append( + map.get(key) + .replaceAll(idToString(0xfd), idToString(0xfd, 0x0d)) + .replaceAll(idToString(0xff), idToString(0xfd, 0x0f)) + .replaceAll(idToString(0xfe), idToString(0xfd, 0x0e)) + ) + .append(idToString(0xff)) + ; + } + + return r.toString(); + } + + private static Map prep(String string) { + + HashMap map = new HashMap<>(); + + for (int i = 0; i < string.split(idToString(0xff)).length; i++) { + String tile = string.split(idToString(0xff))[i]; + if (tile.contains(idToString(0xfe))) { + if (tile.split(idToString(0xfe)).length == 2) + map.put( + tile + .split(idToString(0xfe))[0] + .replaceAll(idToString(0xfd, 0x0e), idToString(0xfe)) + .replaceAll(idToString(0xfd, 0x0f), idToString(0xff)) + .replaceAll(idToString(0xfd, 0x0d), idToString(0xfd)), + tile + .split(idToString(0xfe))[1] + .replaceAll(idToString(0xfd, 0x0e), idToString(0xfe)) + .replaceAll(idToString(0xfd, 0x0f), idToString(0xff)) + .replaceAll(idToString(0xfd, 0x0d), idToString(0xfd)) + ); + else + map.put( + tile + .split(idToString(0xfe))[0] + .replaceAll(idToString(0xfd, 0x0e), idToString(0xfe)) + .replaceAll(idToString(0xfd, 0x0f), idToString(0xff)) + .replaceAll(idToString(0xfd, 0x0d), idToString(0xfd)) + , "" + ); + } + } + + return map; + } +} diff --git a/src/main/java/de/tudbut/tools/SimpleTimer.java b/src/main/java/de/tudbut/tools/SimpleTimer.java new file mode 100644 index 0000000..da9721b --- /dev/null +++ b/src/main/java/de/tudbut/tools/SimpleTimer.java @@ -0,0 +1,45 @@ +package de.tudbut.tools; + +import java.util.Date; + +public class SimpleTimer { + + private final Lock lock = new Lock(); + + private final int time; + private int timesRan = 0; + private long lastTimeTaken = 0; + + private long last; + public SimpleTimer(int delay) { + time = delay; + } + + private void waitHere() { + lock.waitHere(); + lock.lock(getDelayTime()); + last = new Date().getTime(); + } + + public int getTimesRan() { + return timesRan; + } + + public long getLastTimeTaken() { + return lastTimeTaken; + } + + public void run(Runnable runnable) { + timesRan++; + waitHere(); + try { + runnable.run(); + } finally { + lastTimeTaken = new Date().getTime() - last; + } + } + + private int getDelayTime() { + return time; + } +} diff --git a/src/main/java/de/tudbut/tools/Stack.java b/src/main/java/de/tudbut/tools/Stack.java new file mode 100644 index 0000000..e5b1f8e --- /dev/null +++ b/src/main/java/de/tudbut/tools/Stack.java @@ -0,0 +1,25 @@ +package de.tudbut.tools; + +public class Stack extends Queue { + + public Stack() { } + + protected Stack(Stack stack) { + super(stack); + } + + @Override + public synchronized T next() { + return popTop(); + } + + @Override + public synchronized T peek() { + return getTop(); + } + + @Override + public Stack clone() { + return new Stack<>(this); + } +} diff --git a/src/main/java/de/tudbut/tools/Stopwatch.java b/src/main/java/de/tudbut/tools/Stopwatch.java new file mode 100644 index 0000000..18aa7e5 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Stopwatch.java @@ -0,0 +1,28 @@ +package de.tudbut.tools; + +import java.util.ArrayList; +import java.util.Date; + +public class Stopwatch { + + public Stopwatch() { + + } + + long sa = new Date().getTime(); + ArrayList laps = new ArrayList<>(); + + public long lap() { + long l = getPassedTime(); + laps.add(l); + return l; + } + + public long getLap(int idx) { + return laps.get(idx); + } + + public long getPassedTime() { + return new Date().getTime() - sa; + } +} diff --git a/src/main/java/de/tudbut/tools/StringTools.java b/src/main/java/de/tudbut/tools/StringTools.java new file mode 100644 index 0000000..d581d25 --- /dev/null +++ b/src/main/java/de/tudbut/tools/StringTools.java @@ -0,0 +1,30 @@ +package de.tudbut.tools; + +public class StringTools { + + public static String removeIndents(String s) { + while(s.contains("\n ")) { + s = s.replaceAll("\n ", "\n"); + } + while(s.contains("\n\t")) { + s = s.replaceAll("\n\t", "\n"); + } + return s; + } + + public static String multiply(String s, int i) { + StringBuilder builder = new StringBuilder(); + for (int j = 0; j < i; j++) { + builder.append(s); + } + return builder.toString(); + } + + public static String lengthify(String s, String m, int i) { + return s + multiply(m, (i - s.length()) / m.length()); + } + + public static String lengthifyStart(String s, String m, int i) { + return multiply(m, (i - s.length()) / m.length()) + s; + } +} diff --git a/src/main/java/de/tudbut/tools/ThreadPool.java b/src/main/java/de/tudbut/tools/ThreadPool.java new file mode 100644 index 0000000..336e679 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ThreadPool.java @@ -0,0 +1,87 @@ +package de.tudbut.tools; + +import de.tudbut.type.Stoppable; +import de.tudbut.obj.NotSupportedException; + +import java.util.concurrent.atomic.AtomicInteger; + +public class ThreadPool implements Stoppable { + private final Thread[] threads; + private final Runnable[] toDo; + private final Lock[] locks; + private final Lock hasFree = new Lock(); + private final AtomicInteger freeThreads = new AtomicInteger(); + + public ThreadPool(final int amount, final String name, final boolean enableCrashRecovery) { + threads = new Thread[amount]; + toDo = new Runnable[amount]; + locks = new Lock[amount]; + for (int i = 0 ; i < locks.length ; i++) { + locks[i] = new Lock(); + locks[i].lock(); + } + updateFree(); + + for (int i = 0; i < threads.length; i++) { + final int threadID = i; + threads[i] = new Thread(() -> { + while (!isStopped()) { + freeThreads.getAndIncrement(); + updateFree(); + locks[threadID].waitHere(); + locks[threadID].lock(); + freeThreads.getAndDecrement(); + updateFree(); + + try { + toDo[threadID].run(); + } catch (Throwable e) { + if (enableCrashRecovery) { + new RuntimeException("Thread crash: " + threads[threadID].getName() + "!", e).fillInStackTrace().printStackTrace(); + System.err.println("Thread recovered: " + threads[threadID].getName() + "."); + } else { + throw new RuntimeException("Thread crash: " + threads[threadID].getName() + "!", e); + } + } + } + }, name + ":" + i); + threads[i].start(); + } + } + + private void updateFree() { + if (freeThreads.get() == 0) { + hasFree.lock(); + } + else { + hasFree.unlock(); + } + } + + public void run(Runnable runnable) { + boolean found = false; + while (!found) { + hasFree.waitHere(); + for (int i = 0; i < threads.length; i++) { + if(locks[i].isLocked()) { + toDo[i] = runnable; + locks[i].unlock(); + found = true; + break; + } + } + } + } + + public void start() throws NotSupportedException { + throw new NotSupportedException(); + } + + public int available() { + return freeThreads.get(); + } + + public boolean isBlocked() { + return hasFree.isLocked(); + } +} diff --git a/src/main/java/de/tudbut/tools/ThrowingRunnable.java b/src/main/java/de/tudbut/tools/ThrowingRunnable.java new file mode 100644 index 0000000..8893666 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ThrowingRunnable.java @@ -0,0 +1,5 @@ +package de.tudbut.tools; + +public interface ThrowingRunnable { + void run() throws Exception; +} diff --git a/src/main/java/de/tudbut/tools/Time.java b/src/main/java/de/tudbut/tools/Time.java new file mode 100644 index 0000000..5348e21 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Time.java @@ -0,0 +1,34 @@ +package de.tudbut.tools; + +public class Time { + + public static String ydhmsString(long seconds) { + String s = ""; + s += (seconds / 60 / 60 / 24 / 360) + ":"; + s += (seconds / 60 / 60 / 24 % 360) + ":"; + s += (seconds / 60 / 60 % 24) + ":"; + s += (seconds / 60 % 60) + ":"; + s += (seconds % 60); + return s; + } + + public static String[] ydhmsStrings(long seconds) { + return ydhmsString(seconds).split(":"); + } + + public static String ydhms(long seconds) { + String s = ""; + s += (seconds / 60 / 60 / 24 / 360) + "y "; + s += (seconds / 60 / 60 / 24 % 360) + "d "; + s += (seconds / 60 / 60 % 24) + "h "; + s += (seconds / 60 % 60) + "m "; + s += (seconds % 60) + "s"; + return s; + } + + public static long predictTimeLeft(long start, float progress) { + long current = System.currentTimeMillis(); + long diff = current - start; + return (long) (diff / progress) + start - current; + } +} diff --git a/src/main/java/de/tudbut/tools/Timer.java b/src/main/java/de/tudbut/tools/Timer.java new file mode 100644 index 0000000..14f803e --- /dev/null +++ b/src/main/java/de/tudbut/tools/Timer.java @@ -0,0 +1,72 @@ +package de.tudbut.tools; + +import java.util.Date; + +public class Timer { + + Lock lock = new Lock(); + + int time; + int timesRan = 0; + long lastTimeTaken = 0; + int needed = 0; + + long last; + public Timer(int delay) { + time = delay; + last = new Date().getTime(); + } + + private void waitHere() { + lock.waitHere(); + lock.lock(getDelayTime()); + last = new Date().getTime(); + } + + public int getTimesRan() { + return timesRan; + } + + public long getLastTimeTaken() { + return lastTimeTaken; + } + + public void run(Runnable runnable) { + timesRan++; + waitHere(); + try { + runnable.run(); + } finally { + lastTimeTaken = new Date().getTime() - last; + } + } + + public boolean isFastForward() { + return needed > 0; + } + + public boolean isSuperFastForward() { + return needed > time; + } + + private int getDelayTime() { + long last = this.last; + int time = this.time; + long l = new Date().getTime(); + if (l - last > time) { + needed += l - last - time; + } + if (needed > 0) { + needed -= time - (l - last); + if (needed > time * 2L) { + return 1; + } + else { + return time - needed; + } + } + else + needed = 0; + return time; + } +} diff --git a/src/main/java/de/tudbut/tools/Tools.java b/src/main/java/de/tudbut/tools/Tools.java new file mode 100644 index 0000000..f102bd7 --- /dev/null +++ b/src/main/java/de/tudbut/tools/Tools.java @@ -0,0 +1,532 @@ +package de.tudbut.tools; + +import de.tudbut.type.StringArray; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.sql.Time; +import java.util.*; +import de.tudbut.obj.TypedArray; + +public class Tools { + + public static String[] readf(String format, String s) { + // extracts parts of a string denoted by {} + try { + if(!format.contains("{}")) + return format.equals(s) ? new String[]{} : null; + if(format.contains("{}{}")) throw new IllegalArgumentException("Ambiguous argument: '{}{}' found in format string"); + String f = format; + int occurences = 0; + for(; f.indexOf("{}") != -1; occurences++) { + f = f.substring(f.indexOf("{}") + 2); + } + String[] result = new String[occurences]; + + String originalFormat = format; + for(int n = 0; n <= occurences; n++) { // This may throw if it doesn't match, but that's the same outcome + // shave off blanking space + int i = format.indexOf("{}"); + if(i == -1) i = format.length(); + if(!format.substring(0, i).equals(s.substring(0, i))) return null; // If the previous part didn't match, we can forget about it. + if(n == occurences) { + break; + } + format = format.substring(i + 2); + s = s.substring(i); + // populate braces + int x = format.indexOf("{}"); + if(x != -1) { + result[n] = s.substring(0, s.indexOf(format.substring(0, x))); + } + else { + result[n] = s.substring(0, s.length() - (originalFormat.length() - originalFormat.lastIndexOf("{}") - 2)); + } + s = s.substring(result[n].length()); + } + if(result[occurences - 1] == null) // this happens when a later part doesnt match; + return null; + + return result; + } catch(Exception e) { + return null; + } + } + + public static String readf1(String format, String s) { + // extracts parts of a string denoted by {} + String[] r = readf(format, s); + if(r == null) return null; + if(r.length == 0) return ""; + return r[0]; + } + + public static BufferedReader getStdInput() { + return new BufferedReader(new InputStreamReader(System.in)); + } + + public static String randomOutOfArray(StringArray stringArray) { + return stringArray.asArray()[(int) Math.floor(Math.random() * stringArray.asArray().length)]; + } + + public static T randomOutOfArray(T[] array) { + return array[(int) Math.floor(Math.random() * array.length)]; + } + + public static T randomOutOfArray(TypedArray array) { + return array.get((int) Math.floor(Math.random() * array.length())); + } + + public static String randomString(int length, String pool) { + StringBuilder r = new StringBuilder(); + + for (int i = 0; i < length; i++) { + r.append(pool.charAt(ExtendedMath.random(0, pool.length() - 1))); + } + + return r.toString(); + } + + public static void copyArray(Object array1, Object array2, int copyLength) { + System.arraycopy(array1, 0, array2, 0, copyLength); + } + + public static String randomAlphanumericString(int length) { + String alphabet = "abcdefghijklmnopqrstuvwxyz"; + String pool = alphabet + alphabet.toUpperCase() + "0123456789"; + + return randomString(length, pool); + } + + public static String randomReadableString(int length) { + String pool = "bcdfghjklmnpqrstvwxyz"; + String readablePool = "aeiou"; + + StringBuilder r = new StringBuilder(); + for (int i = 0; i < length; i++) { + r.append(pool.charAt(ExtendedMath.random(0, pool.length() - 1))) + .append(readablePool.charAt(ExtendedMath.random(0, readablePool.length() - 1))); + } + return r.substring(0, length); + } + + public static String getTime() { + return new Time(new Date().getTime()).toString(); + } + + public static String stringSwitch(Map switchMap, String value) { + if (switchMap.get(value) != null) { + return switchMap.get(value); + } + + return switchMap.get("__default"); + } + + public static Map toSwitchMap(Map alreadyExisting, String newKey, String newVal) { + alreadyExisting.put(newKey, newVal); + alreadyExisting.putIfAbsent("__default", ""); + return alreadyExisting; + } + + public static Map newSwitchMap(String defaultVal) { + HashMap r = new HashMap<>(); + + r.put("__default", defaultVal); + + return r; + } + + public static Map stringToMap(String mapStringParsable) { + LinkedHashMap map = new LinkedHashMap<>(); + + String[] splitTiles = mapStringParsable.split(";"); + for (int i = 0; i < splitTiles.length; i++) { + String tile = splitTiles[i]; + String[] splitTile = tile.split(":"); + if (tile.contains(":")) { + if (splitTile.length == 2) + map.put( + splitTile[0] + .replaceAll("%I", ":") + .replaceAll("%B", ";") + .replaceAll("%P", "%"), + splitTile[1].equals("%N") + ? null + : splitTile[1] + .replaceAll("%I", ":") + .replaceAll("%B", ";") + .replaceAll("%P", "%")); + else + map.put( + splitTile[0] + .replaceAll("%I", ":") + .replaceAll("%B", ";") + .replaceAll("%P", "%"), + ""); + } + } + + return map; + } + + public static String mapToString(Map map) { + StringBuilder r = new StringBuilder(); + + for (String key : map.keySet().toArray(new String[0])) { + + r.append(key.replaceAll("%", "%P").replaceAll(";", "%B").replaceAll(":", "%I")) + .append(":") + .append( + map.get(key) == null + ? "%N" + : map.get(key) + .replaceAll("%", "%P") + .replaceAll(";", "%B") + .replaceAll(":", "%I")) + .append(";"); + } + + return r.toString(); + } + + public static byte[] charArrayToByteArray(char[] chars) { + byte[] bytes = new byte[chars.length]; + + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) (int) chars[i]; + } + + return bytes; + } + + public static int[] charArrayToIntArray(char[] chars) { + int[] ints = new int[chars.length]; + + for (int i = 0; i < ints.length; i++) { + ints[i] = chars[i]; + } + + return ints; + } + + public static char[] intArrayToCharArray(int[] ints) { + char[] chars = new char[ints.length]; + + for (int i = 0; i < ints.length; i++) { + chars[i] = (char) ints[i]; + } + + return chars; + } + + public static int[] byteArrayToIntArray(byte[] bytes) { + int[] ints = new int[bytes.length]; + + for (int i = 0; i < ints.length; i++) { + ints[i] = Byte.toUnsignedInt(bytes[i]); + } + + return ints; + } + + public static int[] byteArrayToUnsignedIntArray(byte[] bytes) { + int[] ints = new int[bytes.length]; + + for (int i = 0; i < ints.length; i++) { + ints[i] = Byte.toUnsignedInt(bytes[i]); + } + + return ints; + } + + public static String wildcardToRegex(String s) { + String r = ""; + char[] charArray = s.toCharArray(); + for (int i = 0, charArrayLength = charArray.length; i < charArrayLength; i++) { + char c = charArray[i]; + r += ("[" + c + "]").replaceAll("\\^", "\\^"); + } + return "^" + + r.replaceAll("\\[\\\\]", "[\\\\}") + .replaceAll("\\[\\*]", "(.|\n)*") + .replaceAll("\\[\\?]", "[.\n]") + "$"; + } + + public static T firstNonNull(T... objects) { + for (int i = 0; i < objects.length; i++) { + if(objects[i] != null) + return objects[i]; + } + return null; + } + + public static class TFS { + + public static String createTFS(String sep) { + Map mainMap = new HashMap<>(); + mainMap.put("head", "\u0000\u0001" + sep + "\u0000\u0002\u0020\u0000\u0003/\u0000"); + return mapToString(mainMap); + } + + public static String getFromHead(String tfs, String key) { + Map tfsMap = stringToMap(tfs); + + for (String val : tfsMap.get("head").split("\\x{0000}")) { + if (val.startsWith(key)) return val.substring(1); + } + return null; + } + + public static String getPath(String tfs, String path) { + String p = Objects.requireNonNull(getFromHead(tfs, "\u0003")); + return path.startsWith("/") ? path : (p.equals("/") ? p : p + "/") + path; + } + + public static String getFile(String tfs, String path) { + Map tfsMap = stringToMap(tfs); + + return tfsMap.get(getPath(tfs, path)); + } + + public static String getFileContent(String file) { + return stringToMap(file).get("content"); + } + + public static String createFile(String tfs, String path, String content) + throws TFSException.TFSFileAlreadyExistsException { + if (getFile(tfs, path) == null) { + Map tfsMap = stringToMap(tfs); + Map fileMap = new HashMap<>(); + + fileMap.put("head", "\u0000"); + fileMap.put("content", content); + fileMap.put("mods", String.valueOf(new Date().getTime())); + fileMap.put("lastMod", String.valueOf(new Date().getTime())); + + tfsMap.put(getPath(tfs, path), mapToString(fileMap)); + return mapToString(tfsMap); + } else throw new TFSException.TFSFileAlreadyExistsException(); + } + + public static String modFile(String tfs, String path, String newContent) + throws TFSException.TFSFileNotFoundException { + if (getFile(tfs, path) != null) { + Map tfsMap = stringToMap(tfs); + Map fileMap = stringToMap(tfsMap.get(getPath(tfs, path))); + + fileMap.put("content", newContent); + fileMap.put("mods", fileMap.get("mods") + ";" + new Date().getTime()); + fileMap.put("lastMod", String.valueOf(new Date().getTime())); + + tfsMap.put(getPath(tfs, path), mapToString(fileMap)); + return mapToString(tfsMap); + } else throw new TFSException.TFSFileNotFoundException(); + } + + public static String cd(String tfs, String path) throws TFSException.TFSPathNotFromRootException { + if (path.startsWith(Objects.requireNonNull(getFromHead(tfs, "\u0001")))) { + Map tfsMap = stringToMap(tfs); + + StringBuilder newHead = new StringBuilder(); + for (String val : tfsMap.get("head").split("\\x{0000}")) { + if (val.startsWith("\u0003")) val = "\u0003" + path; + newHead.append(val).append("\u0000"); + } + tfsMap.put("head", newHead.toString()); + + return mapToString(tfsMap); + } else throw new TFSException.TFSPathNotFromRootException(); + } + + public static class TFSException extends Exception { + + public static class TFSFileAlreadyExistsException extends TFSException {} + + public static class TFSFileNotFoundException extends TFSException {} + + public static class TFSPathNotFromRootException extends TFSException {} + } + } + + public static class ObjectMapping { + public static Map objectToMap(Object o) throws IllegalAccessException { + Map map = new HashMap<>(); + + Class c = o.getClass(); + for (Field field : c.getFields()) { + if (field.getType() == String.class) { + map.put( + field.getName(), + "str\u0000" + + ((String) field.get(new Object())) + .replaceAll("\\x{0000}", "\u00010") + .replaceAll("\\x{0001}", "\u00011")); + } + + if (field.getType().isInstance(new HashMap())) { + map.put( + field.getName(), + "map\u0000" + + mapToString((Map) field.get(new Object())) + .replaceAll("\\x{0000}", "\u00010") + .replaceAll("\\x{0001}", "\u00011")); + } + + if (field.getType() == int.class) { + map.put(field.getName(), "int\u0000" + field.get(new Object())); + } + + if (field.getType() == long.class) { + map.put(field.getName(), "lon\u0000" + field.get(new Object())); + } + + if (field.getType() == double.class) { + map.put(field.getName(), "dou\u0000" + field.get(new Object())); + } + + if (field.getType() == float.class) { + map.put(field.getName(), "flo\u0000" + field.get(new Object())); + } + + if (field.getType() == boolean.class) { + map.put(field.getName(), "boo\u0000" + field.get(new Object())); + } + } + + return map; + } + + public static void mapToObject(Object o, Map map) throws IllegalAccessException { + Class c = o.getClass(); + for (String key : map.keySet()) { + String type = map.get(key).split("\\x{0000}")[0]; + String val = map.get(key) + .split("\\x{0000}")[1] + .replaceAll("\\x{0001}0", "\u0000") + .replaceAll("\\x{0001}1", "\u0001"); + + try { + Field field = c.getField(key); + + if (type.equals("str")) { + field.set(new Object(), val); + } + + if (type.equals("map")) { + field.set(new Object(), stringToMap(val)); + } + + if (type.equals("int")) { + field.set(new Object(), Integer.parseInt(val)); + } + + if (type.equals("lon")) { + field.set(new Object(), Long.parseLong(val)); + } + + if (type.equals("dou")) { + field.set(new Object(), Double.parseDouble(val)); + } + + if (type.equals("flo")) { + field.set(new Object(), Float.parseFloat(val)); + } + + if (type.equals("boo")) { + field.set(new Object(), Boolean.parseBoolean(val)); + } + } catch (NoSuchFieldException ignore) { + } + } + } + + public static Map staticObjectToMap(Class c) throws IllegalAccessException { + Map map = new HashMap<>(); + + for (Field field : c.getFields()) { + if (field.getType() == String.class && field.get(new Object()) != null) { + map.put( + field.getName(), + "str\u0000" + + ((String) field.get(new Object())) + .replaceAll("\\x{0000}", "\u00010") + .replaceAll("\\x{0001}", "\u00011")); + } + + if (field.getType().isInstance(new HashMap()) && field.get(new Object()) != null) { + map.put( + field.getName(), + "map\u0000" + + mapToString((Map) field.get(new Object())) + .replaceAll("\\x{0000}", "\u00010") + .replaceAll("\\x{0001}", "\u00011")); + } + + if (field.getType() == int.class) { + map.put(field.getName(), "int\u0000" + field.get(new Object())); + } + + if (field.getType() == long.class) { + map.put(field.getName(), "lon\u0000" + field.get(new Object())); + } + + if (field.getType() == double.class) { + map.put(field.getName(), "dou\u0000" + field.get(new Object())); + } + + if (field.getType() == float.class) { + map.put(field.getName(), "flo\u0000" + field.get(new Object())); + } + + if (field.getType() == boolean.class) { + map.put(field.getName(), "boo\u0000" + field.get(new Object())); + } + } + + return map; + } + + public static void mapToStaticObject(Class c, Map map) throws IllegalAccessException { + for (String key : map.keySet()) { + String type = map.get(key).split("\\x{0000}")[0]; + String val = map.get(key) + .split("\\x{0000}")[1] + .replaceAll("\\x{0001}0", "\u0000") + .replaceAll("\\x{0001}1", "\u0001"); + + try { + Field field = c.getField(key); + + if (type.equals("str")) { + field.set(new Object(), val); + } + + if (type.equals("map")) { + field.set(new Object(), stringToMap(val)); + } + + if (type.equals("int")) { + field.set(new Object(), Integer.parseInt(val)); + } + + if (type.equals("lon")) { + field.set(new Object(), Long.parseLong(val)); + } + + if (type.equals("dou")) { + field.set(new Object(), Double.parseDouble(val)); + } + + if (type.equals("flo")) { + field.set(new Object(), Float.parseFloat(val)); + } + + if (type.equals("boo")) { + field.set(new Object(), Boolean.parseBoolean(val)); + } + } catch (NoSuchFieldException ignore) { + } + } + } + } +} diff --git a/src/main/java/de/tudbut/tools/Tools2.java b/src/main/java/de/tudbut/tools/Tools2.java new file mode 100644 index 0000000..a08733d --- /dev/null +++ b/src/main/java/de/tudbut/tools/Tools2.java @@ -0,0 +1,144 @@ +package de.tudbut.tools; + +import de.tudbut.io.StreamReader; +import de.tudbut.obj.Partial; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +public class Tools2 { + + /** + * Java 9+ is unreliable when using java.awt.Toolkit#getScreenSize() + * @return The screen size, with all monitors. + */ + public static Rectangle fullScreenSize() { + GraphicsDevice[] screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices(); + Rectangle r = new Rectangle(0, 0, 0, 0); + for (GraphicsDevice screenDevice : screenDevices) { + Rectangle bounds = screenDevice.getDefaultConfiguration().getBounds(); + r.width = Math.max(bounds.x + bounds.width, r.width); + r.height = Math.max(bounds.y + bounds.height, r.height); + } + return r; + } + + public synchronized static BufferedImage screenshot() throws AWTException { + return new Robot().createScreenCapture(fullScreenSize()); + } + + public static String getStringStackTrace(Throwable throwable) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + throwable.printStackTrace(new PrintStream(stream)); + return stream.toString(); + } + + public synchronized static void addFilesToZIP(File zipFile, Partial.Listener done, File... files) throws IOException { + byte[] buf = new byte[StreamReader.BUFFER_SIZE]; + + ZipInputStream zin = new ZipInputStream(new ByteArrayInputStream(new StreamReader(new FileInputStream(zipFile)).readAllAsBytes())); + ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile)); + + + ZipEntry entry = zin.getNextEntry(); + while (entry != null) { + try { + String name = entry.getName(); + boolean notInFiles = true; + for (File f : files) { + if (f.getName().equals(name)) { + notInFiles = false; + break; + } + } + if (notInFiles) { + out.putNextEntry(new ZipEntry(name)); + int len; + while ((len = zin.read(buf)) > 0) { + out.write(buf, 0, len); + } + } + entry = zin.getNextEntry(); + } catch (EOFException ignore) { + entry = null; + } + } + zin.close(); + for (File file : files) { + InputStream in = new FileInputStream(file); + out.putNextEntry(new ZipEntry(file.getName())); + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.closeEntry(); + in.close(); + done.onComplete(file); + } + + out.close(); + } + public synchronized static void addFilesToZIP(File zipFile, File... files) throws IOException { + addFilesToZIP(zipFile, file -> {}, files); + } + + public static void deleteDir(File dir) { + try { + File[] files = dir.listFiles(); + assert files != null; + for (File file : files) { + if (file.isDirectory()) { + deleteDir(file); + } else + file.delete(); + } + dir.delete(); + } catch (Exception ignore) { + } + } + + public static void copyDir(File dir, File dest) { + + Path sourcePath = dir.toPath(); + Path targetPath = dest.toPath(); + try(Stream filePaths = Files.walk(sourcePath)) { + filePaths.forEach(filePath -> { + try { + if (Files.isRegularFile(filePath)) { + Path newFile = targetPath.resolve(sourcePath.relativize(filePath)); + Files.copy(filePath, newFile); + }else{ + Path newDir = targetPath.resolve(sourcePath.relativize(filePath)); + Files.createDirectory(newDir); + } + } catch (IOException ignored) { + } + }); + } catch (IOException ignored) { + } + } + + public static double round(double d, int i) { + if(i < 0) + throw new IllegalArgumentException(); + + return Math.round(d * Math.pow(10, i)) / Math.pow(10, i); + } + + public static double roll(double d, double min, double max) { + while (d < min) { + d += max - min; + } + while (d >= max) { + d -= max - min; + } + return d; + } +} diff --git a/src/main/java/de/tudbut/tools/TrayApp.java b/src/main/java/de/tudbut/tools/TrayApp.java new file mode 100644 index 0000000..b25f5cb --- /dev/null +++ b/src/main/java/de/tudbut/tools/TrayApp.java @@ -0,0 +1,32 @@ +package de.tudbut.tools; + +import de.tudbut.rendering.Maths2D; + +import java.awt.*; +import java.awt.image.BufferedImage; + +public class TrayApp { + + private static SystemTray tray; + static { + if(SystemTray.isSupported()) + tray = SystemTray.getSystemTray(); + } + private final TrayIcon trayIcon; + + public TrayApp(BufferedImage icon) throws AWTException { + if(tray == null) + throw new AWTException("Tray not supported"); + trayIcon = new TrayIcon(Maths2D.distortImage(icon, tray.getTrayIconSize().width, tray.getTrayIconSize().height, 1)); + trayIcon.setImageAutoSize(true); + tray.add(trayIcon); + } + + public void setImage(BufferedImage image) { + trayIcon.setImage(Maths2D.distortImage(image, 32, 32, 1)); + } + + public void onClick(Runnable runnable) { + trayIcon.addActionListener((e) -> runnable.run()); + } +} diff --git a/src/main/java/de/tudbut/tools/Value.java b/src/main/java/de/tudbut/tools/Value.java new file mode 100644 index 0000000..9c07e4d --- /dev/null +++ b/src/main/java/de/tudbut/tools/Value.java @@ -0,0 +1,9 @@ +package de.tudbut.tools; + +public class Value { + public final T value; + + public Value(T value) { + this.value = value; + } +} diff --git a/src/main/java/de/tudbut/tools/ValueArray.java b/src/main/java/de/tudbut/tools/ValueArray.java new file mode 100644 index 0000000..d38e219 --- /dev/null +++ b/src/main/java/de/tudbut/tools/ValueArray.java @@ -0,0 +1,27 @@ +package de.tudbut.tools; + +import de.tudbut.type.FInfo; +import de.tudbut.obj.TypedArray; +import de.tudbut.obj.TypedList; + +public class ValueArray { + private final T[] values; + public final int length; + + public ValueArray(T[] values) { + this.values = values.clone(); + this.length = this.values.length; + } + + public T get(int i) { + return values[i]; + } + + @FInfo( + s="" + + "@return TypedArray, but castable to TypedList" + ) + public TypedArray toTypedArray() { + return new TypedList<>(values.clone()).lock(); + } +} diff --git a/src/main/java/de/tudbut/tools/VarTools.java b/src/main/java/de/tudbut/tools/VarTools.java new file mode 100644 index 0000000..0055df0 --- /dev/null +++ b/src/main/java/de/tudbut/tools/VarTools.java @@ -0,0 +1,30 @@ +package de.tudbut.tools; + +import de.tudbut.timer.AsyncRunnable; + +public class VarTools { + + public static O objectOrNull(T t, ArrayTools.Getter getter) { + try { + return getter.get(t); + } catch (Throwable throwable) { + return null; + } + } + + public static O objectOrNull(ArrayTools.Getter getter) { + try { + return getter.get(null); + } catch (Throwable throwable) { + return null; + } + } + + public static O objectOrNull(AsyncRunnable getter) { + try { + return getter.run(); + } catch (Throwable throwable) { + return null; + } + } +} diff --git a/src/main/java/de/tudbut/tools/YMLFile.java b/src/main/java/de/tudbut/tools/YMLFile.java new file mode 100644 index 0000000..7b8c3d1 --- /dev/null +++ b/src/main/java/de/tudbut/tools/YMLFile.java @@ -0,0 +1,54 @@ +package de.tudbut.tools; + +import de.tudbut.type.FileFormatException; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +public class YMLFile extends FileRW { + protected Map map = new HashMap<>(); + + public YMLFile(String path) throws Exception { + super(path); + if (!path.endsWith(".yml") && !path.endsWith(".yaml")) { + throw new FileFormatException(); + } + else { + this.remap(); + } + } + + public void remap() throws Exception { + map = new HashMap<>(); + + + String[] var1 = this.getContent().asArray(); + + for (String line : var1) { + if (!line.equals("")) { + String theLine = line.substring(line.split(": ")[0].length() + 2); + if (theLine.startsWith("\"") && theLine.endsWith("\"")) { + theLine = theLine.substring(1, theLine.length() - 1); + } + + this.map.put(line.split(": ")[0], theLine); + } + } + + } + + public void mapToObj(Object object) throws IllegalAccessException { + Field[] fields = object.getClass().getFields(); + int var4 = fields.length; + + for (Field field : fields) { + field.set(object, this.map.get(field.getName())); + } + + } + + public String getValue(String key) { + return this.map.get(key); + } +} diff --git a/src/main/java/de/tudbut/tools/bintools/BinFileRW.java b/src/main/java/de/tudbut/tools/bintools/BinFileRW.java new file mode 100644 index 0000000..2c3d963 --- /dev/null +++ b/src/main/java/de/tudbut/tools/bintools/BinFileRW.java @@ -0,0 +1,39 @@ +package de.tudbut.tools.bintools; + +import de.tudbut.io.StreamReader; +import de.tudbut.tools.FileRW; + +import java.io.BufferedOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +public class BinFileRW extends FileRW { + int[] chars; + + public BinFileRW(String path) throws IOException { + super(path); + } + + public int[] getBinContent() throws IOException { + rereadFile(); + return chars; + } + + public void setBinContent(int[] content) throws IOException { + this.chars = content; + int[] nChars = new int[this.chars.length]; + System.arraycopy(chars, 0, nChars, 0, chars.length); + chars = nChars; + FileOutputStream fos = new FileOutputStream(file); + BufferedOutputStream fileWriter = new BufferedOutputStream(fos); + for (int ch : chars) { + fileWriter.write(ch); + } + fileWriter.close(); + } + + public void rereadFile() throws IOException { + chars = new StreamReader(new FileInputStream(file)).readAllAsUnsignedBytes(); + } +} diff --git a/src/main/java/de/tudbut/tools/bintools/encoding/Seed.java b/src/main/java/de/tudbut/tools/bintools/encoding/Seed.java new file mode 100644 index 0000000..0877331 --- /dev/null +++ b/src/main/java/de/tudbut/tools/bintools/encoding/Seed.java @@ -0,0 +1,25 @@ +package de.tudbut.tools.bintools.encoding; + +import de.tudbut.tools.Hasher; + +public class Seed { + private final String seed; + + public Seed(String s) { + seed = s; + } + + public static String random() { + try { + return Hasher.sha512hex(Math.random() + "_" + Math.random()); + } + catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public String getSeed() { + return seed; + } +} diff --git a/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV1.java b/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV1.java new file mode 100644 index 0000000..d8fc342 --- /dev/null +++ b/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV1.java @@ -0,0 +1,173 @@ +package de.tudbut.tools.bintools.encoding; + +import de.tudbut.type.ByteArrayList; + +@SuppressWarnings("DuplicatedCode") +public class TCryptV1 { + + protected Seed seed; + + public TCryptV1(String seed) { + if (seed == null) { + seed = Seed.random(); + } + + this.seed = new Seed(seed); + } + + public Value encrypt(byte[] content) { + ByteArrayList newContent = new ByteArrayList(); + + //System.out.println("Encrypting with seed: " + seed.getSeed()); + + for (int c = 0; c < content.length; c += seed.getSeed().length()) { + + for (int i = 0; i < seed.getSeed().length(); i++) { + + int add; + switch (seed.getSeed().charAt(i)) { + case '0': + add = 0; + break; + case '1': + add = 1; + break; + case '2': + add = 2; + break; + case '3': + add = 3; + break; + case '4': + add = 4; + break; + case '5': + add = 5; + break; + case '6': + add = 6; + break; + case '7': + add = 7; + break; + case '8': + add = 8; + break; + case '9': + add = 9; + break; + case 'a': + add = -1; + break; + case 'b': + add = -2; + break; + case 'c': + add = -3; + break; + case 'd': + add = -4; + break; + case 'e': + add = -5; + break; + case 'f': + add = -6; + break; + default: + throw new IllegalStateException("Unexpected value: " + seed.getSeed().charAt(i)); + } + + byte theByte; + + if ((c + i) >= content.length) + break; + else + theByte = content[c + i]; + + //System.out.println("Adding char: " + theChar + " -> " + (char) (theChar + (char) add)); + newContent.add((byte) (theByte + add)); + } + } + + return new Value(newContent.toByteArray()); + } + + public Value decrypt(byte[] content) { + ByteArrayList newContent = new ByteArrayList(); + + //System.out.println("Decrypting with seed: " + seed.getSeed()); + + for (int c = 0; c < content.length; c += seed.getSeed().length()) { + + for (int i = 0; i < seed.getSeed().length(); i++) { + + int rem; + switch (seed.getSeed().charAt(i)) { + case '0': + rem = 0; + break; + case '1': + rem = 1; + break; + case '2': + rem = 2; + break; + case '3': + rem = 3; + break; + case '4': + rem = 4; + break; + case '5': + rem = 5; + break; + case '6': + rem = 6; + break; + case '7': + rem = 7; + break; + case '8': + rem = 8; + break; + case '9': + rem = 9; + break; + case 'a': + rem = -1; + break; + case 'b': + rem = -2; + break; + case 'c': + rem = -3; + break; + case 'd': + rem = -4; + break; + case 'e': + rem = -5; + break; + case 'f': + rem = -6; + break; + default: + throw new IllegalStateException("Unexpected value: " + seed.getSeed().charAt(i)); + } + + byte theByte; + + if ((c + i) >= content.length) + break; + else + theByte = content[c + i]; + + //System.out.println("Adding char: " + theChar + " -> " + (char) (theChar + (char) add)); + newContent.add((byte) (theByte - rem)); + } + } + + return new Value(newContent.toByteArray()); + } +} diff --git a/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV2.java b/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV2.java new file mode 100644 index 0000000..a2f6401 --- /dev/null +++ b/src/main/java/de/tudbut/tools/bintools/encoding/TCryptV2.java @@ -0,0 +1,34 @@ +package de.tudbut.tools.bintools.encoding; + +import de.tudbut.tools.Tools; +import de.tudbut.tools.bintools.BinFileRW; + +import java.io.IOException; + +public class TCryptV2 extends TCryptV1 { + public TCryptV2(String seed) { + super(seed); + } + + public Value encryptFile(String file) throws IOException { + Value n = this.encrypt(Tools.charArrayToByteArray(Tools.intArrayToCharArray(new BinFileRW(file).getBinContent()))); + int[] r = new int[n.getB().length]; + + for (int i = 0; i < r.length; i++) { + r[i] = Byte.toUnsignedInt(n.getB()[i]); + } + + return new Value(r); + } + + public Value decryptFile(String file) throws IOException { + Value n = this.decrypt(Tools.charArrayToByteArray(Tools.intArrayToCharArray(new BinFileRW(file).getBinContent()))); + int[] r = new int[n.getB().length]; + + for (int i = 0; i < r.length; i++) { + r[i] = Byte.toUnsignedInt(n.getB()[i]); + } + + return new Value(r); + } +} diff --git a/src/main/java/de/tudbut/tools/bintools/encoding/Value.java b/src/main/java/de/tudbut/tools/bintools/encoding/Value.java new file mode 100644 index 0000000..29e4d03 --- /dev/null +++ b/src/main/java/de/tudbut/tools/bintools/encoding/Value.java @@ -0,0 +1,54 @@ +package de.tudbut.tools.bintools.encoding; + +import java.util.Objects; + +public class Value { + private final String stringValue; + private final char[] charValue; + private final int[] intValue; + private final byte[] byteValue; + + public Value(String s) { + stringValue = s; + byteValue = null; + this.intValue = null; + charValue = null; + } + + public Value(char[] cs) { + charValue = cs; + byteValue = null; + this.stringValue = null; + this.intValue = null; + } + + public Value(int[] is) { + intValue = is; + byteValue = null; + this.stringValue = null; + this.charValue = null; + } + + public Value(byte[] bs) { + byteValue = bs; + intValue = null; + this.stringValue = null; + this.charValue = null; + } + + public String getS() { + return stringValue; + } + + public char[] getC() { + return Objects.requireNonNull(charValue).clone(); + } + + public int[] getI() { + return Objects.requireNonNull(intValue).clone(); + } + + public byte[] getB() { + return Objects.requireNonNull(byteValue).clone(); + } +} diff --git a/src/main/java/de/tudbut/tools/encryption/Key.java b/src/main/java/de/tudbut/tools/encryption/Key.java new file mode 100644 index 0000000..414bd8e --- /dev/null +++ b/src/main/java/de/tudbut/tools/encryption/Key.java @@ -0,0 +1,145 @@ +package de.tudbut.tools.encryption; + +import de.tudbut.parsing.TCN; +import de.tudbut.tools.Hasher; +import de.tudbut.tools.ObjectSerializerTCN; +import de.tudbut.tools.Tools; + +import java.util.Objects; + +/** + * Key to encrypt objects and strings + */ +public class Key implements Cloneable { + + protected final String string; + + /** + * Generates a random Key + */ + public Key() { + string = Tools.randomAlphanumericString(1024); + } + + /** + * Constructs a Key + * @param s Key as string + */ + public Key(String s) { + string = s; + } + + /** + * Constructs a Key + * @param bytes Key as byte[] + */ + public Key(byte[] bytes) { + string = new String(bytes); + } + + /** + * Compares two keys + * @param other The key to compare to + * @return If other is equal to this + */ + public boolean equals(Key other) { + return string.equals(other.string); + } + + /** + * Hashes the Kay + * @return the hash + */ + public String toHashString() { + String[] strings = string.split("_"); + StringBuilder hash = new StringBuilder(); + for (int i = 0 ; i < strings.length ; i++) { + hash.append(Hasher.sha512hex(strings[i])); + } + return hash.toString(); + } + + /** + * Gets the bytes of the key + * @return the bytes of the key + */ + public byte[] toBytes() { + return string.getBytes(); + } + + /** + * Returns the key as string. USE {@link #toHashString} TO GET A HASH, THIS WILL RETURN THE ENCRYPTION KEY! + * @return the key as string + */ + @Override + public String toString() { + return string; + } + + /** + * Encrypts a string + * @param s string to encrypt + * @return encrypted string + */ + public String encryptString(String s) { + char[] bytes = s.toCharArray(); + char[] eb = string.toCharArray(); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + bytes[idx] = (char) ((int) bytes[idx] + (int) eb[j]); + } + } + return new String(bytes); + } + + /** + * Decrypts a string + * @param s string to decrypt + * @return decrypted string + */ + public String decryptString(String s) { + char[] bytes = s.toCharArray(); + char[] eb = string.toCharArray(); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + bytes[idx] = (char) ((int) bytes[idx] - (int) eb[j]); + } + } + return new String(bytes); + } + + /** + * Encrypts an object + * @param o object to encrypt + * @return encrypted string + */ + public String encryptObject(Object o) { + return encryptString(Tools.mapToString(Objects.requireNonNull(new ObjectSerializerTCN(o).convertAll().done(new TCN())).toMap())); + } + + /** + * Decrypts an object + * @param s string to decrypt + * @return decrypted object + */ + public T decryptObject(String s) { + return new ObjectSerializerTCN(TCN.readMap(Tools.stringToMap(decryptString(s)))).convertAll().done(); + } + + @SuppressWarnings("CloneDoesntDeclareCloneNotSupportedException") + @Override + protected Key clone() { + try { + return (Key) super.clone(); + } + catch (CloneNotSupportedException e) { + return new Key(string); + } + } +} diff --git a/src/main/java/de/tudbut/tools/encryption/KeyStream.java b/src/main/java/de/tudbut/tools/encryption/KeyStream.java new file mode 100644 index 0000000..4bc4ffe --- /dev/null +++ b/src/main/java/de/tudbut/tools/encryption/KeyStream.java @@ -0,0 +1,23 @@ +package de.tudbut.tools.encryption; + +public class KeyStream { + + private int encPos = 0, decPos = 0; + private final char[] key; + + public KeyStream(Key key) { + this.key = key.string.toCharArray(); + } + + public int encrypt(int i) { + return proc(i + key[encPos++ % key.length]); + } + + public int decrypt(int i) { + return proc(i - key[decPos++ % key.length]); + } + + private int proc(int i) { + return i & 0xff; + } +} diff --git a/src/main/java/de/tudbut/tools/encryption/RawKey.java b/src/main/java/de/tudbut/tools/encryption/RawKey.java new file mode 100644 index 0000000..5fab806 --- /dev/null +++ b/src/main/java/de/tudbut/tools/encryption/RawKey.java @@ -0,0 +1,89 @@ +package de.tudbut.tools.encryption; + +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; + +/** + * Key to encrypt objects and strings + */ +public class RawKey extends Key { + + /** + * Generates a random Key + */ + public RawKey() { + super(); + } + + /** + * Constructs a Key + * @param s Key as string + */ + public RawKey(String s) { + super(s); + } + + /** + * Constructs a Key + * @param bytes Key as byte[] + */ + public RawKey(byte[] bytes) { + super(new String(bytes, StandardCharsets.ISO_8859_1)); + } + + /** + * Gets the bytes of the key + * @return the bytes of the key + */ + public byte[] toBytes() { + return string.getBytes(StandardCharsets.ISO_8859_1); + } + + /** + * Encrypts a string + * @param s string to encrypt + * @return encrypted string + */ + public String encryptString(String s) { + byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1); + byte[] eb = string.getBytes(StandardCharsets.ISO_8859_1); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + bytes[idx] = (byte) ((int) bytes[idx] + (int) eb[j]); + } + } + return new String(bytes, StandardCharsets.ISO_8859_1); + } + + /** + * Decrypts a string + * @param s string to decrypt + * @return decrypted string + */ + public String decryptString(String s) { + byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1); + byte[] eb = string.getBytes(StandardCharsets.ISO_8859_1); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + bytes[idx] = (byte) ((int) bytes[idx] - (int) eb[j]); + } + } + return new String(bytes, StandardCharsets.ISO_8859_1); + } + + @Override + protected RawKey clone() { + try { + return (RawKey) Object.class.getDeclaredMethod("clone").invoke(this); + } + catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + return new RawKey(string); + } + } +} diff --git a/src/main/java/de/tudbut/tools/encryption/SaferRawKey.java b/src/main/java/de/tudbut/tools/encryption/SaferRawKey.java new file mode 100644 index 0000000..59e2dcf --- /dev/null +++ b/src/main/java/de/tudbut/tools/encryption/SaferRawKey.java @@ -0,0 +1,59 @@ +package de.tudbut.tools.encryption; + +import java.nio.charset.StandardCharsets; + +public class SaferRawKey extends RawKey { + + public SaferRawKey() { + super(); + } + + public SaferRawKey(String s) { + super(s); + } + + public SaferRawKey(byte[] bytes) { + super(bytes); + } + + /** + * Encrypts a string + * @param s string to encrypt + * @return encrypted string + */ + public String encryptString(String s) { + byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1); + byte[] eb = string.getBytes(StandardCharsets.ISO_8859_1); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + byte o = bytes[idx]; + bytes[idx] += eb[j]; + eb[(j + 1) % p] += o; + } + } + return new String(bytes, StandardCharsets.ISO_8859_1); + } + + /** + * Decrypts a string + * @param s string to decrypt + * @return decrypted string + */ + public String decryptString(String s) { + byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1); + byte[] eb = string.getBytes(StandardCharsets.ISO_8859_1); + int len = bytes.length; + int p = eb.length; + for (int i = 0 ; i < len ; i+=p) { + for (int j = 0 ; j < p && i + j < len ; j++) { + int idx = i + j; + bytes[idx] -= eb[j]; + eb[(j + 1) % p] += bytes[idx]; + } + } + return new String(bytes, StandardCharsets.ISO_8859_1); + } +} diff --git a/src/main/java/de/tudbut/type/ByteArrayList.java b/src/main/java/de/tudbut/type/ByteArrayList.java new file mode 100644 index 0000000..173b8d1 --- /dev/null +++ b/src/main/java/de/tudbut/type/ByteArrayList.java @@ -0,0 +1,19 @@ +package de.tudbut.type; + +import de.tudbut.tools.ArrayTools; + +import java.util.ArrayList; + +public class ByteArrayList extends ArrayList { + + public byte[] toByteArray() { + Byte[] a = ArrayTools.arrayFromList(this); + byte[] b = new byte[size()]; + + for (int i = 0; i < size(); i++) { + b[i] = a[i]; + } + + return b; + } +} diff --git a/src/main/java/de/tudbut/type/CharArrayList.java b/src/main/java/de/tudbut/type/CharArrayList.java new file mode 100644 index 0000000..85f1ef5 --- /dev/null +++ b/src/main/java/de/tudbut/type/CharArrayList.java @@ -0,0 +1,19 @@ +package de.tudbut.type; + +import de.tudbut.tools.ArrayTools; + +import java.util.ArrayList; + +public class CharArrayList extends ArrayList { + + public char[] toCharArray() { + Character[] a = ArrayTools.arrayFromList(this); + char[] b = new char[size()]; + + for (int i = 0; i < size(); i++) { + b[i] = a[i]; + } + + return b; + } +} diff --git a/src/main/java/de/tudbut/type/FInfo.java b/src/main/java/de/tudbut/type/FInfo.java new file mode 100644 index 0000000..f3e1a31 --- /dev/null +++ b/src/main/java/de/tudbut/type/FInfo.java @@ -0,0 +1,5 @@ +package de.tudbut.type; + +public @interface FInfo { + String s(); +} diff --git a/src/main/java/de/tudbut/type/FileFormatException.java b/src/main/java/de/tudbut/type/FileFormatException.java new file mode 100644 index 0000000..1214d9b --- /dev/null +++ b/src/main/java/de/tudbut/type/FileFormatException.java @@ -0,0 +1,5 @@ +package de.tudbut.type; + +public class FileFormatException extends Exception { + +} diff --git a/src/main/java/de/tudbut/type/INothing.java b/src/main/java/de/tudbut/type/INothing.java new file mode 100644 index 0000000..d5369ab --- /dev/null +++ b/src/main/java/de/tudbut/type/INothing.java @@ -0,0 +1,5 @@ +package de.tudbut.type; + +public interface INothing { + +} diff --git a/src/main/java/de/tudbut/type/IntArrayList.java b/src/main/java/de/tudbut/type/IntArrayList.java new file mode 100644 index 0000000..9186afa --- /dev/null +++ b/src/main/java/de/tudbut/type/IntArrayList.java @@ -0,0 +1,17 @@ +package de.tudbut.type; + +import java.util.ArrayList; + +public class IntArrayList extends ArrayList { + + public int[] toIntArray() { + Integer[] a = toArray(new Integer[0]); + int[] b = new int[size()]; + + for (int i = 0; i < size(); i++) { + b[i] = a[i]; + } + + return b; + } +} diff --git a/src/main/java/de/tudbut/type/Nothing.java b/src/main/java/de/tudbut/type/Nothing.java new file mode 100644 index 0000000..c24437c --- /dev/null +++ b/src/main/java/de/tudbut/type/Nothing.java @@ -0,0 +1,8 @@ +package de.tudbut.type; + +public class Nothing { + public static void voidObject(Object toVoid) { + // literally does nothing with it expect killing it + Runtime.getRuntime().gc(); + } +} diff --git a/src/main/java/de/tudbut/type/O.java b/src/main/java/de/tudbut/type/O.java new file mode 100644 index 0000000..cc357b3 --- /dev/null +++ b/src/main/java/de/tudbut/type/O.java @@ -0,0 +1,5 @@ +package de.tudbut.type; + +public @interface O { + String n(); +} diff --git a/src/main/java/de/tudbut/type/ShortArrayList.java b/src/main/java/de/tudbut/type/ShortArrayList.java new file mode 100644 index 0000000..0fc96b4 --- /dev/null +++ b/src/main/java/de/tudbut/type/ShortArrayList.java @@ -0,0 +1,19 @@ +package de.tudbut.type; + +import de.tudbut.tools.ArrayTools; + +import java.util.ArrayList; + +public class ShortArrayList extends ArrayList { + + public short[] toIntArray() { + Short[] a = ArrayTools.arrayFromList(this); + short[] b = new short[size()]; + + for (int i = 0; i < size(); i++) { + b[i] = a[i]; + } + + return b; + } +} diff --git a/src/main/java/de/tudbut/type/Stoppable.java b/src/main/java/de/tudbut/type/Stoppable.java new file mode 100644 index 0000000..95cc15a --- /dev/null +++ b/src/main/java/de/tudbut/type/Stoppable.java @@ -0,0 +1,25 @@ +package de.tudbut.type; + +import de.tudbut.obj.InstanceBoundMap; +import de.tudbut.obj.NotSupportedException; + +public interface Stoppable { + InstanceBoundMap vars = new InstanceBoundMap<>(); + + default void stop() { + vars.set(this, C.STOPPED, true); + } + + default void start() throws NotSupportedException { + throw new NotSupportedException(); + } + + default boolean isStopped() { + vars.setIfNull(this, C.STOPPED, false); + return vars.get(this, C.STOPPED); + } + + class C { + private static final String STOPPED = "stopped"; + } +} diff --git a/src/main/java/de/tudbut/type/StringArray.java b/src/main/java/de/tudbut/type/StringArray.java new file mode 100644 index 0000000..7b17bc9 --- /dev/null +++ b/src/main/java/de/tudbut/type/StringArray.java @@ -0,0 +1,55 @@ +package de.tudbut.type; + +import java.util.Arrays; +import java.util.List; + +public class StringArray { + private String[] array; + + public StringArray(String[] array) { + this.array = array; + } + + public StringArray() { + this.array = new String[]{}; + } + + public String join(String s) { + StringBuilder joinedString = null; + for (String arrayItem : this.array) { + if (joinedString == null) + joinedString = new StringBuilder(arrayItem); + else + joinedString.append(s).append(arrayItem); + } + if (joinedString != null) { + return joinedString.toString(); + } + return ""; + } + + public String[] asArray() { + return this.array; + } + + public List asList() { + return Arrays.asList(this.array); + } + + public void add(String s) { + String[] old = this.array.clone(); + this.array = new String[this.array.length + 1]; + + System.arraycopy(old, 0, this.array, 0, old.length); + + this.array[this.array.length - 1] = s; + } + + public void clear() { + this.array = new String[]{}; + } + + public void set(String[] array) { + this.array = array; + } +} diff --git a/src/main/java/de/tudbut/type/Token.java b/src/main/java/de/tudbut/type/Token.java new file mode 100644 index 0000000..63fdd15 --- /dev/null +++ b/src/main/java/de/tudbut/type/Token.java @@ -0,0 +1,38 @@ +package de.tudbut.type; + +import de.tudbut.tools.Tools; + +public class Token { + public static byte LENGTH = 64; + private final int[] ints = new int[LENGTH]; + + public Token(String token) { + if (token.length() == LENGTH) { + for (int i = 0; i < token.length(); i++) { + ints[i] = token.toCharArray()[i]; + } + } + else + throw new StringIndexOutOfBoundsException("Must be " + LENGTH); + } + + public static Token gen() { + StringBuilder pool = new StringBuilder(); + for (char i = 0; i < '\u00ff'; i++) { + pool.append(i); + } + + return new Token(Tools.randomString(LENGTH, pool.toString())); + } + + public String toString() { + StringBuilder string = new StringBuilder(); + + for (int i = 0; i < ints.length; i++) { + string.append((char) ints[i]); + } + + return string.toString(); + } +} + diff --git a/src/main/java/de/tudbut/type/Vector2d.java b/src/main/java/de/tudbut/type/Vector2d.java new file mode 100644 index 0000000..c9ecdd4 --- /dev/null +++ b/src/main/java/de/tudbut/type/Vector2d.java @@ -0,0 +1,92 @@ +package de.tudbut.type; + +import de.tudbut.tools.Tools; +import de.tudbut.obj.Mappable; + +import java.util.Map; + +public class Vector2d implements Mappable { + private double x, y; + + public Vector2d(double x, double y) { + this.x = x; + this.y = y; + } + + public Vector2d set(double x, double y) { + this.x = x; + this.y = y; + return this; + } + + public Vector2d set(Vector2d vec) { + return set(vec.x, vec.y); + } + + public Vector2d add(Vector2d vec) { + set(x + vec.x, y + vec.y); + return this; + } + + public Vector2d add(double x, double y) { + set(x + this.x, y + this.y); + return this; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + @Override + public Vector2d clone() { + return new Vector2d(x, y); + } + + public Vector2d multiply(double i) { + set(x * i, y * i); + return this; + } + public Vector2d multiply(double mx, double my) { + set(x * mx, y * my); + return this; + } + public Vector2d multiply(Vector2d vec) { + set(x * vec.x, y * vec.y); + return this; + } + + public Vector2d negate() { + set(-x, -y); + return this; + } + + public String toString() { + return "x:" + x + ";y:" + y; + } + + @Override + public Map map() { + return Tools.stringToMap("x:" + x + ";y:" + y); + } + + public static Vector2d fromMap(Map map) { + return + new Vector2d( + Double.parseDouble(map.get("x")), + Double.parseDouble(map.get("y")) + ) + ; + } +} diff --git a/src/main/java/de/tudbut/type/Vector3d.java b/src/main/java/de/tudbut/type/Vector3d.java new file mode 100644 index 0000000..661fd4f --- /dev/null +++ b/src/main/java/de/tudbut/type/Vector3d.java @@ -0,0 +1,182 @@ +package de.tudbut.type; + +import de.tudbut.tools.Tools; +import de.tudbut.obj.Mappable; + +import java.util.Map; + +public class Vector3d implements Mappable { + private double x, y, z; + + public Vector3d(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public static Vector3d add(Vector3d vector1, Vector3d vector2) { + return new Vector3d(vector1.getX() + vector2.getX(), vector1.getY() + vector2.getY(), vector1.getZ() + vector2.getZ()); + } + + public static Vector3d subtract(Vector3d vector1, Vector3d vector2) { + return new Vector3d(vector1.getX() - vector2.getX(), vector1.getY() - vector2.getY(), vector1.getZ() - vector2.getZ()); + } + + public static Vector3d multiply(Vector3d vector1, Vector3d vector2) { + return new Vector3d(vector1.getX() * vector2.getX(), vector1.getY() * vector2.getY(), vector1.getZ() * vector2.getZ()); + } + + public static Vector3d divide(Vector3d vector1, Vector3d vector2) { + return new Vector3d(vector1.getX() / vector2.getX(), vector1.getY() / vector2.getY(), vector1.getZ() / vector2.getZ()); + } + + public static double length(Vector3d vector) { + return Math.sqrt(vector.getX() * vector.getX() + vector.getY() * vector.getY() + vector.getZ() * vector.getZ()); + } + + public static Vector3d normalize(Vector3d vector) { + double len = Vector3d.length(vector); + return Vector3d.divide(vector, new Vector3d(len, len, len)); + } + + public static double dot(Vector3d vector1, Vector3d vector2) { + return vector1.getX() * vector2.getX() + vector1.getY() * vector2.getY() + vector1.getZ() * vector2.getZ(); + } + + public Vector3d set(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + public Vector3d set(Vector3d vector) { + return set(vector.x, vector.y, vector.z); + } + + public Vector3d add(Vector3d vector) { + set(x + vector.x, y + vector.y, z + vector.z); + return this; + } + + public Vector3d add(double x, double y, double z) { + set(x + this.x, y + this.y, z + this.z); + return this; + } + + @Override + public int hashCode() { + final long prime = 31; + long result = 1; + result = prime * result + Double.doubleToLongBits(x); + result = prime * result + Double.doubleToLongBits(y); + result = prime * result + Double.doubleToLongBits(z); + return (int) result / 4; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vector3d other = (Vector3d) obj; + if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) + return false; + if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) + return false; + return Double.doubleToLongBits(z) == Double.doubleToLongBits(other.z); + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + public double getZ() { + return z; + } + + public void setZ(double z) { + this.z = z; + } + + public Vector3d multiply(double i) { + set(x * i, y * i, z * i); + return this; + } + + public Vector3d multiply(double x, double y, double z) { + set(this.x * x, this.y * y, this.z * z); + return this; + } + + public Vector3d multiply(Vector3d vec) { + set(this.x * vec.x, this.y * vec.y, this.z * vec.z); + return this; + } + + public Vector3d negate() { + set(-x, -y, -z); + return this; + } + + public boolean isGreaterThan(Vector3d vec) { + return + x > vec.x && + y > vec.y && + z > vec.z; + } + + public boolean isSmallerThan(Vector3d vec) { + return + x < vec.x && + y < vec.y && + z < vec.z; + } + + public boolean isGreaterOrEqualThan(Vector3d vec) { + return + x >= vec.x && + y >= vec.y && + z >= vec.z; + } + + public boolean isSmallerOrEqualThan(Vector3d vec) { + return + x <= vec.x && + y <= vec.y && + z <= vec.z; + } + + @Override + public Vector3d clone() { + return new Vector3d(x, y, z); + } + + public String toString() { + return "x:" + x + ";y:" + y + ";z:" + z; + } + + @Override + public Map map() { + return Tools.stringToMap("x:" + x + ";y:" + y + ";z:" + z); + } + + public static Vector3d fromMap(Map map) { + return new Vector3d(Double.parseDouble(map.get("x")), Double.parseDouble(map.get("y")), Double.parseDouble(map.get("z"))); + } +} diff --git a/src/main/java/de/tudbut/type/WIP.java b/src/main/java/de/tudbut/type/WIP.java new file mode 100644 index 0000000..04131fe --- /dev/null +++ b/src/main/java/de/tudbut/type/WIP.java @@ -0,0 +1,5 @@ +package de.tudbut.type; + +public @interface WIP { + boolean works() default false; +} diff --git a/src/main/java/de/tudbut/ui/consoleui/ConsoleUI.java b/src/main/java/de/tudbut/ui/consoleui/ConsoleUI.java new file mode 100644 index 0000000..0335739 --- /dev/null +++ b/src/main/java/de/tudbut/ui/consoleui/ConsoleUI.java @@ -0,0 +1,56 @@ +package de.tudbut.ui.consoleui; + +import de.tudbut.type.StringArray; +import de.tudbut.type.WIP; + +@WIP( + works = true +) +public class ConsoleUI { + public static String HORIZONTAL_LINE = "—"; + public static String VERTICAL_LINE = "│"; + + protected static StringArray create( + String title, String bgColor, + String fontColor, + String rColor, StringArray theText + ) { + StringBuilder x = new StringBuilder(); + for (int i = 0; i < title.length(); i++) { + x.append("─"); + } + for (int i1 = 0; i1 < theText.asArray().length; i1++) { + String s = theText.asArray()[i1]; + StringBuilder y = new StringBuilder(); + for (int i2 = 0; i2 < title.length() + 8 - s.length(); i2++) { + y.append(" "); + } + theText.asArray()[i1] = color(fontColor, bgColor) + s + y; + } + theText.asArray()[0] = "│" + theText.asArray()[0]; + return new StringArray( + ( + "" + + color(rColor, bgColor) + "┌────" + title + "────┐\u001b[0m\n" + + color(rColor, bgColor) + + theText.join( + color(rColor, bgColor) + + "│\u001b[0m\n" + color(rColor, bgColor) + + "│" + color(rColor, bgColor) + ) + + color(rColor, bgColor) + "│\u001b[0m\n" + + color(rColor, bgColor) + "└────" + x + "────┘\u001b[0m" + ).split("\n") + ); + } + + private static String color(String cUpper, String cLower) { + return "\u001b[38;5;" + cUpper + "m\u001b[48;5;" + cLower + "m"; + } + + protected static void clear() { + for (int i = 0; i < 2048; i++) { + System.out.print("\u001b[K\u001b[A\u001b[K"); + } + } +} diff --git a/src/main/java/de/tudbut/ui/windowgui/AdaptedGraphics.java b/src/main/java/de/tudbut/ui/windowgui/AdaptedGraphics.java new file mode 100644 index 0000000..ef13ec7 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/AdaptedGraphics.java @@ -0,0 +1,30 @@ +package de.tudbut.ui.windowgui; + +import java.awt.*; + +public class AdaptedGraphics { + private final Graphics graphics; + + public AdaptedGraphics(Graphics graphics) { + this.graphics = graphics; + } + + public void setColor(int r, int g, int b) { + this.graphics.setColor(new Color(r, g, b)); + } + + public void setColor(int rgb) { + if(rgb >= 0 && rgb <= 0xffffff) + this.graphics.setColor(new Color(rgb, false)); + else + this.graphics.setColor(new Color(rgb, true)); + } + + public Graphics g() { + return graphics; + } + + public void drawImage(int x, int y, Image image) { + graphics.drawImage(image, x, y, null); + } +} diff --git a/src/main/java/de/tudbut/ui/windowgui/Design.java b/src/main/java/de/tudbut/ui/windowgui/Design.java new file mode 100644 index 0000000..2ea8274 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/Design.java @@ -0,0 +1,6 @@ +package de.tudbut.ui.windowgui; + +@Deprecated +public class Design { + +} diff --git a/src/main/java/de/tudbut/ui/windowgui/FontRenderer.java b/src/main/java/de/tudbut/ui/windowgui/FontRenderer.java new file mode 100644 index 0000000..0c9be80 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/FontRenderer.java @@ -0,0 +1,66 @@ +package de.tudbut.ui.windowgui; + +import de.tudbut.obj.Vector2i; + +import java.awt.*; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.image.BufferedImage; + +public class FontRenderer { + public Font myFont; + public FontRenderContext context; + + public FontRenderer(int size) { + myFont = new Font("serif", Font.PLAIN, size); + context = new FontRenderContext(null, true, false); + } + + public int getTextWidth(String text) { + int r = 0; + + for (int i = 0; i < text.split("\n").length; i++) + r += ((int) myFont.getStringBounds(text.split("\n")[i], context).getWidth()) + 4; + + return r; + } + + public int getTextHeight(String text) { + int r = 0; + + for (int i = 0; i < text.split("\n").length; i++) + r += ((int) myFont.getStringBounds(text.split("\n")[i], context).getHeight()) + 4; + + return r; + } + + private static Rectangle getStringBounds(String s, Font font, FontRenderContext context) { + GlyphVector gv = font.createGlyphVector(context, s); + return gv.getPixelBounds(context, 0, 0); + } + + public Image renderText(String text, int color) { + BufferedImage image = new BufferedImage(getTextWidth(text), getTextHeight(text), BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = ((Graphics2D) image.getGraphics()); + graphics.setRenderingHint( + RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON + ); + + graphics.setColor(new Color(color)); + graphics.setFont(myFont); + for (int i = 0; i < text.split("\n").length; i++) + graphics.drawString(text.split("\n")[i], 0, (myFont.getSize() + 4) * (i + 1)); + + return image; + } + + public Vector2i getCoordsForCentered(Vector2i coords, String text) { + return + new Vector2i( + coords.getX() - getTextWidth(text) / 2, + coords.getY() - getTextHeight(text) / 2 + ) + ; + } +} diff --git a/src/main/java/de/tudbut/ui/windowgui/GUIWindow.java b/src/main/java/de/tudbut/ui/windowgui/GUIWindow.java new file mode 100644 index 0000000..5caf405 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/GUIWindow.java @@ -0,0 +1,7 @@ +package de.tudbut.ui.windowgui; + +import de.tudbut.type.WIP; + +@WIP(works = false) +public class GUIWindow { +} diff --git a/src/main/java/de/tudbut/ui/windowgui/RenderableWindow.java b/src/main/java/de/tudbut/ui/windowgui/RenderableWindow.java new file mode 100644 index 0000000..a1f3190 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/RenderableWindow.java @@ -0,0 +1,197 @@ +package de.tudbut.ui.windowgui; + +import de.tudbut.tools.Keyboard; +import de.tudbut.tools.Mouse; +import de.tudbut.type.*; +import de.tudbut.obj.Vector2i; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Date; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class RenderableWindow { + public final BufferedImage image; + public final AtomicInteger xSize = new AtomicInteger(1); + public final AtomicInteger ySize = new AtomicInteger(1); + protected BufferedImage buffer; + protected BufferedImage bufferTMP; + private final AtomicInteger fps = new AtomicInteger(); + private final JFrame window; + private final JLabel label; + private RenderRunnable[] toRender = new RenderRunnable[1024]; + private RenderRunnable[] toRenderTMP = new RenderRunnable[1024]; + private final AtomicLong frameAt = new AtomicLong(new Date().getTime()); + private final AtomicLong lastFrameAt = new AtomicLong(new Date().getTime()); + private short nextRenderRunnable = 0; + private final int tryFps; + + public RenderableWindow(int x, int y, String name, int tryFps, boolean showFPS) { + this(x,y,name,tryFps,showFPS,true); + } + + public RenderableWindow(int x, int y, String name, int tryFps, boolean showFPS, boolean decorated) { + this.tryFps = tryFps; + window = new JFrame(name); + image = new BufferedImage(x, y, BufferedImage.TYPE_INT_ARGB); + window.setUndecorated(!decorated); + window.setVisible(true); + window.setSize(x, y); + label = new JLabel() { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics g2 = g.create(); + g2.drawImage(buffer, 0, 0, window); + g2.drawImage(image, 0, 0, window); + g2.dispose(); + } + }; + window.add(label); + Keyboard.startListening(window); + Mouse.startListening(window); + Timer timer = new Timer(1000 / tryFps, e -> { + label.repaint(); + xSize.set(window.getSize().width); + ySize.set(window.getSize().height); + if (showFPS) + window.setTitle(name + " | " + fps.get() + " FPS"); + }); + timer.start(); + label.repaint(); + window.repaint(); + } + + public void calcInsets(int delay) { + new Thread(() -> { + try { + Thread.sleep(delay); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + Vector2i sizeOnScreen = getSize().add(getInsets().multiply(2)); + window.setSize( + sizeOnScreen.getX(), + sizeOnScreen.getY() + ); + }).start(); + } + + public Vector2i getMousePos() { + Vector2i pos = Mouse.getMousePos(); + Insets insets = window.getInsets(); + return new Vector2i( + (int) - window.getLocation().getX() - insets.left, + (int) - window.getLocation().getY() - insets.top + ).add(pos); + } + + public boolean mouseIsBetween(Vector2i pos0, Vector2i pos1) { + Vector2i mousePos = getMousePos(); + return + mousePos.getX() > pos0.getX() && + mousePos.getY() > pos0.getY() && + mousePos.getX() < pos1.getX() && + mousePos.getY() < pos1.getY(); + } + + public boolean mouseInRangeFrom(Vector2i pos, Vector2i range) { + Vector2i mousePos = getMousePos(); + return + mousePos.getX() > pos.getX() && + mousePos.getY() > pos.getY() && + mousePos.getX() < pos.getX() + range.getX() && + mousePos.getY() < pos.getY() + range.getY(); + } + + public Vector2i getSizeOnScreen() { + return + new Vector2i(xSize.get(), ySize.get()) + .negate() + .add( + new Vector2i( + window.getInsets().left + window.getInsets().right, + window.getInsets().top + window.getInsets().bottom + ) + ) + .negate(); + } + + public Vector2i getInsets() { + return new Vector2i( + window.getInsets().left + window.getInsets().right, + window.getInsets().top + window.getInsets().bottom + ); + } + + public Vector2i getSize() { + return + new Vector2i(xSize.get(), ySize.get()); + } + + public int getWindowBarHeight() { + return window.getInsets().top; + } + + public int getFps() { + return fps.get(); + } + + public JFrame getWindow() { + return window; + } + + public JLabel getLabel() { + return label; + } + + public void render(RenderRunnable renderRunnable) { + toRenderTMP[nextRenderRunnable] = renderRunnable; + nextRenderRunnable++; + } + + public void prepareRender() { + nextRenderRunnable = 0; + if (toRenderTMP[0] != null) + toRender = toRenderTMP; + toRenderTMP = new RenderRunnable[1024]; + } + + public void doRender() { + BufferedImage i = new BufferedImage(xSize.get(), ySize.get(), BufferedImage.TYPE_INT_ARGB); + + Graphics g = i.getGraphics(); + AdaptedGraphics ag = new AdaptedGraphics(g); + + for (RenderRunnable r : toRender) { + if (r == null) + break; + + r.render(ag, g, image); + } + + bufferTMP = i; + } + + @FInfo(s = "aka. finishRender") + public void swapBuffers() { + frameAt.set(new Date().getTime()); + try { + fps.set((int) (1000 / (frameAt.get() - lastFrameAt.get()))); + } + catch (ArithmeticException ignore) { + fps.set(tryFps); + } + lastFrameAt.set(new Date().getTime()); + BufferedImage var1 = buffer; + buffer = bufferTMP; + bufferTMP = var1; + } + + public interface RenderRunnable { + void render(AdaptedGraphics ag, Graphics gr, BufferedImage img); + } +} diff --git a/src/main/java/de/tudbut/ui/windowgui/WindowGUI.java b/src/main/java/de/tudbut/ui/windowgui/WindowGUI.java new file mode 100644 index 0000000..da2f9f7 --- /dev/null +++ b/src/main/java/de/tudbut/ui/windowgui/WindowGUI.java @@ -0,0 +1,126 @@ +package de.tudbut.ui.windowgui; + +import de.tudbut.logger.Logger; +import de.tudbut.tools.Config; +import de.tudbut.window.Window; + +import javax.swing.*; +import java.awt.event.ActionListener; +import java.nio.file.Files; +import java.nio.file.Paths; + +@Deprecated +public class WindowGUI { + public static String PATH = "tmp/gui"; + private static long nextWindowID = 0; + private int nextActionListenerID = 0; + private final int nextDesignID = 0; + private final ActionListener[] listeners = new ActionListener[1023]; + private final Logger logger = new Logger("WindowGUI"); + //private static Design[] designs = new Design[1024]; + + protected Config create(String name) throws Exception { + if (!Files.exists(Paths.get(PATH))) { + Files.createDirectory(Paths.get("tmp")); + Files.createDirectory(Paths.get(PATH)); + } + + if (!Files.exists(Paths.get(PATH + "/" + nextWindowID))) { + Files.createDirectory(Paths.get(PATH + "/" + nextWindowID)); + Files.createDirectory(Paths.get(PATH + "/" + nextWindowID + "/button")); + } + Config r = new Config(PATH + "/" + nextWindowID + ".window"); + r.set("name", name); + r.set("id", String.valueOf(nextWindowID)); + //r.set("design", "null"); + r.set("nextButtonY", "0"); + r.set("nextButtonID", "0"); + r.set("buttons", "0"); + nextWindowID++; + return r; + } + + protected Config addButton( + Config window, String text, int sizeX, + int sizeY + ) throws Exception { + int nextButtonY = Integer.parseInt(window.get("nextButtonY")); + int nextButtonID = Integer.parseInt(window.get("nextButtonID")); + Config r = new Config(PATH + "/" + window.get("id") + "/button/" + nextButtonID + ".button"); + r.set("text", text); + r.set("id", nextButtonID + ""); + r.set("posX", "0"); + r.set("posY", nextButtonY + ""); + r.set("sizeX", sizeX + ""); + r.set("sizeY", sizeY + ""); + r.set("windowID", window.get("id")); + r.set("listenerID", "x"); + r.set("invisible", "0"); + window.set("buttons", (Integer.parseInt(window.get("buttons")) + 1) + ""); + nextButtonY += sizeY + 2; + nextButtonID++; + window.set("nextButtonY", nextButtonY + ""); + window.set("nextButtonID", nextButtonID + ""); + return r; + } + + protected void delButton(Config button, Config window) throws Exception { + int nextButtonY = Integer.parseInt(window.get("nextButtonY")); + nextButtonY -= Integer.parseInt(button.get("sizeY")) + 2; + button.set("invisible", "1"); + window.set("nextButtonY", nextButtonY + ""); + } + + protected void setButtonVisibility(Config button, boolean visible) + throws Exception { + button.set("invisible", !visible ? "1" : "0"); + } + + + protected void addButtonListener(Config button, ActionListener listener) throws Exception { + listeners[nextActionListenerID] = listener; + button.set("listenerID", nextActionListenerID + ""); + nextActionListenerID++; + } + + protected void render(Config window, Window w) throws Exception { + logger.info("Preparing render..."); + w.label.removeAll(); + w.frame.repaint(); + logger.info("Started render..."); + + JButton[] buttons = new JButton[Integer.parseInt(window.get("buttons"))]; + for (int i = 0; i < buttons.length; i++) { + Config config = new Config(PATH + "/" + window.get("id") + "/button/" + i + ".button"); + JButton button = new JButton(config.get("text")); + button.setLocation(Integer.parseInt(config.get("posX")), Integer.parseInt(config.get("posY"))); + button.setSize(Integer.parseInt(config.get("sizeX")), Integer.parseInt(config.get("sizeY"))); + if (!config.get("listenerID").equals("x")) + if (listeners[Integer.parseInt(config.get("listenerID"))] != null) + button.addActionListener(listeners[Integer.parseInt(config.get("listenerID"))]); + + if (config.get("invisible").equals("1")) { + button.setVisible(false); + button.setSize(0, 0); + button.setEnabled(false); + } + buttons[i] = button; + } + + logger.info("Finishing render..."); + + for (JButton button : buttons) { + w.label.add(button); + } + + w.frame.setTitle(window.get("name")); + + logger.info("Finished! Updating..."); + + w.label.updateUI(); + w.frame.repaint(); + + logger.info("Rendered!"); + + } +} diff --git a/src/main/java/de/tudbut/window/Window.java b/src/main/java/de/tudbut/window/Window.java new file mode 100644 index 0000000..1dcd32b --- /dev/null +++ b/src/main/java/de/tudbut/window/Window.java @@ -0,0 +1,146 @@ +package de.tudbut.window; + +import de.tudbut.logger.LoggerSink; +import de.tudbut.global.DebugStateManager; + +import javax.swing.*; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.util.concurrent.atomic.AtomicReference; + +public class Window { + private final AtomicReference logger; + public JFrame frame = new JFrame(); + public JLabel label = new JLabel(); + private String text; + private final boolean isMain; + private int exitValue = 0; + private String name; + private String title; + private boolean isClosed = true; + private final boolean messages = true; + + public Window(String name, String label, boolean isMain) { + this.logger = DebugStateManager.debugLoggerReference(); + this.frame.setName(name); + this.label.setName(name); + this.frame.setTitle(label); + if (isMain) { + this.frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + this.isMain = true; + } + else { + this.frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + this.isMain = false; + } + this.label.setVerticalAlignment(1); + this.frame.getContentPane().add(this.label); + this.name = name; + this.title = label; + this.frame.addWindowListener(new WindowListener() { + @Override + public void windowOpened(WindowEvent windowEvent) { + } + + @Override + public void windowClosing(WindowEvent windowEvent) { + } + + @Override + public void windowClosed(WindowEvent windowEvent) { + Window.this.isClosed = true; + Window.this.logger.get().info("Window was closed"); + if (Window.this.isMain) Window.this.delete(); + else Window.this.close(); + } + + @Override + public void windowDeactivated(WindowEvent windowEvent) { + } + + @Override + public void windowIconified(WindowEvent windowEvent) { + } + + @Override + public void windowDeiconified(WindowEvent windowEvent) { + } + + @Override + public void windowActivated(WindowEvent windowEvent) { + } + + }); + this.logger.get().info("Created window"); + + new Timer(100, (ignore) -> { + frame.repaint(); + this.label.repaint(); + this.label.updateUI(); + frame.repaint(); + this.label.repaint(); + }); + } + + public Window setText(String string) { + text = string; + this.label.setText("" + string.replaceAll("[\\\\\\n]", "
") + ""); + this.logger.get().info("Set text to '" + string + "'"); + return this; + } + + public Window setExitValue(int v) { + this.exitValue = v; + this.logger.get().info("Set exit value to " + v); + return this; + } + + public Window addText(String string) { + text += string; + this.label.setText("" + + text.replaceAll("[\\\\\\n]", "
") + ""); + this.logger.get().info("Added text '" + string + "'"); + return this; + } + + public Window open() { + this.frame.setVisible(true); + this.logger.get().info("Opened window"); + this.isClosed = false; + return this; + } + + public Window close() { + this.frame.setVisible(false); + this.logger.get().info("Closed window"); + this.isClosed = true; + return this; + } + + public Window setSize(int x, int y) { + this.frame.setSize(x, y); + this.logger.get().info("Resized window"); + return this; + } + + public Window delete() { + this.frame.dispose(); + this.isClosed = true; + if (this.isMain) { + this.logger.get().info("Deleted window"); + logger.get().warn("EXITING - Window '" + this.name + "' was deleted"); + System.exit(this.exitValue); + } + return this; + } + + public Window restore() { + Window restoredWindow = new Window(this.name, this.title, this.isMain); + restoredWindow.setText(this.label.getText()).setSize(this.frame.getWidth(), this.frame.getHeight()); + return restoredWindow; + } + + public boolean isClosed() { + return this.isClosed; + } +} diff --git a/src/test/java/AsyncTest.java b/src/test/java/AsyncTest.java new file mode 100644 index 0000000..889f310 --- /dev/null +++ b/src/test/java/AsyncTest.java @@ -0,0 +1,109 @@ +import de.tudbut.async.Reject; +import de.tudbut.async.Resolve; +import de.tudbut.async.Task; +import de.tudbut.async.TaskQueue; +import de.tudbut.parsing.AsyncJSON; + +import static de.tudbut.async.Async.*; + +/** + * @author TudbuT + * @since 03 Jun 2022 + */ + +public class AsyncTest { + + public static void main(String[] args) throws IllegalAccessException, InterruptedException { + context(TaskQueue.main); + new Task<>((resolve, reject) -> { + if (5 / 0 == 0) { + resolve.call("works!"); + } + }, String.class).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task((resolve, reject) -> { + try { + if(5 / 0 == 0) { + resolve.call("works!"); + } + } catch (Exception e) { + reject.call(e); + } + }).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task<>((resolve, reject) -> { + if (5 / 0 == 0) { + throw new Resolve("works!"); + } + }, String.class).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task((resolve, reject) -> { + try { + if(5 / 0 == 0) { + throw new Resolve("works!"); + } + } catch (Exception e) { + throw new Reject(e); + } + }).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task((resolve, reject) -> { + try { + if(5 / 0 == 0) { + throw new Resolve("works!"); + } + } catch (Exception e) { + throw new Reject(e); + } + }).then(System.err::println).ok(); + new Task<>((resolve, reject) -> { + if (5 * 0 == 0) { + resolve.call("works!"); + } + }, String.class).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task((resolve, reject) -> { + try { + if(5 * 0 == 0) { + resolve.call("works!"); + } + } catch (Exception e) { + reject.call(e); + } + }).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task<>((resolve, reject) -> { + if (5 * 0 == 0) { + throw new Resolve("works!"); + } + }, String.class).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task((resolve, reject) -> { + try { + if(5 * 0 == 0) { + throw new Resolve("works!"); + } + } catch (Exception e) { + throw new Reject(e); + } + }).then(System.err::println).err(Throwable::printStackTrace).ok(); + + new Task((resolve, reject) -> { + new Task((resolve1, reject1) -> { + resolve.call("works!"); + }).ok().await(); + }).then(System.err::println).err(Throwable::printStackTrace).ok(); + new Task(((resolve, reject) -> { + resolve.call(500); + })) + .compose((i, res, rej) -> res.call(String.valueOf(i))) + .then(System.err::println) + .ok(); + new Task(((resolve, reject) -> { + resolve.call(500); + })) + .ok() + .compose((i, res, rej) -> res.call(String.valueOf(i))) + .then(System.err::println); + System.out.println(AsyncJSON.read("{\"test\":{\"a\":\"b\",\"c\":[\"d\"]}}").ok().await().toString()); + System.out.println(AsyncJSON.write(AsyncJSON.read("{\"test\":{\"a\":\"b\",\"c\":[\"d\"]}}").ok().await()).ok().await()); + + TaskQueue.main.finish(); + Thread.sleep(55); + System.err.println("\n>>> Output should be 5 exceptions and 5 'works!' and 1 '500' and 2 json objects!!!"); + System.exit(0); + } +} diff --git a/src/test/java/CSTCN2.java b/src/test/java/CSTCN2.java new file mode 100644 index 0000000..6f44d86 --- /dev/null +++ b/src/test/java/CSTCN2.java @@ -0,0 +1,50 @@ +import java.util.Arrays; + +import de.tudbut.parsing.JSON; +import de.tudbut.parsing.TCN; +import de.tudbut.tools.*; +import de.tudbut.tools.*; +import de.tudbut.parsing.*; +import de.tudbut.io.*; +import java.io.FileInputStream; + +public class CSTCN2 { + private final String t1 = null; + private final String[][] t2 = null; + private static int t3 = 0; + private static boolean bool = false; + + //public CSTCN2(String t1, String[][] t2) { + // this.t1 = t1; + // this.t2 = t2; + //} + + public String toString() { + return "t1=" + t1 + ",t2=" + t2 + ",t3=" + t3; + } + + public static void main(String[] args) throws Exception { + CSTCN2 obj = (CSTCN2)ConfigSaverTCN2.read(JSON.read(new StreamReader(new FileInputStream("test.json")).readAllAsString()), null); + System.out.println(JSON.writeReadable((TCN)ConfigSaverTCN2.write(obj, true, true))); + + String s; + + + s = "I would like info on the player Player.360."; + System.out.println(Arrays.toString(Tools.readf("I would like info on the player {}.", s))); // yes + System.out.println(Arrays.toString(Tools.readf("I would like info on the {} {}.", s))); // yes + System.out.println(Arrays.toString(Tools.readf("I would like {} on the {} {}.", s))); // yes + System.out.println(Arrays.toString(Tools.readf("{} would like {} on the {} {}.", s))); // yes + s = "I would like info on the player Player.360..."; + System.out.println(Arrays.toString(Tools.readf("I would like info on the player {}...", s))); // yes + System.out.println(Arrays.toString(Tools.readf("I would like info on the {} {}...", s))); // yes + System.out.println(Arrays.toString(Tools.readf("I would like {} on the {} {}...", s))); // yes + System.out.println(Arrays.toString(Tools.readf("{} would like {} on the {} {}.", s))); // yes + s = "I would like info on the on the player on the Player.360..."; + System.out.println(Arrays.toString(Tools.readf("I would like info on the player {}...", s))); // no + System.out.println(Arrays.toString(Tools.readf("I would like info on the {} {}...", s))); // yes + System.out.println(Arrays.toString(Tools.readf("I would like {} on the {} {}...", s))); // yes + s = "Cancel all tasks!"; + System.out.println(Tools.readf1("Cancel all tasks!", s)); + } +} diff --git a/src/test/java/ClassDiscoverTest.java b/src/test/java/ClassDiscoverTest.java new file mode 100644 index 0000000..232739b --- /dev/null +++ b/src/test/java/ClassDiscoverTest.java @@ -0,0 +1,15 @@ +import de.tudbut.tools.DiscoverClasses; +import de.tudbut.tools.Registry; +import de.tudbut.parsing.TCN; + +import java.io.IOException; + +public class ClassDiscoverTest { + public static void main(String[] args) throws IOException, IllegalAccessException { + Registry registry = new Registry("registrytest.tcnm"); + TCN data = registry.register("test"); + if(data.get("Package") == null) + data.set("Package", "de.tudbut.tools"); + System.out.println(DiscoverClasses.of(ClassLoader.getSystemClassLoader()).in(data.getString("Package")).run()); + } +} diff --git a/src/test/java/DiscordRPCTest.java b/src/test/java/DiscordRPCTest.java new file mode 100644 index 0000000..270a304 --- /dev/null +++ b/src/test/java/DiscordRPCTest.java @@ -0,0 +1,9 @@ +import java.io.IOException; + +public class DiscordRPCTest { + + public static void main(String[] args) throws InterruptedException, IOException, NoSuchFieldException, IllegalAccessException { + + + } +} diff --git a/src/test/java/KeyTest.java b/src/test/java/KeyTest.java new file mode 100644 index 0000000..5c68d25 --- /dev/null +++ b/src/test/java/KeyTest.java @@ -0,0 +1,10 @@ +import de.tudbut.tools.encryption.SaferRawKey; + +public class KeyTest { + + public static void main(String[] args) { + SaferRawKey key = new SaferRawKey("hi"); + System.out.println(key.encryptString("hello!")); + System.out.println(key.decryptString(key.encryptString("hello!"))); + } +} diff --git a/src/test/java/PBIC2Test.java b/src/test/java/PBIC2Test.java new file mode 100644 index 0000000..8f24fb5 --- /dev/null +++ b/src/test/java/PBIC2Test.java @@ -0,0 +1,53 @@ +import de.tudbut.net.http.HTTPRequest; +import de.tudbut.net.http.HTTPRequestType; +import de.tudbut.net.http.HTTPServer; +import de.tudbut.net.pbic2.PBIC2AEventHandler; +import de.tudbut.net.pbic2.PBIC2AListener; +import de.tudbut.net.pbic2.PBIC2Client; +import de.tudbut.net.pbic2.PBIC2Server; +import de.tudbut.tools.encryption.KeyStream; +import de.tudbut.tools.encryption.RawKey; + +import java.io.IOException; + +public class PBIC2Test { + + public static void main(String[] args) throws IOException { + + HTTPServer server = new HTTPServer(5000, null, Runnable::run); + RawKey key = new RawKey("hello"); + KeyStream stream0 = new KeyStream(key); + KeyStream stream1 = new KeyStream(key); + + server.addHandler(request -> { + PBIC2Server serv = new PBIC2Server(request, stream0::encrypt, stream0::decrypt); + for (int i = 0 ; i < 100000 ; i++) { + StringBuilder builder = new StringBuilder(); + double l = Math.random() * 1000; + System.out.println((int) l); + for (int j = 0 ; j < (int) l ; j++) { + builder.append((char) (Math.random() * Character.MAX_VALUE + 1)); + } + System.out.println(serv.writeMessageWithResponse(builder.toString()).length()); + Thread.sleep(100); + } + }); + server.listen(); + + HTTPRequest request = new HTTPRequest(HTTPRequestType.POST, "localhost", 5000, "/"); + PBIC2Client client = new PBIC2Client(request, stream1::encrypt, stream1::decrypt); + PBIC2AEventHandler handler = new PBIC2AEventHandler(); + handler.start(client, new PBIC2AListener() { + @Override + public void onMessage(String message) throws IOException { + System.out.println(message.length()); + client.writeMessage("hhhh"); + } + + @Override + public void onError(Throwable throwable) { + + } + }); + } +} diff --git a/src/test/java/RenderTest.java b/src/test/java/RenderTest.java new file mode 100644 index 0000000..a7f0d4c --- /dev/null +++ b/src/test/java/RenderTest.java @@ -0,0 +1,21 @@ +import de.tudbut.type.Vector3d; +import de.tudbut.rendering.Projection3D; +import de.tudbut.rendering.RenderOutputType; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +public class RenderTest { + + public static void main(String[] args) throws IOException { + + Projection3D d = new Projection3D(new AtomicInteger(100), new AtomicInteger(100), RenderOutputType.BUFFEREDIMAGE, 0xff80a0ff); + d.setColor(0xff000000); + d.fillRectangle(new Vector3d(1, 1, 10), new Vector3d(-1, 1, 10), new Vector3d(-1, -1, 10), new Vector3d(1, -1, 10)); + BufferedImage render = (BufferedImage) d.render(); + ImageIO.write(render, "png", new File("output.png")); + } +} diff --git a/src/test/java/SecurityTest.java b/src/test/java/SecurityTest.java new file mode 100644 index 0000000..82abf6d --- /dev/null +++ b/src/test/java/SecurityTest.java @@ -0,0 +1,43 @@ +import de.tudbut.security.AccessKiller; +import de.tudbut.security.DataKeeper; +import de.tudbut.security.StrictnessBuilder; +import de.tudbut.security.permissionmanager.CallClassRestriction; + +import java.lang.reflect.Field; + +public class SecurityTest { + + private static final DataKeeper secret = new DataKeeper<>(new CallClassRestriction(AllowedAccessClass.class), StrictnessBuilder.empty(), "hii"); + + public static void main(String[] args) throws InterruptedException { + Thread.sleep(500); + AccessKiller.killReflectionFor(SecurityTest.class); + AllowedAccessClass.print(); + System.out.println("THIS IS GOOD!"); + System.out.println(); + try { + Field secretField = SecurityTest.class.getDeclaredField("secret"); + secretField.setAccessible(true); + System.out.println(secretField.get(null)); + System.out.println("THIS SHOULD NOT HAVE HAPPENED!!"); + } catch (Throwable e) { + e.printStackTrace(System.out); + System.out.println("THIS IS GOOD!"); + } + System.out.println(); + try { + secret.access(x -> System.out.println("Evil access (NOT OK): " + x.getValue())); + } catch (Throwable e) { + e.printStackTrace(System.out); + System.out.println("THIS IS GOOD!"); + } + Thread.sleep(500); + System.exit(0); + } + + public static class AllowedAccessClass { + public static void print() { + secret.access(x -> System.out.println("Normal access (OK): " + x.getValue())); + } + } +} diff --git a/src/test/java/TCNDBTest.java b/src/test/java/TCNDBTest.java new file mode 100644 index 0000000..f7986c5 --- /dev/null +++ b/src/test/java/TCNDBTest.java @@ -0,0 +1,13 @@ +import de.tudbut.parsing.JSON; +import de.tudbut.parsing.TCN; + +public class TCNDBTest { + + public static void main(String[] args) { + TCN tcn = new TCN(); + tcn.set("{}{}{}\"\"", "{}}][{\"\"\""); + System.out.println(JSON.write(tcn)); + + + } +} diff --git a/tuddylib b/tuddylib new file mode 120000 index 0000000..096fa8b --- /dev/null +++ b/tuddylib @@ -0,0 +1 @@ +tuddylib \ No newline at end of file