Skip to content

Commit

Permalink
Simplify the default method logic
Browse files Browse the repository at this point in the history
Interfaces can not override any method from Object and the extension
metadata is only created for interfaces. So there is no need to do any
checking.

Rip out all of the check logic, add constant methods (except for
toString).

Cache the finalizer if present in extension metadata.
  • Loading branch information
hgschmie committed Jun 30, 2023
1 parent 56386b1 commit d7817e0
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
import static org.jdbi.v3.core.extension.ExtensionFactory.FactoryFlag.DONT_USE_PROXY;
import static org.jdbi.v3.core.extension.ExtensionFactory.FactoryFlag.NON_VIRTUAL_FACTORY;
import static org.jdbi.v3.core.extension.ExtensionHandler.EQUALS_HANDLER;
import static org.jdbi.v3.core.extension.ExtensionHandler.EQUALS_METHOD;
import static org.jdbi.v3.core.extension.ExtensionHandler.HASHCODE_HANDLER;
import static org.jdbi.v3.core.extension.ExtensionHandler.HASHCODE_METHOD;
import static org.jdbi.v3.core.extension.ExtensionHandler.NULL_HANDLER;
import static org.jdbi.v3.core.internal.JdbiClassUtils.EQUALS_METHOD;
import static org.jdbi.v3.core.internal.JdbiClassUtils.HASHCODE_METHOD;
import static org.jdbi.v3.core.internal.JdbiClassUtils.TOSTRING_METHOD;
import static org.jdbi.v3.core.extension.ExtensionHandler.TOSTRING_METHOD;

