Add new annotations from upstream

This commit is contained in:
MattBDev 2020-07-13 15:02:35 -04:00
parent 1e80c0429a
commit 204de7eab5
2 changed files with 154 additions and 0 deletions

View File

@ -0,0 +1,97 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.util;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import java.lang.reflect.Method;
import java.util.stream.Stream;
public class DeprecationUtil {
private DeprecationUtil() {
}
/**
* Verify that one of the two functions is overridden. Caller method must be the new method,
* annotated with {@link NonAbstractForCompatibility}.
*
* @param implementingClass the result of calling {@link Object#getClass()}
*/
public static void checkDelegatingOverride(Class<?> implementingClass) {
// pull the information about the caller
StackTraceElement caller = Throwables.lazyStackTrace(new Throwable()).get(1);
// find the matching caller method
Method callingMethod = getCallingMethod(caller);
NonAbstractForCompatibility annotation =
callingMethod.getAnnotation(NonAbstractForCompatibility.class);
// get the deprecated method
Method deprecatedMethod;
try {
deprecatedMethod = implementingClass.getMethod(
annotation.delegateName(), annotation.delegateParams()
);
} catch (NoSuchMethodException e) {
throw new AssertionError(
"Missing method referenced by " + NonAbstractForCompatibility.class, e
);
}
// Check if the deprecated method was overridden. If the declaring class is the caller's
// class, then it wasn't. That means that the caller method (i.e. the new method) should be
// overridden by the implementing class.
// There's no need to check if the new method has been overridden, since the only other
// way this could be reached is if someone calls `super.xyz`, which they have no reason to.
if (deprecatedMethod.getDeclaringClass().getName().equals(caller.getClassName())) {
throw new IllegalStateException("Class " + implementingClass.getName()
+ " must override " + methodToString(callingMethod));
}
}
private static Method getCallingMethod(StackTraceElement callerInfo) {
Method[] declaredMethods;
try {
declaredMethods = Class.forName(callerInfo.getClassName()).getDeclaredMethods();
} catch (ClassNotFoundException e) {
throw new AssertionError("Caller class missing?", e);
}
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.isAnnotationPresent(NonAbstractForCompatibility.class) &&
declaredMethod.getName().equals(callerInfo.getMethodName())) {
return declaredMethod;
}
}
throw new IllegalStateException("Failed to find caller method " +
callerInfo.getMethodName() + " annotated with " + NonAbstractForCompatibility.class);
}
private static String methodToString(Method method) {
StringBuilder builder = new StringBuilder(method.getDeclaringClass().getCanonicalName())
.append('.')
.append(method.getName())
.append('(');
Joiner.on(", ").appendTo(builder, Stream.of(method.getParameterTypes())
.map(Class::getSimpleName)
.iterator());
builder.append(')');
return builder.toString();
}
}

View File

@ -0,0 +1,57 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.util;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* The annotated method is only non-{@code abstract} for compatibility with old subclasses,
* and will be made {@code abstract} in the next major version of WorldEdit.
*
* <p>
* Any new subclasses <em>must</em> override the annotated method, failing to do so will result in
* an exception at runtime.
* </p>
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NonAbstractForCompatibility {
// Note that this annotation only functions properly if no other method in the same class
// shares the name of the annotated function AND is also annotated with this annotation.
// Otherwise, we cannot uniquely determine the calling method via reflection hacks.
// This could be changed, but it's not currently necessary.
/**
* The name of the method delegated to by the annotated method.
*/
String delegateName();
/**
* The parameter types of the method delegated to by the annotated method.
*/
Class<?>[] delegateParams();
}