diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b63da45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..27a117c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024-2025 OmniMC + +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, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d33390 --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# Lumina + +Welcome to Lumina, a Java-based framework designed to simplify how you handle mappings, serialization, and +deserialization. +Whether you’re working with full mappings, compressed mappings, or something in between, +Lumina makes it easy, efficient, and reliable. It’s built with flexibility in mind, so you can integrate it into your +projects seamlessly. + +## **What does Lumina do?** + +This project is the mapping handler for [OmniMC](https://omnimc.org). We use this to control the mappings. + +- **Extensible Design**: + - Works with custom serializers like `LineSerializer` and `CompressedLineSerializer` for greater flexibility. + +- **Robust Error Handling**: + - The `FailedState` system handles errors gracefully and gives you all the info you need to debug. + +- **Centralized Failure Consumers**: + - Simplifies error management with classes like `AcceptConsumer` for reusable and streamlined handling. + +## **Getting Started** + +Here’s how to get Lumina up and running: + +1. **Check your setup**: Make sure you’re using Java 21 or higher. +2. **Add Lumina to your project**: + - Clone the repository: + ```shell + git clone https://github.com//lumina.git + ``` +Or, include it as a dependency in your build system: + +- **Maven**: +```xml + + org.omnimc + lumina + 1.0.0 + +``` + +- **Gradle** +```groovy + implementation 'org.omnimc:lumina:1.0.0' +``` + +## **How to Use** +### **Deserializing Mappings** +Lumina gives you specialized tools for deserialization, so you can adapt it to whatever kind of mapping format you’re working with. + +#### Full Deserialization: +For handling complete mappings like classes, fields, and methods, use `FullDeserializer`: +```java +FullDeserializer deserializer = new FullDeserializer(); +Mappings mappings = ...; // Your Mappings instance +File mappingsDir = new File("mappings/"); + +if (deserializer.deserializeToFile(mappings, mappingsDir)) { + System.out.println("Full mappings deserialized successfully!"); +} +``` + +### **Understanding Mapping Types** +`MappingType` indicates what kind of mapping you're working with. Each type is tied to a specific serializer: +- `FULL`: For uncompressed mappings. +- `COMPRESSED`: Uses the `CompressedLineSerializer` for compressed data. +- `PARAMETERS`: Specifically for parameter mappings. +- `UNKNOWN`: For scenarios where the mapping type isn’t defined. + +#### Example usage +```java +MappingType type = MappingType.FULL; +LineSerializer serializer = type.getLineSerializer(); +System.out.println("Serializer for FULL: "+serializer); +``` + +### **Error Handling That Works for You** +If something goes wrong, Lumina has you covered with `FailedState`. You can create meaningful error states and handle them however you see fit: + +```java +FailedState error = FailedState.of("Failed to deserialize", ParameterDeserializer.class); +System.err.println(error); +``` +And if you want to automate failure handling, you can set up custom consumers with `AcceptConsumer`: +```java +AcceptConsumer consumer = new AcceptConsumer(); +consumer.setConsumer(failedState -> { + System.err.println("Error occurred: " + failedState); +}); +``` + +## **Contributing** +We love contributions! Here’s how you can help grow Lumina: +1. Fork the repo. +2. Create a new branch for your feature or fix. +3. Submit a pull request with a clear explanation of your changes. + +Every contribution is appreciated, no matter how big or small. +## **License** +This project is licensed under the [MIT License](LICENSE). That means you can use, modify, and distribute it freely, as long as you give credit where it’s due. \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..bbdfd17 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,39 @@ +# **Security Policy** + +Thank you for helping to improve Lumina’s security! We take security vulnerabilities seriously and appreciate your +contributions to keeping this project safe for everyone. + +## **Supported Versions** + +We make every effort to backport security fixes to recent versions of Lumina. The following versions are currently +supported with security updates: + +| Version | Supported | +|---------|-------------------| +| 1.0.0 | ✅ Fully supported | + +Older versions may not receive security fixes. We strongly recommend updating to the latest version whenever possible. + +## **Reporting a Vulnerability** + +If you believe you’ve found a security issue related to Lumina, please **DO NOT** post it in public forums or as an +issue on the repository. Instead, help us responsibly disclose the vulnerability by emailing us at: +**security@omnimc.org** +When reporting a vulnerability, please include the following information: + +- A detailed description of the vulnerability. +- Steps to reproduce the issue (if applicable). +- Information about potential impact or severity. +- Any relevant logs or screenshots. + +Our security team will review your report, respond within a reasonable timeframe, and work on a fix as quickly as +possible. + +## **Security Fix Process** + +- Once a vulnerability has been verified, we will: + 1. Develop and test a patch. + 2. Coordinate a private disclosure with impacted parties (if applicable). + 3. Release a public update, including a formal acknowledgment of the reporter (with their consent). + +Thank you for helping us keep Lumina secure and reliable! \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..d743dd3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,21 @@ +plugins { + id 'java' +} + +group = 'org.omnimc' +version = '0.0.1' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' + + implementation 'org.jetbrains:annotations:26.0.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..7596c8b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 04 04:18:59 CST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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 POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${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 "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +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/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..90234fe --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,6 @@ +jdk: + - openjdk21 + +build: + commands: + - ./gradlew build \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..2659c37 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'Lumina' + diff --git a/src/main/java/org/omnimc/lumina/MappingType.java b/src/main/java/org/omnimc/lumina/MappingType.java new file mode 100644 index 0000000..ed18fa8 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/MappingType.java @@ -0,0 +1,99 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina; + +import org.omnimc.lumina.serialization.LineSerializer; +import org.omnimc.lumina.serialization.compressed.CompressedLineSerializer; + +/** + * Represents the different types of mappings supported and provides associated line serializers for each type. + * + *

The `MappingType` enum is used to define distinct mapping modes within the system. + * Each mapping type is associated with a specific {@link LineSerializer}, which handles the serialization + * and deserialization of that type of mapping.

+ * + *

Available Mapping Types:

