Skip to content

Commit

Permalink
chore: merge upstream for WolfVariant additions
Browse files Browse the repository at this point in the history
Also adds some improvements on the generator to read classes with non-registry backed instances

Signed-off-by: Gabriel Harris-Rouquette <[email protected]>
  • Loading branch information
gabizou committed Jan 23, 2025
2 parents b751024 + a425adb commit 1d0b6f0
Show file tree
Hide file tree
Showing 21 changed files with 1,830 additions and 1,254 deletions.
5 changes: 2 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
root = true

[*mixins*.json]
indent_size = 4

[*]
charset = utf-8
end_of_line = lf
Expand All @@ -12,6 +9,8 @@ insert_final_newline = false
max_line_length = 120
tab_width = 4

[*mixins*.json]
indent_size = 2

[*.yaml]
indent_size = 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* 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.spongepowered.vanilla.generator;

import static com.github.javaparser.ast.Modifier.createModifierList;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.javadoc.Javadoc;
import com.github.javaparser.javadoc.description.JavadocDescription;
import net.minecraft.resources.ResourceLocation;
import org.tinylog.Logger;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;

public class ClassFieldsValidator<V> implements Generator {

private final String relativePackageName;
private final String targetClassSimpleName;
private final Class<?> clazz;
private final Function<String, ResourceLocation> mapping;

public ClassFieldsValidator(
final String targetRelativePackage,
final String targetClassSimpleName,
final Class<?> clazz
) {
this(targetRelativePackage, targetClassSimpleName, clazz, name -> ResourceLocation.tryBuild("sponge", name.toLowerCase(Locale.ROOT)));
}

public ClassFieldsValidator(
final String targetRelativePackage,
final String targetClassSimpleName,
final Class<?> clazz,
final Function<String, ResourceLocation> mapping
) {
this.relativePackageName = targetRelativePackage;
this.targetClassSimpleName = targetClassSimpleName;
this.clazz = clazz;
this.mapping = mapping;
}

@Override
public String name() {
return "elements of class " + this.clazz.getName();
}

@Override
@SuppressWarnings("unchecked")
public void generate(final Context ctx) {
// We read an existing class, then go through existing fields, make sure they exist, and add any new ones.
final var compilationUnit = ctx.compilationUnit(this.relativePackageName, this.targetClassSimpleName);
final var primaryTypeDeclaration = compilationUnit.getPrimaryType()
.orElseThrow(() -> new IllegalStateException("Could not find primary type for registry type " + this.targetClassSimpleName));

primaryTypeDeclaration.setJavadocComment(new Javadoc(JavadocDescription.parseText(Generator.GENERATED_FILE_JAVADOCS)));

final List<ResourceLocation> map = new ArrayList<>();
for (final var field : this.clazz.getDeclaredFields()) {
if (!java.lang.reflect.Modifier.isPublic(field.getModifiers())
|| !java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
continue;

}
final var name = field.getName();
final var key = this.mapping.apply(name);
map.add(key);
}

// Find index of first field member
// Take out all field members from the members list
final var members = primaryTypeDeclaration.getMembers();
final var fields = new HashMap<ResourceLocation, FieldDeclaration>();
int lastNonFieldIndex = -1;
for (final var it = members.listIterator(); it.hasNext(); ) {
final var node = it.next();
if (lastNonFieldIndex == -1 && node instanceof FieldDeclaration) {
lastNonFieldIndex = it.nextIndex() - 1;
}

if (node instanceof FieldDeclaration) {
final var field = (FieldDeclaration) node;
fields.put(this.extractFieldIdentifier(field), field);
it.remove();
}
}

// Now, iterate the registry, discovering which fields were added and removed
final var added = new HashSet<ResourceLocation>();
final var processedFields = new ArrayList<FieldDeclaration>(map.size());
final Set<ResourceLocation> allKeys = new HashSet<>(map);
for (final ResourceLocation key : allKeys) {
final FieldDeclaration existing = fields.remove(key);
if (existing != null) {
processedFields.add(existing);
} else {
added.add(key);
processedFields.add(this.makeField(this.targetClassSimpleName, "key", key));
}
}

// Sort field entries and add them back to the class
processedFields.sort(Comparator.comparing(field -> field.getVariable(0).getNameAsString()));
primaryTypeDeclaration.getMembers().addAll(lastNonFieldIndex, processedFields);

if (!added.isEmpty()) {
Logger.info("Added {} entries to {} that will require manual action to implement: {}", added.size(), this.targetClassSimpleName, added);
}
if (!fields.isEmpty()) {
Logger.info("Removed {} entries from {} because they are no longer present in the game: {}", fields.size(), this.targetClassSimpleName, fields.keySet());
}
}

// Attempt to get a resource location from the field by parsing its initializer
private ResourceLocation extractFieldIdentifier(final FieldDeclaration declaration) {
if (declaration.getVariables().isEmpty()) {
throw new IllegalStateException("No variables for " + declaration);
}
final VariableDeclarator var = declaration.getVariable(0);
final Expression initializer = var.getInitializer().orElse(null);
if (!(initializer instanceof MethodCallExpr) || ((MethodCallExpr) initializer).getArguments().size() != 1) {
return ResourceLocation.parse(var.getNameAsString().toLowerCase(Locale.ROOT)); // a best guess
}

final Expression argument = ((MethodCallExpr) initializer).getArgument(0);
if (!(argument instanceof final MethodCallExpr keyInitializer)
|| keyInitializer.getArguments().size() < 1) {
return ResourceLocation.parse(var.getNameAsString().toLowerCase(Locale.ROOT)); // a best guess
}

if (keyInitializer.getArguments().size() == 1) { // method name as namespace
return ResourceLocation.fromNamespaceAndPath(keyInitializer.getNameAsString(), keyInitializer.getArgument(0).asStringLiteralExpr().asString());
} else if (keyInitializer.getArguments().size() == 2) { // (namespace, path)
return ResourceLocation.fromNamespaceAndPath(
keyInitializer.getArgument(0).asStringLiteralExpr().asString(),
keyInitializer.getArgument(1).asStringLiteralExpr().asString()
);
} else {
return ResourceLocation.parse(var.getNameAsString().toLowerCase(Locale.ROOT)); // a best guess
}

}

private FieldDeclaration makeField(final String ownType, final String factoryMethod, final ResourceLocation element) {
final FieldDeclaration fieldDeclaration = new FieldDeclaration();
final VariableDeclarator variable = new VariableDeclarator(StaticJavaParser.parseType("DefaultedRegistryReference<FixMe>"), Types.keyToFieldName(element.getPath()));
fieldDeclaration.getVariables().add(variable);
fieldDeclaration.setModifiers(createModifierList(Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC, Modifier.Keyword.FINAL));
variable.setInitializer(new MethodCallExpr(new NameExpr(ownType), factoryMethod, new NodeList<>(RegistryEntriesValidator.resourceKey(element))));
return fieldDeclaration;
}

public static MethodCallExpr resourceKey(final ResourceLocation location) {
Objects.requireNonNull(location, "location");
final var resourceKey = new NameExpr("ResourceKey");
return switch (location.getNamespace()) {
case "minecraft" ->
new MethodCallExpr(resourceKey, "minecraft", new NodeList<>(new StringLiteralExpr(location.getPath())));
case "brigadier" ->
new MethodCallExpr(resourceKey, "brigadier", new NodeList<>(new StringLiteralExpr(location.getPath())));
case "sponge" ->
new MethodCallExpr(resourceKey, "sponge", new NodeList<>(new StringLiteralExpr(location.getPath())));
default -> new MethodCallExpr(
resourceKey, "of", new NodeList<>(new StringLiteralExpr(location.getNamespace()), new StringLiteralExpr(location.getPath())));
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
import java.nio.file.Files;
import java.nio.file.Path;

final class Context {
public final class Context {

static final String INDENT = " ";
static final String BASE_PACKAGE = "org.spongepowered.api";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
import java.util.Locale;
import java.util.Objects;

class EnumEntriesValidator<V> implements Generator {
public class EnumEntriesValidator<V> implements Generator {

private final String relativePackageName;
private final String targetClassSimpleName;
Expand All @@ -57,12 +57,12 @@ class EnumEntriesValidator<V> implements Generator {

private final String namespace;

EnumEntriesValidator(
final String targetRelativePackage,
final String targetClassSimpleName,
final Class<? extends Enum<?>> clazz,
final String keyFunction,
final String namespace
public EnumEntriesValidator(
final String targetRelativePackage,
final String targetClassSimpleName,
final Class<? extends Enum<?>> clazz,
final String keyFunction,
final String namespace
) {
this.relativePackageName = targetRelativePackage;
this.targetClassSimpleName = targetClassSimpleName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

import java.io.IOException;

interface Generator {
public interface Generator {

String GENERATED_FILE_JAVADOCS = "<!-- This file is automatically generated. Any manual changes will be overwritten. -->";

Expand Down
Loading

0 comments on commit 1d0b6f0

Please sign in to comment.