mirror of https://github.com/PaperMC/Paper.git
improve source parsing to be more accurate
The previous way had several flaw and was not really future proof. This new system is based on an iterative string reader similar to how brigadier parse arguments which simplify a lot of things tracking the cursor internally. The comments and javadoc are now properly skipped to prevent commented line to be counted as an import. The first scope detection to stop the parser once all imports have been processed now detect the first '{' char uncommented. Previously it only worked for api gen and was really restrictive since it detected the following pattern: <class type> <class name> as a workaround. The annotations are also skipped to prevent curly bracket that could be in it to be counted too (this include the arguments as well). Type name extended on multi line are also supported and checked at runtime as defined in the JLS: https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.8 For fully qualified name, each part separated by a dot are checked. Keyword are also checked to prevent @interface to be considered as a regular annotation when it's more a class definition. Whitespace and comments in between type name are stripped to collect the right import in the end. Annotation between the package name and the class name are not supported but is not needed for import and annotation since they are forbidden by the compiler. Multi line stuff is handled using a list of tasks to process in the right order for the imports or annotations with two types of task: repeat and basic. This new system is now used in the marker comment detection and in the old generated code test. There's also a lot of tests to make sure no regression happen in the future that can be enabled by inluding the tag 'parser' (disabled in CI runner by default). Additionnaly marker stub are not anymore allowed by default while parsing import, annotation and metadata stuff but can be reenabled with the system property 'paper.generator.rewriter.searchMarkerInMetadata' for experimental/testing purpose. Short type name resolution (based on the collected imports) has been redone to supports static star import and inner class type referenced either implicitly or via import.
This commit is contained in:
parent
a479d759be
commit
31fbaaa0c4
|
@ -30,12 +30,25 @@ tasks.register<JavaExec>("generate") {
|
|||
project(":paper-server").sourceSets["main"].java.srcDirs.first().toString())
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
systemProperty("paper.generator.rewriter.container.api", file("generated").toString()) // todo move to the sourceset
|
||||
systemProperty("paper.generator.rewriter.container.server", file("generatedServerTest").toString()) // todo move to the sourceset
|
||||
inputs.dir("generated")
|
||||
inputs.dir("generatedServerTest")
|
||||
tasks {
|
||||
test {
|
||||
useJUnitPlatform {
|
||||
if (false && System.getenv()["CI"]?.toBoolean() == true) {
|
||||
// the CI shouldn't run the test since it's not included by default but just in case this is moved to its own repo
|
||||
excludeTags("parser")
|
||||
} else {
|
||||
// excludeTags("parser") // comment this line while working on parser related things
|
||||
}
|
||||
}
|
||||
systemProperty("paper.generator.rewriter.container.api", file("generated").toString()) // todo move to the sourceset
|
||||
systemProperty("paper.generator.rewriter.container.server", file("generatedServerTest").toString()) // todo move to the sourceset
|
||||
inputs.dir("generated")
|
||||
inputs.dir("generatedServerTest")
|
||||
}
|
||||
|
||||
compileTestJava {
|
||||
options.compilerArgs.add("-parameters")
|
||||
}
|
||||
}
|
||||
|
||||
group = "io.papermc.paper"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io.papermc.generator;
|
||||
|
||||
import io.papermc.generator.rewriter.CompositeRewriter;
|
||||
import io.papermc.generator.rewriter.replace.CompositeRewriter;
|
||||
import io.papermc.generator.rewriter.SourceRewriter;
|
||||
import io.papermc.generator.rewriter.types.EnumCloneRewriter;
|
||||
import io.papermc.generator.rewriter.types.EnumRegistryRewriter;
|
||||
|
@ -90,7 +90,6 @@ public interface Generators {
|
|||
}
|
||||
|
||||
SourceRewriter[] API_REWRITE = {
|
||||
//new EnumCloneRewriter(Pose.class, net.minecraft.world.entity.Pose.class, "Pose", false)
|
||||
new EnumRegistryRewriter<>(Fluid.class, Registries.FLUID, "Fluid", false),
|
||||
new EnumRegistryRewriter<>(Sound.class, Registries.SOUND_EVENT, "Sound", true) {
|
||||
@Override
|
||||
|
@ -191,7 +190,7 @@ public interface Generators {
|
|||
new RegistryFieldRewriter<>(Wolf.Variant.class, Registries.WOLF_VARIANT, "WolfVariant", "getVariant"),
|
||||
new MemoryKeyRewriter("MemoryKey"),
|
||||
new TagRewriter(Tag.class, "Tag"),
|
||||
new MapPaletteRewriter("MapPalette#colors"),
|
||||
new MapPaletteRewriter("MapPalette#colors")
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.papermc.generator.rewriter;
|
|||
|
||||
import io.papermc.generator.utils.ClassHelper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.util.Objects;
|
||||
|
||||
public record ClassNamed(String packageName, String simpleName, String dottedNestedName, @Nullable Class<?> knownClass) {
|
||||
|
||||
|
@ -42,6 +43,35 @@ public record ClassNamed(String packageName, String simpleName, String dottedNes
|
|||
return this;
|
||||
}
|
||||
|
||||
public boolean isRoot() {
|
||||
return this.root() == this;
|
||||
}
|
||||
|
||||
public ClassNamed parent() {
|
||||
if (this.knownClass != null) {
|
||||
Class<?> parentClass = this.knownClass.getEnclosingClass();
|
||||
if (parentClass == null) {
|
||||
return this;
|
||||
}
|
||||
return new ClassNamed(parentClass);
|
||||
}
|
||||
|
||||
int dotIndex = this.dottedNestedName.lastIndexOf('.');
|
||||
if (dotIndex != -1) {
|
||||
String name = this.dottedNestedName.substring(0, dotIndex);
|
||||
|
||||
int lastDotIndex = name.lastIndexOf('.');
|
||||
final String simpleName;
|
||||
if (lastDotIndex != -1) {
|
||||
simpleName = name.substring(lastDotIndex + 1);
|
||||
} else {
|
||||
simpleName = name; // root
|
||||
}
|
||||
return new ClassNamed(this.packageName, simpleName, name, null);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public String canonicalName() {
|
||||
if (this.knownClass != null) {
|
||||
return this.knownClass.getCanonicalName();
|
||||
|
@ -50,4 +80,28 @@ public record ClassNamed(String packageName, String simpleName, String dottedNes
|
|||
return this.packageName + '.' + this.dottedNestedName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (this.knownClass != null) {
|
||||
return this.knownClass.hashCode();
|
||||
}
|
||||
return Objects.hash(this.packageName, this.dottedNestedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || o.getClass() != this.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ClassNamed other = (ClassNamed) o;
|
||||
if (this.knownClass != null && other.knownClass != null) {
|
||||
return this.knownClass == other.knownClass;
|
||||
}
|
||||
return this.packageName.equals(other.packageName) &&
|
||||
this.dottedNestedName.equals(other.dottedNestedName);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,213 +0,0 @@
|
|||
package io.papermc.generator.rewriter;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Sets;
|
||||
import io.papermc.generator.Main;
|
||||
import io.papermc.generator.rewriter.utils.Annotations;
|
||||
import io.papermc.generator.rewriter.context.ImportCollector;
|
||||
import io.papermc.generator.rewriter.context.ImportTypeCollector;
|
||||
import io.papermc.generator.utils.ClassHelper;
|
||||
import io.papermc.generator.utils.Formatting;
|
||||
import io.papermc.paper.generated.GeneratedFrom;
|
||||
import net.minecraft.SharedConstants;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class SearchReplaceRewriter implements SourceRewriter {
|
||||
|
||||
@VisibleForTesting
|
||||
public static final String INDENT_UNIT = " ";
|
||||
@VisibleForTesting
|
||||
public static final String PAPER_START_FORMAT = "Paper start";
|
||||
private static final String PAPER_END_FORMAT = "Paper end";
|
||||
@VisibleForTesting
|
||||
public static final String GENERATED_COMMENT_FORMAT = "// %s - Generated/%s"; // {0} = PAPER_START_FORMAT|PAPER_END_FORMAT {1} = pattern
|
||||
|
||||
protected final ClassNamed rewriteClass;
|
||||
protected final String pattern;
|
||||
protected final boolean equalsSize;
|
||||
|
||||
public SearchReplaceRewriter(Class<?> rewriteClass, String pattern, boolean equalsSize) {
|
||||
this(new ClassNamed(rewriteClass), pattern, equalsSize);
|
||||
}
|
||||
|
||||
public SearchReplaceRewriter(ClassNamed rewriteClass, String pattern, boolean equalsSize) {
|
||||
this.rewriteClass = rewriteClass;
|
||||
this.pattern = pattern;
|
||||
this.equalsSize = equalsSize;
|
||||
}
|
||||
|
||||
// only when equalsSize = false
|
||||
@ApiStatus.OverrideOnly
|
||||
protected void insert(SearchMetadata metadata, StringBuilder builder) {}
|
||||
|
||||
// only when equalsSize = true
|
||||
@ApiStatus.OverrideOnly
|
||||
protected void replaceLine(SearchMetadata metadata, StringBuilder builder) {}
|
||||
|
||||
protected void beginSearch() {}
|
||||
|
||||
protected SearchReplaceRewriter getRewriterFor(String pattern) {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Set<String> getPatterns() {
|
||||
return Set.of(this.pattern);
|
||||
}
|
||||
|
||||
private void searchAndReplace(BufferedReader reader, StringBuilder content) throws IOException {
|
||||
Set<String> patterns = this.getPatterns();
|
||||
Preconditions.checkState(!patterns.isEmpty());
|
||||
this.beginSearch();
|
||||
|
||||
Set<String> foundPatterns = new HashSet<>();
|
||||
StringBuilder strippedContent = null;
|
||||
|
||||
final ImportCollector importCollector = new ImportTypeCollector(this.rewriteClass.root());
|
||||
String rootClassDeclaration = null;
|
||||
|
||||
if (this.rewriteClass.knownClass() != null) {
|
||||
Class<?> rootClass = ClassHelper.getRootClass(this.rewriteClass.knownClass());
|
||||
rootClassDeclaration = "%s %s".formatted(ClassHelper.getDeclaredType(rootClass), this.rewriteClass.root().simpleName()); // this should be improved to take in account server gen and comments
|
||||
}
|
||||
|
||||
String indent = Formatting.incrementalIndent(INDENT_UNIT, this.rewriteClass);
|
||||
SearchReplaceRewriter foundRewriter = null;
|
||||
boolean inBody = false;
|
||||
int i = 0;
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
// collect import to avoid fqn when not needed
|
||||
if (importCollector != ImportCollector.NO_OP && !inBody) {
|
||||
if (line.startsWith("import ") && line.endsWith(";")) {
|
||||
importCollector.consume(line);
|
||||
}
|
||||
if (rootClassDeclaration != null && line.contains(rootClassDeclaration)) { // might fail on comments but good enough
|
||||
inBody = true;
|
||||
}
|
||||
}
|
||||
|
||||
Optional<String> endPattern = this.searchPattern(line, indent, PAPER_END_FORMAT, patterns);
|
||||
if (endPattern.isPresent()) {
|
||||
if (foundRewriter == null) {
|
||||
throw new IllegalStateException("Start generated comment missing for pattern " + endPattern.get() + " in class " + this.rewriteClass.simpleName() + " at line " + (i + 1));
|
||||
}
|
||||
if (foundRewriter.pattern.equals(endPattern.get())) {
|
||||
if (!foundRewriter.equalsSize) {
|
||||
appendGeneratedComment(content, indent);
|
||||
|
||||
foundRewriter.insert(new SearchMetadata(importCollector, indent, strippedContent.toString(), i), content);
|
||||
strippedContent = null;
|
||||
}
|
||||
foundRewriter = null;
|
||||
} else {
|
||||
throw new IllegalStateException("End generated comment doesn't match for pattern " + foundRewriter.pattern + " in " + this.rewriteClass.simpleName() + " at line " + (i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if (foundRewriter == null) {
|
||||
content.append(line);
|
||||
content.append('\n');
|
||||
} else {
|
||||
if (foundRewriter.equalsSize) {
|
||||
// there's no generated comment here since when the size is equals the replaced content doesn't depend on the game content
|
||||
// if it does that means the replaced content might not be equals during MC update because of adding/removed content
|
||||
foundRewriter.replaceLine(new SearchMetadata(importCollector, indent, line, i), content);
|
||||
} else {
|
||||
strippedContent.append(line);
|
||||
strippedContent.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
Optional<String> startPattern = this.searchPattern(line, null, PAPER_START_FORMAT, patterns);
|
||||
if (startPattern.isPresent()) {
|
||||
if (foundRewriter != null) {
|
||||
throw new IllegalStateException("Nested generated comments are not allowed for " + this.rewriteClass.simpleName() + " at line " + (i + 1));
|
||||
}
|
||||
int startPatternIndex = line.indexOf(GENERATED_COMMENT_FORMAT.formatted(PAPER_START_FORMAT, startPattern.get()));
|
||||
indent = " ".repeat(startPatternIndex); // update indent based on the comments for flexibility
|
||||
if (indent.length() % INDENT_UNIT.length() != 0) {
|
||||
throw new IllegalStateException("Start generated comment is not properly indented at line " + (i + 1));
|
||||
}
|
||||
|
||||
foundRewriter = this.getRewriterFor(startPattern.get());
|
||||
foundPatterns.add(foundRewriter.pattern);
|
||||
if (!foundRewriter.equalsSize) {
|
||||
strippedContent = new StringBuilder();
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (foundRewriter != null) {
|
||||
throw new IllegalStateException("End generated comment " + foundRewriter.pattern + " missing for " + this.rewriteClass.simpleName());
|
||||
}
|
||||
|
||||
Set<String> diff = Sets.difference(patterns, foundPatterns);
|
||||
if (!diff.isEmpty()) {
|
||||
throw new IllegalStateException("SRT didn't found some expected generated comments: " + diff.toString());
|
||||
}
|
||||
}
|
||||
|
||||
protected String getFilePath() {
|
||||
return "%s/%s.java".formatted(
|
||||
this.rewriteClass.packageName().replace('.', '/'),
|
||||
this.rewriteClass.root().simpleName()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToFile(Path parent) throws IOException {
|
||||
String filePath = this.getFilePath();
|
||||
|
||||
Path path = parent.resolve(filePath);
|
||||
StringBuilder content = new StringBuilder();
|
||||
try (BufferedReader buffer = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
this.searchAndReplace(buffer, content);
|
||||
}
|
||||
|
||||
// Files.writeString(path, content.toString(), StandardCharsets.UTF_8); // todo
|
||||
Path createdPath;
|
||||
if (path.toString().contains("Paper/Paper-API/src/")) {
|
||||
createdPath = Main.generatedPath.resolve(filePath);
|
||||
} else {
|
||||
createdPath = Main.generatedServerPath.resolve(filePath);
|
||||
}
|
||||
Files.createDirectories(createdPath.getParent());
|
||||
Files.writeString(createdPath, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private void appendGeneratedComment(StringBuilder builder, String indent) {
|
||||
builder.append(indent).append("// %s %s".formatted(
|
||||
Annotations.annotationStyle(GeneratedFrom.class),
|
||||
SharedConstants.getCurrentVersion().getName()
|
||||
));
|
||||
builder.append('\n');
|
||||
}
|
||||
|
||||
private Optional<String> searchPattern(String rawLine, @Nullable String indent, String prefix, Set<String> patterns) {
|
||||
boolean strict = indent != null;
|
||||
String line = strict ? rawLine : rawLine.stripLeading();
|
||||
|
||||
for (String pattern : patterns) {
|
||||
String comment = GENERATED_COMMENT_FORMAT.formatted(prefix, pattern);
|
||||
if (strict ? line.equals(indent + comment) : line.equals(comment)) {
|
||||
return Optional.of(pattern);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
|
@ -1,28 +1,42 @@
|
|||
package io.papermc.generator.rewriter.context;
|
||||
|
||||
import io.papermc.generator.rewriter.ClassNamed;
|
||||
|
||||
public interface ImportCollector {
|
||||
|
||||
ImportCollector NO_OP = new ImportCollector() {
|
||||
@Override
|
||||
public String getStaticAlias(final String fqn) {
|
||||
return fqn;
|
||||
public void addImport(final String typeName) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName(final Class<?> clazz) {
|
||||
return clazz.getCanonicalName();
|
||||
public void addStaticImport(final String typeName) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(final String line) {
|
||||
|
||||
public String getStaticMemberShortName(final String fullName) {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName(final ClassNamed type) {
|
||||
return type.canonicalName();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
String getStaticAlias(String fqn);
|
||||
void addImport(String typeName);
|
||||
|
||||
String getTypeName(Class<?> clazz);
|
||||
void addStaticImport(String typeName);
|
||||
|
||||
void consume(String line);
|
||||
String getStaticMemberShortName(String fullName);
|
||||
|
||||
default String getShortName(Class<?> type) {
|
||||
return this.getShortName(new ClassNamed(type));
|
||||
}
|
||||
|
||||
String getShortName(ClassNamed type);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package io.papermc.generator.rewriter.context;
|
||||
|
||||
import io.papermc.generator.rewriter.ClassNamed;
|
||||
import io.papermc.generator.utils.ClassHelper;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import io.papermc.generator.rewriter.parser.StringReader;
|
||||
import io.papermc.generator.utils.Formatting;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
@ -10,11 +13,13 @@ import java.util.Set;
|
|||
|
||||
public class ImportTypeCollector implements ImportCollector {
|
||||
|
||||
private final Map<Class<?>, String> typeCache = new HashMap<>();
|
||||
private final Map<ClassNamed, String> typeCache = new HashMap<>();
|
||||
|
||||
private final Set<String> imports = new HashSet<>();
|
||||
private final Set<String> globalImports = new HashSet<>();
|
||||
private final Map<String, String> staticImports = new HashMap<>(); // <fqn.alias:alias>
|
||||
|
||||
private final Map<String, String> staticImports = new HashMap<>(); // <fqn.id:id>
|
||||
private final Set<String> globalStaticImports = new HashSet<>();
|
||||
|
||||
private final ClassNamed rewriteClass;
|
||||
|
||||
|
@ -22,56 +27,130 @@ public class ImportTypeCollector implements ImportCollector {
|
|||
this.rewriteClass = rewriteClass;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void addImport(String fqn) {
|
||||
if (fqn.endsWith("*")) {
|
||||
this.globalImports.add(fqn.substring(0, fqn.lastIndexOf('.')));
|
||||
@Override
|
||||
public void addImport(String typeName) {
|
||||
if (typeName.endsWith("*")) {
|
||||
this.globalImports.add(typeName.substring(0, typeName.lastIndexOf('.')));
|
||||
} else {
|
||||
this.imports.add(fqn);
|
||||
this.imports.add(typeName);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void addStaticImport(String fqn) { // todo support star import?
|
||||
this.staticImports.put(fqn, fqn.substring(fqn.lastIndexOf('.') + 1));
|
||||
@Override
|
||||
public void addStaticImport(String typeName) {
|
||||
if (typeName.endsWith("*")) {
|
||||
this.globalStaticImports.add(typeName.substring(0, typeName.lastIndexOf('.')));
|
||||
} else {
|
||||
this.staticImports.put(typeName, typeName.substring(typeName.lastIndexOf('.') + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStaticAlias(String fqn) {
|
||||
return this.staticImports.getOrDefault(fqn, fqn);
|
||||
public String getStaticMemberShortName(String fullName) {
|
||||
if (this.staticImports.containsKey(fullName)) {
|
||||
return this.staticImports.get(fullName);
|
||||
}
|
||||
|
||||
// global imports
|
||||
int lastDotIndex = fullName.lastIndexOf('.');
|
||||
if (lastDotIndex == -1) {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
String parentCanonicalName = fullName.substring(0, lastDotIndex);
|
||||
if (this.globalStaticImports.contains(parentCanonicalName)) {
|
||||
return fullName.substring(lastDotIndex + 1);
|
||||
}
|
||||
return fullName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName(Class<?> clazz) {
|
||||
return this.typeCache.computeIfAbsent(clazz, type -> {
|
||||
Class<?> rootClass = ClassHelper.getRootClass(type);
|
||||
final String typeName;
|
||||
if (this.imports.contains(rootClass.getName()) ||
|
||||
type.getPackageName().equals(this.rewriteClass.packageName()) || // same package don't need fqn too (include self class too)
|
||||
this.globalImports.contains(type.getPackageName())) { // star import
|
||||
typeName = ClassHelper.retrieveFullNestedName(type);
|
||||
} else {
|
||||
typeName = type.getCanonicalName();
|
||||
private String getShortName0(ClassNamed type, Set<String> imports, Set<String> globalImports, boolean fetchStatic) {
|
||||
ClassNamed foundClass = type;
|
||||
int advancedNode = 0;
|
||||
while (!imports.contains(foundClass.canonicalName()) &&
|
||||
!globalImports.contains(foundClass.parent().canonicalName())) {
|
||||
if (foundClass.isRoot() || // top classes with package check is handled before
|
||||
(fetchStatic && !Modifier.isStatic(foundClass.knownClass().getModifiers())) // static imports are allowed for regular class too but only when the inner classes are all static
|
||||
) {
|
||||
foundClass = null;
|
||||
break;
|
||||
}
|
||||
foundClass = foundClass.parent();
|
||||
advancedNode++;
|
||||
}
|
||||
if (foundClass != null) {
|
||||
String typeName;
|
||||
if (advancedNode > 0) { // direct inner class import
|
||||
String originalNestedName = type.dottedNestedName();
|
||||
int skipNode = Formatting.countOccurrences(originalNestedName, '.') - advancedNode;
|
||||
StringReader reader = new StringReader(originalNestedName);
|
||||
while (skipNode > 0) {
|
||||
reader.skipString('.');
|
||||
reader.skip(); // skip dot
|
||||
skipNode--;
|
||||
}
|
||||
typeName = reader.readRemainingString();
|
||||
} else {
|
||||
typeName = type.simpleName();
|
||||
}
|
||||
|
||||
return typeName;
|
||||
}
|
||||
|
||||
return type.canonicalName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName(ClassNamed type) {
|
||||
return this.typeCache.computeIfAbsent(type, key -> {
|
||||
if (key.knownClass() != null && Modifier.isStatic(key.knownClass().getModifiers())) {
|
||||
// this is only supported when the class is known for now but generally static imports should stick to member of class not the class itself
|
||||
String name = getShortName0(key, this.staticImports.keySet(), this.globalStaticImports, true);
|
||||
if (!name.equals(key.canonicalName())) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
if ((key.parent().isRoot() && this.globalImports.contains(key.packageName())) || // star import on package for top classes and one level classes only!
|
||||
(key.isRoot() && key.packageName().equals(this.rewriteClass.packageName()))) { // same package don't need fqn too for top classes
|
||||
return key.dottedNestedName();
|
||||
}
|
||||
|
||||
// self classes (with inner classes)
|
||||
// todo rework this logic order should be smth like: root stuff -> regular import -> inner stuff (-> static import if valid)
|
||||
// and remove the implicit part too
|
||||
Set<String> currentImports = this.imports;
|
||||
if (key.packageName().equals(this.rewriteClass.packageName()) &&
|
||||
this.rewriteClass.root().equals(key.root())) {
|
||||
int depth = Formatting.countOccurrences(key.dottedNestedName(), '.');
|
||||
int fromDepth = Formatting.countOccurrences(this.rewriteClass.dottedNestedName(), '.');
|
||||
if (fromDepth < depth) {
|
||||
ClassNamed parent = key;
|
||||
while (true) {
|
||||
ClassNamed up = parent.parent();
|
||||
if (this.rewriteClass.equals(up)) {
|
||||
break;
|
||||
}
|
||||
parent = up;
|
||||
}
|
||||
currentImports = new HashSet<>(this.imports);
|
||||
currentImports.add(parent.canonicalName()); // implicit import
|
||||
} else {
|
||||
return type.simpleName();
|
||||
}
|
||||
}
|
||||
|
||||
return getShortName0(key, currentImports, this.globalImports, false);
|
||||
});
|
||||
}
|
||||
|
||||
private void addImportLine(String importLine) {
|
||||
if (importLine.startsWith("import static ")) {
|
||||
addStaticImport(importLine.substring("import static ".length()));
|
||||
} else {
|
||||
addImport(importLine.substring("import ".length()));
|
||||
}
|
||||
@VisibleForTesting
|
||||
public Pair<Set<String>, Set<String>> getImports() {
|
||||
return Pair.of(this.imports, this.globalImports);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(String line) {
|
||||
for (String rawImport : line.split(";")) {
|
||||
String importLine = rawImport.trim();
|
||||
if (importLine.startsWith("import ")) {
|
||||
addImportLine(importLine);
|
||||
}
|
||||
}
|
||||
@VisibleForTesting
|
||||
public Pair<Set<String>, Set<String>> getStaticImports() {
|
||||
return Pair.of(this.staticImports.keySet(), this.globalStaticImports);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package io.papermc.generator.rewriter.parser;
|
||||
|
||||
public enum ClosureType {
|
||||
PARENTHESIS("(", ")"),
|
||||
CURLY_BRACKET("{", "}"),
|
||||
BRACKET("[", "]"),
|
||||
COMMENT("/*", "*/");
|
||||
|
||||
public final String start;
|
||||
public final String end;
|
||||
|
||||
ClosureType(String start, String end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package io.papermc.generator.rewriter.parser;
|
||||
|
||||
import io.papermc.generator.rewriter.context.ImportCollector;
|
||||
import io.papermc.generator.rewriter.parser.step.IterativeStep;
|
||||
import io.papermc.generator.rewriter.parser.step.StepHolder;
|
||||
import io.papermc.generator.rewriter.parser.step.factory.AnnotationSteps;
|
||||
import io.papermc.generator.rewriter.parser.step.factory.ImportSteps;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class LineParser {
|
||||
|
||||
private final Set<ClosureType> closures = EnumSet.noneOf(ClosureType.class);
|
||||
private final Deque<IterativeStep> steps = new ArrayDeque<>(10);
|
||||
|
||||
public boolean advanceEnclosure(ClosureType type, StringReader line) {
|
||||
boolean inside = this.closures.contains(type);
|
||||
String closure = !inside ? type.start : type.end;
|
||||
if (line.trySkipString(closure)) { // closure has been consumed
|
||||
if (!inside) {
|
||||
return this.closures.add(type);
|
||||
} else {
|
||||
return this.closures.remove(type);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean skipComment(StringReader line) {
|
||||
int previousCursor = line.getCursor();
|
||||
if (this.closures.contains(ClosureType.COMMENT) || this.advanceEnclosure(ClosureType.COMMENT, line)) { // open comment?
|
||||
while (!this.advanceEnclosure(ClosureType.COMMENT, line) && line.canRead()) { // closed comment?
|
||||
line.skip();
|
||||
}
|
||||
return line.getCursor() > previousCursor;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean skipCommentOrWhitespace(StringReader line) {
|
||||
boolean skipped = false;
|
||||
while (this.skipComment(line) || line.skipWhitespace() > 0) {
|
||||
skipped = true;
|
||||
}
|
||||
return skipped;
|
||||
}
|
||||
|
||||
public boolean trySkipCommentOrWhitespaceUntil(StringReader line, char terminator) {
|
||||
int previousCursor = line.getCursor();
|
||||
boolean skipped = this.skipCommentOrWhitespace(line);
|
||||
if (skipped && line.canRead() && line.peek() != terminator) {
|
||||
line.setCursor(previousCursor);
|
||||
skipped = false;
|
||||
}
|
||||
|
||||
return skipped;
|
||||
}
|
||||
|
||||
public boolean skipCommentOrWhitespaceInName(StringReader line, NameCursorState state) {
|
||||
if (state == NameCursorState.AFTER_DOT) {
|
||||
return this.skipCommentOrWhitespace(line);
|
||||
} else if (state == NameCursorState.INVALID_CHAR) { // this is tricky todo redo this part later
|
||||
boolean skipped = this.trySkipCommentOrWhitespaceUntil(line, '.');
|
||||
int previousCursor = line.getCursor();
|
||||
if (!skipped && this.skipCommentOrWhitespace(line) && line.canRead() && this.nextSingleLineComment(line)) {
|
||||
// ignore single line comment at the end of the name
|
||||
line.setCursor(line.getTotalLength());
|
||||
skipped = true;
|
||||
} else {
|
||||
line.setCursor(previousCursor);
|
||||
}
|
||||
return skipped;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean nextSingleLineComment(StringReader line) {
|
||||
return line.peek() == '/' && line.canRead(2) && line.peek(1) == '/';
|
||||
}
|
||||
|
||||
public boolean consumeImports(StringReader line, ImportCollector collector) {
|
||||
outerLoop:
|
||||
while (line.canRead()) {
|
||||
IterativeStep step;
|
||||
while ((step = this.steps.poll()) != null) {
|
||||
step.run(line, this);
|
||||
if (!line.canRead()) {
|
||||
break outerLoop;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.skipCommentOrWhitespace(line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.nextSingleLineComment(line)) {
|
||||
// check single line comment only after multi line to avoid ignoring the end of multi line comment starting with // on the newline
|
||||
break;
|
||||
}
|
||||
|
||||
// not commented
|
||||
char c = line.peek();
|
||||
if (AnnotationSteps.canStart(c)) { // handle annotation with param to avoid open curly bracket that occur in array argument
|
||||
this.enqueue(new AnnotationSteps());
|
||||
continue;
|
||||
} else if (c == '{') {
|
||||
return true;
|
||||
} else if (ImportSteps.canStart(line)) {
|
||||
this.enqueue(new ImportSteps(collector));
|
||||
continue;
|
||||
}
|
||||
|
||||
line.skip();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void enqueue(StepHolder holder) {
|
||||
for (IterativeStep step : holder.initialSteps()) {
|
||||
if (!this.steps.offerLast(step)) {
|
||||
throw new IllegalStateException("Cannot add a step into the queue!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addPriorityStep(IterativeStep step) {
|
||||
if (!this.steps.offerFirst(step)) {
|
||||
throw new IllegalStateException("Cannot add a priority step into the queue!");
|
||||
}
|
||||
}
|
||||
|
||||
public void clearRemainingSteps() {
|
||||
this.steps.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.papermc.generator.rewriter.parser;
|
||||
|
||||
public enum NameCursorState {
|
||||
AFTER_DOT,
|
||||
INVALID_CHAR
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package io.papermc.generator.rewriter.parser;
|
||||
|
||||
public class ProtoTypeName {
|
||||
|
||||
private final String initialName;
|
||||
private StringBuilder currentName;
|
||||
|
||||
private ProtoTypeName(String name) {
|
||||
this.initialName = name;
|
||||
}
|
||||
|
||||
public void append(String part) {
|
||||
if (this.currentName == null) {
|
||||
this.currentName = new StringBuilder(this.initialName);
|
||||
}
|
||||
this.currentName.append(part);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.currentName != null ? this.currentName.toString() : this.initialName;
|
||||
}
|
||||
|
||||
public boolean shouldCheckStartIdentifier() {
|
||||
if (this.currentName != null) {
|
||||
return this.currentName.isEmpty() || this.currentName.lastIndexOf(".") == this.currentName.length() - 1;
|
||||
}
|
||||
return this.initialName.isEmpty() || this.initialName.lastIndexOf('.') == this.initialName.length() - 1;
|
||||
}
|
||||
|
||||
public static ProtoTypeName create(String name) {
|
||||
return new ProtoTypeName(name);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
package io.papermc.generator.rewriter.parser;
|
||||
|
||||
import com.mojang.brigadier.ImmutableStringReader;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
// based on brigadier string reader with some extra/removed features for rewriter
|
||||
public class StringReader implements ImmutableStringReader {
|
||||
|
||||
private final String string;
|
||||
private int cursor;
|
||||
|
||||
public StringReader(final StringReader other) {
|
||||
this.string = other.string;
|
||||
this.cursor = other.cursor;
|
||||
}
|
||||
|
||||
public StringReader(final String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setCursor(final int cursor) {
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemainingLength() {
|
||||
return string.length() - cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalLength() {
|
||||
return string.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCursor() {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRead() {
|
||||
return string.substring(0, cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemaining() {
|
||||
return string.substring(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(final int length) {
|
||||
return cursor + length <= string.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
return canRead(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char peek() {
|
||||
return string.charAt(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char peek(final int offset) {
|
||||
return string.charAt(cursor + offset);
|
||||
}
|
||||
|
||||
public char read() {
|
||||
return string.charAt(cursor++);
|
||||
}
|
||||
|
||||
public void skip() {
|
||||
cursor++;
|
||||
}
|
||||
|
||||
// new features
|
||||
|
||||
public int peekPoint() {
|
||||
return this.string.codePointAt(this.cursor);
|
||||
}
|
||||
|
||||
public int skipWhitespace() {
|
||||
int i = 0;
|
||||
while (canRead() && Character.isWhitespace(peek())) {
|
||||
this.skip();
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
public int skipChars(final char value) {
|
||||
int i = 0;
|
||||
while (this.canRead() && this.peek() == value) {
|
||||
this.skip();
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
public void skipString(final char terminator) {
|
||||
while (this.canRead() && this.peek() != terminator) {
|
||||
this.skip();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean trySkipWhitespace(final int size) {
|
||||
int delta = 0;
|
||||
int previousCursor = this.cursor;
|
||||
while (delta < size && this.canRead() && Character.isWhitespace(this.peek())) {
|
||||
this.skip();
|
||||
delta++;
|
||||
}
|
||||
if (delta == size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.setCursor(previousCursor);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean trySkipChars(final int size, final char value) {
|
||||
int delta = 0;
|
||||
int previousCursor = this.cursor;
|
||||
while (delta < size && this.canRead() && this.peek() == value) {
|
||||
this.skip();
|
||||
delta++;
|
||||
}
|
||||
if (delta == size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.setCursor(previousCursor);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean trySkipString(final String value) {
|
||||
char[] chars = value.toCharArray();
|
||||
int delta = 0;
|
||||
int previousCursor = this.cursor;
|
||||
while (this.canRead() && delta < chars.length && chars[delta] == this.peek()) {
|
||||
this.skip();
|
||||
delta++;
|
||||
}
|
||||
if (delta == chars.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.setCursor(previousCursor);
|
||||
return false;
|
||||
}
|
||||
|
||||
public String readRemainingString() {
|
||||
final int start = this.cursor;
|
||||
this.cursor = this.getTotalLength();
|
||||
return this.string.substring(start);
|
||||
}
|
||||
|
||||
public String readStringUntil(final char terminator) {
|
||||
final int start = this.cursor;
|
||||
this.skipString(terminator);
|
||||
return this.string.substring(start, this.cursor);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getStringUntil(final char terminator) {
|
||||
final int start = this.cursor;
|
||||
this.skipString(terminator);
|
||||
if (!this.canRead()) {
|
||||
return null;
|
||||
}
|
||||
return this.string.substring(start, this.cursor);
|
||||
}
|
||||
|
||||
// cleaner is used to skip stuff like : net/* hi */./**/kyori.adventure.translation/**/.Translatable within the type name
|
||||
public String getPartNameUntil(final char terminator, final BiPredicate<StringReader, NameCursorState> cleaner, final boolean forImport, final BooleanSupplier checkStartGetter) { // this break the concept of this a class a bit but it's not worth making a code point equivalent for only this method
|
||||
boolean hasCleaner = cleaner != null;
|
||||
boolean checkStart = checkStartGetter.getAsBoolean();
|
||||
StringBuilder name = new StringBuilder();
|
||||
while (this.canRead()) {
|
||||
int c = this.peekPoint();
|
||||
if (c == terminator) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (checkStart) { // had a dot before
|
||||
if (hasCleaner && cleaner.test(this, NameCursorState.AFTER_DOT)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isJavaIdChar = checkStart ? Character.isJavaIdentifierStart(c) : Character.isJavaIdentifierPart(c);
|
||||
if (!isJavaIdChar && (checkStart || c != '.') && !(c == '*' && forImport)) { // star should be allowed only at the end for import todo
|
||||
if (hasCleaner && cleaner.test(this, NameCursorState.INVALID_CHAR)) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
char[] chars = Character.toChars(c);
|
||||
name.append(chars);
|
||||
this.cursor += chars.length;
|
||||
checkStart = c == '.';
|
||||
}
|
||||
return name.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package io.papermc.generator.rewriter.parser.step;
|
||||
|
||||
import io.papermc.generator.rewriter.parser.LineParser;
|
||||
import io.papermc.generator.rewriter.parser.StringReader;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class BasicIterativeStep implements IterativeStep {
|
||||
|
||||
private final BiConsumer<StringReader, LineParser> runner;
|
||||
|
||||
protected BasicIterativeStep(BiConsumer<StringReader, LineParser> runner) {
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(StringReader line, LineParser parser) {
|
||||
this.runner.accept(line, parser);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package io.papermc.generator.rewriter.parser.step;
|
||||
|
||||
import io.papermc.generator.rewriter.parser.LineParser;
|
||||
import io.papermc.generator.rewriter.parser.StringReader;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
public interface IterativeStep {
|
||||
|
||||
void run(StringReader line, LineParser parser);
|
||||
|
||||
static IterativeStep create(BiConsumer<StringReader, LineParser> runner) {
|
||||
return new BasicIterativeStep(runner);
|
||||
}
|
||||
|
||||
static IterativeStep createUntil(BiPredicate<StringReader, LineParser> runner) {
|
||||
return new RepeatIterativeStep(runner);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package io.papermc.generator.rewriter.parser.step;
|
||||
|
||||
import io.papermc.generator.rewriter.parser.LineParser;
|
||||
import io.papermc.generator.rewriter.parser.StringReader;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
public class RepeatIterativeStep implements IterativeStep {
|
||||
|
||||
private final BiPredicate<StringReader, LineParser> runner;
|
||||
|
||||
protected RepeatIterativeStep(BiPredicate<StringReader, LineParser> runner) {
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(StringReader line, LineParser parser) {
|
||||
if (this.runner.test(line, parser)) {
|
||||
parser.addPriorityStep(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.papermc.generator.rewriter.parser.step;
|
||||
|
||||
public interface StepHolder {
|
||||
|
||||
IterativeStep[] initialSteps();
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package io.papermc.generator.rewriter.parser.step.factory;
|
||||
|
||||
import io.papermc.generator.rewriter.parser.ClosureType;
|
||||
import io.papermc.generator.rewriter.parser.LineParser;
|
||||
import io.papermc.generator.rewriter.parser.ProtoTypeName;
|
||||
import io.papermc.generator.rewriter.parser.step.IterativeStep;
|
||||
import io.papermc.generator.rewriter.parser.step.StepHolder;
|
||||
import io.papermc.generator.rewriter.parser.StringReader;
|
||||
import io.papermc.generator.utils.NamingManager;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
// start once "@" is detected unless commented
|
||||
// order is: skipAtSign -> skipPartName -> checkOpenParenthesis (-> skipParentheses)
|
||||
public final class AnnotationSteps implements StepHolder {
|
||||
|
||||
public static boolean canStart(char currentChar) {
|
||||
return currentChar == '@';
|
||||
}
|
||||
|
||||
private final IterativeStep skipParenthesesStep = IterativeStep.createUntil(this::skipParentheses);
|
||||
|
||||
private @MonotonicNonNull ProtoTypeName name;
|
||||
|
||||
public void skipAtSign(StringReader line, LineParser parser) {
|
||||
line.skip(); // skip @
|
||||
}
|
||||
|
||||
public boolean skipPartName(StringReader line, LineParser parser) {
|
||||
boolean checkStartId = this.name == null || this.name.shouldCheckStartIdentifier();
|
||||
|
||||
if (!checkStartId) { // this part is not in the import steps since import always need a semicolon at their end so it's easier to parse them
|
||||
if (!parser.trySkipCommentOrWhitespaceUntil(line, '.')) { // expect a dot for multi line annotation when the previous line doesn't end by a dot itself
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
parser.skipCommentOrWhitespace(line);
|
||||
}
|
||||
if (!line.canRead()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String name = line.getPartNameUntil('(', parser::skipCommentOrWhitespaceInName, false,
|
||||
() -> checkStartId);
|
||||
|
||||
if (line.canRead() && parser.nextSingleLineComment(line)) {
|
||||
// ignore single line comment at the end and allow the name to continue
|
||||
line.setCursor(line.getTotalLength());
|
||||
}
|
||||
|
||||
if (this.name == null) {
|
||||
this.name = ProtoTypeName.create(name);
|
||||
} else {
|
||||
this.name.append(name);
|
||||
}
|
||||
return !line.canRead();
|
||||
}
|
||||
|
||||
public boolean skipParentheses(StringReader line, LineParser parser) {
|
||||
while (!parser.advanceEnclosure(ClosureType.PARENTHESIS, line)) { // closed parenthesis?
|
||||
if (!line.canRead()) { // parenthesis on another line?
|
||||
return true;
|
||||
}
|
||||
line.skip();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// filter out @interface
|
||||
public void checkAnnotationName(StringReader line, LineParser parser) {
|
||||
String name = this.name.getName();
|
||||
if (name.isEmpty() || NamingManager.hasIllegalKeyword(name)) { // keyword are checked after to simplify things
|
||||
parser.clearRemainingSteps();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkOpenParenthesis(StringReader line, LineParser parser) {
|
||||
parser.skipCommentOrWhitespace(line); // since skipPartName fail fast this is needed for space between the typeName and the parenthesis
|
||||
if (!line.canRead()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parser.advanceEnclosure(ClosureType.PARENTHESIS, line)) { // open parenthesis?
|
||||
parser.addPriorityStep(this.skipParenthesesStep);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IterativeStep[] initialSteps() {
|
||||
return new IterativeStep[] {
|
||||
IterativeStep.create(this::skipAtSign),
|
||||
IterativeStep.createUntil(this::skipPartName),
|
||||
IterativeStep.create(this::checkAnnotationName),
|
||||
IterativeStep.createUntil(this::checkOpenParenthesis),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package io.papermc.generator.rewriter.parser.step.factory;
|
||||
|
||||
import io.papermc.generator.rewriter.context.ImportCollector;
|
||||
import io.papermc.generator.rewriter.parser.LineParser;
|
||||
import io.papermc.generator.rewriter.parser.ProtoTypeName;
|
||||
import io.papermc.generator.rewriter.parser.step.IterativeStep;
|
||||
import io.papermc.generator.rewriter.parser.step.StepHolder;
|
||||
import io.papermc.generator.rewriter.parser.StringReader;
|
||||
import io.papermc.generator.utils.NamingManager;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
// start once "import" is detected unless commented
|
||||
// order is: enforceSpace -> checkStatic (-> enforceSpace) -> getPartName -> collectImport
|
||||
public final class ImportSteps implements StepHolder {
|
||||
|
||||
public static boolean canStart(StringReader line) {
|
||||
return line.trySkipString("import");
|
||||
}
|
||||
|
||||
private final IterativeStep enforceSpaceStep = IterativeStep.create(this::enforceSpace);
|
||||
|
||||
private final ImportCollector collector;
|
||||
private boolean isStatic;
|
||||
private @MonotonicNonNull ProtoTypeName name;
|
||||
|
||||
public ImportSteps(ImportCollector collector) {
|
||||
this.collector = collector;
|
||||
}
|
||||
|
||||
public void enforceSpace(StringReader line, LineParser parser) {
|
||||
if (line.canRead() && parser.nextSingleLineComment(line)) {
|
||||
// ignore single line comment at the end of import/static
|
||||
line.setCursor(line.getTotalLength());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parser.skipComment(line)) {
|
||||
if (line.skipWhitespace() == 0) { // expect at least one space between import, static and type name unless a multi comment is here to fill the gap
|
||||
parser.clearRemainingSteps();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkStatic(StringReader line, LineParser parser) {
|
||||
parser.skipCommentOrWhitespace(line);
|
||||
if (!line.canRead()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (line.trySkipString("static")) {
|
||||
parser.addPriorityStep(this.enforceSpaceStep);
|
||||
this.isStatic = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void collectImport(StringReader line, LineParser parser) {
|
||||
String name = this.name.getName();
|
||||
if (name.isEmpty() || NamingManager.hasIllegalKeyword(name)) { // keyword are checked after to simplify things
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isStatic) {
|
||||
this.collector.addStaticImport(name);
|
||||
} else {
|
||||
this.collector.addImport(name);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getPartName(StringReader line, LineParser parser) {
|
||||
parser.skipCommentOrWhitespace(line);
|
||||
if (!line.canRead()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String name = line.getPartNameUntil(';', parser::skipCommentOrWhitespaceInName, true,
|
||||
() -> this.name == null || this.name.shouldCheckStartIdentifier());
|
||||
|
||||
if (line.canRead() && parser.nextSingleLineComment(line)) {
|
||||
// ignore single line comment at the end of the name
|
||||
line.setCursor(line.getTotalLength());
|
||||
}
|
||||
|
||||
if (this.name == null) {
|
||||
this.name = ProtoTypeName.create(name);
|
||||
} else {
|
||||
this.name.append(name);
|
||||
}
|
||||
return !line.canRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IterativeStep[] initialSteps() {
|
||||
return new IterativeStep[] {
|
||||
this.enforceSpaceStep,
|
||||
IterativeStep.createUntil(this::checkStatic),
|
||||
IterativeStep.createUntil(this::getPartName),
|
||||
IterativeStep.create(this::collectImport)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.papermc.generator.rewriter.replace;
|
||||
|
||||
public record CommentMarker(String pattern, boolean start, int indent) {
|
||||
|
||||
private CommentMarker() {
|
||||
this("", false, 0);
|
||||
}
|
||||
|
||||
public static final CommentMarker EMPTY_MARKER = new CommentMarker();
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package io.papermc.generator.rewriter;
|
||||
package io.papermc.generator.rewriter.replace;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.generator.rewriter.ClassNamed;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -24,13 +25,23 @@ public class CompositeRewriter extends SearchReplaceRewriter {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVersionDependant() {
|
||||
for (SearchReplaceRewriter rewriter : this.getRewriters()) {
|
||||
if (rewriter.isVersionDependant()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SearchReplaceRewriter getRewriterFor(String pattern) {
|
||||
return this.rewriterByPattern.get(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getPatterns() {
|
||||
public Set<String> getPatterns() {
|
||||
return this.rewriterByPattern.keySet();
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.papermc.generator.rewriter;
|
||||
package io.papermc.generator.rewriter.replace;
|
||||
|
||||
import io.papermc.generator.rewriter.context.ImportCollector;
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
package io.papermc.generator.rewriter.replace;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.generator.Main;
|
||||
import io.papermc.generator.rewriter.ClassNamed;
|
||||
import io.papermc.generator.rewriter.SourceRewriter;
|
||||
import io.papermc.generator.rewriter.parser.LineParser;
|
||||
import io.papermc.generator.rewriter.utils.Annotations;
|
||||
import io.papermc.generator.rewriter.context.ImportCollector;
|
||||
import io.papermc.generator.rewriter.context.ImportTypeCollector;
|
||||
import io.papermc.generator.rewriter.parser.StringReader;
|
||||
import io.papermc.generator.utils.Formatting;
|
||||
import io.papermc.paper.generated.GeneratedFrom;
|
||||
import net.minecraft.SharedConstants;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static io.papermc.generator.rewriter.replace.CommentMarker.EMPTY_MARKER;
|
||||
|
||||
public class SearchReplaceRewriter implements SourceRewriter {
|
||||
|
||||
protected static final String INDENT_UNIT = " ";
|
||||
@VisibleForTesting
|
||||
public static final int INDENT_SIZE = INDENT_UNIT.length();
|
||||
@VisibleForTesting
|
||||
public static final char INDENT_CHAR = INDENT_UNIT.charAt(0);
|
||||
|
||||
@VisibleForTesting
|
||||
public static final String PAPER_START_FORMAT = "Paper start";
|
||||
private static final String PAPER_END_FORMAT = "Paper end";
|
||||
@VisibleForTesting
|
||||
public static final String GENERATED_COMMENT_FORMAT = "// %s - Generated/%s"; // {0} = PAPER_START_FORMAT|PAPER_END_FORMAT {1} = pattern
|
||||
|
||||
@ApiStatus.Experimental // import collector is probably outdated in that case and new typeName are not handled
|
||||
private static final boolean SEARCH_MARKER_IN_METADATA = Boolean.getBoolean("paper.generator.rewriter.searchMarkerInMetadata");
|
||||
|
||||
protected final ClassNamed rewriteClass;
|
||||
protected final String pattern;
|
||||
private final boolean exactReplacement;
|
||||
|
||||
public SearchReplaceRewriter(Class<?> rewriteClass, String pattern, boolean exactReplacement) {
|
||||
this(new ClassNamed(rewriteClass), pattern, exactReplacement);
|
||||
}
|
||||
|
||||
public SearchReplaceRewriter(ClassNamed rewriteClass, String pattern, boolean exactReplacement) {
|
||||
this.rewriteClass = rewriteClass;
|
||||
this.pattern = pattern;
|
||||
this.exactReplacement = exactReplacement;
|
||||
}
|
||||
|
||||
// only when exactReplacement = false
|
||||
@ApiStatus.OverrideOnly
|
||||
protected void insert(SearchMetadata metadata, StringBuilder builder) {
|
||||
throw new UnsupportedOperationException("This rewriter (" + this.getClass().getCanonicalName() + ") doesn't support removal and insertion!");
|
||||
}
|
||||
|
||||
// only when exactReplacement = true
|
||||
@ApiStatus.OverrideOnly
|
||||
protected void replaceLine(SearchMetadata metadata, StringBuilder builder) {
|
||||
throw new UnsupportedOperationException("This rewriter (" + this.getClass().getCanonicalName() + ") doesn't support exact replacement!");
|
||||
}
|
||||
|
||||
protected void beginSearch() {}
|
||||
|
||||
protected SearchReplaceRewriter getRewriterFor(String pattern) {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Set<String> getPatterns() {
|
||||
return Set.of(this.pattern);
|
||||
}
|
||||
|
||||
private void searchAndReplace(BufferedReader reader, StringBuilder content) throws IOException {
|
||||
Set<String> patterns = this.getPatterns();
|
||||
Preconditions.checkState(!patterns.isEmpty());
|
||||
this.beginSearch();
|
||||
|
||||
Set<String> remainingPatterns = new HashSet<>(patterns);
|
||||
StringBuilder strippedContent = null;
|
||||
|
||||
ClassNamed root = this.rewriteClass.root();
|
||||
final ImportCollector importCollector = new ImportTypeCollector(root);
|
||||
final LineParser lineParser = new LineParser();
|
||||
|
||||
String indent = Formatting.incrementalIndent(INDENT_UNIT, this.rewriteClass);
|
||||
SearchReplaceRewriter foundRewriter = null;
|
||||
boolean inBody = false;
|
||||
int i = 0;
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
StringReader lineIterator = new StringReader(line);
|
||||
// collect import to avoid fqn when not needed
|
||||
int previousCursor = lineIterator.getCursor();
|
||||
if (importCollector != ImportCollector.NO_OP && !inBody && !line.isEmpty()) {
|
||||
if (lineParser.consumeImports(lineIterator, importCollector)) {
|
||||
inBody = true;
|
||||
}
|
||||
}
|
||||
if (SEARCH_MARKER_IN_METADATA) {
|
||||
lineIterator.setCursor(previousCursor);
|
||||
}
|
||||
|
||||
CommentMarker marker = EMPTY_MARKER;
|
||||
if (!line.isEmpty() && (inBody || SEARCH_MARKER_IN_METADATA)) {
|
||||
marker = this.searchMarker(lineIterator, foundRewriter == null ? null : indent, remainingPatterns); // change this to patterns if needed to allow two rewrite of the same pattern in the same file
|
||||
}
|
||||
|
||||
if (marker != EMPTY_MARKER) {
|
||||
if (!marker.start()) {
|
||||
if (foundRewriter == null) {
|
||||
throw new IllegalStateException("Generated start comment is missing for pattern " + marker.pattern() + " in " + this.rewriteClass.canonicalName() + " at line " + (i + 1));
|
||||
}
|
||||
if (foundRewriter.pattern.equals(marker.pattern())) {
|
||||
if (!foundRewriter.exactReplacement) {
|
||||
appendGeneratedComment(content, indent);
|
||||
|
||||
foundRewriter.insert(new SearchMetadata(importCollector, indent, strippedContent.toString(), i), content);
|
||||
strippedContent = null;
|
||||
}
|
||||
remainingPatterns.remove(foundRewriter.pattern);
|
||||
foundRewriter = null;
|
||||
// regular line
|
||||
content.append(line);
|
||||
content.append('\n');
|
||||
} else {
|
||||
throw new IllegalStateException("Generated end comment doesn't match for pattern " + foundRewriter.pattern + " in " + this.rewriteClass.canonicalName() + " at line " + (i + 1));
|
||||
}
|
||||
} else {
|
||||
if (foundRewriter != null) {
|
||||
throw new IllegalStateException("Nested generated comments are not allowed for " + this.rewriteClass.canonicalName() + " at line " + (i + 1));
|
||||
}
|
||||
if (marker.indent() % INDENT_SIZE != 0) {
|
||||
throw new IllegalStateException("Generated start comment is not properly indented at line " + (i + 1) + " for pattern " + marker.pattern() + " in " + this.rewriteClass.canonicalName());
|
||||
}
|
||||
indent = " ".repeat(marker.indent()); // update indent based on the comments for flexibility
|
||||
|
||||
foundRewriter = this.getRewriterFor(marker.pattern());
|
||||
if (!foundRewriter.exactReplacement) {
|
||||
strippedContent = new StringBuilder();
|
||||
}
|
||||
// regular line
|
||||
content.append(line);
|
||||
content.append('\n');
|
||||
}
|
||||
} else {
|
||||
if (foundRewriter == null) {
|
||||
content.append(line);
|
||||
content.append('\n');
|
||||
} else {
|
||||
if (foundRewriter.exactReplacement) {
|
||||
// there's no generated comment here since when the size is equals the replaced content doesn't depend on the game content
|
||||
// if it does that means the replaced content might not be equals during MC update because of adding/removed content
|
||||
foundRewriter.replaceLine(new SearchMetadata(importCollector, indent, line, i), content);
|
||||
} else {
|
||||
strippedContent.append(line);
|
||||
strippedContent.append('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (foundRewriter != null) {
|
||||
throw new IllegalStateException("Generated end comment is missing for pattern " + foundRewriter.pattern + " in " + this.rewriteClass.canonicalName());
|
||||
}
|
||||
|
||||
if (!remainingPatterns.isEmpty()) {
|
||||
throw new IllegalStateException("SRT didn't found some expected generated comment for the following patterns: " + remainingPatterns.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public String getRelativeFilePath() {
|
||||
return "%s/%s.java".formatted(
|
||||
this.rewriteClass.packageName().replace('.', '/'),
|
||||
this.rewriteClass.root().simpleName()
|
||||
);
|
||||
}
|
||||
|
||||
public boolean isVersionDependant() {
|
||||
return !this.exactReplacement;
|
||||
}
|
||||
|
||||
public ClassNamed getRewrittenClass() {
|
||||
return this.rewriteClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToFile(Path parent) throws IOException {
|
||||
String filePath = this.getRelativeFilePath();
|
||||
|
||||
Path path = parent.resolve(filePath);
|
||||
StringBuilder content = new StringBuilder();
|
||||
try (BufferedReader buffer = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
this.searchAndReplace(buffer, content);
|
||||
}
|
||||
|
||||
// Files.writeString(path, content.toString(), StandardCharsets.UTF_8); // todo
|
||||
Path createdPath;
|
||||
if (this.rewriteClass.knownClass() != null) {
|
||||
createdPath = Main.generatedPath.resolve(filePath);
|
||||
} else {
|
||||
createdPath = Main.generatedServerPath.resolve(filePath);
|
||||
}
|
||||
Files.createDirectories(createdPath.getParent());
|
||||
Files.writeString(createdPath, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private void appendGeneratedComment(StringBuilder builder, String indent) {
|
||||
builder.append(indent).append("// %s %s".formatted(
|
||||
Annotations.annotationStyle(GeneratedFrom.class),
|
||||
SharedConstants.getCurrentVersion().getName()
|
||||
));
|
||||
builder.append('\n');
|
||||
}
|
||||
|
||||
public CommentMarker searchMarker(StringReader lineIterator, @Nullable String indent, @Nullable Set<String> patterns) {
|
||||
boolean strict = indent != null;
|
||||
final int indentSize;
|
||||
if (strict) {
|
||||
if (!lineIterator.trySkipChars(indent.length(), indent.charAt(0))) {
|
||||
return EMPTY_MARKER;
|
||||
}
|
||||
indentSize = indent.length();
|
||||
} else {
|
||||
indentSize = lineIterator.skipChars(INDENT_CHAR);
|
||||
}
|
||||
|
||||
boolean foundStart = lineIterator.trySkipString(GENERATED_COMMENT_FORMAT.formatted(PAPER_START_FORMAT, ""));
|
||||
boolean foundEnd = !foundStart && lineIterator.trySkipString(GENERATED_COMMENT_FORMAT.formatted(PAPER_END_FORMAT, ""));
|
||||
if (!foundStart && !foundEnd) {
|
||||
return EMPTY_MARKER;
|
||||
}
|
||||
|
||||
String pattern = lineIterator.readRemainingString();
|
||||
if (patterns == null || patterns.contains(pattern)) { // patterns will be null only for tests
|
||||
return new CommentMarker(pattern, foundStart, indentSize);
|
||||
}
|
||||
return EMPTY_MARKER;
|
||||
}
|
||||
}
|
|
@ -7,12 +7,12 @@ public class EnumCloneRewriter<T extends Enum<T>, A extends Enum<A>> extends Enu
|
|||
|
||||
private final Class<T> basedOn;
|
||||
|
||||
public EnumCloneRewriter(final Class<A> rewriteClass, final Class<T> basedOn, final String pattern, boolean equalsSize) {
|
||||
this(new ClassNamed(rewriteClass), basedOn, pattern, equalsSize);
|
||||
public EnumCloneRewriter(final Class<A> rewriteClass, final Class<T> basedOn, final String pattern, boolean exactReplacement) {
|
||||
this(new ClassNamed(rewriteClass), basedOn, pattern, exactReplacement);
|
||||
}
|
||||
|
||||
public EnumCloneRewriter(final ClassNamed rewriteClass, final Class<T> basedOn, final String pattern, boolean equalsSize) {
|
||||
super(rewriteClass, pattern, equalsSize);
|
||||
public EnumCloneRewriter(final ClassNamed rewriteClass, final Class<T> basedOn, final String pattern, boolean exactReplacement) {
|
||||
super(rewriteClass, pattern, exactReplacement);
|
||||
this.basedOn = basedOn;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.papermc.generator.rewriter.types;
|
|||
|
||||
import com.google.common.base.Suppliers;
|
||||
import io.papermc.generator.Main;
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.utils.Annotations;
|
||||
import io.papermc.generator.rewriter.ClassNamed;
|
||||
import io.papermc.generator.utils.Formatting;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.papermc.generator.rewriter.types;
|
||||
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.ClassNamed;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
@ -12,12 +12,12 @@ public abstract class EnumRewriter<T, A extends Enum<A>> extends SearchReplaceRe
|
|||
@MonotonicNonNull
|
||||
private Iterator<T> values;
|
||||
|
||||
protected EnumRewriter(final Class<A> rewriteClass, final String pattern, final boolean equalsSize) {
|
||||
super(rewriteClass, pattern, equalsSize);
|
||||
protected EnumRewriter(final Class<A> rewriteClass, final String pattern, final boolean exactReplacement) {
|
||||
super(rewriteClass, pattern, exactReplacement);
|
||||
}
|
||||
|
||||
protected EnumRewriter(final ClassNamed rewriteClass, final String pattern, final boolean equalsSize) {
|
||||
super(rewriteClass, pattern, equalsSize);
|
||||
protected EnumRewriter(final ClassNamed rewriteClass, final String pattern, final boolean exactReplacement) {
|
||||
super(rewriteClass, pattern, exactReplacement);
|
||||
}
|
||||
|
||||
protected abstract Iterable<T> getValues();
|
||||
|
|
|
@ -3,8 +3,8 @@ package io.papermc.generator.rewriter.types;
|
|||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Suppliers;
|
||||
import io.papermc.generator.Main;
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.utils.Annotations;
|
||||
import io.papermc.generator.utils.Formatting;
|
||||
import io.papermc.generator.utils.RegistryUtils;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.papermc.generator.rewriter.types;
|
||||
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.ClassNamed;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import java.util.Iterator;
|
||||
|
@ -11,12 +11,12 @@ public abstract class SwitchCaseRewriter extends SearchReplaceRewriter {
|
|||
@MonotonicNonNull
|
||||
private Iterator<String> cases;
|
||||
|
||||
protected SwitchCaseRewriter(final Class<?> rewriteClass, final String pattern, final boolean equalsSize) {
|
||||
super(rewriteClass, pattern, equalsSize);
|
||||
protected SwitchCaseRewriter(final Class<?> rewriteClass, final String pattern, final boolean exactReplacement) {
|
||||
super(rewriteClass, pattern, exactReplacement);
|
||||
}
|
||||
|
||||
protected SwitchCaseRewriter(final ClassNamed rewriteClass, final String pattern, final boolean equalsSize) {
|
||||
super(rewriteClass, pattern, equalsSize);
|
||||
protected SwitchCaseRewriter(final ClassNamed rewriteClass, final String pattern, final boolean exactReplacement) {
|
||||
super(rewriteClass, pattern, exactReplacement);
|
||||
}
|
||||
|
||||
protected abstract Iterable<String> getCases();
|
||||
|
|
|
@ -2,8 +2,8 @@ package io.papermc.generator.rewriter.types;
|
|||
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.papermc.generator.rewriter.ClassNamed;
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchReplaceRewriter;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
@ -12,12 +12,12 @@ public abstract class SwitchRewriter<T> extends SearchReplaceRewriter {
|
|||
|
||||
protected @MonotonicNonNull Return<T> defaultValue;
|
||||
|
||||
protected SwitchRewriter(final Class<?> rewriteClass, final String pattern, final boolean equalsSize) {
|
||||
super(rewriteClass, pattern, equalsSize);
|
||||
protected SwitchRewriter(final Class<?> rewriteClass, final String pattern, final boolean exactReplacement) {
|
||||
super(rewriteClass, pattern, exactReplacement);
|
||||
}
|
||||
|
||||
protected SwitchRewriter(final ClassNamed rewriteClass, final String pattern, final boolean equalsSize) {
|
||||
super(rewriteClass, pattern, equalsSize);
|
||||
protected SwitchRewriter(final ClassNamed rewriteClass, final String pattern, final boolean exactReplacement) {
|
||||
super(rewriteClass, pattern, exactReplacement);
|
||||
}
|
||||
|
||||
protected record Return<T>(T object, String code) {}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package io.papermc.generator.rewriter.types;
|
||||
|
||||
import io.papermc.generator.Main;
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.utils.Annotations;
|
||||
import io.papermc.generator.utils.Formatting;
|
||||
import java.util.Collection;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.papermc.generator.rewriter.types.simple;
|
||||
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.types.Types;
|
||||
import io.papermc.generator.utils.BlockStateMapping;
|
||||
import java.util.Comparator;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.papermc.generator.rewriter.types.simple;
|
||||
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.types.Types;
|
||||
import io.papermc.generator.utils.BlockEntityMapping;
|
||||
import io.papermc.generator.utils.Formatting;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.papermc.generator.rewriter.types.simple;
|
||||
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.types.Types;
|
||||
import io.papermc.generator.utils.Formatting;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.papermc.generator.rewriter.types.simple;
|
||||
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchReplaceRewriter;
|
||||
import net.minecraft.world.level.material.MapColor;
|
||||
import org.bukkit.map.MapPalette;
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ package io.papermc.generator.rewriter.types.simple;
|
|||
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.gson.internal.Primitives;
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.utils.Annotations;
|
||||
import io.papermc.generator.utils.ClassHelper;
|
||||
import io.papermc.generator.utils.Formatting;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io.papermc.generator.rewriter.utils;
|
||||
|
||||
import io.papermc.generator.rewriter.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.replace.SearchMetadata;
|
||||
import io.papermc.generator.rewriter.context.ImportCollector;
|
||||
import io.papermc.generator.utils.ClassHelper;
|
||||
import io.papermc.generator.utils.Formatting;
|
||||
|
@ -14,7 +14,7 @@ import static io.papermc.generator.utils.Formatting.quoted;
|
|||
public final class Annotations {
|
||||
|
||||
public static String annotation(Class<? extends Annotation> clazz, ImportCollector collector) {
|
||||
return "@%s".formatted(collector.getTypeName(clazz));
|
||||
return "@%s".formatted(collector.getShortName(clazz));
|
||||
}
|
||||
|
||||
public static String annotationStyle(Class<? extends Annotation> clazz) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.papermc.generator.types.craftblockdata;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.generator.utils.BlockStateMapping;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
@ -11,10 +12,8 @@ public final class CraftBlockDataGenerators {
|
|||
|
||||
public static void generate(Path container) throws IOException {
|
||||
for (Map.Entry<Class<? extends Block>, BlockStateMapping.BlockData> entry : BlockStateMapping.MAPPING.entrySet()) {
|
||||
Class<? extends BlockData> api = BlockStateMapping.getBestSuitedApiClass(entry.getKey());
|
||||
if (api == null) {
|
||||
continue;
|
||||
}
|
||||
Class<? extends BlockData> api = BlockStateMapping.getBestSuitedApiClass(entry.getValue());
|
||||
Preconditions.checkState(api != null, "Unknown custom BlockData api class for " + entry.getKey().getCanonicalName());
|
||||
|
||||
new CraftBlockDataGenerator<>(entry.getKey(), entry.getValue(), api).writeToFile(container);
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ import java.util.Map;
|
|||
|
||||
public abstract class DataPropertyWriterBase<T extends Property<?>> implements DataPropertyMaker {
|
||||
|
||||
protected final Class<? extends Block> blockClass;
|
||||
protected final Collection<T> properties;
|
||||
protected final Class<? extends Block> blockClass;
|
||||
|
||||
protected DataPropertyWriterBase(Collection<T> properties, Class<? extends Block> blockClass) {
|
||||
this.properties = properties;
|
||||
|
@ -42,6 +42,7 @@ public abstract class DataPropertyWriterBase<T extends Property<?>> implements D
|
|||
}
|
||||
|
||||
protected void createSyntheticMap(CodeBlock.Builder code, Class<?> indexClass, Map<String, String> fieldNames) {
|
||||
// assume indexClass is an enum with its values matching the property names
|
||||
code.add("$T.of(\n", Map.class).indent();
|
||||
Iterator<T> it = this.properties.iterator();
|
||||
while (it.hasNext()) {
|
||||
|
|
|
@ -321,7 +321,11 @@ public final class BlockStateMapping {
|
|||
return null;
|
||||
}
|
||||
|
||||
BlockData data = MAPPING.get(block);
|
||||
return getBestSuitedApiClass(MAPPING.get(block));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Class<? extends org.bukkit.block.data.BlockData> getBestSuitedApiClass(BlockData data) {
|
||||
if (data.api() != null) {
|
||||
return data.api();
|
||||
}
|
||||
|
|
|
@ -7,22 +7,6 @@ import java.util.Set;
|
|||
|
||||
public final class ClassHelper {
|
||||
|
||||
public static String getDeclaredType(Class<?> clazz) {
|
||||
if (clazz.isAnnotation()) {
|
||||
return "@interface";
|
||||
}
|
||||
if (clazz.isInterface()) {
|
||||
return "interface";
|
||||
}
|
||||
if (clazz.isEnum()) {
|
||||
return "enum";
|
||||
}
|
||||
if (clazz.isRecord()) {
|
||||
return "record";
|
||||
}
|
||||
return "class";
|
||||
}
|
||||
|
||||
public static Class<?> getRootClass(Class<?> clazz) {
|
||||
Class<?> rootClass = clazz;
|
||||
Class<?> parentClass = clazz;
|
||||
|
|
|
@ -71,7 +71,7 @@ public final class Formatting {
|
|||
return Optional.of(resourcePath.substring(tagsIndex + tagDir.length() + 1, dotIndex)); // namespace/tags/registry_key/[tag_key].json
|
||||
}
|
||||
|
||||
private static int countOccurrences(String value, char match) {
|
||||
public static int countOccurrences(String value, char match) {
|
||||
int count = 0;
|
||||
for (int i = 0, len = value.length(); i < len; i++) {
|
||||
if (value.charAt(i) == match) {
|
||||
|
@ -96,7 +96,7 @@ public final class Formatting {
|
|||
}
|
||||
|
||||
public static String quoted(String value) {
|
||||
return String.format("\"%s\"", value);
|
||||
return "\"" + value + "\"";
|
||||
}
|
||||
|
||||
public static String floatStr(float value) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.papermc.generator.utils;
|
|||
import com.google.common.base.CaseFormat;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import javax.lang.model.SourceVersion;
|
||||
|
@ -82,6 +83,17 @@ public class NamingManager {
|
|||
return name;
|
||||
}
|
||||
|
||||
public static final Pattern FULLY_QUALIFIED_NAME_SEPARATOR = Pattern.compile(".", Pattern.LITERAL);
|
||||
|
||||
public static boolean hasIllegalKeyword(String typeName) {
|
||||
for (String part : FULLY_QUALIFIED_NAME_SEPARATOR.split(typeName)) {
|
||||
if (SourceVersion.isKeyword(part)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class NameWrapper {
|
||||
|
||||
private final String keyword;
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package io.papermc.generator.rewriter;
|
||||
|
||||
import io.papermc.generator.Generators;
|
||||
import io.papermc.generator.rewriter.parser.StringReader;
|
||||
import io.papermc.generator.rewriter.replace.CommentMarker;
|
||||
import io.papermc.generator.rewriter.replace.SearchReplaceRewriter;
|
||||
import io.papermc.generator.rewriter.utils.Annotations;
|
||||
import io.papermc.paper.generated.GeneratedFrom;
|
||||
import net.minecraft.SharedConstants;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
|
@ -13,6 +16,12 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static io.papermc.generator.rewriter.replace.CommentMarker.EMPTY_MARKER;
|
||||
import static io.papermc.generator.rewriter.replace.SearchReplaceRewriter.INDENT_CHAR;
|
||||
import static io.papermc.generator.rewriter.replace.SearchReplaceRewriter.INDENT_SIZE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@Disabled
|
||||
public class OldGeneratedCodeTest {
|
||||
|
||||
private static final String API_CONTAINER = System.getProperty("paper.generator.rewriter.container.api");
|
||||
|
@ -26,27 +35,13 @@ public class OldGeneratedCodeTest {
|
|||
CURRENT_VERSION = SharedConstants.getCurrentVersion().getName();
|
||||
}
|
||||
|
||||
private boolean versionDependent(SearchReplaceRewriter srt) {
|
||||
if (srt instanceof CompositeRewriter compositeRewriter) {
|
||||
boolean versionDependent = false;
|
||||
for (SearchReplaceRewriter rewriter : compositeRewriter.getRewriters()) {
|
||||
if (!rewriter.equalsSize) {
|
||||
versionDependent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return versionDependent;
|
||||
}
|
||||
return !srt.equalsSize;
|
||||
}
|
||||
|
||||
private void checkOutdated(String container, SourceRewriter[] rewriters) throws IOException {
|
||||
for (SourceRewriter rewriter : rewriters) {
|
||||
if (!(rewriter instanceof SearchReplaceRewriter srt) || !versionDependent(srt)) {
|
||||
if (!(rewriter instanceof SearchReplaceRewriter srt) || !srt.isVersionDependant()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Path path = Path.of(container, srt.getFilePath());
|
||||
Path path = Path.of(container, srt.getRelativeFilePath());
|
||||
if (Files.notExists(path)) { // todo remove after the repo change
|
||||
continue;
|
||||
}
|
||||
|
@ -58,21 +53,42 @@ public class OldGeneratedCodeTest {
|
|||
break;
|
||||
}
|
||||
lineCount++;
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
StringReader lineIterator = new StringReader(line);
|
||||
CommentMarker marker = srt.searchMarker(lineIterator, null, null);
|
||||
if (marker != EMPTY_MARKER) {
|
||||
if (!marker.start()) {
|
||||
continue;
|
||||
}
|
||||
int startIndentSize = marker.indent();
|
||||
if (startIndentSize % INDENT_SIZE != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int startPatternIndex = line.indexOf(SearchReplaceRewriter.GENERATED_COMMENT_FORMAT.formatted(SearchReplaceRewriter.PAPER_START_FORMAT, ""));
|
||||
if (startPatternIndex != -1 && (startPatternIndex % SearchReplaceRewriter.INDENT_UNIT.length()) == 0 && line.stripTrailing().equals(line)) {
|
||||
String nextLine = reader.readLine();
|
||||
if (nextLine == null) {
|
||||
break;
|
||||
}
|
||||
lineCount++;
|
||||
if (nextLine.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
StringReader nextLineIterator = new StringReader(nextLine);
|
||||
int indentSize = nextLineIterator.skipChars(INDENT_CHAR);
|
||||
if (indentSize != startIndentSize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String generatedComment = "// %s ".formatted(Annotations.annotationStyle(GeneratedFrom.class));
|
||||
int generatedIndex = nextLine.indexOf(generatedComment);
|
||||
if (generatedIndex != -1 && (generatedIndex % SearchReplaceRewriter.INDENT_UNIT.length()) == 0 && nextLine.stripTrailing().equals(nextLine)) {
|
||||
String generatedVersion = nextLine.substring(generatedIndex + generatedComment.length());
|
||||
Assertions.assertEquals(CURRENT_VERSION, generatedVersion,
|
||||
if (nextLineIterator.trySkipString(generatedComment) && nextLineIterator.canRead()) {
|
||||
String generatedVersion = nextLineIterator.readRemainingString();
|
||||
assertEquals(CURRENT_VERSION, generatedVersion,
|
||||
"Code at line %s in %s is marked as being generated in version %s when the current version is %s".formatted(
|
||||
lineCount, srt.rewriteClass.canonicalName(),
|
||||
lineCount, srt.getRewrittenClass().canonicalName(),
|
||||
generatedVersion, CURRENT_VERSION));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.area;//{
|
||||
|
||||
public @interface AnnotationClass {
|
||||
// @interface should be invalidated and not detected as an annotation
|
||||
// via its keyword: interface
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.area;
|
||||
|
||||
@SuppressWarnings({"DeprecatedIsStillUsed", "deprecation"}) // Paper
|
||||
public class AnnotationPresentClass {
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.area;
|
||||
|
||||
@SuppressWarnings
|
||||
(
|
||||
{
|
||||
"DeprecatedIsStillUsed"
|
||||
,
|
||||
"deprecation"
|
||||
}
|
||||
)
|
||||
// Paper
|
||||
public class FancyNewlineAnnotationPresentClass {
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.area;//{
|
||||
|
||||
public/* d */class/* d */FancyScopeClass/* d */
|
||||
/* d */{//d
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.area;//{
|
||||
|
||||
public/* d */class/* d */FancyScopeClass2/* d */ /* d */{//d
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.area;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@SuppressWarnings//discord /**/
|
||||
/* a*/ ( /* a b*/
|
||||
{ /*a*/
|
||||
"DeprecatedIsStillUsed" /*a*/
|
||||
/*a*/ , /**/// a
|
||||
"deprecation"
|
||||
} // dq
|
||||
) // a
|
||||
// Paper
|
||||
@//a
|
||||
ApiStatus//b
|
||||
.//c
|
||||
Internal//d
|
||||
@/* a*/
|
||||
ApiStatus/* a*/./* a*/
|
||||
Experimental/* a*///a/* a*/
|
||||
public class MixedAnnotationPresentClass {
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.area;//{
|
||||
|
||||
public class NearScopeClass{
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.area;//{
|
||||
|
||||
public class NewlineScopedClass
|
||||
{
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.area;//{
|
||||
|
||||
// { //{
|
||||
//{
|
||||
/*
|
||||
{
|
||||
*/
|
||||
|
||||
/**
|
||||
* {
|
||||
* }
|
||||
* {@linkplain SimpleTrapClass}
|
||||
*/
|
||||
public class SimpleTrapClass {
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports;
|
||||
|
||||
import/* hi */org.bukkit.NamespacedKey; // multi line comment filled gap between import and typeName
|
||||
import org.bukkit/* my */./*hidden*/Statistic/*message*/.Type/* ! */; // comment in type name
|
||||
// import org.bukkit.block.Commented;
|
||||
/*
|
||||
import org.bukkit.block.Commented;
|
||||
*/
|
||||
/*
|
||||
import org.bukkit.block.Commented;
|
||||
*//*
|
||||
import org.bukkit.block.Commented;
|
||||
*/// import org.bukkit.block.Commented;import org.bukkit.block.Commented;
|
||||
|
||||
import/* hi */static/* ! */org.bukkit. Statistic.ANIMALS_BRED; // multi line comment filled gap between static and typeName
|
||||
// import static org.bukkit.Statistic.COMMENTED;
|
||||
/*
|
||||
import static org.bukkit.Statistic.COMMENTED;
|
||||
*/
|
||||
/*
|
||||
import static org.bukkit.Statistic.COMMENTED;
|
||||
*//*
|
||||
import static org.bukkit.Statistic.COMMENTED;
|
||||
*/// import static org.bukkit.Statistic.COMMENTED;import static org.bukkit.Statistic.COMMENTED;
|
||||
import static org.bukkit.Material./* hi */* /* ! */;
|
||||
|
||||
public class FancyCommentImportType {
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports;import org.bukkit.NamespacedKey; import org.bukkit.Statistic.Type;import org.bukkit.*;/**/import static org.bukkit.Statistic.ANIMALS_BRED; /* */ import static org.bukkit.Material.*;public class FancyInlinedImportType {
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports;
|
||||
|
||||
import
|
||||
org
|
||||
.
|
||||
bukkit
|
||||
.
|
||||
|
||||
NamespacedKey
|
||||
; // multi line import
|
||||
import org
|
||||
.bukkit
|
||||
.
|
||||
Statistic.
|
||||
Type; // multi line import with dot art
|
||||
|
||||
import
|
||||
org
|
||||
.
|
||||
bukkit
|
||||
.
|
||||
|
||||
*
|
||||
|
||||
; // multi line star import
|
||||
|
||||
import
|
||||
static
|
||||
org
|
||||
.
|
||||
|
||||
bukkit
|
||||
.
|
||||
Statistic
|
||||
.
|
||||
ANIMALS_BRED
|
||||
; // multi line static import
|
||||
|
||||
import
|
||||
static org
|
||||
.
|
||||
bukkit
|
||||
.
|
||||
Material
|
||||
|
||||
.
|
||||
*
|
||||
; // multi line star import
|
||||
|
||||
public class FancyNewlineImportType {
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports;
|
||||
|
||||
import org. bukkit.NamespacedKey ;
|
||||
import org.bukkit .Statistic. Type;
|
||||
import org .bukkit.* ;
|
||||
import static org. bukkit. Statistic.ANIMALS_BRED ;
|
||||
import static org.bukkit. Material. * ;
|
||||
|
||||
public class FancySpaceImportType {
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports;
|
||||
|
||||
import// discard?
|
||||
org /* hi */
|
||||
/* hi */ .
|
||||
bukkit /* hi */ // /*/
|
||||
/* hi */ .
|
||||
|
||||
NamespacedKey// discard?
|
||||
/* hi */; // multi line import
|
||||
|
||||
import// discard?
|
||||
static// discard?
|
||||
org// discard?
|
||||
/* hi */ . /* hi */
|
||||
|
||||
/* hi */ bukkit /* hi */
|
||||
/* hi */ .
|
||||
/* a */
|
||||
// a
|
||||
|
||||
Material
|
||||
|
||||
.
|
||||
*// discard?
|
||||
; // multi line star import
|
||||
|
||||
public class MixedCommentImportType {
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports;
|
||||
|
||||
import org.bukkit.NamespacedKey; // root class import
|
||||
import org.bukkit.Statistic.Type; // inner class import (directly referenced)
|
||||
import org.bukkit.*; // star import (whole package + one level)
|
||||
import static org.bukkit.Statistic.ANIMALS_BRED; // static import
|
||||
import static org.bukkit.Material.*; // static star import
|
||||
|
||||
public class StandardImportType {
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports.name;
|
||||
|
||||
import org.bukkit.*;
|
||||
import static org.bukkit.Statistic.*;
|
||||
|
||||
public class GlobalImportType {
|
||||
{
|
||||
var a = Material.VINE;
|
||||
var b = ARMOR_CLEANED;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports.name;
|
||||
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.name.one.*;
|
||||
|
||||
public class PackageClassImportType {
|
||||
{
|
||||
var a = SamePackageClass.class;
|
||||
var b = OneDepthClass.class;
|
||||
var c = OneDepthClass.NonStaticClass.class;
|
||||
var d = OneDepthClass.StaticClass.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports.name;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import static org.bukkit.Statistic.ARMOR_CLEANED;
|
||||
import static org.bukkit.Statistic.valueOf;
|
||||
|
||||
public class RegularImportType {
|
||||
{
|
||||
var a = Material.VINE;
|
||||
var b = ARMOR_CLEANED;
|
||||
valueOf(b.name());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports.name;
|
||||
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.name.one.OneDepthClass.*;
|
||||
|
||||
public class RemoteGlobalInnerClassImportType {
|
||||
{
|
||||
var c = NonStaticClass.class;
|
||||
var d = StaticClass.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports.name;
|
||||
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.name.one.OneDepthClass.NonStaticClass;
|
||||
import static io.papermc.generator.rewriter.data.sample.parser.imports.name.one.OneDepthClass.StaticClass;
|
||||
|
||||
public class RemoteInnerClassImportType {
|
||||
{
|
||||
var b = NonStaticClass.class;
|
||||
var c = StaticClass.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports.name;
|
||||
|
||||
import static io.papermc.generator.rewriter.data.sample.parser.imports.name.one.OneDepthClass.*;
|
||||
|
||||
public class RemoteStaticGlobalInnerClassImportType {
|
||||
{
|
||||
var b = io.papermc.generator.rewriter.data.sample.parser.imports.name.one.OneDepthClass.NonStaticClass.class;
|
||||
var c = StaticClass.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports.name;
|
||||
|
||||
public class SamePackageClass {
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports.name;
|
||||
|
||||
public class SelfInnerClassImportType {
|
||||
public class A {
|
||||
public class B {
|
||||
public class C {
|
||||
{
|
||||
var a = A.class;
|
||||
var b = B.class;
|
||||
var c = C.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var a = A.class;
|
||||
var b = A.B.class;
|
||||
var c = A.B.C.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.papermc.generator.rewriter.data.sample.parser.imports.name.one;
|
||||
|
||||
public class OneDepthClass {
|
||||
|
||||
public static class StaticClass {
|
||||
|
||||
}
|
||||
|
||||
public class NonStaticClass {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package io.papermc.generator.rewriter.data.yml;
|
||||
|
||||
public class ImportMapping {
|
||||
|
||||
public ImportSet imports;
|
||||
public ImportSet staticImports;
|
||||
|
||||
public ImportSet getImports() {
|
||||
return this.imports;
|
||||
}
|
||||
|
||||
public ImportSet getStaticImports() {
|
||||
return this.staticImports;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package io.papermc.generator.rewriter.data.yml;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
public class ImportSet {
|
||||
|
||||
public Set<String> single;
|
||||
public Set<String> global;
|
||||
|
||||
public Set<String> single() {
|
||||
return this.single == null ? Collections.emptySet() : this.single;
|
||||
}
|
||||
|
||||
public Set<String> global() {
|
||||
return this.global == null ? Collections.emptySet() : this.global;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package io.papermc.generator.rewriter.data.yml;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ImportShortNameMapping {
|
||||
|
||||
public Map<String, String> imports;
|
||||
public Map<String, String> staticImports;
|
||||
|
||||
/**
|
||||
* Note: unlike {@link #getStaticImports()} (which only use a dot), nested class must be identified using '$' since the typeName is then
|
||||
* converted into a class object
|
||||
*/
|
||||
public Map<String, String> getImports() {
|
||||
return this.imports;
|
||||
}
|
||||
|
||||
public Map<String, String> getStaticImports() {
|
||||
return this.staticImports;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package io.papermc.generator.rewriter.data.yml;
|
||||
|
||||
import org.junit.jupiter.params.converter.TypedArgumentConverter;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.nodes.ScalarNode;
|
||||
import org.yaml.snakeyaml.nodes.Tag;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class YmlMappingConverter<T> extends TypedArgumentConverter<String, T> {
|
||||
|
||||
private static final LoaderOptions OPTIONS;
|
||||
static {
|
||||
OPTIONS = new LoaderOptions();
|
||||
OPTIONS.setNestingDepthLimit(3);
|
||||
}
|
||||
|
||||
private final Constructor yamlConstructor;
|
||||
|
||||
protected YmlMappingConverter(Class<T> clazz, String relativePackage) {
|
||||
super(String.class, clazz);
|
||||
if (relativePackage == null) {
|
||||
this.yamlConstructor = new Constructor(clazz, OPTIONS);
|
||||
} else {
|
||||
this.yamlConstructor = new Constructor(clazz, OPTIONS) {
|
||||
|
||||
@Override
|
||||
public String constructScalar(ScalarNode node) {
|
||||
if (node.getTag() == Tag.STR && node.getValue().startsWith("(_).")) {
|
||||
return node.getValue().replaceFirst("\\(_\\)", relativePackage);
|
||||
}
|
||||
|
||||
return super.constructScalar(node);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T convert(String path) {
|
||||
try (InputStream input = this.getClass().getClassLoader().getResourceAsStream(path)) {
|
||||
return new Yaml(this.yamlConstructor).load(input);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package io.papermc.generator.rewriter.parser;
|
||||
|
||||
import io.papermc.generator.rewriter.ClassNamed;
|
||||
import io.papermc.generator.rewriter.context.ImportTypeCollector;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.FancyInlinedImportType;
|
||||
import io.papermc.generator.rewriter.data.yml.ImportMapping;
|
||||
import io.papermc.generator.rewriter.data.yml.ImportSet;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.FancyCommentImportType;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.FancyNewlineImportType;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.FancySpaceImportType;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.MixedCommentImportType;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.StandardImportType;
|
||||
import io.papermc.generator.rewriter.data.yml.YmlMappingConverter;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.converter.ConvertWith;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class ImportCollectTest extends ParserTest {
|
||||
|
||||
private static Arguments fileToArgs(Class<?> sampleClass) {
|
||||
return Arguments.of(
|
||||
CONTAINER.resolve(sampleClass.getCanonicalName().replace('.', '/') + ".java"),
|
||||
sampleClass,
|
||||
"parser/expected/imports/%s.yml".formatted(sampleClass.getSimpleName())
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> fileProvider() {
|
||||
return Stream.of(
|
||||
StandardImportType.class,
|
||||
FancySpaceImportType.class,
|
||||
FancyCommentImportType.class,
|
||||
FancyNewlineImportType.class,
|
||||
FancyInlinedImportType.class,
|
||||
MixedCommentImportType.class
|
||||
).map(ImportCollectTest::fileToArgs);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("fileProvider")
|
||||
public void testImports(Path path,
|
||||
Class<?> sampleClass,
|
||||
@ConvertWith(ImportMappingConverter.class) ImportMapping expected) throws IOException {
|
||||
final ImportTypeCollector importCollector = new ImportTypeCollector(new ClassNamed(sampleClass));
|
||||
parseFile(path, importCollector);
|
||||
|
||||
String name = sampleClass.getSimpleName();
|
||||
|
||||
Pair<Set<String>, Set<String>> imports = importCollector.getImports();
|
||||
ImportSet expectedImports = expected.getImports();
|
||||
assertEquals(expectedImports.single(), imports.left(), "Regular imports doesn't match for " + name);
|
||||
assertEquals(expectedImports.global(), imports.right(), "Regular global imports doesn't match for " + name);
|
||||
|
||||
Pair<Set<String>, Set<String>> staticImports = importCollector.getStaticImports();
|
||||
ImportSet expectedStaticImports = expected.getStaticImports();
|
||||
assertEquals(expectedStaticImports.single(), staticImports.left(), "Static imports doesn't match for " + name);
|
||||
assertEquals(expectedStaticImports.global(), staticImports.right(), "Static global imports doesn't match for " + name);
|
||||
}
|
||||
|
||||
private static class ImportMappingConverter extends YmlMappingConverter<ImportMapping> {
|
||||
|
||||
protected ImportMappingConverter() {
|
||||
super(ImportMapping.class, null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package io.papermc.generator.rewriter.parser;
|
||||
|
||||
import io.papermc.generator.rewriter.ClassNamed;
|
||||
import io.papermc.generator.rewriter.context.ImportTypeCollector;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.area.AnnotationClass;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.area.AnnotationPresentClass;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.area.FancyNewlineAnnotationPresentClass;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.area.FancyScopeClass;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.area.FancyScopeClass2;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.area.MixedAnnotationPresentClass;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.area.NearScopeClass;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.area.NewlineScopedClass;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.area.SimpleTrapClass;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class ParserMetadataAreaTest extends ParserTest {
|
||||
|
||||
private static final Path CONTAINER = Path.of(System.getProperty("user.dir"), "src/test/java");
|
||||
|
||||
private static Arguments file(Class<?> sampleClass, String expectedLastLine) {
|
||||
String name = sampleClass.getSimpleName();
|
||||
return Arguments.of(
|
||||
CONTAINER.resolve(sampleClass.getCanonicalName().replace('.', '/') + ".java"),
|
||||
sampleClass,
|
||||
name,
|
||||
expectedLastLine
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> fileProvider() {
|
||||
return Stream.of(
|
||||
file(
|
||||
SimpleTrapClass.class,
|
||||
"public class SimpleTrapClass {"
|
||||
),
|
||||
file(
|
||||
AnnotationClass.class,
|
||||
"public @interface AnnotationClass {"
|
||||
),
|
||||
file(
|
||||
AnnotationPresentClass.class,
|
||||
"public class AnnotationPresentClass {"
|
||||
),
|
||||
file(
|
||||
FancyNewlineAnnotationPresentClass.class,
|
||||
"public class FancyNewlineAnnotationPresentClass {"
|
||||
),
|
||||
file(
|
||||
MixedAnnotationPresentClass.class,
|
||||
"public class MixedAnnotationPresentClass {"
|
||||
),
|
||||
file(
|
||||
NewlineScopedClass.class,
|
||||
"{"
|
||||
),
|
||||
file(
|
||||
NearScopeClass.class,
|
||||
"public class NearScopeClass{"
|
||||
),
|
||||
file(
|
||||
FancyScopeClass.class,
|
||||
" /* d */{//d"
|
||||
),
|
||||
file(
|
||||
FancyScopeClass2.class,
|
||||
"public/* d */class/* d */FancyScopeClass2/* d */ /* d */{//d"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("fileProvider")
|
||||
public void testAreaEnd(Path path,
|
||||
Class<?> sampleClass,
|
||||
String name,
|
||||
String expectedLastLine) throws IOException {
|
||||
final ImportTypeCollector importCollector = new ImportTypeCollector(new ClassNamed(sampleClass));
|
||||
parseFile(path, importCollector, line -> {
|
||||
assertEquals(expectedLastLine, line, "Parser didn't stop at the expected line for " + name);
|
||||
},
|
||||
() -> {
|
||||
fail("File is empty or doesn't contains the required top scope needed for this test to run");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.papermc.generator.rewriter.parser;
|
||||
|
||||
import io.papermc.generator.rewriter.context.ImportCollector;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Tag("parser")
|
||||
public class ParserTest {
|
||||
|
||||
protected static final Path CONTAINER = Path.of(System.getProperty("user.dir"), "src/test/java");
|
||||
|
||||
protected void parseFile(Path path, ImportCollector importCollector) throws IOException {
|
||||
parseFile(path, importCollector, str -> {}, () -> {});
|
||||
}
|
||||
|
||||
protected void parseFile(Path path, ImportCollector importCollector, Consumer<String> enterBodyCallback, Runnable eofCallback) throws IOException {
|
||||
final LineParser lineParser = new LineParser();
|
||||
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
eofCallback.run();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!line.isEmpty() && lineParser.consumeImports(new StringReader(line), importCollector)) {
|
||||
enterBodyCallback.accept(line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package io.papermc.generator.rewriter.parser;
|
||||
|
||||
import io.papermc.generator.rewriter.ClassNamed;
|
||||
import io.papermc.generator.rewriter.context.ImportTypeCollector;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.name.GlobalImportType;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.name.PackageClassImportType;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.name.RegularImportType;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.name.RemoteGlobalInnerClassImportType;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.name.RemoteInnerClassImportType;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.name.RemoteStaticGlobalInnerClassImportType;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.name.SamePackageClass;
|
||||
import io.papermc.generator.rewriter.data.sample.parser.imports.name.SelfInnerClassImportType;
|
||||
import io.papermc.generator.rewriter.data.yml.ImportShortNameMapping;
|
||||
import io.papermc.generator.rewriter.data.yml.YmlMappingConverter;
|
||||
import io.papermc.generator.utils.ClassHelper;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.converter.ConvertWith;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
public class TypeShortNameTest extends ParserTest {
|
||||
|
||||
private static Arguments rootClass(Class<?> sampleClass) {
|
||||
return innerClass(sampleClass, sampleClass);
|
||||
}
|
||||
|
||||
private static Arguments innerClass(Class<?> sampleClass, Class<?> sampleInnerClass) {
|
||||
String name = sampleClass.getSimpleName();
|
||||
return Arguments.of(
|
||||
CONTAINER.resolve(sampleClass.getCanonicalName().replace('.', '/') + ".java"),
|
||||
sampleInnerClass,
|
||||
name,
|
||||
"parser/expected/imports/name/%s.yml".formatted(sampleInnerClass.getName().substring(sampleInnerClass.getPackageName().length() + 1))
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> fileProvider() {
|
||||
return Stream.of(
|
||||
rootClass(RegularImportType.class),
|
||||
rootClass(GlobalImportType.class),
|
||||
rootClass(PackageClassImportType.class),
|
||||
rootClass(RemoteGlobalInnerClassImportType.class),
|
||||
rootClass(RemoteStaticGlobalInnerClassImportType.class),
|
||||
rootClass(RemoteInnerClassImportType.class),
|
||||
rootClass(SelfInnerClassImportType.class),
|
||||
innerClass(SelfInnerClassImportType.class, SelfInnerClassImportType.A.B.C.class)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("fileProvider")
|
||||
public void testTypeName(Path path,
|
||||
Class<?> sampleClass,
|
||||
String name,
|
||||
@ConvertWith(ImportShortNameMappingConverter.class) ImportShortNameMapping mapping) throws IOException {
|
||||
final ImportTypeCollector importCollector = new ImportTypeCollector(new ClassNamed(sampleClass));
|
||||
parseFile(path, importCollector);
|
||||
|
||||
assertFalse(mapping.getImports() == null && mapping.getStaticImports() == null, "Empty expected import mapping!");
|
||||
|
||||
if (mapping.getImports() != null) {
|
||||
for (Map.Entry<String, String> expect : mapping.getImports().entrySet()) {
|
||||
Class<?> runtimeClass = ClassHelper.classOr(expect.getKey(), null);
|
||||
assertNotNull(runtimeClass, "Runtime class cannot be null for import " + expect.getKey());
|
||||
assertEquals(expect.getValue(), importCollector.getShortName(runtimeClass),
|
||||
() -> "Short name of " + expect.getKey() + " doesn't match with collected imports for " + name + "! Import found: " + importCollector.getImports());
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.getStaticImports() != null) {
|
||||
for (Map.Entry<String, String> expect : mapping.getStaticImports().entrySet()) {
|
||||
assertEquals(expect.getValue(), importCollector.getStaticMemberShortName(expect.getKey()),
|
||||
() -> "Short name of static member/class " + expect.getKey() + " doesn't match with collected imports for " + name + "! Static imports found: " + importCollector.getStaticImports());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImportShortNameMappingConverter extends YmlMappingConverter<ImportShortNameMapping> {
|
||||
|
||||
protected ImportShortNameMappingConverter() {
|
||||
super(ImportShortNameMapping.class, SamePackageClass.class.getPackageName());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
imports:
|
||||
single:
|
||||
- "org.bukkit.NamespacedKey"
|
||||
- "org.bukkit.Statistic.Type"
|
||||
|
||||
staticImports:
|
||||
single:
|
||||
- "org.bukkit.Statistic.ANIMALS_BRED"
|
||||
global:
|
||||
- "org.bukkit.Material"
|
|
@ -0,0 +1,12 @@
|
|||
imports:
|
||||
single:
|
||||
- "org.bukkit.NamespacedKey"
|
||||
- "org.bukkit.Statistic.Type"
|
||||
global:
|
||||
- "org.bukkit"
|
||||
|
||||
staticImports:
|
||||
single:
|
||||
- "org.bukkit.Statistic.ANIMALS_BRED"
|
||||
global:
|
||||
- "org.bukkit.Material"
|
|
@ -0,0 +1,12 @@
|
|||
imports:
|
||||
single:
|
||||
- "org.bukkit.NamespacedKey"
|
||||
- "org.bukkit.Statistic.Type"
|
||||
global:
|
||||
- "org.bukkit"
|
||||
|
||||
staticImports:
|
||||
single:
|
||||
- "org.bukkit.Statistic.ANIMALS_BRED"
|
||||
global:
|
||||
- "org.bukkit.Material"
|
|
@ -0,0 +1,12 @@
|
|||
imports:
|
||||
single:
|
||||
- "org.bukkit.NamespacedKey"
|
||||
- "org.bukkit.Statistic.Type"
|
||||
global:
|
||||
- "org.bukkit"
|
||||
|
||||
staticImports:
|
||||
single:
|
||||
- "org.bukkit.Statistic.ANIMALS_BRED"
|
||||
global:
|
||||
- "org.bukkit.Material"
|
|
@ -0,0 +1,7 @@
|
|||
imports:
|
||||
single:
|
||||
- "org.bukkit.NamespacedKey"
|
||||
|
||||
staticImports:
|
||||
global:
|
||||
- "org.bukkit.Material"
|
|
@ -0,0 +1,12 @@
|
|||
imports:
|
||||
single:
|
||||
- "org.bukkit.NamespacedKey"
|
||||
- "org.bukkit.Statistic.Type"
|
||||
global:
|
||||
- "org.bukkit"
|
||||
|
||||
staticImports:
|
||||
single:
|
||||
- "org.bukkit.Statistic.ANIMALS_BRED"
|
||||
global:
|
||||
- "org.bukkit.Material"
|
|
@ -0,0 +1,7 @@
|
|||
imports: {
|
||||
"org.bukkit.Material": "Material",
|
||||
}
|
||||
|
||||
staticImports: {
|
||||
"org.bukkit.Statistic.ARMOR_CLEANED": "ARMOR_CLEANED"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
imports: {
|
||||
"(_).SamePackageClass": "SamePackageClass",
|
||||
"(_).one.OneDepthClass": "OneDepthClass",
|
||||
"(_).one.OneDepthClass$NonStaticClass": "OneDepthClass.NonStaticClass",
|
||||
"(_).one.OneDepthClass$StaticClass": "OneDepthClass.StaticClass"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
imports: {
|
||||
"org.bukkit.Material": "Material",
|
||||
}
|
||||
|
||||
staticImports: {
|
||||
"org.bukkit.Statistic.ARMOR_CLEANED": "ARMOR_CLEANED",
|
||||
"org.bukkit.Statistic.valueOf": "valueOf",
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
imports: {
|
||||
"(_).one.OneDepthClass$NonStaticClass": "NonStaticClass",
|
||||
"(_).one.OneDepthClass$StaticClass": "StaticClass"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
imports: {
|
||||
"(_).one.OneDepthClass$NonStaticClass": "NonStaticClass"
|
||||
}
|
||||
|
||||
staticImports: {
|
||||
"(_).one.OneDepthClass.StaticClass": "StaticClass"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
imports: {
|
||||
"(_).one.OneDepthClass$NonStaticClass": "(_).one.OneDepthClass.NonStaticClass",
|
||||
"(_).one.OneDepthClass$StaticClass": "StaticClass"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
imports: {
|
||||
"(_).SelfInnerClassImportType$A": "A",
|
||||
"(_).SelfInnerClassImportType$A$B": "B",
|
||||
"(_).SelfInnerClassImportType$A$B$C": "C",
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
imports: {
|
||||
"(_).SelfInnerClassImportType$A": "A",
|
||||
"(_).SelfInnerClassImportType$A$B": "A.B",
|
||||
"(_).SelfInnerClassImportType$A$B$C": "A.B.C",
|
||||
}
|
Loading…
Reference in New Issue