final class ExtensionFactoryDelegate implements ExtensionFactory {

Expand Down Expand Up @@ -126,29 +126,19 @@ public <E> E attach(Class<E> extensionType, HandleSupplier handleSupplier) {
// those will only be added if they don't already exist in the method handler map.

// If these methods are added, they are special because they operate on the proxy object itself, not the underlying object
if (extensionMetaData.addToString) {
ExtensionHandler toStringHandler = (h, target, args) ->
"Jdbi extension proxy for " + extensionType.getName() + "@" + Integer.toHexString(proxy.hashCode());
handlers.put(TOSTRING_METHOD,
extensionMetaData.new ExtensionHandlerInvoker(proxy, TOSTRING_METHOD, toStringHandler, handleSupplier, instanceConfig));
}

if (extensionMetaData.addEquals) {
handlers.put(EQUALS_METHOD,
extensionMetaData.new ExtensionHandlerInvoker(proxy, EQUALS_METHOD, EQUALS_HANDLER, handleSupplier, instanceConfig));
}
ExtensionHandler toStringHandler = (h, target, args) ->
"Jdbi extension proxy for " + extensionType.getName() + "@" + Integer.toHexString(proxy.hashCode());
handlers.put(TOSTRING_METHOD, extensionMetaData.new ExtensionHandlerInvoker(proxy, TOSTRING_METHOD, toStringHandler, handleSupplier, instanceConfig));

if (extensionMetaData.addHashCode) {
handlers.put(HASHCODE_METHOD,
extensionMetaData.new ExtensionHandlerInvoker(proxy, HASHCODE_METHOD, HASHCODE_HANDLER, handleSupplier, instanceConfig));
}
handlers.put(EQUALS_METHOD, extensionMetaData.new ExtensionHandlerInvoker(proxy, EQUALS_METHOD, EQUALS_HANDLER, handleSupplier, instanceConfig));
handlers.put(HASHCODE_METHOD, extensionMetaData.new ExtensionHandlerInvoker(proxy, HASHCODE_METHOD, HASHCODE_HANDLER, handleSupplier, instanceConfig));

// add all methods that are delegated to the underlying object / existing handlers
extensionMetaData.getExtensionMethods().forEach(method ->
handlers.put(method, extensionMetaData.createExtensionHandlerInvoker(delegatedInstance, method, handleSupplier, instanceConfig)));

// finalize is double special. Add this unconditionally, even if subclasses try to override it.
extensionMetaData.finalizer.ifPresent(method -> handlers.put(method,
extensionMetaData.getFinalizer().ifPresent(method -> handlers.put(method,
extensionMetaData.new ExtensionHandlerInvoker(proxy, method, NULL_HANDLER, handleSupplier, instanceConfig)));

return extensionType.cast(proxy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.lang.reflect.Method;

import org.jdbi.v3.core.config.ConfigRegistry;
import org.jdbi.v3.core.internal.JdbiClassUtils;
import org.jdbi.v3.core.internal.exceptions.Unchecked;
import org.jdbi.v3.meta.Alpha;
import org.jdbi.v3.meta.Beta;
Expand All @@ -44,6 +45,15 @@ public interface ExtensionHandler {
/** Handler that only returns null independent of any input parameters. */
ExtensionHandler NULL_HANDLER = (handleSupplier, target, args) -> null;

/** Constant for {@link Object#equals(Object)}. */
Method EQUALS_METHOD = JdbiClassUtils.methodLookup(Object.class, "equals", Object.class);

/** Constant for {@link Object#hashCode()}. */
Method HASHCODE_METHOD = JdbiClassUtils.methodLookup(Object.class, "hashCode");

/** Constant for {@link Object#toString()}. */
Method TOSTRING_METHOD = JdbiClassUtils.methodLookup(Object.class, "toString");

/**
* Gets invoked to return a value for the method that this handler was bound to.
* @param handleSupplier A {@link HandleSupplier} instance for accessing the handle and its related objects
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,7 @@ public final class ExtensionMetadata {
private final ConfigCustomizer instanceConfigCustomizer;
private final Map<Method, ? extends ConfigCustomizer> methodConfigCustomizers;
private final Map<Method, ExtensionHandler> methodHandlers;

final boolean addEquals;
final boolean addHashCode;
final boolean addToString;
final Optional<Method> finalizer;
private final Optional<Method> finalizer;

/**
* Returns a new {@link ExtensionMetadata.Builder} instance.
Expand All @@ -66,16 +62,13 @@ private ExtensionMetadata(
Class<?> extensionType,
ConfigCustomizer instanceConfigCustomizer,
Map<Method, ? extends ConfigCustomizer> methodConfigCustomizers,
Map<Method, ExtensionHandler> methodHandlers) {
Map<Method, ExtensionHandler> methodHandlers,
Optional<Method> finalizer) {
this.extensionType = extensionType;
this.instanceConfigCustomizer = instanceConfigCustomizer;
this.methodConfigCustomizers = Collections.unmodifiableMap(methodConfigCustomizers);
this.methodHandlers = Collections.unmodifiableMap(methodHandlers);

addToString = checkMethodMissing(extensionType, "toString");
addEquals = checkMethodMissing(extensionType, "equals", Object.class);
addHashCode = checkMethodMissing(extensionType, "hashCode");
finalizer = JdbiClassUtils.safeMethodLookup(extensionType, "finalize");
this.finalizer = finalizer;
}

public Class<?> extensionType() {
Expand Down Expand Up @@ -119,6 +112,14 @@ public Set<Method> getExtensionMethods() {
return methodHandlers.keySet();
}

/**
* Returns a reference to a method that overrides {@link Object#finalize()} if it exists.
* @return An {@link Optional} containing a {@link Method} if a finalizer exists.
*/
public Optional<Method> getFinalizer() {
return finalizer;
}

/**
* Creates an {@link ExtensionHandlerInvoker} instance for a specific method.
* @param target The target object on which the invoker should work
Expand Down Expand Up @@ -154,6 +155,8 @@ public static final class Builder {

private final Collection<Method> extensionTypeMethods = new HashSet<>();

private final Optional<Method> finalizer;

Builder(Class<?> extensionType) {
this.extensionType = extensionType;

Expand All @@ -171,6 +174,8 @@ public static final class Builder {
throw new UnableToCreateExtensionException("%s has ambiguous methods (%s) found, please resolve with an explicit override",
extensionType, methods);
});

this.finalizer = JdbiClassUtils.safeMethodLookup(extensionType, "finalize");
}

/**
Expand Down Expand Up @@ -284,7 +289,7 @@ public ExtensionMetadata build() {
.forEach(configCustomizer -> this.addMethodConfigCustomizer(method, configCustomizer)));
}

return new ExtensionMetadata(extensionType, instanceConfigCustomizer, methodConfigCustomizers, methodHandlers);
return new ExtensionMetadata(extensionType, instanceConfigCustomizer, methodConfigCustomizers, methodHandlers, finalizer);
}

private Optional<ExtensionHandler> findExtensionHandlerFor(Class<?> extensionType, Method method) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,6 @@
* Helper class for various internal reflection operations.
*/
public final class JdbiClassUtils {
public static final Method EQUALS_METHOD;
public static final Method HASHCODE_METHOD;
public static final Method TOSTRING_METHOD;

static {
EQUALS_METHOD = JdbiClassUtils.methodLookup(Object.class, "equals", Object.class);
HASHCODE_METHOD = JdbiClassUtils.methodLookup(Object.class, "hashCode");
TOSTRING_METHOD = JdbiClassUtils.methodLookup(Object.class, "toString");
}

private JdbiClassUtils() {
throw new UtilityClassException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
import org.jdbi.v3.core.extension.Extensions;
import org.jdbi.v3.core.internal.exceptions.Unchecked;

public class OnDemandExtensions implements JdbiConfig<OnDemandExtensions> {
import static org.jdbi.v3.core.extension.ExtensionHandler.EQUALS_METHOD;
import static org.jdbi.v3.core.extension.ExtensionHandler.HASHCODE_METHOD;
import static org.jdbi.v3.core.extension.ExtensionHandler.TOSTRING_METHOD;

public class OnDemandExtensions implements JdbiConfig<OnDemandExtensions> {
private Factory onDemandExtensionFactory;

public OnDemandExtensions() {
Expand All @@ -55,15 +58,15 @@ private Object createProxy(Jdbi jdbi, Class<?> extensionType, Class<?>... extraT
jdbi.getConfig(Extensions.class).onCreateProxy();

InvocationHandler handler = (proxy, method, args) -> {
if (JdbiClassUtils.EQUALS_METHOD.equals(method)) {
if (EQUALS_METHOD.equals(method)) {
return proxy == args[0];
}

if (JdbiClassUtils.HASHCODE_METHOD.equals(method)) {
if (HASHCODE_METHOD.equals(method)) {
return System.identityHashCode(proxy);
}

if (JdbiClassUtils.TOSTRING_METHOD.equals(method)) {
if (TOSTRING_METHOD.equals(method)) {
return "Jdbi on demand proxy for " + extensionType.getName() + "@" + Integer.toHexString(System.identityHashCode(proxy));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jdbi.v3.core.extension;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.extension.annotation.UseExtensionHandler;
import org.jdbi.v3.core.junit5.H2DatabaseExtension;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import static org.assertj.core.api.Assertions.assertThat;

public class TestDefaultExtensionMethods {

@RegisterExtension
public H2DatabaseExtension h2Extension = H2DatabaseExtension.instance();

private Handle handle;

@BeforeEach
public void setUp() {
handle = h2Extension.getSharedHandle();
handle.getConfig(Extensions.class).register(new ExtensionFrameworkTestFactory());
}

@Test
public void testDefaultMethodToString() {
DefaultMethodInterface dmo1 = handle.attach(DefaultMethodInterface.class);
assertThat(dmo1.toString()).startsWith("Jdbi extension proxy for " + DefaultMethodInterface.class.getName());

DefaultMethodInterface dmo2 = handle.attach(DefaultMethodInterface.class);

assertThat(dmo1.toString()).startsWith("Jdbi extension proxy for " + DefaultMethodInterface.class.getName());
assertThat(dmo2.toString()).startsWith("Jdbi extension proxy for " + DefaultMethodInterface.class.getName());
assertThat(dmo1.equals(dmo2)).isFalse();
assertThat(dmo2.equals(dmo1)).isFalse();

assertThat(dmo1.hashCode()).isNotEqualTo(dmo2.hashCode());
assertThat(dmo1.hashCode()).isEqualTo(System.identityHashCode(dmo1));
}

@Test
public void testDefaultMethodEquals() {
DefaultMethodInterface dmo1 = handle.attach(DefaultMethodInterface.class);
DefaultMethodInterface dmo2 = handle.attach(DefaultMethodInterface.class);

assertThat(dmo1.equals(dmo2)).isFalse();
assertThat(dmo2.equals(dmo1)).isFalse();
}

@Test
public void testDefaultMethodHashCode() {
DefaultMethodInterface dmo1 = handle.attach(DefaultMethodInterface.class);
DefaultMethodInterface dmo2 = handle.attach(DefaultMethodInterface.class);

assertThat(dmo1.hashCode()).isNotEqualTo(dmo2.hashCode());
assertThat(dmo1.hashCode()).isEqualTo(System.identityHashCode(dmo1));
assertThat(dmo2.hashCode()).isEqualTo(System.identityHashCode(dmo2));
}

@Test
public void testFinalizeOverride() throws Throwable {
FinalizeInterface fm = handle.attach(FinalizeInterface.class);
assertThat(fm.testFinalize()).isEqualTo("a ok");
}

public interface DefaultMethodInterface {
@ForTest
void testMethod();
}

public interface FinalizeInterface {
@ForTest
void testMethod();

default String testFinalize() {
this.finalize();
return "a ok";
}

default void finalize() {

}
}

@Retention(RetentionPolicy.RUNTIME)
@UseExtensionHandler(id = "test", value = TestExtensionAnnotations.Foo.Impl.class)
public @interface ForTest {

class Impl implements ExtensionHandler {

@Override
public Object invoke(HandleSupplier handleSupplier, Object target, Object[] args) {
return "foo";
}
}
}
}

0 comments on commit d7817e0

Please sign in to comment.