Skip to content

Conversation

@ApenasOLinco
Copy link

@ApenasOLinco ApenasOLinco commented Nov 21, 2025

Before anything

First of, hello! My name is Lincoln and I am a Brazilian Java developer.

You may have not noticed, but I started using your tool in a passion project of mine, Vulpis Updater, in the last few weeks. Before anything else, I would like to thank you for your amazing job and for providing this tool as an open source, MIT-licensed project. I really appreciate it.

As my project grew in complexity, I realized that I would need to retrieve information about the authors of the mods I was reading. For that reason, I decided to implement that feature myself.

The thing is, on this process, I realized I would need to do a general refactor of the project to make implementing new features such as the one I was trying to add more manageable.

I understand if you decide not to merge this PR, since the changes i've made are pretty invasive and radical. I would like to point out, though, that the functioning of the tool stays the same. No previously-supported functionality of the tool was lost. The only changes were on HOW BasicModInfoParser does it.

Regardless of merging or not, just know your work is appreciated! Sincerely.

Changes

ModInfoKeys.java

When dealing with the JSON and TOML elements, the information about the key names needed to access their properties (such as "modid", "authorList", etc.) was dispersed through the Platform Enum class. Since this information varies from Platform to Platform, I've taken the initiative to move this information to a new ModInfoKeys.java class that has all the wanted fields. This class stores the keys in its fields, and each field can be accessed to retreive the key name information.

public ModInfoKeys(  
  String modIdKey,  
  String displayNameKey,  
  String versionKey,  
  String descriptionKey,  
  String logoFileKey,  
  String authorsKey,  
  String[] dependencyKeys  
) { ... }

Each Platform receives an object of this class at construction, here's an example using the Fabric constructor:

FABRIC(  
  new ModInfoKeys(  
	  "id",  
	  "name",  
	  "version",  
	  "description",  
	  "icon",  
	  "authors",  
	  new String[]{  
		  "depends",  
		  "recommends",  
		  "suggests",  
		  "breaks",  
		  "conflicts"  
	  }  
 ),
 "fabric.mod.json"  
) { ... }

This object is later used by the below mentioned classes to retrieve the correct information.

Acess to data Objects

I've created a DataAdapter<T,A> class that is responsible for standarizing access to the data structures the tool has to interact with (as of the time of this message, JSON and TOML). The current implementations are JsonAdapter and TomlAdapter.

Parsing logic

The mod parsing logic had little to no modularization before. I've created a IDependencyParser interface that declares methods to parse dependencies. I've also implemented it in all the current supported platforms: ForgeDependencyParser, LegacyForgeDependencyParser, FabricDependencyParser, and QuiltDependencyParser.

There was also a general cleanup of the ParserUtils class. The platform-specific code was either moved elsewhere or was centralized inside the new static method createModInfoFrom(), which generifies and centralizes the creation of new ModInfos. This new method is Platform-agnostic, instead delegating platform-specific processes, such as the parsing of dependencies of the retrieval of information about a mod, to the aforementioned IDependencyParser implementations.

Removal of uneeded type parameters in some classes

The Version class had a recursive type parameter (Version<T extends Version<T>>). The type parameter turned out to not be necessary at all! Every piece of code that used this notation was refactored to support the removal. This makes related code easier to understand.

And , of course, the addition of author information support

I've added the @Nullable authors property to the StandardBasicModInfo class as a List of String. The ModInfoKeys also comes with a String[] authorsKey property, which of course varies between platforms. I've made some tests and the parsing of authors seems to be working fine. The only thing is, it is still not splitting author strings that have commas, so if a mod has an authors property like "Author1, author2", it would be considered a single author. I plan to make a PR later to fix this.

