aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
author zaaarf <me@zaaarf.foo>2024-06-04 20:03:51 +0200
committer zaaarf <me@zaaarf.foo>2024-06-04 20:03:51 +0200
commit4d330a731bf44bcca291a23407c06ed063193921 (patch)
treedad468e37c33fcc264a49ce0c73a0d3b763ab034 /src/main
parentf8bf9996c92d15ee8499bda25f0a23070a6f7752 (diff)
feat: better inner classes api, removed deprecation warnings since that stuff is there to stayHEAD0.7.0dev
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/ftbsc/lll/exceptions/AmbiguousDefinitionException.java12
-rw-r--r--src/main/java/ftbsc/lll/exceptions/InvalidClassNameException.java15
-rw-r--r--src/main/java/ftbsc/lll/exceptions/InvalidResourceException.java22
-rw-r--r--src/main/java/ftbsc/lll/processor/LilleroProcessor.java2
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/Find.java22
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/Patch.java13
-rw-r--r--src/main/java/ftbsc/lll/processor/containers/ClassContainer.java58
-rw-r--r--src/main/java/ftbsc/lll/processor/containers/FieldContainer.java17
-rw-r--r--src/main/java/ftbsc/lll/processor/utils/ASTUtils.java41
9 files changed, 103 insertions, 99 deletions
diff --git a/src/main/java/ftbsc/lll/exceptions/AmbiguousDefinitionException.java b/src/main/java/ftbsc/lll/exceptions/AmbiguousDefinitionException.java
index 1befaa8..a00c2e2 100644
--- a/src/main/java/ftbsc/lll/exceptions/AmbiguousDefinitionException.java
+++ b/src/main/java/ftbsc/lll/exceptions/AmbiguousDefinitionException.java
@@ -1,8 +1,7 @@
package ftbsc.lll.exceptions;
/**
- * Thrown when the processor finds multiple methods matching the
- * given criteria.
+ * Thrown when the processor finds multiple methods matching the given criteria.
*/
public class AmbiguousDefinitionException extends RuntimeException {
@@ -13,13 +12,4 @@ public class AmbiguousDefinitionException extends RuntimeException {
public AmbiguousDefinitionException(String message) {
super(message);
}
-
- /**
- * Constructs a new ambiguous definition exception with the specified detail message and cause.
- * @param message the detail message
- * @param cause the cause, may be null (indicating nonexistent or unknown cause)
- */
- public AmbiguousDefinitionException(String message, Throwable cause) {
- super(message, cause);
- }
}
diff --git a/src/main/java/ftbsc/lll/exceptions/InvalidClassNameException.java b/src/main/java/ftbsc/lll/exceptions/InvalidClassNameException.java
new file mode 100644
index 0000000..caa468e
--- /dev/null
+++ b/src/main/java/ftbsc/lll/exceptions/InvalidClassNameException.java
@@ -0,0 +1,15 @@
+package ftbsc.lll.exceptions;
+
+/**
+ * Thrown when the user provides manually an invalid class name.
+ */
+public class InvalidClassNameException extends RuntimeException {
+
+ /**
+ * Constructs a new exception for the specified name.
+ * @param name the name in question
+ */
+ public InvalidClassNameException(String name) {
+ super(String.format("Provided class name %s is not valid!", name));
+ }
+}
diff --git a/src/main/java/ftbsc/lll/exceptions/InvalidResourceException.java b/src/main/java/ftbsc/lll/exceptions/InvalidResourceException.java
deleted file mode 100644
index 76f12a5..0000000
--- a/src/main/java/ftbsc/lll/exceptions/InvalidResourceException.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package ftbsc.lll.exceptions;
-
-/**
- * Thrown when a resource passed as an argument is not found.
- */
-public class InvalidResourceException extends RuntimeException {
-
- /**
- * Empty constructor, used when the provided resource exists but is empty.
- */
- public InvalidResourceException() {
- super("The specified resource was empty!");
- }
-
- /**
- * Named constructor, used when the specified resource doesn't exist.
- * @param name the resource name
- */
- public InvalidResourceException(String name) {
- super(String.format("Specified resource %s was not found!", name));
- }
-}
diff --git a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
index 568e2e6..117cad6 100644
--- a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
+++ b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java
@@ -151,7 +151,7 @@ public class LilleroProcessor extends AbstractProcessor {
//find class information
Patch patchAnn = cl.getAnnotation(Patch.class);
ClassContainer targetClass = ClassContainer.from(
- patchAnn, Patch::value, patchAnn.innerName(), this.getProcessorOptions()
+ patchAnn, Patch::value, patchAnn.inner(), this.getProcessorOptions()
);
//find package information
Element packageElement = cl.getEnclosingElement();
diff --git a/src/main/java/ftbsc/lll/processor/annotations/Find.java b/src/main/java/ftbsc/lll/processor/annotations/Find.java
index 14c1cc0..403a39e 100644
--- a/src/main/java/ftbsc/lll/processor/annotations/Find.java
+++ b/src/main/java/ftbsc/lll/processor/annotations/Find.java
@@ -26,15 +26,13 @@ public @interface Find {
Class<?> value() default Object.class;
/**
- * This is the inner class name to append after a $ symbol to the already acquired
- * fully-qualified name. If it's a number instead of a valid name, the class will be
- * treated as an anonymous class, and will therefore be automatically unverified.
- * For a {@link TypeProxy}, this refers to the class itself rather than the parent.
+ * For a {@link TypeProxy}, this refers to the target itself rather than its parent.
* @return the name of the inner class that contains the target, defaults to empty
* string (not an inner class)
+ * @see Patch#inner() for details
* @since 0.5.0
*/
- String innerName() default "";
+ String[] inner() default {};
/**
* For a {@link FieldProxy}, this is the name of the field to find. If omitted,
@@ -42,7 +40,7 @@ public @interface Find {
* For a {@link MethodProxy} it indicates an attempt to match by name only, with
* this name. This will issue a warning unless warnings are disabled. It will fail
* and throw an exception if multiple methods with that name are found in the
- * relevant class. It is generally recommended that you use a @link Target} stub
+ * relevant class. It is generally recommended that you use a {@link Target} stub
* for methods, as this can lead to unpredictable behaviour at runtime.
* It will have no effect on a {@link TypeProxy}.
* @return the name of the target, will default to the empty string (the name of
@@ -52,20 +50,16 @@ public @interface Find {
String name() default "";
/**
- * This overrules the type of a field. Only to be used in the case (such as fields of
+ * This overrules a field type. Only to be used in the case (such as in fields of
* anonymous classes) of fields whose parents cannot be reached at processing time.
* @return a {@link Class} representing the type.
- * @deprecated This is only meant as a temporary solution until a better handling
- * is implemented; only use this if strictly necessary as it may be
- * removed or changed even across revisions.
*/
- @Deprecated
Class<?> type() default Object.class;
/**
+ * This is to be used in cases where private inner classes are used as parameters.
* @return the inner class name to be used with {@link #type()}
- * @deprecated See {@link #type()}'s deprecation notice for more info.
+ * @see Patch#inner() for details
*/
- @Deprecated
- String typeInner() default "";
+ String[] typeInner() default {};
}
diff --git a/src/main/java/ftbsc/lll/processor/annotations/Patch.java b/src/main/java/ftbsc/lll/processor/annotations/Patch.java
index 1254663..55715ef 100644
--- a/src/main/java/ftbsc/lll/processor/annotations/Patch.java
+++ b/src/main/java/ftbsc/lll/processor/annotations/Patch.java
@@ -20,12 +20,13 @@ public @interface Patch {
Class<?> value();
/**
- * This is the inner class name to append after a $ symbol to the already acquired
- * fully-qualified name. If it's a number instead of a valid name, the class will be
- * treated as an anonymous class, and will therefore be automatically unverified.
- * @return the name of the inner class that contains the target, defaults to empty
- * string (not an inner class)
+ * This contains the inner class name(s) to append, separated by a $ symbol, to the already
+ * acquired fully-qualified name.
+ * If a number is provided instead of a valid name, the class will be treated as an
+ * anonymous class, and will therefore be skipped in verification.
+ * @return the name or path of the inner class that contain the target, defaults to array containing
+ * a single empty string (not an inner class)
* @since 0.5.0
*/
- String innerName() default "";
+ String[] inner() default {};
}
diff --git a/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java b/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java
index 047bc47..e4ba1ca 100644
--- a/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java
+++ b/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java
@@ -32,13 +32,13 @@ public class ClassContainer {
public final TypeElement elem;
/**
- * Private constructor, called from {@link #from(Annotation, Function, String, ProcessorOptions)}.
+ * Private constructor, called from {@link #from(Annotation, Function, String[], ProcessorOptions)}.
* @param fqn the fully-qualified name of the target class
* @param innerNames an array of Strings containing the path to the inner class, may be null
* @param options the {@link ProcessorOptions} to be used
*/
private ClassContainer(String fqn, String[] innerNames, ProcessorOptions options) {
- //find and validate
+ // find and validate
TypeElement elem = options.env.getElementUtils().getTypeElement(fqn);
if(elem == null)
@@ -49,23 +49,11 @@ public class ClassContainer {
);
if(innerNames != null) {
+ boolean skip = false;
for(String inner : innerNames) {
- if(inner == null) continue;
- fqnBuilder.append("$").append(inner);
- try {
- int anonClassCounter = Integer.parseInt(inner);
- //anonymous classes cannot be validated!
- if(options.anonymousClassWarning)
- options.env.getMessager().printMessage(
- Diagnostic.Kind.WARNING,
- String.format(
- "Anonymous classes cannot be verified by the processor. The existence of %s$%s is not guaranteed!",
- fqnBuilder, anonClassCounter
- )
- );
- elem = null;
- break;
- } catch(NumberFormatException exc) {
+ if(inner != null) fqnBuilder.append('$').append(inner);
+ if(skip) continue;
+ if(shouldValidate(inner)) {
elem = elem
.getEnclosedElements()
.stream()
@@ -74,11 +62,24 @@ public class ClassContainer {
.filter(e -> e.getSimpleName().contentEquals(inner))
.findFirst()
.orElse(null);
+ } else {
+ options.env.getMessager().printMessage(
+ Diagnostic.Kind.WARNING,
+ String.format(
+ "Anonymous class %s$%s and its children cannot be verified by the processor!",
+ fqnBuilder,
+ inner
+ )
+ );
+ elem = null;
+ skip = true;
+ continue;
}
if(elem == null)
throw new TargetNotFoundException("class", inner);
}
}
+
this.data = getClassData(fqnBuilder.toString(), options.mapper);
this.elem = elem;
}
@@ -87,17 +88,22 @@ public class ClassContainer {
* Safely extracts a {@link Class} from an annotation and gets its fully qualified name.
* @param ann the annotation containing the class
* @param classFunction the annotation function returning the class
- * @param innerName a string containing the inner class name or nothing
+ * @param innerNames a string containing the inner class name or nothing
* @param options the {@link ProcessorOptions} to be used
* @param <T> the type of the annotation carrying the information
* @return the fully qualified name of the given class
* @since 0.5.0
*/
- public static <T extends Annotation> ClassContainer from(T ann, Function<T, Class<?>> classFunction, String innerName, ProcessorOptions options) {
- String fqn;
- String[] inner;
- fqn = getTypeFromAnnotation(ann, classFunction, options.env).toString();
- inner = innerName.equals("") ? null : innerName.split("//$");
+ public static <T extends Annotation> ClassContainer from(
+ T ann,
+ Function<T, Class<?>> classFunction,
+ String[] innerNames,
+ ProcessorOptions options
+ ) {
+ String fqn = getTypeFromAnnotation(ann, classFunction, options.env).toString();
+ String[] inner = innerNames != null && innerNames.length != 0
+ ? String.join("$", innerNames).split("\\$")
+ : null;
return new ClassContainer(fqn, inner, options);
}
@@ -123,8 +129,8 @@ public class ClassContainer {
* @since 0.5.0
*/
public static ClassContainer findOrFallback(ClassContainer fallback, Patch p, Find f, ProcessorOptions options) {
- if(f == null) return ClassContainer.from(p, Patch::value, p.innerName(), options);
- ClassContainer cl = ClassContainer.from(f, Find::value, f.innerName(), options);
+ if(f == null) return ClassContainer.from(p, Patch::value, p.inner(), options);
+ ClassContainer cl = ClassContainer.from(f, Find::value, f.inner(), options);
return cl.data.name.equals("java/lang/Object") ? fallback : cl;
}
}
diff --git a/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java b/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java
index a9c56ee..7a2a913 100644
--- a/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java
+++ b/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java
@@ -58,7 +58,7 @@ public class FieldContainer {
*/
private FieldContainer(ClassContainer parent, String name, String descriptor, ProcessorOptions options) {
this.parent = parent;
- if(parent.elem == null) { //unverified
+ if(parent.elem == null) { // unverified
if(descriptor == null)
throw new AmbiguousDefinitionException("Cannot use name-based lookups for fields of unverifiable classes!");
this.elem = null;
@@ -81,9 +81,9 @@ public class FieldContainer {
* @since 0.5.0
*/
public static FieldContainer from(VariableElement finder, ProcessorOptions options) {
- //the parent always has a @Patch annotation
+ // the parent always has a @Patch annotation
Patch patchAnn = finder.getEnclosingElement().getAnnotation(Patch.class);
- //the finder always has a @Find annotation
+ // the finder always has a @Find annotation
Find f = finder.getAnnotation(Find.class);
ClassContainer parent = ClassContainer.findOrFallback(
@@ -96,12 +96,11 @@ public class FieldContainer {
if(fieldType.toString().equals("java.lang.Object")) {
descriptor = null;
} else {
- if(fieldType.getKind() != TypeKind.VOID && !fieldType.getKind().isPrimitive())
- descriptor = //jank af but this is temporary anyway
- "L" + ClassContainer.from(
- f, Find::type, f.typeInner(), options
- ).data.nameMapped + ";";
- else descriptor = descriptorFromType(fieldType, options.env);
+ if(fieldType.getKind() != TypeKind.VOID && !fieldType.getKind().isPrimitive()) {
+ descriptor = String.format("L%s;", ClassContainer.from(
+ f, Find::type, f.typeInner(), options
+ ).data.nameMapped);
+ } else descriptor = descriptorFromType(fieldType, options.env);
}
return new FieldContainer(parent, name, descriptor, options);
diff --git a/src/main/java/ftbsc/lll/processor/utils/ASTUtils.java b/src/main/java/ftbsc/lll/processor/utils/ASTUtils.java
index 6989e5e..3976ef3 100644
--- a/src/main/java/ftbsc/lll/processor/utils/ASTUtils.java
+++ b/src/main/java/ftbsc/lll/processor/utils/ASTUtils.java
@@ -1,9 +1,6 @@
package ftbsc.lll.processor.utils;
-import ftbsc.lll.exceptions.AmbiguousDefinitionException;
-import ftbsc.lll.exceptions.MappingNotFoundException;
-import ftbsc.lll.exceptions.NotAProxyException;
-import ftbsc.lll.exceptions.TargetNotFoundException;
+import ftbsc.lll.exceptions.*;
import ftbsc.lll.mapper.utils.Mapper;
import ftbsc.lll.mapper.data.ClassData;
import ftbsc.lll.mapper.data.FieldData;
@@ -13,12 +10,14 @@ import ftbsc.lll.processor.containers.ClassContainer;
import ftbsc.lll.proxies.ProxyType;
import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
@@ -148,7 +147,7 @@ public class ASTUtils {
t = env.getTypeUtils().erasure(t); //type erasure
StringBuilder desc = new StringBuilder();
- //add array brackets
+ // add array brackets
while(t.getKind() == TypeKind.ARRAY) {
desc.append("[");
t = ((ArrayType) t).getComponentType();
@@ -261,7 +260,7 @@ public class ASTUtils {
*/
public static FieldData getFieldData(String parent, String name, Mapper mapper) {
try {
- name = name.replace('.', '/'); //just in case
+ name = name.replace('.', '/'); // just in case
if(mapper != null)
return mapper.getFieldData(parent, name);
} catch(MappingNotFoundException ignored) {}
@@ -286,7 +285,7 @@ public class ASTUtils {
boolean strict, boolean field, ProcessingEnvironment env) {
if(parent.elem == null)
throw new TargetNotFoundException("parent class", parent.data.name);
- //try to find by name
+ // try to find by name
List<Element> candidates = parent.elem.getEnclosedElements()
.stream()
.filter(e -> (field && e instanceof VariableElement) || e instanceof ExecutableElement)
@@ -304,8 +303,8 @@ public class ASTUtils {
"Found %d members named %s in class %s!", candidates.size(), name, parent.data.name));
} else {
if(field) {
- //fields can verify the signature for extra safety
- //but there can only be 1 field with a given name
+ // fields can verify the signature for extra safety
+ // but there can only be 1 field with a given name
if(!descriptorFromType(candidates.get(0).asType(), env).equals(descr))
throw new TargetNotFoundException("field", String.format(
"%s with descriptor %s", name, descr), parent.data.name);
@@ -348,7 +347,7 @@ public class ASTUtils {
continue;
if (env.getElementUtils().overrides(method, (ExecutableElement) elem, context)) {
method = (ExecutableElement) elem;
- break; //found
+ break; // found
}
}
@@ -406,4 +405,26 @@ public class ASTUtils {
throw new NotAProxyException(v.getEnclosingElement().getSimpleName().toString(), v.getSimpleName().toString());
}
}
+
+ /**
+ * A pattern for efficiently recognising numeric strings.
+ * @since 0.7.0
+ */
+ private final static Pattern NUMERIC = Pattern.compile("^[0-9]+$");
+
+ /**
+ * Checks whether a certain class name is valid, and whether the processor is able to validate
+ * its existence.
+ * @param name the name to validate
+ * @return true if it's a valid class name, false if it's an anonymous class identifier
+ * @throws InvalidClassNameException if an invalid name was provided
+ * @since 0.7.0
+ */
+ public static boolean shouldValidate(String name) throws InvalidClassNameException {
+ if(SourceVersion.isIdentifier(name) && !SourceVersion.isKeyword(name)) {
+ return true; // if it's a valid name, proceed
+ } else if(NUMERIC.matcher(name).matches()) {
+ return false;
+ } else throw new InvalidClassNameException(name);
+ }
}