From 4d330a731bf44bcca291a23407c06ed063193921 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Tue, 4 Jun 2024 20:03:51 +0200 Subject: feat: better inner classes api, removed deprecation warnings since that stuff is there to stay --- .../exceptions/AmbiguousDefinitionException.java | 12 +---- .../lll/exceptions/InvalidClassNameException.java | 15 ++++++ .../lll/exceptions/InvalidResourceException.java | 22 -------- .../java/ftbsc/lll/processor/LilleroProcessor.java | 2 +- .../java/ftbsc/lll/processor/annotations/Find.java | 22 +++----- .../ftbsc/lll/processor/annotations/Patch.java | 13 ++--- .../lll/processor/containers/ClassContainer.java | 58 ++++++++++++---------- .../lll/processor/containers/FieldContainer.java | 17 +++---- .../java/ftbsc/lll/processor/utils/ASTUtils.java | 41 +++++++++++---- 9 files changed, 103 insertions(+), 99 deletions(-) create mode 100644 src/main/java/ftbsc/lll/exceptions/InvalidClassNameException.java delete mode 100644 src/main/java/ftbsc/lll/exceptions/InvalidResourceException.java (limited to 'src/main') 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 the type of the annotation carrying the information * @return the fully qualified name of the given class * @since 0.5.0 */ - public static ClassContainer from(T ann, Function> classFunction, String innerName, ProcessorOptions options) { - String fqn; - String[] inner; - fqn = getTypeFromAnnotation(ann, classFunction, options.env).toString(); - inner = innerName.equals("") ? null : innerName.split("//$"); + public static ClassContainer from( + T ann, + Function> 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 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); + } } -- cgit v1.2.3-56-ga3b1