…ction)` and `parseLegacyForgesDependencies(String[], JsonObject)`

As of now, they're still not in use
This commit refactors the version parsing and comparison logic to be more consistent and object-oriented. Previously, each version type (MavenVersion, LooseSemanticVersion) had its own separate way of being parsed, which was inflexible and led to duplicated logic.
To address this, the Version interface has been converted into an abstract base class. This allows different versioning schemes to share common behavior and properties, making the system cleaner and easier to extend.

• A shared parse method is now part of the Version base class, replacing the previous static methods in each implementation.

• The comparison logic (compareTo) has been updated to work with the new abstract Version class, ensuring different version types can still be compared correctly and safely.

• Common data, like the original version string, is now handled in the base class, reducing boilerplate.

This is not an ideal, let alone definitive, solution for the problemas that came with the previous versions of these classes. I was thinking of implementing the `Factory` design pattern to better address initialization logic, but since I'm in a rush at the moment, this solution will be enough.
…ccess

This commit refactors the DataAdapter hierarchy to improve its structure and add new functionality. The DataAdapter interface has been converted into an abstract class. This change allows for a backingObject to be managed in the base class, which simplifies the concrete JsonAdapter and TomlAdapter implementations by removing redundant fields and constructor logic.

Nested Property Access: The JsonAdapter now supports accessing nested properties using dot notation (e.g., parent.child.key), making it much easier to work with complex JSON objects.
This commit creates FabricDependencyParser, `ForgeDependencyParser`, `LegacyForgeDependencyParser`, `QuiltDependencyParser` and their interface, `IDependencyParser`.

This separates the dependency parsing from the `Platform` code and makes it easier to implement more complex logic.

As of the time of this commit, the parsing logic has not yet been altered to use these classes.
The property keys are not the same for fabric and quilt, so a helper method is not necessary
The interface was only being implemented inside `FabricModInfo`, at such point we're better off not using interfaces at all
As of the time of this commit, the field is not yet used
…FabricDependencyParser.parseSingleDependency`
This commit adapts the `ParserUtils` and `Platform` code structures to support the new system for parsing mod info.
This change provides full implementations for handling all the supported mod loader formats. The core logic is now delegated to dedicated parser classes, making the system cleaner and easier to maintain.
Key changes include:

Concrete Parser Implementations: ForgeDependencyParser, LegacyForgeDependencyParser, FabricDependencyParser, and QuiltDependencyParser are now fully implemented to handle their respective metadata formats (mods.toml, legacy strings, fabric.mod.json, and quilt.mod.json).

Integration with Platforms: The Platform enum now uses these new parsers to correctly extract dependency information for each mod loader, centralizing the parsing process.

This new architecture makes the dependency analysis more understandable and provides a solid foundation for supporting additional mod loaders or format changes in the future.
@ApenasOLinco
Copy link
Author

ApenasOLinco commented Nov 21, 2025

P.S.: a good portion of the commits i've made have descriptive messages in case you want to understand better my reasoning. I've also added extensive commenting and documentation on the code where possible.

@ApenasOLinco ApenasOLinco changed the title Add support for mod authors retrieval for all platforms, modularization of dependency parsing logicgeneral refactor of the project Add support for mod authors retrieval for all platforms, modularization of dependency parsing logic, general refactor of the project Nov 21, 2025
@RaydanOMGr
Copy link
Owner

Great pull request, but may I ask why you put the parse method into Version? I believe it would be better to have a separate VersionParser (possibly as an enum) that has a method that returns an Optional<T extends Version>, rather than have the parse method be in the Version itself, as it introduces mutability, whereas Version is supposed to be immutable
Your way of parsing Version also introduces the necessity for there to be a generic argument, as the parse method should return an Optional of the specific type (e.g. Optional<LooseSemanticVersion> for a new LooseSemanticVersion().parse(...)) since most different implementations of Version have some extra methods the user may want to retrieve themselves, as seen in me.andreasmelone.basicmodinfoparser.test.LooseSemanticVersionParserTests

This pull request also seems to break neoforge parsing (since it parses mods as a table, but it's an list of tables iirc) and I have encountered the issue with JsonAdapter#getListOrString throwing a NullPointerException, since you do not check whether the element is null

Due to all of this, I can sadly not accept it currently

@ApenasOLinco
Copy link
Author

Hello and thank you for the review, I will be trying to work on these issues shortly.

@ApenasOLinco
Copy link
Author

On the NullPointerException part, may I ask what mod jar/jars did you use as test to get the Exception? Thanks in advance!

…onFactories` instead of the raw `Version` constructors
…ticVersionFactory` instead of the `LooseSemanticVersion` constructor
@ApenasOLinco
Copy link
Author

Fixes

Version Parsing

I've created a new VersionFactory<T extends Version> interface and its implementations, MavenVersionFactory implements VersionFactory<MavenVersion> and LooseSemanticVersionFactory implements VersionFactory<LooseSemanticVersion>.

Decoupling the initialization logic from the actual object allows immutable Version instances, as you pointed out earlier.

@FunctionalInterface  
public interface VersionFactory<T extends Version> {  
  Optional<T> parseVersion(final String versionString);  
}
public class MavenVersionFactory implements VersionFactory<MavenVersion> {
    // ...
}
public class LooseSemanticVersionFactory implements VersionFactory<LooseSemanticVersion> {
    // ...
}

Neoforge parsing

As you pointed out, I was trying to get the mods array inside neoforge mods metadata as a single table. I made a quick fix by iterating over the mods array and returning all the parsed infos as the result of NEOFORGE#parseFileData.

NullPointerException

I could not find any mods that had a JsonNull as their fields' values to test this, but I added a !element.isJsonNull() check before anything else regardless. This should stop the throw!

If you spot anything else, don't hesitate to tell me so I can fix it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants