Skip to content
This repository has been archived by the owner on Jan 30, 2024. It is now read-only.

Commit

Permalink
Support an explicit fallback method for augmentations
Browse files Browse the repository at this point in the history
Failed dispatch can have a last chance to be resolved thanks to a normalized fallback method defined in augmentations.
Such mecahism permits ease the implemententtion of dynamic methods, DSL or fluent APIs.
  • Loading branch information
danielpetisme authored and Julien Ponge committed Jun 25, 2015
1 parent aa2284f commit daade3c
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 13 deletions.
23 changes: 22 additions & 1 deletion src/main/java/fr/insalyon/citi/golo/runtime/MethodInvocationSupport.java 100644 → 100755
Expand Up @@ -17,6 +17,7 @@
package fr.insalyon.citi.golo.runtime;

import gololang.DynamicObject;
import gololang.FunctionReference;

import java.lang.invoke.*;
import java.util.*;
Expand Down Expand Up @@ -159,6 +160,26 @@ public static Object fallback(InlineCache inlineCache, Object[] args) throws Thr
Class<?> receiverClass = args[0].getClass();
MethodHandle target = lookupTarget(receiverClass, inlineCache, args);

if (target == null) {
InlineCache fallbackCallSite = new InlineCache(
inlineCache.callerLookup,
"fallback",
methodType(Object.class, Object.class, Object.class, Object[].class),
false,
"name", "args");
Object[] fallbackArgs = new Object[] {
args[0],
inlineCache.name,
Arrays.copyOfRange(args,1,args.length)
};
target = lookupTarget(receiverClass, fallbackCallSite, fallbackArgs);
if (target != null) {
return fallback(fallbackCallSite, fallbackArgs);
} else {
throw new NoSuchMethodError(receiverClass + "::" + inlineCache.name);
}
}

MethodHandle guard = CLASS_GUARD.bindTo(receiverClass);
MethodHandle fallback = inlineCache.getTarget();
MethodHandle root = guardWithTest(guard, target, fallback);
Expand Down Expand Up @@ -214,6 +235,6 @@ private static MethodHandle findTarget(Class<?> receiverClass, InlineCache inlin
target = new AugmentationMethodFinder(inlineCache, receiverClass, args).find();
if (target != null) { return target; }

throw new NoSuchMethodError(receiverClass + "::" + inlineCache.name);
return null;
}
}
11 changes: 8 additions & 3 deletions src/main/java/gololang/FunctionReference.java 100644 → 100755
Expand Up @@ -126,6 +126,7 @@ public Object invoke(Object... args) throws Throwable {
public String toString() {
return "FunctionReference{" +
"handle=" + handle +
", parameterNames=" + Arrays.toString(parameterNames) +
'}';
}

Expand Down Expand Up @@ -237,10 +238,14 @@ public Object spread(Object... arguments) throws Throwable {
}

private String[] dropParameterNames(int from, int size) {
if (this.parameterNames == null) { return null; }
if (this.parameterNames == null) {
return null;
}
String[] filtered = new String[this.parameterNames.length - size];
System.arraycopy(parameterNames, 0, filtered, 0, from);
System.arraycopy(parameterNames, from + size, filtered, from, this.parameterNames.length - size - from);
if(filtered.length > 0) {
System.arraycopy(parameterNames, 0, filtered, 0, from);
System.arraycopy(parameterNames, from + size, filtered, from, this.parameterNames.length - size - from);
}
return filtered;
}
}
11 changes: 6 additions & 5 deletions src/main/java/gololang/Predefined.java 100644 → 100755
Expand Up @@ -20,11 +20,11 @@

