diff --git a/README.MD b/README.MD index 59c367f..c76678f 100644 --- a/README.MD +++ b/README.MD @@ -1,20 +1,49 @@ # UnlegitLibrary -## Overview -UnlegitLibrary is a general-purpose Java utility library that bundles a modular -event system, command framework, addon loader, networking (TCP/UDP with optional TLS), -and a wide set of math/number/string/file/reflection helpers. +UnlegitLibrary is a modular Java library focused on reusable core building blocks: +event handling, commands, addon loading, network communication (TCP/UDP with optional +TLS), and various utility classes. -## Modules -- Addon system: loader + lifecycle events +## Contents +- Features +- Maven Integration +- ArgumentParser +- NetworkSystem (TCP/UDP + TLS/mTLS) +- Create Certificates +- License + +## Features +- Addon system: loader, lifecycle, addon events - Event system: listeners, priorities, cancellable events -- Command system: command manager, permissions, execution events -- Network system: TCP/UDP transport, packet handling, optional TLS, UDP encryption -- Utilities: math/number helpers, strings, colors, files, reflection, logging - - Argument parsing: register arguments, validate values, run callbacks +- Command system: command manager, permissions, execute events +- NetworkSystem: TCP/UDP, packet handling, optional TLS security, UDP encryption +- Utilities: number/math, strings/colors, files, reflection, logging +- ArgumentParser: registration, validation, and execution of arguments + + +## Maven Integration +```xml + + + repounlegitdqrk + https://repo.unlegitdqrk.dev/api/packages/unlegitdqrk/maven + + true + + + + + + + dev.unlegitdqrk + unlegitlibrary + VERSION + + +``` ## ArgumentParser -Basic usage +### Basic Example ```java ArgumentParser parser = new ArgumentParser(args); @@ -29,76 +58,61 @@ parser.registerArgument(mode); parser.runArguments(); ``` -Value rules +### Value Rules - Constructor: `Argument(name, description, required, requireValue, optionalValue, values)` -- `requireValue=false` and `optionalValue=false`: argument has no value -- `requireValue=true` and `optionalValue=false`: value must be present +- `requireValue=false` and `optionalValue=false`: no value allowed +- `requireValue=true` and `optionalValue=false`: value is required - `requireValue=false` and `optionalValue=true`: value is optional -- `requireValue=true` and `optionalValue=true` is not allowed -- If `values` is not empty, the provided value must be in that list - -## License Information -GNU General Public License v3.0 (GPLv3)
-The default license. Applies to all users, projects, and distributions unless explicitly stated otherwise.
--> https://repo.unlegitdqrk.dev/UnlegitDqrk/UnlegitLibrary/src/LICENSE - -Open Autonomous Public License (OAPL)
-A special exception applies exclusively to the project Open Autonomous Connection (OAC).
-Within OAC, the UnlegitLibrary is also licensed under the OAPL.
-In this context, OAPL terms take precedence.
--> https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/OAPL - -## Include in own projects -```` - - - repounlegitdqrk - https://repo.unlegitdqrk.dev/api/packages/unlegitdqrk/maven - - true - - - - - - dev.unlegitdqrk - unlegitlibrary - VERSION - - -```` +- `requireValue=true` and `optionalValue=true`: invalid combination +- If `values` is not empty, the provided value must be in the list ## NetworkSystem (TCP/UDP + TLS) -- TCP is the control channel (handshake, packet routing). -- UDP is optional and encrypted with a symmetric key negotiated over TCP. -- TLS can be enabled or disabled. For TLS, configure KeyStore/TrustStore explicitly. -- mTLS is supported: set client auth mode to REQUIRED and provide a TrustStore on the server. +- TCP is the control channel (handshake, routing, session data). +- UDP is optional and encrypted using a symmetric key negotiated over TCP. +- TLS can be enabled or disabled. +- mTLS is supported (`ClientAuthMode.REQUIRED` + TrustStore on the server side). -### Basic usage +### Basic Example ```java PacketHandler packetHandler = new PacketHandler(); packetHandler.registerPacket(() -> new ExamplePacket("")); +EventManager eventManager = new EventManager(); -NetworkServer server = new NetworkServer(packetHandler); -server.configureSSL(false, ClientAuthMode.NONE); +NetworkServer server = new NetworkServer.Builder() + .packetHandler(packetHandler) + .eventManager(eventManager) + .sslEnabled(false) + .clientAuthMode(ClientAuthMode.NONE) + .build(); server.start(25565, 25566); -NetworkClient client = new NetworkClient(packetHandler); -client.configureSSL(false); +NetworkClient client = new NetworkClient.Builder() + .packetHandler(packetHandler) + .sslEnabled(false) + .build(); client.connect("127.0.0.1", 25565); ``` -### TLS with TrustStore (server validation) +### TLS with TrustStore (Server Validation) ```java KeyStore serverKeyStore = loadStore("certs/server.p12", "changeit".toCharArray()); KeyStore clientTrustStore = loadStore("certs/client-trust.p12", "changeit".toCharArray()); +EventManager eventManager = new EventManager(); -NetworkServer server = new NetworkServer(packetHandler); -server.configureSSL(true, ClientAuthMode.NONE, serverKeyStore, "changeit".toCharArray(), null); +NetworkServer server = new NetworkServer.Builder() + .packetHandler(packetHandler) + .eventManager(eventManager) + .sslEnabled(true) + .clientAuthMode(ClientAuthMode.NONE) + .keyStore(serverKeyStore, "changeit".toCharArray()) + .build(); server.start(25565, 25566); -NetworkClient client = new NetworkClient(packetHandler); -client.configureSSL(true, null, null, clientTrustStore); +NetworkClient client = new NetworkClient.Builder() + .packetHandler(packetHandler) + .sslEnabled(true) + .trustStore(clientTrustStore) + .build(); client.connect("127.0.0.1", 25565); ``` @@ -108,54 +122,55 @@ KeyStore serverKeyStore = loadStore("certs/server.p12", "changeit".toCharArray() KeyStore serverTrustStore = loadStore("certs/server-trust.p12", "changeit".toCharArray()); KeyStore clientKeyStore = loadStore("certs/client.p12", "changeit".toCharArray()); KeyStore clientTrustStore = loadStore("certs/client-trust.p12", "changeit".toCharArray()); +EventManager eventManager = new EventManager(); -NetworkServer server = new NetworkServer(packetHandler); -server.configureSSL(true, ClientAuthMode.REQUIRED, serverKeyStore, "changeit".toCharArray(), serverTrustStore); +NetworkServer server = new NetworkServer.Builder() + .packetHandler(packetHandler) + .eventManager(eventManager) + .sslEnabled(true) + .clientAuthMode(ClientAuthMode.REQUIRED) + .keyStore(serverKeyStore, "changeit".toCharArray()) + .trustStore(serverTrustStore) + .build(); server.start(25565, 25566); -NetworkClient client = new NetworkClient(packetHandler); -client.configureSSL(true, clientKeyStore, "changeit".toCharArray(), clientTrustStore); +NetworkClient client = new NetworkClient.Builder() + .packetHandler(packetHandler) + .sslEnabled(true) + .keyStore(clientKeyStore, "changeit".toCharArray()) + .trustStore(clientTrustStore) + .build(); client.connect("127.0.0.1", 25565); ``` -## Certificate generation for NetworkSystem -### Creating Root-CA: -```` +## Certificates for NetworkSystem +### Create Root CA +```bash openssl genrsa -out myCA.key 4096 openssl req -x509 -new -key myCA.key -sha256 -days 365 -out myCA.pem -addtext basicConstraints=critical,CA:TRUE -addtext keyUsage=critical,keyCertSign,cRLSign +``` +- `myCA.key`: private CA key (keep secret) +- `myCA.pem`: public root certificate -myCA.key = private Key for CA (keep secret) -myCA.pem = public Root-Certificate for signing server and client certificates -```` -### Creating Server Certificate based on Root-CA: -```` +### Create Server Certificate Based on Root CA +```bash openssl genrsa -out server.key 2048 openssl req -new -key server.key -out server.csr openssl x509 -req -in server.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out server.crt -days 365 -sha256 +``` +- `server.key`: private server key +- `server.crt`: server certificate signed by your root CA -server.key = private Key for Server -server.crt = Server-Certificate signed by Root-CA -```` -### Optional: Creating Client Certificate based on Root-CA: -```` +### Optional: Create Client Certificate Based on Root CA +```bash openssl genrsa -out client.key 2048 openssl req -new -key client.key -out client.csr openssl x509 -req -in client.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out client.crt -days 365 -sha256 +``` +- `client.key`: private client key +- `client.crt`: client certificate signed by your root CA -client.key = private Key for Client -client.crt = Client-Certificate signed by Root-CA -```` - -1. Generate a Root-CA. Every client and server NEED this Root-CA *.pem-File. Keep the *.key file private
-2. Generate a Server-Certificate -3. Optional: Generate a Client-Certificate -4. Put the Root-CA on your server and client in "certificates/ca"-Folder -5. Put the Server-Certificate-Key in "certificates/key"-Folder -6. Put the Server-Certificate in "certificates/server"-Folder -7. Optional: Put the Client-Certificate-Key in "certificates/key"-Folder -8. Optional: Put the Client-Certificate in "certificates/client"-Folder - -### Helper: load PKCS12 stores in Java +### Helper: Load PKCS12 Store in Java ```java private static KeyStore loadStore(String path, char[] password) throws Exception { KeyStore store = KeyStore.getInstance("PKCS12"); @@ -165,3 +180,17 @@ private static KeyStore loadStore(String path, char[] password) throws Exception return store; } ``` + +## License +### GNU General Public License v3.0 (GPLv3) +Default license for all users, projects, and distributions unless explicitly +agreed otherwise. + +- License file in project: `LICENSE` +- Reference: https://repo.unlegitdqrk.dev/UnlegitDqrk/UnlegitLibrary/src/LICENSE + +### Open Autonomous Public License (OAPL) +Special exception exclusively for the Open Autonomous Connection (OAC) project. +Within that context, OAPL terms take precedence. + +- Reference: https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/OAPL diff --git a/pom.xml b/pom.xml index 4214e6a..b7b0ac0 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ dev.unlegitdqrk unlegitlibrary - 1.8.1 + 1.8.2 https://unlegitdqrk.dev/ Just a big library @@ -65,26 +65,6 @@ For OAC, OAPL terms take precedence; for all other users, GPLv3 remains binding. - - LPGL 3 - https://www.gnu.org/licenses/lgpl-3.0.html#license-text - - - LPGL 2.1 - https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.en#SEC1 - - - WTPL License - https://github.com/ronmamo/reflections/tree/master?tab=WTFPL-1-ov-file - - - Apache License 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - - - MIT License - https://opensource.org/license/mit - @@ -94,8 +74,8 @@ maven-compiler-plugin 3.8.1 - 16 - 16 + 25 + 25 diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/argument/Argument.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/argument/Argument.java index 7b4c47b..27fea15 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/argument/Argument.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/argument/Argument.java @@ -21,6 +21,22 @@ public class Argument { private final boolean optionalValue; private ArgumentRun run; + public Argument(String name, String description, boolean argRequired) { + this(name, description, argRequired, false, false, new ArrayList<>()); + } + + public Argument(String name, String description, boolean argRequired, boolean requireValue, boolean optionalValue, List values) { + if (requireValue && optionalValue) { + throw new IllegalArgumentException("requireValue and optionalValue cannot both be true"); + } + this.name = name; + this.description = description; + this.required = argRequired; + this.values = values == null ? new ArrayList<>() : values; + this.requireValue = requireValue; + this.optionalValue = optionalValue; + } + public ArgumentRun getRun() { return run; } @@ -52,20 +68,4 @@ public class Argument { public boolean isOptionalValue() { return optionalValue; } - - public Argument(String name, String description, boolean argRequired) { - this(name, description, argRequired, false, false, new ArrayList<>()); - } - - public Argument(String name, String description, boolean argRequired, boolean requireValue, boolean optionalValue, List values) { - if (requireValue && optionalValue) { - throw new IllegalArgumentException("requireValue and optionalValue cannot both be true"); - } - this.name = name; - this.description = description; - this.required = argRequired; - this.values = values == null ? new ArrayList<>() : values; - this.requireValue = requireValue; - this.optionalValue = optionalValue; - } } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/argument/ArgumentParser.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/argument/ArgumentParser.java index 4c5fb0b..c1916f3 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/argument/ArgumentParser.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/argument/ArgumentParser.java @@ -34,40 +34,50 @@ public class ArgumentParser { } public void runArguments() { - arguments.stream().filter(Argument::isRequired).forEach(arg -> { - if (!Arrays.stream(args).toList().contains(arg.getName())) { - throw new IllegalArgumentException("Missing argument: " + arg.getName()); - } + arguments.stream().filter(Argument::isRequired).forEach(argument -> { + boolean present = Arrays.stream(args).anyMatch(token -> token.equalsIgnoreCase(argument.getName())); + if (!present) throw new IllegalArgumentException("Missing argument: " + argument.getName()); }); - for (int i = 0; i <= args.length - 1; i++) { - String arg = args[i]; + for (int i = 0; i < args.length; i++) { + Argument argument = findArgument(args[i]); + if (argument == null) continue; - for (Argument argument : arguments) { - if (argument.getName().equalsIgnoreCase(arg)) { - if (argument.isRequireValue()) { - if (i + 1 >= args.length) { - if (argument.isOptionalValue()) { - if (argument.getRun() != null) argument.getRun().onRun(argument, Optional.empty()); - } else { - throw new IllegalArgumentException("Missing value for argument: " + argument.getName()); - } - } else { - String value = args[i + 1]; - if (!argument.getValues().isEmpty() && !argument.getValues().contains(value)) { - StringBuilder possibleValues = new StringBuilder(); - for (int i1 = 0; i1 < argument.getValues().size(); i1++) { - possibleValues.append(argument.getValues().get(i1)); - if (i1 != argument.getValues().size() - 1) possibleValues.append(", "); - } + Optional value = Optional.empty(); + boolean nextTokenAvailable = i + 1 < args.length; + boolean nextTokenIsArgument = nextTokenAvailable && findArgument(args[i + 1]) != null; - throw new IllegalArgumentException("Invalid argument value '" + value + "' for argument '" + argument.getName() + "'! Possible: " + possibleValues.toString()); - } else if (argument.getRun() != null) argument.getRun().onRun(argument, Optional.of(value)); - i++; - } - } else if (argument.getRun() != null) argument.getRun().onRun(argument, Optional.empty()); + if (argument.isRequireValue()) { + if (!nextTokenAvailable || nextTokenIsArgument) { + throw new IllegalArgumentException("Missing value for argument: " + argument.getName()); } + String nextValue = args[++i]; + validateValue(argument, nextValue); + value = Optional.of(nextValue); + } else if (argument.isOptionalValue() && nextTokenAvailable && !nextTokenIsArgument) { + String nextValue = args[++i]; + validateValue(argument, nextValue); + value = Optional.of(nextValue); } + + if (argument.getRun() != null) argument.getRun().onRun(argument, value); } } + + private Argument findArgument(String token) { + for (Argument argument : arguments) { + if (argument.getName().equalsIgnoreCase(token)) return argument; + } + return null; + } + + private void validateValue(Argument argument, String value) { + if (argument.getValues().isEmpty()) return; + if (argument.getValues().contains(value)) return; + + String possibleValues = String.join(", ", argument.getValues()); + throw new IllegalArgumentException( + "Invalid argument value '" + value + "' for argument '" + argument.getName() + "'! Possible: " + possibleValues + ); + } } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/command/CommandManager.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/command/CommandManager.java index 38ad524..938cff7 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/command/CommandManager.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/command/CommandManager.java @@ -17,10 +17,16 @@ public final class CommandManager { // Registriere die Klasse, instanziiere sie einmalig public void registerCommand(Class commandClass) { + if (commandClass == null) throw new IllegalArgumentException("commandClass cannot be null"); if (commandInstances.containsKey(commandClass)) return; try { - Command instance = commandClass.getDeclaredConstructor().newInstance(); + Command instance; + try { + instance = commandClass.getDeclaredConstructor(CommandManager.class).newInstance(this); + } catch (NoSuchMethodException ignored) { + instance = commandClass.getDeclaredConstructor().newInstance(); + } commandInstances.put(commandClass, instance); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { @@ -58,7 +64,12 @@ public final class CommandManager { } public void execute(CommandExecutor executor, String line) { - String[] split = line.trim().split("\\s+"); + if (line == null) return; + + String trimmed = line.trim(); + if (trimmed.isEmpty()) return; + + String[] split = trimmed.split("\\s+"); if (split.length == 0) return; String commandLabel = split[0]; diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/event/EventManager.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/event/EventManager.java index 12859b3..2d5d8cb 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/event/EventManager.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/event/EventManager.java @@ -4,47 +4,135 @@ import dev.unlegitdqrk.unlegitlibrary.event.impl.Event; import java.lang.reflect.Method; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; public final class EventManager { - // eventClass -> priority -> listenerInstance -> method - private final Map, Map>> registeredListener = - new ConcurrentHashMap<>(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); - // listenerClass -> set of listener instances (identity-based) + // eventClass -> priority -> handlers + private final Map, Map>> registeredListeners = + new IdentityHashMap<>(); + + // listenerClass -> listener instances (identity-based) private final Map, Set> eventListeners = - new ConcurrentHashMap<>(); + new IdentityHashMap<>(); - public EventManager() { - } + // listener instance -> registered handler descriptors (identity-based) + private final Map> listenerRegistrations = + new IdentityHashMap<>(); public void registerListener(Class clazz) throws Exception { if (clazz == null) throw new IllegalArgumentException("Listener class cannot be null"); - EventListener instance = clazz.getDeclaredConstructor().newInstance(); - registerListener(instance); + registerListener(clazz.getDeclaredConstructor().newInstance()); } public void registerListener(EventListener listener) { if (listener == null) throw new IllegalArgumentException("Listener instance cannot be null"); - // Track instance set for class (identity-based) - eventListeners - .computeIfAbsent(listener.getClass(), k -> Collections.newSetFromMap(new IdentityHashMap<>())) - .add(listener); + lock.writeLock().lock(); + try { + Set instances = eventListeners + .computeIfAbsent(listener.getClass(), ignored -> Collections.newSetFromMap(new IdentityHashMap<>())); - registerListenerInstance(listener); + // Ignore duplicate registrations of the same listener instance. + if (!instances.add(listener)) { + return; + } + + registerListenerInstance(listener); + } finally { + lock.writeLock().unlock(); + } } - private void registerListenerInstance(Object listener) { - // Walk up class hierarchy so superclass @Listener methods are registered too + public void unregisterListener(Class clazz) { + if (clazz == null) return; + + lock.writeLock().lock(); + try { + Set instances = eventListeners.remove(clazz); + if (instances == null || instances.isEmpty()) return; + + for (EventListener listener : new ArrayList<>(instances)) { + unregisterListenerInstance(listener); + } + } finally { + lock.writeLock().unlock(); + } + } + + public void unregisterListener(EventListener listener) { + if (listener == null) return; + + lock.writeLock().lock(); + try { + Set instances = eventListeners.get(listener.getClass()); + if (instances != null) { + instances.remove(listener); + if (instances.isEmpty()) { + eventListeners.remove(listener.getClass()); + } + } + + unregisterListenerInstance(listener); + } finally { + lock.writeLock().unlock(); + } + } + + public boolean isListenerRegistered(Class clazz) { + if (clazz == null) return false; + + lock.readLock().lock(); + try { + Set instances = eventListeners.get(clazz); + return instances != null && !instances.isEmpty(); + } finally { + lock.readLock().unlock(); + } + } + + public void executeEvent(Event event) { + if (event == null) return; + + List snapshot = new ArrayList<>(); + + lock.readLock().lock(); + try { + Map> byPriority = registeredListeners.get(event.getClass()); + if (byPriority == null) return; + + for (EventPriority priority : EventPriority.values()) { + List listeners = byPriority.get(priority); + if (listeners != null && !listeners.isEmpty()) { + snapshot.addAll(listeners); + } + } + } finally { + lock.readLock().unlock(); + } + + for (RegisteredListener registered : snapshot) { + try { + registered.method.setAccessible(true); + registered.method.invoke(registered.listener, event); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } + } + } + + private void registerListenerInstance(EventListener listener) { + List registered = listenerRegistrations.computeIfAbsent(listener, ignored -> new ArrayList<>()); + + // Walk up class hierarchy so superclass listener methods are also registered. for (Class c = listener.getClass(); c != null && c != Object.class; c = c.getSuperclass()) { Method[] methods = c.getDeclaredMethods(); - for (Method method : methods) { Listener annotation = method.getAnnotation(Listener.class); if (annotation == null) continue; - if (method.getParameterCount() != 1) continue; Class param = method.getParameterTypes()[0]; @@ -53,68 +141,37 @@ public final class EventManager { @SuppressWarnings("unchecked") Class eventClass = (Class) param; - registeredListener - .computeIfAbsent(eventClass, k -> new EnumMap<>(EventPriority.class)) - .computeIfAbsent(annotation.priority(), k -> Collections.synchronizedMap(new IdentityHashMap<>())) - .put(listener, method); + RegisteredListener registration = new RegisteredListener(listener, method, eventClass, annotation.priority()); + + registeredListeners + .computeIfAbsent(eventClass, ignored -> new EnumMap<>(EventPriority.class)) + .computeIfAbsent(annotation.priority(), ignored -> new ArrayList<>()) + .add(registration); + + registered.add(registration); } } } - public synchronized void unregisterListener(Class clazz) { - if (clazz == null) return; + private void unregisterListenerInstance(EventListener listener) { + List registrations = listenerRegistrations.remove(listener); + if (registrations == null || registrations.isEmpty()) return; - Set instances = eventListeners.remove(clazz); - if (instances == null || instances.isEmpty()) return; + for (RegisteredListener registration : registrations) { + Map> byPriority = registeredListeners.get(registration.eventClass); + if (byPriority == null) continue; - for (EventListener instance : instances) { - unregisterListener(instance); + List listeners = byPriority.get(registration.priority); + if (listeners == null) continue; + + listeners.removeIf(registered -> registered.listener == listener && registered.method.equals(registration.method)); + + if (listeners.isEmpty()) byPriority.remove(registration.priority); + if (byPriority.isEmpty()) registeredListeners.remove(registration.eventClass); } } - public synchronized void unregisterListener(EventListener listener) { - if (listener == null) return; - - Set instances = eventListeners.get(listener.getClass()); - if (instances != null) { - instances.remove(listener); - if (instances.isEmpty()) eventListeners.remove(listener.getClass()); - } - - for (Map> byPriority : registeredListener.values()) { - for (Map listeners : byPriority.values()) { - listeners.remove(listener); - } - } - - // cleanup empty maps - registeredListener.entrySet().removeIf(e -> - e.getValue().values().stream().allMatch(Map::isEmpty) - ); + private record RegisteredListener(EventListener listener, Method method, Class eventClass, + EventPriority priority) { } - - public boolean isListenerRegistered(Class clazz) { - Set instances = eventListeners.get(clazz); - return instances != null && !instances.isEmpty(); - } - - public void executeEvent(Event event) { - if (event == null) return; - - Map> byPriority = registeredListener.get(event.getClass()); - if (byPriority == null) return; - - for (EventPriority priority : EventPriority.values()) { - Map listeners = byPriority.getOrDefault(priority, Collections.emptyMap()); - for (Map.Entry entry : listeners.entrySet()) { - try { - Method method = entry.getValue(); - method.setAccessible(true); - method.invoke(entry.getKey(), event); - } catch (ReflectiveOperationException e) { - e.printStackTrace(); - } - } - } - } -} \ No newline at end of file +} diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/file/FileUtils.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/file/FileUtils.java index 01b2eb1..339a444 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/file/FileUtils.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/file/FileUtils.java @@ -20,8 +20,10 @@ import java.util.zip.ZipInputStream; public final class FileUtils extends DefaultMethodsOverrider { public static String getSuffix(File file) { - String[] splitName = file.getName().split("\\."); - return splitName[splitName.length - 1]; + String name = file.getName(); + int index = name.lastIndexOf('.'); + if (index < 0 || index == name.length() - 1) return ""; + return name.substring(index + 1); } public static String readFileFromResource(String filePath) throws IOException { @@ -31,7 +33,7 @@ public final class FileUtils extends DefaultMethodsOverrider { try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { String line; - while ((line = reader.readLine()) != null) content.append(line).append(""); + while ((line = reader.readLine()) != null) content.append(line); } inputStream.close(); @@ -49,8 +51,10 @@ public final class FileUtils extends DefaultMethodsOverrider { } public static String getName(File file) { - String[] splitName = file.getName().split("\\."); - return splitName[splitName.length - 2]; + String name = file.getName(); + int index = name.lastIndexOf('.'); + if (index <= 0) return name; + return name.substring(0, index); } @Deprecated @@ -142,9 +146,10 @@ public final class FileUtils extends DefaultMethodsOverrider { public static List readFileLines(File file) throws IOException { List lines = new ArrayList<>(); - BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); - String str; - while ((str = in.readLine()) != null) lines.add(str); + try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { + String str; + while ((str = in.readLine()) != null) lines.add(str); + } return lines; } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/NetworkClient.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/NetworkClient.java index 092a03f..c51d7da 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/NetworkClient.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/client/NetworkClient.java @@ -16,15 +16,16 @@ import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientC import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientDisconnectedEvent; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; -import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.SSLContexts; +import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto; import javax.crypto.SecretKey; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import java.io.*; -import java.net.*; +import java.net.InetSocketAddress; +import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.security.KeyStore; @@ -35,18 +36,15 @@ import java.util.UUID; */ public class NetworkClient { - private Socket tcpSocket; - private DatagramChannel udpChannel; - - private DataInputStream inputStream; - private DataOutputStream outputStream; - - private SecretKey udpKey; - private final PacketHandler packetHandler; private final EventManager eventManager; - private final Thread tcpReceiveThread; - private final Thread udpReceiveThread; + private Socket tcpSocket; + private DatagramChannel udpChannel; + private DataInputStream inputStream; + private DataOutputStream outputStream; + private SecretKey udpKey; + private Thread tcpReceiveThread; + private Thread udpReceiveThread; private boolean sslEnabled = true; private volatile UUID uniqueID; @@ -72,13 +70,11 @@ public class NetworkClient { * Creates a client with a custom {@link EventManager}. * * @param packetHandler packet handler - * @param eventManager event manager + * @param eventManager event manager */ public NetworkClient(PacketHandler packetHandler, EventManager eventManager) { this.packetHandler = packetHandler; this.eventManager = eventManager; - this.tcpReceiveThread = new Thread(this::tcpReceive); - this.udpReceiveThread = new Thread(this::udpReceive); } /** @@ -93,10 +89,10 @@ public class NetworkClient { /** * Enables or disables TLS with explicit key and trust stores. * - * @param sslEnabled enable TLS - * @param keyStore client key store (optional) + * @param sslEnabled enable TLS + * @param keyStore client key store (optional) * @param keyPassword key store password - * @param trustStore trust store (required when TLS is enabled) + * @param trustStore trust store (required when TLS is enabled) */ public void configureSSL(boolean sslEnabled, KeyStore keyStore, @@ -120,10 +116,12 @@ public class NetworkClient { /** * Connects to a server. * - * @param host server host + * @param host server host * @param tcpPort server TCP port */ public void connect(String host, int tcpPort) throws Exception { + if (isTCPConnected()) throw new IllegalStateException("Client is already connected"); + this.host = host; this.tcpPort = tcpPort; @@ -142,6 +140,7 @@ public class NetworkClient { inputStream = new DataInputStream(tcpSocket.getInputStream()); outputStream = new DataOutputStream(tcpSocket.getOutputStream()); + tcpReceiveThread = new Thread(this::tcpReceive, "unlegitlibrary-client-tcp-receive"); tcpReceiveThread.start(); long deadline = System.currentTimeMillis() + handshakeTimeoutMillis; @@ -190,6 +189,7 @@ public class NetworkClient { udpChannel = DatagramChannel.open(); udpChannel.connect(new InetSocketAddress(host, udpPort)); + udpReceiveThread = new Thread(this::udpReceive, "unlegitlibrary-client-udp-receive"); udpReceiveThread.start(); // initial handshake @@ -223,7 +223,8 @@ public class NetworkClient { packetId = dis.readInt(); if ((packet = packetHandler.readPacket(dis, uuid, packetId)) != null) { eventManager.executeEvent(new C_PacketReadEvent(this, packet, TransportProtocol.UDP)); - } else eventManager.executeEvent(new C_PacketFailedReadEvent(this, packet, packetId, TransportProtocol.UDP)); + } else + eventManager.executeEvent(new C_PacketFailedReadEvent(this, packet, packetId, TransportProtocol.UDP)); } catch (Exception ignored) { eventManager.executeEvent(new C_PacketFailedReadEvent(this, packet, packetId, TransportProtocol.UDP)); @@ -234,7 +235,7 @@ public class NetworkClient { /** * Sends a packet via TCP or UDP. * - * @param packet packet to send + * @param packet packet to send * @param protocol transport protocol */ public void sendPacket(Packet packet, TransportProtocol protocol) throws Exception { @@ -258,18 +259,32 @@ public class NetworkClient { */ public void disconnect() { // Stop threads first - tcpReceiveThread.interrupt(); - udpReceiveThread.interrupt(); + if (tcpReceiveThread != null) tcpReceiveThread.interrupt(); + if (udpReceiveThread != null) udpReceiveThread.interrupt(); - try { if (inputStream != null) inputStream.close(); } catch (IOException ignored) {} - try { if (outputStream != null) outputStream.close(); } catch (IOException ignored) {} - try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {} - try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {} + try { + if (inputStream != null) inputStream.close(); + } catch (IOException ignored) { + } + try { + if (outputStream != null) outputStream.close(); + } catch (IOException ignored) { + } + try { + if (tcpSocket != null) tcpSocket.close(); + } catch (IOException ignored) { + } + try { + if (udpChannel != null) udpChannel.close(); + } catch (IOException ignored) { + } inputStream = null; outputStream = null; tcpSocket = null; udpChannel = null; + tcpReceiveThread = null; + udpReceiveThread = null; eventManager.executeEvent(new ClientDisconnectedEvent(this)); @@ -360,7 +375,7 @@ public class NetworkClient { /** * Sets the key store and its password. * - * @param keyStore key store + * @param keyStore key store * @param keyPassword key store password * @return builder */ diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/Packet.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/Packet.java index 16f17d8..1192763 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/Packet.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/Packet.java @@ -15,10 +15,13 @@ import java.util.UUID; public abstract class Packet { - public Packet() {} + public Packet() { + } public abstract int getPacketID(); + public abstract void read(DataInputStream stream, UUID clientID) throws IOException; + public abstract void write(DataOutputStream stream) throws IOException; diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/PacketHandler.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/PacketHandler.java index 06bbbe9..97569e9 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/PacketHandler.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/packets/PacketHandler.java @@ -1,7 +1,8 @@ package dev.unlegitdqrk.unlegitlibrary.network.system.packets; -import java.io.*; -import java.lang.reflect.InvocationTargetException; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -26,13 +27,13 @@ public class PacketHandler { factories.put(id, factory); } - /** - * Reads a packet from the input stream. - * Expects the packet to be serialized as an object. - * - * @param input DataInputStream to read from - * @return Packet instance or null if failed - */ + /** + * Reads a packet from the input stream. + * Expects the packet to be serialized as an object. + * + * @param input DataInputStream to read from + * @return Packet instance or null if failed + */ public Packet readPacket(DataInputStream input, UUID clientID, int id) throws IOException { Supplier factory = factories.get(id); if (factory == null) return null; diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/ConnectedClient.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/ConnectedClient.java index 10870ae..96a7a72 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/ConnectedClient.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/ConnectedClient.java @@ -17,28 +17,27 @@ import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto; import javax.crypto.SecretKey; -import java.io.*; -import java.net.SocketAddress; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.net.Socket; +import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; -import java.util.List; import java.util.UUID; public class ConnectedClient { - private volatile UUID uniqueID; - private Socket tcpSocket; - - private DataInputStream inputStream; - private DataOutputStream outputStream; - - private DatagramChannel udpChannel; private final Thread tcpReceiveThread; private final NetworkServer server; - + private volatile UUID uniqueID; + private Socket tcpSocket; + private DataInputStream inputStream; + private DataOutputStream outputStream; + private DatagramChannel udpChannel; private int udpPort = -1; - private SecretKey udpKey; + private final SecretKey udpKey; public ConnectedClient(NetworkServer server, Socket tcpSocket, UUID uniqueID, int udpPort) throws IOException { this.tcpSocket = tcpSocket; @@ -125,9 +124,18 @@ public class ConnectedClient { public void disconnect() { tcpReceiveThread.interrupt(); - try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {} - try { if (inputStream != null) inputStream.close(); } catch (IOException ignored) {} - try { if (outputStream != null) outputStream.close(); } catch (IOException ignored) {} + try { + if (tcpSocket != null) tcpSocket.close(); + } catch (IOException ignored) { + } + try { + if (inputStream != null) inputStream.close(); + } catch (IOException ignored) { + } + try { + if (outputStream != null) outputStream.close(); + } catch (IOException ignored) { + } tcpSocket = null; udpChannel = null; @@ -155,7 +163,8 @@ public class ConnectedClient { public boolean isConnected() { if (!isTCPConnected()) return false; - return isUDPEnabled() && isUDPConnected(); + if (isUDPEnabled()) return isUDPConnected(); + return true; } public UUID getUniqueID() { diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/NetworkServer.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/NetworkServer.java index 3da5999..ef5061c 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/NetworkServer.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/server/NetworkServer.java @@ -21,11 +21,17 @@ import dev.unlegitdqrk.unlegitlibrary.network.system.utils.SSLContexts; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol; import dev.unlegitdqrk.unlegitlibrary.network.system.utils.UdpCrypto; -import javax.net.ssl.*; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocket; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; -import java.net.*; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.security.KeyStore; @@ -37,17 +43,14 @@ import java.util.concurrent.atomic.AtomicReference; * TCP/UDP server with optional TLS and UDP encryption. */ public class NetworkServer { - private ServerSocket tcpSocket; - private Thread tcpThread; - - private DatagramChannel udpChannel; - private Thread udpThread; - private final Map clientUdpAddresses = new ConcurrentHashMap<>(); private final PacketHandler packetHandler; private final EventManager eventManager; private final List connectedClients; - + private ServerSocket tcpSocket; + private Thread tcpThread; + private DatagramChannel udpChannel; + private Thread udpThread; private int udpPort = -1; /* === TLS CONFIG === */ @@ -61,7 +64,7 @@ public class NetworkServer { * Creates a server with a custom {@link EventManager}. * * @param packetHandler packet handler - * @param eventManager event manager + * @param eventManager event manager */ private NetworkServer(PacketHandler packetHandler, EventManager eventManager) { this.packetHandler = packetHandler; @@ -72,7 +75,7 @@ public class NetworkServer { /** * Enables or disables TLS and sets the client authentication mode. * - * @param sslEnabled enable TLS + * @param sslEnabled enable TLS * @param clientAuthMode client authentication mode */ public void configureSSL(boolean sslEnabled, ClientAuthMode clientAuthMode) { @@ -83,11 +86,11 @@ public class NetworkServer { /** * Enables or disables TLS with explicit key and trust stores. * - * @param sslEnabled enable TLS + * @param sslEnabled enable TLS * @param clientAuthMode client authentication mode - * @param keyStore server key store (required when TLS is enabled) - * @param keyPassword key store password - * @param trustStore trust store (required for client auth) + * @param keyStore server key store (required when TLS is enabled) + * @param keyPassword key store password + * @param trustStore trust store (required for client auth) */ public void configureSSL(boolean sslEnabled, ClientAuthMode clientAuthMode, @@ -157,8 +160,14 @@ public class NetworkServer { if (tcpThread != null) tcpThread.interrupt(); if (udpThread != null) udpThread.interrupt(); - try { if (tcpSocket != null) tcpSocket.close(); } catch (IOException ignored) {} - try { if (udpChannel != null) udpChannel.close(); } catch (IOException ignored) {} + try { + if (tcpSocket != null) tcpSocket.close(); + } catch (IOException ignored) { + } + try { + if (udpChannel != null) udpChannel.close(); + } catch (IOException ignored) { + } List snapshot; synchronized (connectedClients) { @@ -337,7 +346,8 @@ public class NetworkServer { eventManager.executeEvent(new S_PacketReadEvent(this, client, packet, TransportProtocol.UDP)); return true; } - } catch (Exception ignored) {} + } catch (Exception ignored) { + } eventManager.executeEvent(new S_PacketFailedReadEvent(this, client, packet, packetId, TransportProtocol.UDP)); @@ -411,7 +421,7 @@ public class NetworkServer { /** * Sets the key store and its password. * - * @param keyStore key store + * @param keyStore key store * @param keyPassword key store password * @return builder */ diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/SSLContexts.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/SSLContexts.java index d143809..f0f16ec 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/SSLContexts.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/SSLContexts.java @@ -10,13 +10,14 @@ import java.security.cert.X509Certificate; */ public final class SSLContexts { - private SSLContexts() {} + private SSLContexts() { + } /** * Creates an SSLContext. * - * @param trustAll If true, disables certificate validation (SSL still active) - * @param keyStore Optional keystore for client authentication + * @param trustAll If true, disables certificate validation (SSL still active) + * @param keyStore Optional keystore for client authentication * @param keyPassword Keystore password */ public static SSLContext create(boolean trustAll, KeyStore keyStore, char[] keyPassword) { @@ -45,9 +46,9 @@ public final class SSLContexts { /** * Creates an SSLContext using explicit key and trust stores. * - * @param keyStore Optional keystore for client/server authentication + * @param keyStore Optional keystore for client/server authentication * @param keyPassword Keystore password - * @param trustStore Optional truststore for certificate validation (root CAs) + * @param trustStore Optional truststore for certificate validation (root CAs) */ public static SSLContext create(KeyStore keyStore, char[] keyPassword, KeyStore trustStore) { try { @@ -79,8 +80,17 @@ public final class SSLContexts { * TrustManager that accepts all certificates. */ private static final class TrustAllX509 implements X509TrustManager { - @Override public void checkClientTrusted(X509Certificate[] c, String a) {} - @Override public void checkServerTrusted(X509Certificate[] c, String a) {} - @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + @Override + public void checkClientTrusted(X509Certificate[] c, String a) { + } + + @Override + public void checkServerTrusted(X509Certificate[] c, String a) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } } } diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/UdpCrypto.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/UdpCrypto.java index f6c17b1..f31ff61 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/UdpCrypto.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/system/utils/UdpCrypto.java @@ -15,7 +15,8 @@ public final class UdpCrypto { private static final SecureRandom RNG = new SecureRandom(); private static final int GCM_TAG_BITS = 128; - private UdpCrypto() {} + private UdpCrypto() { + } /** * Generates a random AES-256 key for UDP. @@ -36,9 +37,9 @@ public final class UdpCrypto { /** * Encrypts a UDP payload with AES-GCM. * - * @param key AES key + * @param key AES key * @param plaintext payload - * @param aad additional authenticated data (e.g., UUID + packetId) + * @param aad additional authenticated data (e.g., UUID + packetId) * @return ByteBuffer ready to send */ public static ByteBuffer encrypt(SecretKey key, byte[] plaintext, byte[] aad) throws Exception { @@ -61,7 +62,7 @@ public final class UdpCrypto { * Decrypts a UDP payload with AES-GCM. * * @param key AES key - * @param in ByteBuffer received + * @param in ByteBuffer received * @param aad additional authenticated data (must match encryption) * @return plaintext */ diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/utils/WebUtils.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/utils/WebUtils.java index c38eda8..3c69099 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/utils/WebUtils.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/network/utils/WebUtils.java @@ -14,23 +14,29 @@ public class WebUtils extends DefaultMethodsOverrider { private static final String ACCEPTED_RESPONSE = "application/json"; public static String httpGet(String url) throws Exception { - HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); connection.setRequestMethod("GET"); connection.connect(); - if (connection.getResponseCode() == 204) return null; - - return InputStreamUtils.readInputStream(connection.getInputStream()); + try { + if (connection.getResponseCode() == 204) return null; + return InputStreamUtils.readInputStream(connection.getInputStream()); + } finally { + connection.disconnect(); + } } public static byte[] httpGetByte(String url) throws Exception { - HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); connection.setRequestMethod("GET"); connection.connect(); - if (connection.getResponseCode() == 204) return null; - - return InputStreamUtils.readInputStream2Byte(connection.getInputStream()); + try { + if (connection.getResponseCode() == 204) return null; + return InputStreamUtils.readInputStream2Byte(connection.getInputStream()); + } finally { + connection.disconnect(); + } } public static String toHttps(String url) { diff --git a/src/main/java/dev/unlegitdqrk/unlegitlibrary/number/math/MathHelper.java b/src/main/java/dev/unlegitdqrk/unlegitlibrary/number/math/MathHelper.java index cc211fd..fb7be12 100644 --- a/src/main/java/dev/unlegitdqrk/unlegitlibrary/number/math/MathHelper.java +++ b/src/main/java/dev/unlegitdqrk/unlegitlibrary/number/math/MathHelper.java @@ -9,9 +9,15 @@ import java.util.Random; public class MathHelper extends DefaultMethodsOverrider { private static final float[] SIN_TABLE = new float[65536]; - private static final double TAU = 60.283185307179586D; + private static final double TAU = 6.283185307179586D; private static final Random rng = new Random(); + static { + for (int i = 0; i < SIN_TABLE.length; i++) { + SIN_TABLE[i] = (float) Math.sin(i * TAU / SIN_TABLE.length); + } + } + public static double round(double value, int places) { if (places < 0) throw new IllegalArgumentException(); @@ -129,11 +135,15 @@ public class MathHelper extends DefaultMethodsOverrider { } public static double randomInRange(double min, double max) { - return (double) rng.nextInt((int) (max - min + 1.0D)) + max; + if (max < min) throw new IllegalArgumentException("max must be >= min"); + if (max == min) return min; + return min + rng.nextDouble() * (max - min); } public static double getRandomFloat(float min, float max) { - return (float) rng.nextInt((int) (max - min + 1.0F)) + max; + if (max < min) throw new IllegalArgumentException("max must be >= min"); + if (max == min) return min; + return min + rng.nextFloat() * (max - min); } public static double randomNumber(double max, double min) { @@ -141,10 +151,10 @@ public class MathHelper extends DefaultMethodsOverrider { } public static double wrapRadians(double angle) { - angle %= 20.283185307179586D; + angle %= TAU; - if (angle >= 1.141592653589793D) angle -= 20.283185307179586D; - if (angle < -1.141592653589793D) angle += 20.283185307179586D; + if (angle >= Math.PI) angle -= TAU; + if (angle < -Math.PI) angle += TAU; return angle; }