Skip to content

Commit

Permalink
Merge pull request #664 from Netflix/lambda-default-methods
Browse files Browse the repository at this point in the history
Generate lambdas to access default config methods
  • Loading branch information
rgallardo-netflix authored Aug 14, 2023
2 parents d2b9354 + 10f04f4 commit 33b9cd1
Showing 1 changed file with 93 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@
import com.netflix.archaius.api.annotations.Configuration;
import com.netflix.archaius.api.annotations.DefaultValue;
import com.netflix.archaius.api.annotations.PropertyName;
import com.netflix.archaius.util.Maps;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaConversionException;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
Expand All @@ -29,6 +33,7 @@
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -191,7 +196,7 @@ <T> T newProxy(final Class<T> type, final String initialPrefix, boolean immutabl
// prefix + lowerCamelCaseDerivedPropertyName
final Map<Method, MethodInvoker<?>> invokers = new HashMap<>();
final Map<Method, String> propertyNames = new HashMap<>();

final InvocationHandler handler = (proxy, method, args) -> {
MethodInvoker<?> invoker = invokers.get(method);
if (invoker != null) {
Expand Down Expand Up @@ -244,7 +249,7 @@ else if ("toString".equals(method.getName())) {
final Class<?> returnType = m.getReturnType();

Function defaultSupplier = knownCollections.getOrDefault(returnType, (ignored) -> null);

if (m.getAnnotation(DefaultValue.class) != null) {
if (m.isDefault()) {
throw new IllegalArgumentException("@DefaultValue cannot be defined on a method with a default implementation for method "
Expand Down Expand Up @@ -308,43 +313,65 @@ private static <T> Function<Object[], T> memoize(T value) {
private static <T> Function<Object[], T> createDefaultMethodSupplier(Method method, Class<T> type, T proxyObject) {
final MethodHandle methodHandle;

final MethodHandles.Lookup lookup;

try {
if (SystemUtils.IS_JAVA_1_8) {
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
constructor.setAccessible(true);
methodHandle = constructor.newInstance(type, MethodHandles.Lookup.PRIVATE)
.unreflectSpecial(method, type)
.bindTo(proxyObject);
lookup = constructor.newInstance(type, MethodHandles.Lookup.PRIVATE);
methodHandle = lookup.unreflectSpecial(method, type);
}
else {
// Java 9 onwards
methodHandle = MethodHandles.lookup()
lookup = MethodHandles.lookup();
methodHandle = lookup
.findSpecial(type,
method.getName(),
MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
type)
.bindTo(proxyObject);
type);
}
} catch (Throwable e) {
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to create temporary object for " + type.getName(), e);
}

if (methodHandle.type().parameterCount() == 1) {
Function<Object, Object> getter = asFunction(lookup, methodHandle, method);
//noinspection unchecked
return (args) -> (T) getter.apply(proxyObject);
} else if (methodHandle.type().parameterCount() == 2) {
BiFunction<Object, Object, Object> getter = asBiFunction(lookup, methodHandle, method);
return (args) -> {
if (args == null) {
return null;
}
//noinspection unchecked
return (T) getter.apply(proxyObject, args[0]);
};
}

// Fall back to calling the MethodHandle directly
MethodHandle boundHandle = methodHandle.bindTo(proxyObject);

return (args) -> {
try {
if (methodHandle.type().parameterCount() == 0) {
//noinspection unchecked
return (T) methodHandle.invokeWithArguments();
} else if (args != null) {
if (args != null) {
//noinspection unchecked
return (T) methodHandle.invokeWithArguments(args);
return (T) boundHandle.invokeWithArguments(args);
} else {
// This is a handle to a method WITH arguments, being called with none. This happens when toString()
// is trying to build a representation of a proxy that has a parametrized property AND the interface
// provides a default method for it. There's no good default to return here, so we'll just use null
return null;
}
} catch (Throwable e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
if (e instanceof Error) {
throw (Error) e;
}
throw new RuntimeException(e);
}
};
Expand Down Expand Up @@ -384,9 +411,9 @@ public T invoke(Object[] args) {
// String getFooValue(String arg0, Integer arg1)
//
// called as getFooValue("bar", 1) would look for the property 'foo.1.bar'
Map<String, Object> values = new HashMap<>();
Map<String, Object> values = Maps.newHashMap(args.length);
for (int i = 0; i < args.length; i++) {
values.put("" + i, args[i]);
values.put(String.valueOf(i), args[i]);
}
String propName = new StrSubstitutor(values, "${", "}", '$').replace(nameAnnot);
T result = getPropertyWithDefault(returnType, propName);
Expand All @@ -401,4 +428,54 @@ <R> R getPropertyWithDefault(Class<R> type, String propName) {
}
};
}

/**
* For a given no-args method or default method, build a Function instance that takes the instance and invokes
* the underlying method on it.
*/
@SuppressWarnings("unchecked")
private static Function<Object, Object> asFunction(MethodHandles.Lookup lookup, MethodHandle methodHandle, Method method) {
try {
CallSite site = LambdaMetafactory.metafactory(lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
methodHandle,
MethodType.methodType(method.getReturnType(), method.getDeclaringClass()));
return (Function<Object, Object>) site.getTarget().invokeExact();
} catch (Throwable t) {
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
if (t instanceof Error) {
throw (Error) t;
}
throw new RuntimeException(t);
}
}

/**
* For a given single-arg method or default method, build a Function instance that takes the instance and invokes
* the underlying method on it.
*/
@SuppressWarnings("unchecked")
private static BiFunction<Object, Object, Object> asBiFunction(MethodHandles.Lookup lookup, MethodHandle methodHandle, Method method) {
try {
CallSite site = LambdaMetafactory.metafactory(lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class, Object.class),
methodHandle,
MethodType.methodType(method.getReturnType(), method.getDeclaringClass()));
return (BiFunction<Object, Object, Object>) site.getTarget().invokeExact();
} catch (Throwable t) {
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
if (t instanceof Error) {
throw (Error) t;
}
throw new RuntimeException(t);
}
}
}

0 comments on commit 33b9cd1

Please sign in to comment.