import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandleProxies;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -354,7 +354,7 @@ public static Object asFunctionalInterface(Object type, Object func) throws Thro
put("interfaces", new Tuple(theType.getCanonicalName()));
put("implements", new HashMap<String, FunctionReference>() {
{
put(method.getName(), new FunctionReference(dropArguments(((FunctionReference) func).handle(), 0, Object.class)));
put(method.getName(), new FunctionReference(dropArguments(((FunctionReference) func).handle(), 0, Object.class), Arrays.stream(method.getParameters()).map(Parameter::getName).toArray(String[]::new)));
}
});
}
Expand Down Expand Up @@ -409,14 +409,15 @@ public static Object fun(Object name, Object module, Object arity) throws Throwa
if (validCandidates.size() == 1) {
targetMethod = validCandidates.iterator().next();
targetMethod.setAccessible(true);
String[] parameterNames = Arrays.stream(targetMethod.getParameters()).map(Parameter::getName).toArray(String[]::new);
if(isMethodDecorated(targetMethod)) {
if (functionArity < 0) {
return new FunctionReference(getDecoratedMethodHandle(targetMethod));
return new FunctionReference(getDecoratedMethodHandle(targetMethod), parameterNames);
} else {
return new FunctionReference(getDecoratedMethodHandle(targetMethod, functionArity));
return new FunctionReference(getDecoratedMethodHandle(targetMethod, functionArity), parameterNames);
}
}
return new FunctionReference(MethodHandles.publicLookup().unreflect(targetMethod));
return new FunctionReference(MethodHandles.publicLookup().unreflect(targetMethod), parameterNames);
}
if (validCandidates.size() > 1) {
throw new AmbiguousFunctionReferenceException(("The reference to " + name + " in " + module
Expand Down
27 changes: 23 additions & 4 deletions src/test/java/fr/insalyon/citi/golo/compiler/CompileAndRunTest.java 100644 → 100755
Expand Up @@ -20,10 +20,7 @@
import fr.insalyon.citi.golo.compiler.parser.ASTAssignment;
import fr.insalyon.citi.golo.compiler.parser.ParseException;
import fr.insalyon.citi.golo.runtime.AmbiguousFunctionReferenceException;
import gololang.FunctionReference;
import gololang.GoloStruct;
import gololang.Tuple;
import gololang.Range;
import gololang.*;
import org.testng.annotations.Test;

import java.io.IOException;
Expand Down Expand Up @@ -951,6 +948,28 @@ public void check_augmentations() throws Throwable {
assertThat((String) bang_plop.invoke(null), is("Plop!"));
}

@Test
public void augmentations_with_fallback() throws Throwable {
GoloClassLoader goloClassLoader = new GoloClassLoader(CompileAndRunTest.class.getClassLoader());
Class<?> moduleClass = compileAndLoadGoloModule(SRC, "augmentations-with-fallback.golo", goloClassLoader);
assertThat((String) moduleClass.getMethod("fallbackExists").invoke(null), is("Fallback for this bouh named casper with args[1, 2, 3]"));

Method fallbackDoesNotExists = moduleClass.getMethod("fallbackDoesNotExists");
try {
fallbackDoesNotExists.invoke(null);
fail("NoSuchMethodError should have been thrown");
} catch (Throwable e) {
assertThat(e.getCause(), instanceOf(NoSuchMethodError.class));
assertThat(e.getCause().getMessage(), containsString("class java.util.ArrayList::casper"));
}

Method fallbackOnAugmentedFunctionReference = moduleClass.getMethod("fallbackOnAugmentedFunctionReference");
assertThat((String) fallbackOnAugmentedFunctionReference.invoke(null) , is("Hello John Doe!"));

Method fallbackOnAugmentedClosure = moduleClass.getMethod("fallbackOnAugmentedClosure");
assertThat((String) fallbackOnAugmentedClosure.invoke(null), is("1-2-3"));
}

@Test
public void check_local_named_augmentations() throws Throwable {
GoloClassLoader goloClassLoader = new GoloClassLoader(CompileAndRunTest.class.getClassLoader());
Expand Down
34 changes: 34 additions & 0 deletions src/test/resources/for-execution/augmentations-with-fallback.golo
@@ -0,0 +1,34 @@
module golotest.execution.AugmentationsWithFallback

import java.util.stream.Collectors

augment java.lang.String {
function fallback = |this, name, args...| -> "Fallback for this " + this + " named " + name + " with args" + args: asList()
}

augment Fluent {
function fallback = |this, name, args...| {
return match {
when args: length() is 1 then this: bindAt(name, args: get(0))
otherwise this: bindAt(name, args)
}
}
}

augment gololang.FunctionReference with Fluent

function fallbackExists = -> "bouh" : casper(1,2,3)

function fallbackDoesNotExists = -> java.util.ArrayList() : casper()

function greet = |firstName, lastName| -> "Hello " + firstName + " " + lastName + "!"

function fallbackOnAugmentedFunctionReference = -> ^greet: firstName("John"): lastName("Doe"): invoke()

function fallbackOnAugmentedClosure = {
let joiner = |delimiter, values...| -> values: asList(): stream():
map(|it| -> it: toString()): collect(Collectors.joining(delimiter))

return joiner: values(array[1,2,3]): delimiter("-"): invoke()
}

Empty file modified src/test/resources/for-execution/augmentations.golo 100644 → 100755
Empty file.

0 comments on commit daade3c

Please sign in to comment.