diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/NonClosableConnection.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/NonClosableConnection.java index a534f63ab..29c21b0c5 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/NonClosableConnection.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/NonClosableConnection.java @@ -25,15 +25,45 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection.file; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; +import net.bytebuddy.implementation.MethodDelegation; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.sql.Connection; +import java.sql.SQLException; + +import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy; +import static net.bytebuddy.matcher.ElementMatchers.isFinal; +import static net.bytebuddy.matcher.ElementMatchers.not; /** - * Represents a connection which cannot be closed using the standard {@link #close()} method. + * A wrapper around a {@link Connection} which blocks usage of the default {@link #close()} method. */ -public interface NonClosableConnection extends Connection { +public abstract class NonClosableConnection implements Connection { + + private static final MethodHandle CONSTRUCTOR; + static { + // construct an implementation of NonClosableConnection + Class implClass = new ByteBuddy() + .subclass(NonClosableConnection.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING) + .name(NonClosableConnection.class.getName() + "Impl") + .method(not(isFinal()).and(not(isDeclaredBy(Object.class)))) + .intercept(MethodDelegation.toField("delegate")) + .make() + .load(NonClosableConnection.class.getClassLoader()) + .getLoaded(); + + try { + CONSTRUCTOR = MethodHandles.publicLookup().in(implClass) + .findConstructor(implClass, MethodType.methodType(void.class, Connection.class)) + .asType(MethodType.methodType(NonClosableConnection.class, Connection.class)); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } /** * Creates a {@link NonClosableConnection} that delegates calls to the given {@link Connection}. @@ -42,40 +72,42 @@ public interface NonClosableConnection extends Connection { * @return a non closable connection */ static NonClosableConnection wrap(Connection connection) { - return (NonClosableConnection) Proxy.newProxyInstance( - NonClosableConnection.class.getClassLoader(), - new Class[]{NonClosableConnection.class}, - new Handler(connection) - ); + try { + return (NonClosableConnection) CONSTRUCTOR.invokeExact(connection); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + protected final Connection delegate; + + protected NonClosableConnection(Connection delegate) { + this.delegate = delegate; } /** * Actually {@link #close() closes} the underlying connection. */ - void shutdown(); + public final void shutdown() throws SQLException { + this.delegate.close(); + } - final class Handler implements InvocationHandler { - private final Connection connection; + @Override + public final void close() throws SQLException { + // do nothing + } - Handler(Connection connection) { - this.connection = connection; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - // block calls directly to #close - if (method.getName().equals("close")) { - return null; - } - - // proxy calls to #shutdown to the real #close method - if (method.getName().equals("shutdown")) { - this.connection.close(); - return null; - } - - // delegate all other calls - return method.invoke(this.connection, args); + @Override + public final boolean isWrapperFor(Class iface) throws SQLException { + return iface.isInstance(this.delegate) || this.delegate.isWrapperFor(iface); + } + + @SuppressWarnings("unchecked") + @Override + public final T unwrap(Class iface) throws SQLException { + if (iface.isInstance(this.delegate)) { + return (T) this.delegate; } + return this.delegate.unwrap(iface); } }