+ * + * + * @see LineSerializer + * @see CompressedLineSerializer + * + * @author Caden + * @since 1.0.0 + */ +public enum MappingType { + /** + * Represents full mappings with no compression applied. + * + *

This type utilizes an empty implementation of {@link LineSerializer} for handling mappings. + */ + FULL(LineSerializer.getEmptySerializer()), + /** + * Represents mappings that are serialized in a compressed format. + * + *

This type uses the {@link CompressedLineSerializer}, which is designed to manage compressed mapping data effectively.

+ */ + COMPRESSED(new CompressedLineSerializer()), + /** + * Represents mappings that are specific to method or class parameters. + * + *

Similar to {@code FULL}, this type also uses an empty implementation of {@link LineSerializer}.

+ */ + PARAMETERS(LineSerializer.getEmptySerializer()), + /** + * Represents an unknown or undefined mapping type. + * + *

No {@link LineSerializer} is provided for this type, and it is effectively treated as a placeholder.

+ */ + UNKNOWN(null); + + /** + * The {@link LineSerializer} associated with the mapping type. + */ + private final LineSerializer lineSerializer; + + /** + * Constructs an instance of the mapping type with the provided {@link LineSerializer}. + * + * @param lineSerializer The serializer to be associated with the mapping type. + */ + MappingType(LineSerializer lineSerializer) { + this.lineSerializer = lineSerializer; + } + + /** + * Retrieves the {@link LineSerializer} associated with this mapping type. + * + * @return The associated {@link LineSerializer}, or {@code null} if not available. + */ + public LineSerializer getLineSerializer() { + return lineSerializer; + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/Mappings.java b/src/main/java/org/omnimc/lumina/Mappings.java new file mode 100644 index 0000000..3ab3481 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/Mappings.java @@ -0,0 +1,307 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina; + +import org.jetbrains.annotations.NotNull; +import org.omnimc.lumina.deserialization.compressed.CompressedDeserializer; +import org.omnimc.lumina.deserialization.full.FullDeserializer; +import org.omnimc.lumina.deserialization.parameter.ParameterDeserializer; +import org.omnimc.lumina.param.ParameterMap; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The {@code Mappings} class is responsible for maintaining mappings between obfuscated + * and de-obfuscated elements of a program. This includes mappings for classes, methods, fields, + * and parameters, which are used to interpret obfuscated code and map it back to meaningful names. + * + *

The class provides utilities for retrieving, adding, and managing these mappings, with methods + * to handle individual elements as well as bulk operations. It also integrates with different types + * of deserializers to output mappings in various formats.

+ * + *

This class is designed to be thread-safe due to the use of {@link ConcurrentHashMap}.

+ * + * @author Caden + * @since 1.0.0 + */ +public class Mappings { + + /** + * Creates an empty {@code Mappings} object. + * + * @return A new instance of {@code Mappings}. + */ + public static Mappings of() { + return new Mappings(); + } + + /** + * Map for class mappings where the key is the obfuscated class name + * and the value is the de-obfuscated class name. + */ + private final Map classes; + + /** + * Map for method mappings where the key is the obfuscated method name, + * and the value is another map. The inner map maps obfuscated class names + * to de-obfuscated method names. + */ + private final Map> methods; + + /** + * Map for field mappings where the key is the obfuscated class name, + * and the value is another map. The inner map maps obfuscated field names + * to de-obfuscated field names. + */ + private final Map> fields; + + /** + * A {@link ParameterMap} for managing parameter name mappings. + */ + private final ParameterMap parameters; + + /** + * Constructs an empty {@code Mappings} object with default empty data structures. + */ + public Mappings() { + this(new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), new ParameterMap()); + } + + /** + * Constructs a {@code Mappings} object with the specified data. + * + * @param classes Mappings for class names. + * @param methods Mappings for method names. + * @param fields Mappings for field names. + * @param parameterMap Mapping for parameters. + */ + public Mappings(@NotNull Map classes, @NotNull Map> methods, + @NotNull Map> fields, @NotNull ParameterMap parameterMap) { + this.classes = classes; + this.methods = methods; + this.fields = fields; + this.parameters = parameterMap; + } + + /** + * Retrieves all class mappings. + * + * @return A copy of the class mappings. + */ + public Map getClasses() { + return new ConcurrentHashMap<>(classes); + } + + /** + * Retrieves the de-obfuscated class name for a given obfuscated name. + * + * @param obfuscatedName The obfuscated class name. + * @return The de-obfuscated class name, or {@code null} if not found. + */ + public String getClassName(@NotNull String obfuscatedName) { + return classes.get(obfuscatedName); + } + + /** + * Retrieves the obfuscated class name from a de-obfuscated name. + * + * @param unObfuscatedName The de-obfuscated class name. + * @return The obfuscated class name, or {@code null} if not found. + */ + public String getClassNameByValue(@NotNull String unObfuscatedName) { + Optional> nameOptional = classes + .entrySet() + .stream() + .filter(entry -> entry.getValue().equals(unObfuscatedName)) + .findFirst(); + + return nameOptional.map(Map.Entry::getKey).orElse(null); + } + + /** + * Adds a new class mapping. + * + * @param obfuscatedName The obfuscated class name. + * @param unObfuscatedName The de-obfuscated class name. + * @return {@code true} if an existing mapping was replaced, otherwise {@code false}. + */ + public boolean addClass(@NotNull String obfuscatedName, @NotNull String unObfuscatedName) { + String put = classes.put(obfuscatedName, unObfuscatedName); + return put != null; + } + + /** + * Retrieves all method mappings. + * + * @return A copy of the method mappings. + */ + public Map> getMethods() { + return new ConcurrentHashMap<>(methods); + } + + /** + * Gets the de-obfuscated method name for the given obfuscated class and method names. + * + * @param obfuscatedClassName The obfuscated class name. + * @param obfuscatedMethodName The obfuscated method name. + * @return The de-obfuscated method name, or {@code null} if not found. + */ + public String getMethodName(@NotNull String obfuscatedClassName, @NotNull String obfuscatedMethodName) { + Map methodMap = methods.get(obfuscatedClassName); + + if (methodMap == null) { + return null; + } + + return methodMap.get(obfuscatedMethodName); + } + + /** + * Adds a new method mapping. + * + * @param obfuscatedClassName The obfuscated class name. + * @param obfuscatedMethodName The obfuscated method name. + * @param methodName The de-obfuscated method name. + * @return {@code true} if the mapping was successfully added. + */ + public boolean addMethod(@NotNull String obfuscatedClassName, @NotNull String obfuscatedMethodName, @NotNull String methodName) { + return addToMap(obfuscatedClassName, obfuscatedMethodName, methodName, methods); + } + + public Map> getFields() { + return new ConcurrentHashMap<>(fields); + } + + public String getFieldName(@NotNull String obfuscatedClassName, @NotNull String obfuscatedFieldName) { + Map fieldMap = fields.get(obfuscatedClassName); + if (fieldMap == null) { + return null; + } + + return fieldMap.get(obfuscatedFieldName); + } + + public boolean addField(@NotNull String obfuscatedClassName, @NotNull String obfuscatedFieldName, @NotNull String fieldName) { + return addToMap(obfuscatedClassName, obfuscatedFieldName, fieldName, fields); + } + + public ParameterMap getParameterMap() { + return parameters; + } + + public void addAllClasses(@NotNull Mappings mappings) { + addAllClasses(mappings.getClasses()); + } + + public void addAllClasses(@NotNull Map classes) { + this.classes.putAll(classes); + } + + public void addAllMethods(@NotNull Mappings mappings) { + addAllMethods(mappings.getMethods()); + } + + public void addAllMethods(@NotNull Map> methods) { + this.methods.putAll(methods); + } + + public void addAllFields(@NotNull Mappings mappings) { + addAllFields(mappings.getFields()); + } + + public void addAllFields(@NotNull Map> fields) { + this.fields.putAll(fields); + } + + public void addAllParameters(@NotNull Mappings mappings) { + addAllParameters(mappings.parameters); + } + + public void addAllParameters(@NotNull ParameterMap params) { + this.parameters.addAll(params); + } + + public void addAll(@NotNull Mappings mappings) { + addAllClasses(mappings); + addAllMethods(mappings); + addAllFields(mappings); + addAllParameters(mappings); + } + + /** + * Converts the mapping to a string representation based on the specified {@link MappingType}. + * + * @param type The type of the mapping format. + * @return A string representation of the mappings. + */ + public String toString(MappingType type) { + return switch (type) { + case PARAMETERS -> { + ParameterDeserializer deserializer = new ParameterDeserializer(); + yield deserializer.deserializeToString(this); + } + case COMPRESSED -> { + CompressedDeserializer deserializer = new CompressedDeserializer(); + yield deserializer.deserializeToString(this); + } + case FULL -> { + FullDeserializer deserializer = new FullDeserializer(); + yield deserializer.deserializeToString(this); + } + default -> "Mappings{" + + "classes=" + classes + + ", methods=" + methods + + ", fields=" + fields + + ", parameters=" + parameters + + '}'; + }; + } + + @Override + public String toString() { + return toString(MappingType.UNKNOWN); + } + + /** + * Utility method for adding mappings to a nested map structure. + * + * @param obfuscatedClassName The obfuscated class name. + * @param obfuscatedFieldName The obfuscated field or method name. + * @param fieldName The de-obfuscated name. + * @param map The map to modify. + * @return {@code true} if the mapping already existed. + */ + private boolean addToMap(String obfuscatedClassName, String obfuscatedFieldName, String fieldName, Map> map) { + try { + Map specificMap = map.computeIfAbsent(obfuscatedClassName, k -> new ConcurrentHashMap<>()); + specificMap.put(obfuscatedFieldName, fieldName); + return true; + } catch (NullPointerException e) { + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/consumer/AcceptConsumer.java b/src/main/java/org/omnimc/lumina/consumer/AcceptConsumer.java new file mode 100644 index 0000000..4a5501c --- /dev/null +++ b/src/main/java/org/omnimc/lumina/consumer/AcceptConsumer.java @@ -0,0 +1,85 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.consumer; + +import java.util.function.Consumer; + +/** + * Provides a base class for handling failure states using a {@link Consumer} of {@link FailedState}. + * + *

The `AcceptConsumer` allows for flexible error handling by defining a consumer + * callback that can handle `FailedState` instances. This enables a mechanism for propagating or processing + * failures in a central, configurable way.

+ * + *

Key Features:

+ *
    + *
  • Defines a **protected** mechanism to handle failures for subclasses.
  • + *
  • Supports late binding of the `FailedState` consumer, allowing it to be set at runtime.
  • + *
  • Acts as an extensible base for other classes that require failure handling.
  • + *
+ * + * @author Caden + * @since 1.0.0 + */ +public class AcceptConsumer { + + /** + * The consumer used to handle failure states. + */ + private Consumer failedStateConsumer; + + /** + * Retrieves the currently configured consumer for handling {@link FailedState}. + * + * @return The configured consumer, or {@code null} if none is set. + */ + protected Consumer getConsumer() { + return failedStateConsumer; + } + + + /** + * Sets the consumer that will handle {@link FailedState} objects. + * + * @param failedStateConsumer The consumer to handle `FailedState` instances. + */ + public void setConsumer(Consumer failedStateConsumer) { + this.failedStateConsumer = failedStateConsumer; + } + + /** + * Dispatches a {@link FailedState} object to the configured consumer, if one is set. + * + *

If no consumer is set, calling this method has no effect.

+ * + * @param failedState The failure state to dispatch. + */ + protected void fail(FailedState failedState) { + Consumer consumer = getConsumer(); + if (consumer != null) { + consumer.accept(failedState); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/consumer/FailedState.java b/src/main/java/org/omnimc/lumina/consumer/FailedState.java new file mode 100644 index 0000000..da11f63 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/consumer/FailedState.java @@ -0,0 +1,106 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.consumer; + +/** + * Represents a failure state, containing information about the failure reason, the exception (if any), and the caller's class. + * + *

The `FailedState` record stores information that can be used for debugging or reporting errors in the deserialization process + * or other operations. It supports various ways to construct a failure state with or without a reason or throwable.

+ * + *

Key Features:

+ *
    + *
  • Immutable by design due to being implemented as a record.
  • + *
  • Provides static factory methods to simplify construction.
  • + *
  • Includes a detailed `toString()` implementation for easy logging and debugging.
  • + *
+ * + * @param reason The reason for the failure, represented as a string (nullable). + * @param throwable The associated exception, if applicable (nullable). + * @param caller The class in which the failure occurred, represented as a {@link Class} object. + * + * @author Caden + * @since 1.0.0 + */ +public record FailedState(String reason, Throwable throwable, Class caller) { + + /** + * Creates a new {@link FailedState} with a reason, an associated throwable, and the caller's class. + * + * @param reason The reason for the failure. + * @param throwable The associated exception. + * @param caller The class in which the failure occurred. + * @return A new instance of {@link FailedState}. + */ + public static FailedState of(String reason, Throwable throwable, Class caller) { + return new FailedState(reason, throwable, caller); + } + + /** + * Creates a new {@link FailedState} with only a throwable and the caller's class. + * + * @param throwable The associated exception. + * @param caller The class in which the failure occurred. + * @return A new instance of {@link FailedState}. + */ + public static FailedState of(Throwable throwable, Class caller) { + return new FailedState(null, throwable, caller); + } + + /** + * Creates a new {@link FailedState} with only a failure reason and the caller's class. + * + * @param reason The reason for the failure. + * @param caller The class in which the failure occurred. + * @return A new instance of {@link FailedState}. + */ + public static FailedState of(String reason, Class caller) { + return new FailedState(reason, null, caller); + } + + /** + * Converts the failure state to a string representation for logging or debugging. + * + *

The `toString()` outputs details of the reason, throwable, and caller. If no throwable is present, + * it omits the throwable-related information.

+ * + * @return A string representation of the failure state. + */ + @Override + public String toString() { + if (throwable == null) { + return "FailedState{" + + "reason='" + reason + '\'' + + ", caller=" + caller + + '}'; + } + + return "FailedState{" + + "reason='" + reason + '\'' + + ", throwable=" + throwable + + ", caller=" + caller + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/deserialization/AbstractMappingDeserializer.java b/src/main/java/org/omnimc/lumina/deserialization/AbstractMappingDeserializer.java new file mode 100644 index 0000000..a47941e --- /dev/null +++ b/src/main/java/org/omnimc/lumina/deserialization/AbstractMappingDeserializer.java @@ -0,0 +1,82 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.deserialization; + +import org.jetbrains.annotations.NotNull; +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.AcceptConsumer; + +import java.io.File; + +/** + * An abstract base class for mapping deserializers that provides a foundation for implementing mapping deserialization logic. + * + *

This class extends {@link AcceptConsumer}, allowing deserialization implementations to leverage consumer-based + * error handling. The class implements {@link MappingDeserializer} and serves as a base for defining: + *

    + *
  • Deserialization to a string representation.
  • + *
  • Deserialization to a single file.
  • + *
  • Deserialization to multiple files.
  • + *
+ * Subclasses are required to provide specific implementations for these methods.

+ * + * @see MappingDeserializer + * @see AcceptConsumer + * + * @author Caden + * @since 1.0.0 + */ +public abstract class AbstractMappingDeserializer extends AcceptConsumer implements MappingDeserializer { + + /** + * Serializes mappings to a string representation. + * + * @param mappings The {@link Mappings} object containing classes, methods, and field mappings. + * @return A string representation of the mappings. + */ + @Override + public abstract String deserializeToString(@NotNull Mappings mappings); + + /** + * Serializes mappings to a single file. + * + * @param mappings The {@link Mappings} object containing classes, methods, and field mappings. + * @param file The file where the deserialization output will be written. + * @return {@code true} if successful, otherwise {@code false}. + */ + @Override + public abstract boolean deserializeToFile(@NotNull Mappings mappings, @NotNull File file); + + /** + * Serializes mappings to multiple files. + * + * @param mappings The {@link Mappings} object containing classes, methods, and field mappings. + * @param locations An array of files representing output locations for serialized data. + * @return {@code true} if successful, otherwise {@code false}. + */ + @Override + public abstract boolean deserializeToFiles(@NotNull Mappings mappings, @NotNull File[] locations); + +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/deserialization/MappingDeserializer.java b/src/main/java/org/omnimc/lumina/deserialization/MappingDeserializer.java new file mode 100644 index 0000000..2086751 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/deserialization/MappingDeserializer.java @@ -0,0 +1,76 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.deserialization; + +import org.jetbrains.annotations.NotNull; +import org.omnimc.lumina.Mappings; + +import java.io.File; + +/** + * A contract for converting {@link Mappings} objects into various deserialized outputs. + * + *

This interface defines methods for serializing mappings into different formats, + * including: + *

    + *
  • String-based representation.
  • + *
  • File-based representation for single files.
  • + *
  • File-based representation across multiple files.
  • + *
+ * Implementing classes are expected to define specific serialization logic for these operations.

+ * + * @see Mappings + * + * @author Caden + * @since 1.0.0 + */ +public interface MappingDeserializer { + + /** + * Serializes the provided {@link Mappings} into a string representation. + * + * @param mappings The mappings to be serialized. + * @return A string representation of the mappings. + */ + String deserializeToString(@NotNull Mappings mappings); + + /** + * Serializes the provided {@link Mappings} into a single file. + * + * @param mappings The mappings to be serialized. + * @param file The file where the serialized result will be written. + * @return {@code true} if the operation is successful, otherwise {@code false}. + */ + boolean deserializeToFile(@NotNull Mappings mappings, @NotNull File file); + + /** + * Serializes the provided {@link Mappings} into multiple files. + * + * @param mappings The mappings to be serialized. + * @param locations An array of files to distribute mapping data across. + * @return {@code true} if the operation is successful, otherwise {@code false}. + */ + boolean deserializeToFiles(@NotNull Mappings mappings, @NotNull File[] locations); +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/deserialization/compressed/CompressedDeserializer.java b/src/main/java/org/omnimc/lumina/deserialization/compressed/CompressedDeserializer.java new file mode 100644 index 0000000..88a5ddd --- /dev/null +++ b/src/main/java/org/omnimc/lumina/deserialization/compressed/CompressedDeserializer.java @@ -0,0 +1,221 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.deserialization.compressed; + +import org.jetbrains.annotations.NotNull; +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.FailedState; +import org.omnimc.lumina.deserialization.AbstractMappingDeserializer; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +/** + * A {@link AbstractMappingDeserializer} implementation for handling compressed mapping deserialization. + * + *

This class provides logic for serializing mapping data into a compressed format that is + * human-readable but compact. It supports deserialization into: + *

    + *
  • A single file with the extension `.mmap`.
  • + *
  • A string representation of the mapping data.
  • + *
+ * Multiple files are not supported by this deserializer.

+ * + *

Entries written by this deserializer include class, method, and field mappings, where: + *

    + *
  • Class entries are prefixed with `CLASS`.
  • + *
  • Method entries are prefixed with `METHOD`.
  • + *
  • Field entries are prefixed with `FIELD`.
  • + *
+ *

+ * + * @see AbstractMappingDeserializer + * + * @author Caden + * @since 1.0.0 + */ +public class CompressedDeserializer extends AbstractMappingDeserializer { + + /** + * Serializes the provided {@link Mappings} object into a string representation. + * + *

The returned string is encoded in a compact format that uses prefixes + * (`CLASS`, `METHOD`, `FIELD`) to define different types of mappings.

+ * + * @param mappings The {@link Mappings} object containing data to serialize. + * @return A string representation of the mapping data. + */ + @Override + public String deserializeToString(@NotNull Mappings mappings) { + return makeOutput(mappings); + } + + /** + * Serializes the given {@link Mappings} object into a single `.mmap` file. + * + *

The output file must: + *

    + *
  • Have a `.mmap` file extension.
  • + *
  • Not be a directory.
  • + *
  • Be writable.
  • + *
+ *

+ * + * @param mappings The {@link Mappings} to serialize. + * @param file The target file for the serialized output. + * @return {@code true} if the operation is successful, otherwise {@code false}. + */ + @Override + public boolean deserializeToFile(@NotNull Mappings mappings, @NotNull File file) { + Objects.requireNonNull(mappings, "Mappings is null"); + Objects.requireNonNull(file, "File is null"); + + if (file.isDirectory()) { + this.fail(FailedState.of("File cannot be a directory!", this.getClass())); + return false; + } + + if (!file.getName().endsWith(".mmap")) { + this.fail(FailedState.of("File must end with `.mmap`", this.getClass())); + return false; + } + + if (!file.exists()) { + try { + file.createNewFile(); + } catch (IOException e) { + this.fail(FailedState.of(e, this.getClass())); + return false; + } + } else { + file.delete(); + try { + file.createNewFile(); + } catch (IOException e) { + this.fail(FailedState.of(e, this.getClass())); + return false; + } + } + + String output = makeOutput(mappings); + + try { + FileWriter writer = new FileWriter(file); + writer.write(output); + writer.flush(); + writer.close(); + } catch (IOException e) { + this.fail(FailedState.of(e, this.getClass())); + return false; + } + + return true; + } + + /** + * Deserialization to multiple files is unsupported by this deserializer. + * + * @param mappings The {@link Mappings} object. + * @param locations An array of files to which mappings will be serialized. + * @return Always returns {@code false}. + */ + @Override + public boolean deserializeToFiles(@NotNull Mappings mappings, @NotNull File[] locations) { + this.fail(FailedState.of("Compressed Deserializer does not support Multiple Files!", this.getClass())); + return false; + } + + /** + * Converts the given {@link Mappings} object into a formatted string. + * + *

Each mapping is written as a line in the following format: + *

    + *
  • Class: {@code CLASS obfuscated:deobfuscated}
  • + *
  • Method: {@code METHOD obfuscated:deobfuscated}
  • + *
  • Field: {@code FIELD obfuscated:deobfuscated}
  • + *
+ * Method and field mappings are grouped under their respective classes.

+ * + * @param mainMappings The {@link Mappings} object to format. + * @return A formatted string representing the mappings. + */ + private String makeOutput(Mappings mainMappings) { + StringBuilder builder = new StringBuilder(); + + for (Map.Entry classEntry : mainMappings.getClasses().entrySet()) { + String obfuscatedName = classEntry.getKey(); + String unObfuscatedName = classEntry.getValue(); + + builder.append("CLASS") + .append(" ") + .append(obfuscatedName) + .append(":") + .append(unObfuscatedName) + .append("\n"); + + Map> methods = mainMappings.getMethods(); + if (methods != null) { + Map methodMappings = methods.get(obfuscatedName); + if (methodMappings != null) { + for (Map.Entry methodNames : methodMappings.entrySet()) { + String obfuscatedMethodName = methodNames.getKey(); + String unObfuscatedMethodName = methodNames.getValue(); + + builder.append("METHOD") + .append(" ") + .append(obfuscatedMethodName) + .append(":") + .append(unObfuscatedMethodName) + .append("\n"); + } + } + } + + + Map> fields = mainMappings.getFields(); + if (fields != null) { + Map fieldMappings = fields.get(obfuscatedName); + if (fieldMappings != null) { + for (Map.Entry fieldNames : fieldMappings.entrySet()) { + String obfuscatedFieldName = fieldNames.getKey(); + String unObfuscatedFieldName = fieldNames.getValue(); + + builder.append("FIELD") + .append(" ") + .append(obfuscatedFieldName) + .append(":") + .append(unObfuscatedFieldName) + .append("\n"); + } + } + } + } + + return builder.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/deserialization/full/FullDeserializer.java b/src/main/java/org/omnimc/lumina/deserialization/full/FullDeserializer.java new file mode 100644 index 0000000..4e77f8a --- /dev/null +++ b/src/main/java/org/omnimc/lumina/deserialization/full/FullDeserializer.java @@ -0,0 +1,279 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.deserialization.full; + +import org.jetbrains.annotations.NotNull; +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.FailedState; +import org.omnimc.lumina.deserialization.AbstractMappingDeserializer; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +/** + * A {@link AbstractMappingDeserializer} implementation for handling complete mapping deserialization. + * + *

The `FullDeserializer` allows for the serialization of all mapping categories (classes, fields, and methods) into + * string and file formats. It supports multiple files, where each file is dedicated to a specific mapping category.

+ * + *

The serialized output separates mappings for: + *

    + *
  • Classes – Written into `*.class.mmap` files.
  • + *
  • Methods – Written into `*.method.mmap` files.
  • + *
  • Fields – Written into `*.field.mmap` files.
  • + *
+ *

+ * + * @see AbstractMappingDeserializer + * + * @author Caden + * @since 1.0.0 + */ +public class FullDeserializer extends AbstractMappingDeserializer { + + /** + * Serializes all mappings into a structured, multi-segment string representation. + * + *

The output is divided into sections with clearly defined headers:

+ *
    + *
  • Classes
  • + *
  • Methods
  • + *
  • Fields
  • + *
+ * + * @param mappings The {@link Mappings} object containing mapping data. + * @return A formatted string containing all mappings. + */ + @Override + public String deserializeToString(@NotNull Mappings mappings) { + StringBuilder builder = new StringBuilder(); + + String classOutput = makeClassOutput(mappings); + builder.append("#################### Classes ####################\n"); + builder.append(classOutput); + String methodOutput = makeMethodOutput(mappings); + builder.append("#################### Methods ####################\n"); + builder.append(methodOutput); + String fieldOutput = makeFieldOutput(mappings); + builder.append("#################### Fields ####################\n"); + builder.append(fieldOutput); + + return builder.toString(); + } + + /** + * Serializes mappings into files when the output directory is specified. + * + *

If the provided file `file` is a directory, the deserializer will attempt to locate + * the following files within the directory to write the mappings: + *

    + *
  • `.class.mmap` file for class mappings.
  • + *
  • `.field.mmap` file for field mappings.
  • + *
  • `.method.mmap` file for method mappings.
  • + *
+ * + *

If an appropriate file is not found in the directory, the method will create the required + * files. If the provided file is not a directory, this operation fails, as writing to a single + * file is unsupported by this deserializer.

+ * + * @param mappings The {@link Mappings} object containing mapping data. + * @param file The directory to write the output into. + * @return {@code true} if successful; {@code false} otherwise. + */ + @Override + public boolean deserializeToFile(@NotNull Mappings mappings, @NotNull File file) { + if (file.isDirectory()) { + File classLocation = null; + File fieldLocation = null; + File methodLocation = null; + + for (File listFile : Objects.requireNonNull(file.listFiles())) { + String name = listFile.getName().toLowerCase(); + if (name.endsWith(".class.mmap")) { + classLocation = listFile; + } + + if (name.endsWith(".field.mmap")) { + fieldLocation = listFile; + } + + if (name.endsWith(".method.mmap")) { + methodLocation = listFile; + } + } + + return writeOutput(classLocation, fieldLocation, methodLocation, mappings); + } + + this.fail(FailedState.of("`FullDeserializer` does not support single file deserialization!", this.getClass())); + return false; + } + + /** + * Writes serialized mappings into multiple files based on mapping categories. + * + *

The appropriate categories are identified by file extensions: + *

    + *
  • `.class.mmap` for classes.
  • + *
  • `.field.mmap` for fields.
  • + *
  • `.method.mmap` for methods.
  • + *
+ * Existing files are overwritten, and missing files are created.

+ * + * @param mappings The {@link Mappings} object containing mapping data. + * @param locations An array of file objects to write data into. + * @return {@code true} if serialization is successful; {@code false} otherwise. + */ + @Override + public boolean deserializeToFiles(@NotNull Mappings mappings, @NotNull File[] locations) { + Objects.requireNonNull(mappings); + Objects.requireNonNull(locations); + + File classLocation = null; + File fieldLocation = null; + File methodLocation = null; + + for (@NotNull File location : locations) { + String name = location.getName().toLowerCase(); + if (name.endsWith(".class.mmap")) { + classLocation = location; + } + + if (name.endsWith(".field.mmap")) { + fieldLocation = location; + } + + if (name.endsWith(".method.mmap")) { + methodLocation = location; + } + } + + return writeOutput(classLocation, fieldLocation, methodLocation, mappings); + } + + /** + * Serializes all mappings into their respective output files. + * + *

The mappings are split into three components: + *

    + *
  • Class mappings are written into `classLocation`.
  • + *
  • Field mappings are written into `fieldLocation`.
  • + *
  • Method mappings are written into `methodLocation`.
  • + *
+ * Existing files are overwritten, and missing files are created.

+ * + * @param classLocation The file for class mappings. + * @param fieldLocation The file for field mappings. + * @param methodLocation The file for method mappings. + * @param mappings The {@link Mappings} object containing mapping data. + * @return {@code true} if successful; otherwise {@code false}. + */ + private boolean writeOutput(File classLocation, File fieldLocation, File methodLocation, Mappings mappings) { + writeFile(classLocation, makeClassOutput(mappings)); + writeFile(fieldLocation, makeFieldOutput(mappings)); + writeFile(methodLocation, makeMethodOutput(mappings)); + return true; + } + + /** + * Writes serialized data into the specified file. + * + * @param file The file to write into. + * @param data The serialized data as a string. + */ + private void writeFile(File file, String data) { + if (file != null) { + if (file.exists()) { + file.delete(); + } + + try (FileWriter writer = new FileWriter(file)) { + writer.write(data); + writer.flush(); + } catch (IOException e) { + this.fail(FailedState.of(e, this.getClass())); + } + } + } + + private String makeClassOutput(Mappings mappings) { + StringBuilder builder = new StringBuilder(); + + for (Map.Entry classEntry : mappings.getClasses().entrySet()) { + String obfuscatedClassName = classEntry.getKey(); + String unObfuscatedClassName = classEntry.getValue(); + + builder.append(obfuscatedClassName).append(":").append(unObfuscatedClassName).append("\n"); + } + + return builder.toString(); + } + + private String makeMethodOutput(Mappings mappings) { + StringBuilder builder = new StringBuilder(); + + for (Map.Entry classEntry : mappings.getClasses().entrySet()) { + String obfuscatedClassName = classEntry.getKey(); + + Map methodMappings = mappings.getMethods().get(obfuscatedClassName); + append(builder, obfuscatedClassName, methodMappings); + } + return builder.toString(); + } + + private String makeFieldOutput(Mappings mappings) { + StringBuilder builder = new StringBuilder(); + + for (Map.Entry classEntry : mappings.getClasses().entrySet()) { + String obfuscatedClassName = classEntry.getKey(); + + Map fieldMappings = mappings.getFields().get(obfuscatedClassName); + append(builder, obfuscatedClassName, fieldMappings); + } + return builder.toString(); + } + + private void append(StringBuilder builder, String obfuscatedClassName, Map mappings) { + if (mappings == null) { + return; + } + + builder.append(obfuscatedClassName).append(":\n"); + + for (Map.Entry entry : mappings.entrySet()) { + String obfuscatedMethodName = entry.getKey(); + String unObfuscatedMethodName = entry.getValue(); + + builder.append(obfuscatedMethodName) + .append(":") + .append(unObfuscatedMethodName) + .append("\n"); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/deserialization/parameter/ParameterDeserializer.java b/src/main/java/org/omnimc/lumina/deserialization/parameter/ParameterDeserializer.java new file mode 100644 index 0000000..9dbc7cb --- /dev/null +++ b/src/main/java/org/omnimc/lumina/deserialization/parameter/ParameterDeserializer.java @@ -0,0 +1,177 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.deserialization.parameter; + +import org.jetbrains.annotations.NotNull; +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.FailedState; +import org.omnimc.lumina.deserialization.AbstractMappingDeserializer; +import org.omnimc.lumina.param.ParameterMap; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * A {@link AbstractMappingDeserializer} implementation responsible for serializing parameter mappings. + * + *

The `ParameterDeserializer` handles mappings where parameters of methods are serialized into + * structured outputs. It generates outputs in both string and file formats, with the following structure:

+ *
    + *
  • Classes are prefixed with CLASS and enclosed in square brackets.
  • + *
  • Methods are prefixed with METHOD and enclosed in square brackets.
  • + *
  • Parameters are listed with their index and value.
  • + *
+ * + *

Examples of the serialized structure: + *

+ * CLASS [MyClass] {
+ *     METHOD [myMethod] {
+ *         0: param1;
+ *         1: param2;
+ *     };
+ * };
+ * 
+ *

+ * + * @author Caden + * @since 1.0.0 + */ +public class ParameterDeserializer extends AbstractMappingDeserializer { + + /** + * Converts the parameter mappings in {@link Mappings} into a structured string representation. + * + * @param mappings The {@link Mappings} object containing mapping data. + * @return A structured string representation of the parameter mappings. + */ + @Override + public String deserializeToString(@NotNull Mappings mappings) { + return makeOutput(mappings.getParameterMap()); + } + + /** + * Serializes parameter mappings from the {@link Mappings} object into a single `.parameter.mmap` file. + * + *

If the file already exists, it will be overwritten.

+ * + *

File extension validation is enforced, requiring `.parameter.mmap`. If the extension is invalid or + * the operation fails for any reason, the process will return {@code false} and a {@link FailedState} will + * be triggered.

+ * + * @param mappings The {@link Mappings} object containing mapping data. + * @param file The file to write the serialized data into. + * @return {@code true} if serialization is successful; {@code false} otherwise. + */ + @Override + public boolean deserializeToFile(@NotNull Mappings mappings, @NotNull File file) { + Objects.requireNonNull(file); + Objects.requireNonNull(mappings); + + if (!file.getName().endsWith(".parameter.mmap")) { + this.fail(FailedState.of("File has to end with `.parameter.mmap` for it to qualify!", this.getClass())); + return false; + } + + if (file.exists()) { + file.delete(); + } + + try { + FileWriter writer = new FileWriter(file); + writer.write(makeOutput(mappings.getParameterMap())); + writer.flush(); + writer.close(); + } catch (IOException e) { + this.fail(FailedState.of(e, this.getClass())); + return false; + } + + return true; + } + + /** + * Denies support for serializing parameter mappings into multiple files. + * + *

This method is deliberately unsupported, triggering a failure state when invoked.

+ * + * @param mappings The {@link Mappings} object. + * @param locations An array of file objects to write into. + * @return Always returns {@code false}. + */ + @Override + public boolean deserializeToFiles(@NotNull Mappings mappings, @NotNull File[] locations) { + this.fail(FailedState.of("Parameter Deserializer does not support Multiple Files!", this.getClass())); + return false; + } + + /** + * Generates a string representation of the parameter mappings. + * + * @param parameterMap The {@link ParameterMap} object to be serialized. + * @return A structured string representation of parameter mappings. + */ + private String makeOutput(ParameterMap parameterMap) { + StringBuilder builder = new StringBuilder(); + + for (Map.Entry>> entry : parameterMap.getParameters().entrySet()) { + String className = entry.getKey(); + builder.append("CLASS") + .append(" [") + .append(className) + .append("]") + .append(" {\n"); + + for (Map.Entry> methodEntry : entry.getValue().entrySet()) { + String methodName = methodEntry.getKey(); + builder.append("\t") + .append("METHOD") + .append(" [") + .append(methodName) + .append("]") + .append(" {\n"); + + CopyOnWriteArrayList values = methodEntry.getValue(); + + for (int i = 0; i < values.size(); i++) { + builder.append("\t\t") + .append(i) + .append(": ") + .append(values.get(i)) + .append(";\n"); + } + builder.append("\t};\n"); + } + + builder.append("};\n"); + } + + return builder.toString(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/param/ParameterMap.java b/src/main/java/org/omnimc/lumina/param/ParameterMap.java new file mode 100644 index 0000000..42eba16 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/param/ParameterMap.java @@ -0,0 +1,68 @@ +package org.omnimc.lumina.param; + +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author Caden + * @since 1.0.0 + */ +public class ParameterMap { + + // ClassName unobf, methodName unobf - ParamNode + private final Map>> parameters = new ConcurrentHashMap<>(); + + public Map>> getParameters() { + return new ConcurrentHashMap<>(parameters); + } + + public boolean addParameterName(String className, String methodName, String paramName, int paramIndex) { + Map> methods = parameters.computeIfAbsent(className, k -> new ConcurrentHashMap<>()); + + CopyOnWriteArrayList params = methods.computeIfAbsent(methodName, k -> new CopyOnWriteArrayList<>()); + + while (params.size() <= paramIndex) { + params.add(null); + } + + boolean isNewEntry = params.get(paramIndex) == null || !params.get(paramIndex).equals(paramName); + + params.set(paramIndex, paramName); + methods.put(methodName, params); + parameters.put(className, methods); + + return isNewEntry; + } + + public String getParameterName(String className, String methodName, int index) { + Map> methodNames = parameters.get(className); + if (methodNames == null) { + return null; + } + + CopyOnWriteArrayList paramNames = methodNames.get(methodName); + if (paramNames == null) { + return null; + } + + return paramNames.get(index); + } + + public void addAll(@NotNull Map>> other) { + this.parameters.putAll(other); + } + + public void addAll(ParameterMap parameterMap) { + this.parameters.putAll(parameterMap.parameters); + } + + @Override + public String toString() { + return "ParameterMap{" + + "parameters=" + parameters + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/param/Token.java b/src/main/java/org/omnimc/lumina/param/Token.java new file mode 100644 index 0000000..ceba4d8 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/param/Token.java @@ -0,0 +1,20 @@ +package org.omnimc.lumina.param; + +/** + * @author Caden + * @since 1.0.0 + */ +public class Token { + + public enum Type { + CLASS, + METHOD, + IDENTIFIER_OPEN, + IDENTIFIER_CLOSE, + BRACE_OPEN, + BRACE_CLOSE, + COMMA, + SEMICOLON, + NUMBER; + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/serialization/AbstractMappingSerializer.java b/src/main/java/org/omnimc/lumina/serialization/AbstractMappingSerializer.java new file mode 100644 index 0000000..c981d89 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/serialization/AbstractMappingSerializer.java @@ -0,0 +1,158 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.serialization; + +import org.jetbrains.annotations.NotNull; +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.AcceptConsumer; +import org.omnimc.lumina.consumer.FailedState; + +import java.io.*; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +/** + * Abstract base class for mapping serializers, providing common logic for + * serializing mappings from various input sources (files, URIs, etc.). + * + *

This class extends {@link AcceptConsumer} to manage serialization errors and failures, + * and implements the {@link MappingSerializer} interface to standardize the serialization process.

+ * + *

Supports single-file and multi-file reading through the {@link LineSerializer} parser, with + * an abstract method {@code serialize} for customizing the serialization implementation.

+ * + * @author Caden + * @since 1.0.0 + */ +public abstract class AbstractMappingSerializer extends AcceptConsumer implements MappingSerializer { + + /** + * A {@link Mappings} object to hold the parsed mappings. + */ + protected final Mappings mappings = Mappings.of(); + + /** + * The {@link LineSerializer} used to parse each line of the file or input source. + */ + protected final LineSerializer chosenSerializer; + + /** + * Constructs an instance with the specified {@link LineSerializer}. + * + * @param chosenSerializer The serializer used to parse input lines. + */ + protected AbstractMappingSerializer(LineSerializer chosenSerializer) { + this.chosenSerializer = chosenSerializer; + } + + /** + * Serializes mappings from the given {@link URI} using a specified {@link LineSerializer}. + * + * @param uri The input source {@link URI}. + * @param parser The line parser to process the input. + * @return A {@link Mappings} object containing serialized mappings. + */ + @Override + public abstract Mappings serialize(@NotNull URI uri, @NotNull LineSerializer parser); + + /** + * Serializes mappings from the given {@link URI} using the default {@link LineSerializer}. + * + * @param uri The input source {@link URI}. + * @return A {@link Mappings} object containing serialized mappings. + */ + @Override + public Mappings serialize(@NotNull URI uri) { + return serialize(uri, chosenSerializer); + } + + /** + * Reads a single input file from the specified {@link URI} and parses it using the given {@link LineSerializer}. + * + * @param uri The location of the input file. + * @param parser The line parser to process each line. + * @return A {@link Mappings} object containing the parsed mappings, or {@link #mappings} if parsing fails. + */ + protected Mappings singleFileRead(@NotNull URI uri, @NotNull LineSerializer parser) { + Objects.requireNonNull(uri, "URI cannot be NULL."); + Objects.requireNonNull(parser, "LineSerializer cannot be NULL."); + + int lineNumber; + + try { + InputStream inputStream; + + if ("file".equalsIgnoreCase(uri.getScheme())) { + inputStream = Files.newInputStream(Path.of(uri)); + } else if ("http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme())) { + URL url = uri.toURL(); + inputStream = url.openStream(); + } else { + this.fail(FailedState.of("Unsupported URI scheme: " + uri.getScheme(), this.getClass())); + return null; + } + + try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); + LineNumberReader reader = new LineNumberReader(new InputStreamReader(bufferedInputStream))) { + + String line; + while ((line = reader.readLine()) != null) { + lineNumber = reader.getLineNumber(); + + if (!parser.serializeLine(line, mappings, getConsumer())) { + this.fail(FailedState.of("Failed to parse line: " + lineNumber, this.getClass())); + } + } + } + + } catch (IOException e) { + this.fail(FailedState.of(e, this.getClass())); + return null; + } + + return mappings; + } + + /** + * Reads multiple input files from the specified array of {@link URI}s, parsing each using the given {@link LineSerializer}. + * + * @param uris An array of input {@link URI}s to parse. + * @param parser The line parser to process each file. + * @return A {@link Mappings} object containing the combined mappings from all input files. + */ + protected Mappings multiFileRead(@NotNull URI[] uris, @NotNull LineSerializer parser) { + Objects.requireNonNull(uris, "uris cannot be NULL."); + Objects.requireNonNull(parser, "Parser cannot be NULL."); + + for (@NotNull URI uri : uris) { + singleFileRead(uri, parser); + } + + return mappings; + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/serialization/LineSerializer.java b/src/main/java/org/omnimc/lumina/serialization/LineSerializer.java new file mode 100644 index 0000000..b5192af --- /dev/null +++ b/src/main/java/org/omnimc/lumina/serialization/LineSerializer.java @@ -0,0 +1,65 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.serialization; + +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.FailedState; + +import java.util.function.Consumer; + +/** + * Represents a functional interface for parsing and serializing individual lines of input. + * + *

Each implementation defines the logic for parsing a line, managing failures through + * the provided {@link Consumer} and updating the {@link Mappings} object.

+ * + *

A helper method {@link #getEmptySerializer()} provides a no-op implementation that + * always returns {@code false} for parsing.

+ * + * @author Caden + * @since 1.0.0 + */ +public interface LineSerializer { + + /** + * Provides a no-op implementation of {@link LineSerializer} that always fails. + * + * @return A {@link LineSerializer} instance that does nothing and fails for every line. + */ + static LineSerializer getEmptySerializer() { + return (line, mappings, consumer) -> false; + } + + /** + * Parses and serializes a single line of input and updates the given {@link Mappings} object. + * + * @param line The input line to parse. + * @param mappings The {@link Mappings} object to update with parsed data. + * @param consumer A {@link Consumer} to handle failures during serialization. + * @return {@code true} if the line was successfully serialized, or {@code false} otherwise. + */ + boolean serializeLine(String line, Mappings mappings, Consumer consumer); + +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/serialization/MappingSerializer.java b/src/main/java/org/omnimc/lumina/serialization/MappingSerializer.java new file mode 100644 index 0000000..73f8a39 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/serialization/MappingSerializer.java @@ -0,0 +1,82 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.serialization; + +import org.jetbrains.annotations.NotNull; +import org.omnimc.lumina.MappingType; +import org.omnimc.lumina.Mappings; + +import java.net.URI; + +/** + * Represents a contract for classes responsible for serializing mappings from various input sources. + * + *

Implementations must provide methods to serialize mappings based on a given {@link URI}, + * and optionally a {@link LineSerializer} or a {@link MappingType}.

+ * + *

This interface abstracts the process of reading and converting obfuscated mappings into + * meaningful names using serializers.

+ * + * @see LineSerializer + * @see AbstractMappingSerializer + * @see Mappings + * @see MappingType + * @see URI + * + * @since 1.0.0 + */ +public interface MappingSerializer { + + /** + * Serializes mappings from the given {@link URI} and {@link MappingType}. + * + *

The provided {@link MappingType} determines the type of {@link LineSerializer} to use. + * + * @param uri The input source {@link URI}. + * @param type The {@link MappingType} indicating the type of serialization to perform. + * @return A {@link Mappings} object containing the serialized mappings. + */ + default Mappings serialize(@NotNull URI uri, MappingType type) { + return serialize(uri, type.getLineSerializer()); + } + + /** + * Serializes mappings from the given {@link URI} using the specified {@link LineSerializer}. + * + * @param uri The input source {@link URI}. + * @param parser The {@link LineSerializer} used to parse the input. + * @return A {@link Mappings} object containing the serialized mappings. + */ + Mappings serialize(@NotNull URI uri, @NotNull LineSerializer parser); + + /** + * Serializes mappings from the given {@link URI} using the default serializer. + * + * @param uri The input source {@link URI}. + * @return A {@link Mappings} object containing the serialized mappings. + */ + Mappings serialize(@NotNull URI uri); + +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/serialization/compressed/CompressedLineSerializer.java b/src/main/java/org/omnimc/lumina/serialization/compressed/CompressedLineSerializer.java new file mode 100644 index 0000000..24d9036 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/serialization/compressed/CompressedLineSerializer.java @@ -0,0 +1,129 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.serialization.compressed; + +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.FailedState; +import org.omnimc.lumina.serialization.LineSerializer; + +import java.util.function.Consumer; + +/** + * A {@link LineSerializer} implementation for processing compressed mapping lines. + * + *

This serializer processes each line in a mapping file, extracting and mapping the following types:

+ *
    + *
  • CLASS: Maps obfuscated class names to de-obfuscated names.
  • + *
  • FIELD: Maps obfuscated field names to de-obfuscated names, tied to a parent class.
  • + *
  • METHOD: Maps obfuscated method names to de-obfuscated names, tied to a parent class.
  • + *
+ * + *

Lines must follow the format: {@code TYPE OBFUSCATED_NAME:DEOBFUSCATED_NAME}.

+ * + * @see LineSerializer + * + * @author Caden + * @since 1.0.0 + */ +public class CompressedLineSerializer implements LineSerializer { + + /** + * Retrieves an instance of {@code CompressedLineSerializer}. + * + * @return A new instance of {@code CompressedLineSerializer}. + */ + public static LineSerializer getInstance() { + return new CompressedLineSerializer(); + } + + /** + * Stores the parent class being processed to associate fields and methods with the correct class. + */ + private String parentClass; + + /** + * Parses and serializes a compressed mapping line, updating the given {@link Mappings}. + * + * @param line The line content to parse. + * @param mappings The {@link Mappings} instance where parsed data will be stored. + * @param consumer A {@link Consumer} to handle failures during processing. + * @return {@code true} if the line was successfully parsed, {@code false} otherwise. + */ + @Override + public boolean serializeLine(String line, Mappings mappings, Consumer consumer) { + if (line == null || line.isBlank()) { + return false; + } + + try { + int i = line.indexOf(" "); + if (i == -1) { + consumer.accept(FailedState.of("Invalid line format: missing type and value", this.getClass())); + return false; + } + + String type = line.substring(0, i).trim(); + String value = line.substring(i + 1).trim(); + + String[] split = value.split(":"); + if (split.length != 2) { + consumer.accept(FailedState.of("Invalid value format: " + value, this.getClass())); + return false; + } + + String obfuscatedName = split[0].trim(); + String unObfuscatedName = split[1].trim(); + + switch (type.toUpperCase()) { + case "CLASS": + parentClass = obfuscatedName; + mappings.addClass(obfuscatedName, unObfuscatedName); + break; + case "FIELD": + if (parentClass == null) { + consumer.accept(FailedState.of("FIELD entry without a parent class: " + line, this.getClass())); + return false; + } + mappings.addField(parentClass, obfuscatedName, unObfuscatedName); + break; + case "METHOD": + if (parentClass == null) { + consumer.accept(FailedState.of("METHOD entry without a parent class: " + line, this.getClass())); + return false; + } + mappings.addMethod(parentClass, obfuscatedName, unObfuscatedName); + break; + default: + consumer.accept(FailedState.of("Unknown type: " + type, this.getClass())); + return false; + } + + return true; + } catch (Exception e) { + consumer.accept(FailedState.of(e, this.getClass())); + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/serialization/compressed/CompressedSerializer.java b/src/main/java/org/omnimc/lumina/serialization/compressed/CompressedSerializer.java new file mode 100644 index 0000000..375dbec --- /dev/null +++ b/src/main/java/org/omnimc/lumina/serialization/compressed/CompressedSerializer.java @@ -0,0 +1,85 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.serialization.compressed; + +import org.jetbrains.annotations.NotNull; +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.FailedState; +import org.omnimc.lumina.serialization.AbstractMappingSerializer; +import org.omnimc.lumina.serialization.LineSerializer; + +import java.net.URI; + +/** + * A serializer implementation for handling compressed mapping files. + * + *

This class extends {@link AbstractMappingSerializer} and processes files + * with a `.mmap` extension using the {@link CompressedLineSerializer}. It ensures + * that only valid compressed mapping parsers are used during serialization.

+ * + *

Usage: Compressed mapping files should adhere to a specific format + * containing information about classes, methods, and fields in a compact representation.

+ * + * @see AbstractMappingSerializer + * @see CompressedLineSerializer + * + * @author Caden + * @since 1.0.0 + */ +public class CompressedSerializer extends AbstractMappingSerializer { + + /** + * Constructs a {@code CompressedSerializer} with the {@link CompressedLineSerializer} as its default parser. + */ + protected CompressedSerializer() { + super(CompressedLineSerializer.getInstance()); + } + + /** + * Serializes mappings from a given {@link URI} using {@link CompressedLineSerializer}. + * + *

This method validates that the provided parser is an instance of {@link CompressedLineSerializer}. + * Additionally, it ensures that the mapping file has a `.mmap` extension, failing the operation otherwise.

+ * + * @param uri The input URI representing the mapping file. + * @param parser The parser, which must be a {@link CompressedLineSerializer}. + * @return A {@link Mappings} object containing the serialized mappings, or {@code null} if parsing fails. + */ + @Override + public Mappings serialize(@NotNull URI uri, @NotNull LineSerializer parser) { + if (!(parser instanceof CompressedLineSerializer)) { + this.fail(FailedState.of("You cannot use non Compressed Parsers", this.getClass())); + return null; + } + + String uriName = uri.toString(); + if (!uriName.endsWith(".mmap")) { + this.fail(FailedState.of("URI does not end with .mmap", this.getClass())); + return null; + } + + return super.singleFileRead(uri, parser); + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/serialization/full/FullPathResolver.java b/src/main/java/org/omnimc/lumina/serialization/full/FullPathResolver.java new file mode 100644 index 0000000..54c5037 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/serialization/full/FullPathResolver.java @@ -0,0 +1,63 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.serialization.full; + +/** + * Defines a structure for resolving the paths to class, method, and field mapping files within a directory. + * + *

This record specifies the relative locations of mapping files inside a directory + * to support the {@link FullSerializer} in reading structured mappings.

+ * + *

For example:

+ *
    + *
  • {@code classLocation} defines the file name or path for class mappings (e.g., "class.mmap").
  • + *
  • {@code methodLocation} defines the file name or path for method mappings (e.g., "method.mmap").
  • + *
  • {@code fieldLocation} defines the file name or path for field mappings (e.g., "field.mmap").
  • + *
+ * + * @param classLocation The relative path or file name for class mappings. + * @param methodLocation The relative path or file name for method mappings. + * @param fieldLocation The relative path or file name for field mappings. + * + * @see FullSerializer + * + * @author Caden + * @since 1.0.0 + */ +public record FullPathResolver(String classLocation, String methodLocation, String fieldLocation) { + + public static FullPathResolver of(String classLocation, String methodLocation, String fieldLocation) { + return new FullPathResolver(classLocation, methodLocation, fieldLocation); + } + + @Override + public String toString() { + return "FullPathResolver{" + + "classLocation='" + classLocation + '\'' + + ", methodLocation='" + methodLocation + '\'' + + ", fieldLocation='" + fieldLocation + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/serialization/full/FullSerializer.java b/src/main/java/org/omnimc/lumina/serialization/full/FullSerializer.java new file mode 100644 index 0000000..3ee8818 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/serialization/full/FullSerializer.java @@ -0,0 +1,153 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.serialization.full; + +import org.jetbrains.annotations.NotNull; +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.FailedState; +import org.omnimc.lumina.serialization.AbstractMappingSerializer; +import org.omnimc.lumina.serialization.LineSerializer; +import org.omnimc.lumina.serialization.full.line.ClassLineSerializer; +import org.omnimc.lumina.serialization.full.line.FieldLineSerializer; +import org.omnimc.lumina.serialization.full.line.MethodLineSerializer; + +import java.net.URI; +import java.util.Objects; + +/** + * A serializer implementation for handling full mappings stored in structured directories or files. + * + *

This class extends {@link AbstractMappingSerializer} and processes mappings + * from files with extensions such as `.class.mmap`, `.method.mmap`, and `.field.mmap`. + * It also allows handling structured directories with pre-defined paths for mapping files.

+ * + *

Line serialization is delegated to the following line serializers:

+ *
    + *
  • {@link ClassLineSerializer} for class mappings.
  • + *
  • {@link FieldLineSerializer} for field mappings.
  • + *
  • {@link MethodLineSerializer} for method mappings.
  • + *
+ * + * @see AbstractMappingSerializer + * @see FullPathResolver + * @see ClassLineSerializer + * @see FieldLineSerializer + * @see MethodLineSerializer + * + * @author Caden + * @since 1.0.0 + */ +public class FullSerializer extends AbstractMappingSerializer { + + /** + * Resolves the locations of class, method, and field mapping files in a directory. + */ + private final FullPathResolver resolver; + + /** + * Constructs a {@code FullSerializer} instance with the specified {@link FullPathResolver}. + * + * @param resolver The resolver for locating mapping files within a directory. + */ + public FullSerializer(@NotNull FullPathResolver resolver) { + super(LineSerializer.getEmptySerializer()); + Objects.requireNonNull(resolver); + this.resolver = resolver; + } + + /** + * Serializes mappings from the specified URI using the correct line serializer. + * + *

This method determines the type of file (class, method, field) based on the file extension + * or processes a directory containing structured mapping files. It ensures that only the + * * {@link LineSerializer#getEmptySerializer()} is used as a parser for the operation.

+ * + * @param uri The input URI representing the directory or mapping file. + * @param parser The line parser, which must be {@link LineSerializer#getEmptySerializer()}. + * @return A {@link Mappings} object containing the serialized mappings or {@code null} if an error occurs. + */ + @Override + public Mappings serialize(@NotNull URI uri, @NotNull LineSerializer parser) { + if ((parser != LineSerializer.getEmptySerializer())) { + this.fail(FailedState.of("Cannot accept any parser except `LineSerializer.getEmptySerializer`!", this.getClass())); + return null; + } + + String uriFull = uri.toString(); + + try { + if (uriFull.endsWith("/")) { + return readDirectoryMappings(uriFull); + } else if (uriFull.endsWith(".class.mmap")) { + return singleFileRead(uri, new ClassLineSerializer()); + } else if (uriFull.endsWith(".method.mmap")) { + return singleFileRead(uri, new MethodLineSerializer()); + } else if (uriFull.endsWith(".field.mmap")) { + return singleFileRead(uri, new FieldLineSerializer()); + } else { + this.fail(FailedState.of("Unsupported file format: " + uriFull, this.getClass())); + return null; + } + } catch (IllegalArgumentException e) { + this.fail(FailedState.of("Invalid URI syntax: " + uriFull, this.getClass())); + return null; + } catch (Exception e) { + this.fail(FailedState.of("Unexpected error while reading mappings: " + e.getMessage(), this.getClass())); + return null; + } + } + + /** + * Reads mappings from a structured directory defined by the {@link FullPathResolver}. + * + *

The directory must contain files at the paths specified by the resolver: + * {@code classLocation}, {@code methodLocation}, and {@code fieldLocation}.

+ * + * @param uriFull A string representation of the URI pointing to the directory. + * @return A {@link Mappings} object containing the combined serialized mappings from all files. + */ + private Mappings readDirectoryMappings(String uriFull) { + try { + String classLocation = uriFull + resolver.classLocation(); + String methodLocation = uriFull + resolver.methodLocation(); + String fieldLocation = uriFull + resolver.fieldLocation(); + + URI classURI = URI.create(classLocation); + mappings.addAll(singleFileRead(classURI, new ClassLineSerializer())); + + URI methodURI = URI.create(methodLocation); + mappings.addAll(singleFileRead(methodURI, new MethodLineSerializer())); + + URI fieldURI = URI.create(fieldLocation); + mappings.addAll(singleFileRead(fieldURI, new FieldLineSerializer())); + } catch (IllegalArgumentException e) { + this.fail(FailedState.of("Invalid directory structure or URI syntax: " + uriFull, this.getClass())); + } catch (Exception e) { + this.fail(FailedState.of("Unexpected error while reading directory mappings: " + e.getMessage(), this.getClass())); + } + + return mappings; + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/serialization/full/line/ClassLineSerializer.java b/src/main/java/org/omnimc/lumina/serialization/full/line/ClassLineSerializer.java new file mode 100644 index 0000000..5d84e65 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/serialization/full/line/ClassLineSerializer.java @@ -0,0 +1,81 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.serialization.full.line; + +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.FailedState; +import org.omnimc.lumina.serialization.LineSerializer; + +import java.util.function.Consumer; +import java.util.regex.PatternSyntaxException; + +/** + * A {@link LineSerializer} implementation that serializes class mappings from textual entries. + * + *

This serializer processes each line in the format `obfuscated:unobfuscated`, + * where:

+ *
    + *
  • obfuscated: The obfuscated class name.
  • + *
  • unobfuscated: The corresponding de-obfuscated class name.
  • + *
+ * + *

If the format is invalid or any parsing error occurs, the operation fails with a {@link FailedState}.

+ * + * @see LineSerializer + * + * @author Caden + * @since 1.0.0 + */ +public class ClassLineSerializer implements LineSerializer { + + /** + * Parses and serializes a class mapping line into the {@link Mappings} object. + * + * @param line The line to serialize, expected in the format `obfuscated:unobfuscated`. + * @param mappings The {@link Mappings} object to store the serialized class mappings. + * @param consumer A {@link Consumer} to handle failures during serialization. + * @return {@code true} if the line was successfully serialized; otherwise, {@code false}. + */ + @Override + public boolean serializeLine(String line, Mappings mappings, Consumer consumer) { + try { + String[] split = line.split(":"); + if (split.length != 2) { + consumer.accept(FailedState.of("Invalid value format: " + line, this.getClass())); + return false; + } + + String obfuscatedName = split[0].trim(); + String unObfuscatedName = split[1].trim(); + + mappings.addClass(obfuscatedName, unObfuscatedName); + } catch (PatternSyntaxException e) { + consumer.accept(FailedState.of(e, this.getClass())); + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/serialization/full/line/FieldLineSerializer.java b/src/main/java/org/omnimc/lumina/serialization/full/line/FieldLineSerializer.java new file mode 100644 index 0000000..b1f9dc9 --- /dev/null +++ b/src/main/java/org/omnimc/lumina/serialization/full/line/FieldLineSerializer.java @@ -0,0 +1,112 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.serialization.full.line; + +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.FailedState; +import org.omnimc.lumina.serialization.LineSerializer; + +import java.util.function.Consumer; + +/** + * A {@link LineSerializer} implementation that serializes field mappings from textual entries. + * + *

This serializer processes lines in the following formats:

+ *
    + *
  • Parent Class Definition: Ends with a colon (`:`). Declares the parent class for subsequent entries.
  • + *
  • Field Entries: Follow the format `obfuscated:unobfuscated`, defining a mapping for a field + * under the currently defined parent class.
  • + *
+ * + *

If no parent class is defined prior to parsing a field entry, or if the format is invalid, the operation fails + * with a {@link FailedState}.

+ * + * @see LineSerializer + * + * @author Caden + * @since 1.0.0 + */ +public class FieldLineSerializer implements LineSerializer { + + /** + * Stores the currently defined parent class for field entries. + */ + private String parentClass; + + /** + * Parses and serializes a field mapping line into the {@link Mappings} object. + * + *

The parsing operates as follows:

+ *
    + *
  • If the line ends with a colon (`:`), it sets the parent class for subsequent field entries.
  • + *
  • If the line is a field mapping (`obfuscated:unobfuscated`), it associates the mapping with the current parent class.
  • + *
  • If the format is invalid or no parent class is defined, the {@link Consumer} handles a failure.
  • + *
+ * + * @param line The line to serialize. + * @param mappings The {@link Mappings} object to store the serialized field mappings. + * @param consumer A {@link Consumer} to handle failures during serialization. + * @return {@code true} if the line was successfully serialized; otherwise, {@code false}. + */ + @SuppressWarnings("DuplicatedCode") + @Override + public boolean serializeLine(String line, Mappings mappings, Consumer consumer) { + if (line == null || line.isBlank()) { + consumer.accept(FailedState.of("Line cannot be null or empty!", this.getClass())); + return false; + } + + if (line.endsWith(":")) { + parentClass = line.substring(0, line.length() - 1).trim(); + if (parentClass.isEmpty()) { + consumer.accept(FailedState.of("Parent class name cannot be empty!", this.getClass())); + return false; + } + return true; + } + + if (parentClass == null) { + consumer.accept(FailedState.of("No parent class defined for line: " + line, this.getClass())); + return false; + } + + String[] split = line.split(":"); + if (split.length != 2) { + consumer.accept(FailedState.of("Invalid line format, expected 'obfuscated:unobfuscated': " + line, this.getClass())); + return false; + } + + String obfuscatedName = split[0].trim(); + String unobfuscatedName = split[1].trim(); + + if (obfuscatedName.isEmpty() || unobfuscatedName.isEmpty()) { + consumer.accept(FailedState.of("Obfuscated or unobfuscated name cannot be empty: " + line, this.getClass())); + return false; + } + + mappings.addField(parentClass, obfuscatedName, unobfuscatedName); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/org/omnimc/lumina/serialization/full/line/MethodLineSerializer.java b/src/main/java/org/omnimc/lumina/serialization/full/line/MethodLineSerializer.java new file mode 100644 index 0000000..6febedc --- /dev/null +++ b/src/main/java/org/omnimc/lumina/serialization/full/line/MethodLineSerializer.java @@ -0,0 +1,112 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 OmniMC + * + * 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, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +package org.omnimc.lumina.serialization.full.line; + +import org.omnimc.lumina.Mappings; +import org.omnimc.lumina.consumer.FailedState; +import org.omnimc.lumina.serialization.LineSerializer; + +import java.util.function.Consumer; + +/** + * A {@link LineSerializer} implementation that serializes method mappings from textual entries. + * + *

This serializer processes lines in the following formats:

+ *
    + *
  • Parent Class Definition: Ends with a colon (`:`). Declares the parent class for subsequent entries.
  • + *
  • Method Entries: Follow the format `obfuscated:unobfuscated`, defining a mapping for a method + * under the currently defined parent class.
  • + *
+ * + *

If no parent class is defined prior to parsing a method entry, or if the format is invalid, the operation fails + * with a {@link FailedState}.

+ * + * @see LineSerializer + * + * @author Caden + * @since 1.0.0 + */ +public class MethodLineSerializer implements LineSerializer { + + /** + * Stores the currently defined parent class for method entries. + */ + private String parentClass; + + /** + * Parses and serializes a method mapping line into the {@link Mappings} object. + * + *

The parsing operates as follows:

+ *
    + *
  • If the line ends with a colon (`:`), it sets the parent class for subsequent method entries.
  • + *
  • If the line is a method mapping (`obfuscated:unobfuscated`), it associates the mapping with the current parent class.
  • + *
  • If the format is invalid or no parent class is defined, the {@link Consumer} handles a failure.
  • + *
+ * + * @param line The line to serialize. + * @param mappings The {@link Mappings} object to store the serialized method mappings. + * @param consumer A {@link Consumer} to handle failures during serialization. + * @return {@code true} if the line was successfully serialized; otherwise, {@code false}. + */ + @SuppressWarnings("DuplicatedCode") + @Override + public boolean serializeLine(String line, Mappings mappings, Consumer consumer) { + if (line == null || line.isBlank()) { + consumer.accept(FailedState.of("Line cannot be null or empty!", this.getClass())); + return false; + } + + if (line.endsWith(":")) { + parentClass = line.substring(0, line.length() - 1).trim(); + if (parentClass.isEmpty()) { + consumer.accept(FailedState.of("Parent class name cannot be empty!", this.getClass())); + return false; + } + return true; + } + + if (parentClass == null) { + consumer.accept(FailedState.of("No parent class defined for line: " + line, this.getClass())); + return false; + } + + String[] split = line.split(":"); + if (split.length != 2) { + consumer.accept(FailedState.of("Invalid line format, expected 'obfuscated:unobfuscated': " + line, this.getClass())); + return false; + } + + String obfuscatedName = split[0].trim(); + String unobfuscatedName = split[1].trim(); + + if (obfuscatedName.isEmpty() || unobfuscatedName.isEmpty()) { + consumer.accept(FailedState.of("Obfuscated or unobfuscated name cannot be empty: " + line, this.getClass())); + return false; + } + + mappings.addMethod(parentClass, obfuscatedName, unobfuscatedName); + return true; + } +} \ No newline at end of file diff --git a/src/main/resources/map_data.compressed.json b/src/main/resources/map_data.compressed.json new file mode 100644 index 0000000..6020716 --- /dev/null +++ b/src/main/resources/map_data.compressed.json @@ -0,0 +1,4 @@ +{ + "type": "COMPRESSED", + "location": "*.mmap" +} \ No newline at end of file diff --git a/src/main/resources/map_data.full.json b/src/main/resources/map_data.full.json new file mode 100644 index 0000000..6662b4f --- /dev/null +++ b/src/main/resources/map_data.full.json @@ -0,0 +1,8 @@ +{ + "type": "FULL", + "location": { + "class": "*.class.mmap", + "method": "*.method.mmap", + "field": "*.field.mmap" + } +} \ No newline at end of file diff --git a/src/test/java/Test.java b/src/test/java/Test.java new file mode 100644 index 0000000..cd0d947 --- /dev/null +++ b/src/test/java/Test.java @@ -0,0 +1,13 @@ +import java.io.File; +import java.io.IOException; + +/** + * @author Caden + * @since 1.0.0 + */ +public class Test { + + public static void main(String[] args) throws IOException { + + } +} \ No newline at end of file