Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public Object eval( CallStack callstack, Interpreter interpreter)
return objectArrayAllocation(name, (BSHArrayDimensions)args,
callstack, interpreter );
}
else if ( type instanceof BSHFunctionType ) {
Class<?> funcClass = ((BSHFunctionType)type).getType(callstack, interpreter);
return arrayAllocation( (BSHArrayDimensions)args, funcClass, callstack, interpreter );
}
else
return primitiveArrayAllocation((BSHPrimitiveType)type,
(BSHArrayDimensions)args, callstack, interpreter );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ private boolean isComparableTypes(Class<?> val1Class,
* @param type2 second type to compare
* @return types are similar */
private boolean isSimilarTypes(Class<?> type1, Class<?> type2) {
return null == type2 || type1.isAssignableFrom(type2)
|| type2.isAssignableFrom(type1);
return null == type2 || Types.isClassAssignable(type1, type2)
|| Types.isClassAssignable(type2, type1);
}

/** Object is a non-null and non-void Primitive type.
Expand Down
81 changes: 81 additions & 0 deletions bsh-lambda-300-eee36c8/src/main/java/bsh/BSHFunctionType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*****************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you 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 bsh;

import java.util.ArrayList;
import java.util.List;

/**
* AST node for handling High-Order Function type signatures.
* Syntax: (ReceiverType.(ParamTypes) -> ReturnType)
*/
public class BSHFunctionType extends SimpleNode {
public BSHFunctionType(int id) {
super(id);
}

/**
* Resolves this signature to a unique synthetic Functional Interface Class.
*/
public Class<?> getType(CallStack callstack, Interpreter interpreter) throws EvalError {
SignatureInfo info = collectSignatureInfo(callstack, interpreter);
BshClassManager bcm = interpreter.getClassManager();

// Retrieve the generated interface from the factory
return SyntheticInterfaceFactory.getInstance().getInterface(bcm, info);
}

/**
* Helper to extract signature components from child nodes.
*/
public SignatureInfo collectSignatureInfo(CallStack callstack, Interpreter interpreter) throws EvalError {
Class<?> receiver = null;
List<Class<?>> params = new ArrayList<>();
Class<?> returnType = Void.TYPE;

int numChildren = jjtGetNumChildren();
for (int i = 0; i < numChildren; i++) {
Node child = jjtGetChild(i);
if (child instanceof BSHReceiverType) {
receiver = ((BSHReceiverType) child).getType(callstack, interpreter);
} else if (child instanceof BSHType) {
params.add(((BSHType) child).getType(callstack, interpreter));
} else if (child instanceof BSHReturnType) {
returnType = ((BSHReturnType) child).evalReturnType(callstack, interpreter);
}
}
return new SignatureInfo(receiver, params, returnType);
}

/**
* Structured data for the function signature.
*/
public static class SignatureInfo {
public final Class<?> receiver;
public final List<Class<?>> params;
public final Class<?> returnType;

public SignatureInfo(Class<?> r, List<Class<?>> p, Class<?> rt) {
this.receiver = r;
this.params = p;
this.returnType = rt;
}
}
}
36 changes: 36 additions & 0 deletions bsh-lambda-300-eee36c8/src/main/java/bsh/BSHReceiverType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*****************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you 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 bsh;

/**
* Wraps the receiver type in a high-order function (e.g., Context in Context.() -> void).
*/
public class BSHReceiverType extends SimpleNode {
public BSHReceiverType(int id) {
super(id);
}

/**
* Resolve the receiver's Class type.
*/
public Class<?> getType(CallStack callstack, Interpreter interpreter) throws EvalError {
return ((BSHAmbiguousName) jjtGetChild(0)).toClass(callstack, interpreter);
}
}
12 changes: 12 additions & 0 deletions bsh-lambda-300-eee36c8/src/main/java/bsh/BSHType.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ public String getTypeDescriptor(
Node node = getTypeNode();
if ( node instanceof BSHPrimitiveType )
descriptor = getTypeDescriptor( ((BSHPrimitiveType)node).type );
else if ( node instanceof BSHFunctionType ) {
Class<?> cls = null;
try {
cls = ((BSHFunctionType)node).getType(callstack, interpreter);
} catch (EvalError e) { /* ignore */ }
descriptor = (cls == null) ? "Ljava/lang/Object;" : getTypeDescriptor(cls);
}
else
{
String clasName = ((BSHAmbiguousName)node).text;
Expand Down Expand Up @@ -129,6 +136,11 @@ public Class<?> getType( CallStack callstack, Interpreter interpreter )
// return cached type if available
if ( type != null )
return type;

if ( jjtGetNumChildren() > 0 && jjtGetChild(0) instanceof BSHFunctionType ) {
this.type = ((BSHFunctionType) jjtGetChild(0)).getType(callstack, interpreter);
return this.type;
}

// first node will either be PrimitiveType or AmbiguousName
Node node = getTypeNode();
Expand Down
37 changes: 33 additions & 4 deletions bsh-lambda-300-eee36c8/src/main/java/bsh/BshLambda.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package bsh;

import static bsh.This.Keys.BSHEXTENSIONMETHODRECEIVER;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -114,6 +116,9 @@ protected static boolean isAssignable(Class<?> from, Class<?> to, int round) {
return entry.getKey().isAssignable(method, round);
return false;
}

/** Set whether this lambda has a receiver context */
protected void setHasReceiver(boolean b) { /* Default no-op */ }

/**
* Convert this lambda to a specific functional interface.
Expand All @@ -123,6 +128,15 @@ protected static boolean isAssignable(Class<?> from, Class<?> to, int round) {
protected <T> T convertTo(Class<T> functionalInterface) throws UtilEvalError {
if (!BshLambda.isAssignable(this.dummyType, functionalInterface, Types.BSH_ASSIGNABLE))
throw new UtilEvalError("This BshLambda can't be converted to " + functionalInterface.getName());

/**
* If it's a receiver-type interface, enable receiver mode in the lambda implementation.
* Checked using the marker interface.
*/
if (SyntheticInterfaceFactory.BshReceiverLambdaMarker.class.isAssignableFrom(functionalInterface)) {
this.setHasReceiver(true);
}

try {
return (T) Proxy.newProxyInstance(
functionalInterface.getClassLoader(),
Expand Down Expand Up @@ -173,6 +187,7 @@ private static class BshLambdaFromLambdaExpression extends BshLambda {
private final Class<?>[] paramsTypes;
private final String[] paramsNames;
private final Node bodyNode;
private boolean hasReceiver = false;

public BshLambdaFromLambdaExpression(Node expressionNode, NameSpace declaringNameSpace, Modifiers[] paramsModifiers, Class<?>[] paramsTypes, String[] paramsNames, Node bodyNode) {
super(expressionNode);
Expand All @@ -185,6 +200,8 @@ public BshLambdaFromLambdaExpression(Node expressionNode, NameSpace declaringNam
if (paramsModifiers.length != paramsTypes.length || paramsTypes.length != paramsNames.length)
throw new IllegalArgumentException("The length of 'paramsModifiers', 'paramsTypes' and 'paramsNames' can't be different!");
}

public void setHasReceiver(boolean b) { this.hasReceiver = b; }

protected final Object invokeImpl(Object[] args) throws UtilEvalError, EvalError, TargetError {
if (args.length != this.paramsTypes.length) throw new UtilEvalError("Wrong number of arguments!");
Expand All @@ -207,6 +224,19 @@ protected final Object invokeImpl(Object[] args) throws UtilEvalError, EvalError
/** Initialize a name space for eval the lambda expression's body */
private NameSpace initNameSpace(Object[] args) throws UtilEvalError {
NameSpace nameSpace = new NameSpace(this.declaringNameSpace, "LambdaExpression");

if (this.hasReceiver && args.length > 0) {
Object receiver = args[0];
// Inject the receiver for "this" interception in Name.java
nameSpace.setLocalVariable(BSHEXTENSIONMETHODRECEIVER.toString(), receiver, false);

// Import the receiver's members for direct access
Object rawReceiver = Primitive.unwrap(receiver);
if (rawReceiver != null) {
nameSpace.importObject(rawReceiver);
}
}

for (int i = 0; i < paramsNames.length; i++) {
Class<?> paramType = this.paramsTypes[i];
if (paramType != null)
Expand All @@ -218,11 +248,10 @@ private NameSpace initNameSpace(Object[] args) throws UtilEvalError {
}

protected boolean isAssignable(Method to, int round) {
Type[] toParamsTypes = to.getGenericParameterTypes();
Class<?>[] toParamsTypes = to.getParameterTypes();
if (this.paramsTypes.length != toParamsTypes.length) return false;

// TODO: validate the return type of 'this.bodyNode' ???
return Types.isSignatureAssignable(this.paramsTypes, toParamsTypes, round);

return Types.isSignatureAssignable(toParamsTypes, this.paramsTypes, round);
}

}
Expand Down
2 changes: 1 addition & 1 deletion bsh-lambda-300-eee36c8/src/main/java/bsh/BshMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ private Object invokeImpl(
if ((getParameterCount() == argValues.length) &&
(argValues[lastParamIndex] == null ||
(argValues[lastParamIndex].getClass().isArray() &&
lastP.getComponentType().isAssignableFrom(argValues[lastParamIndex].getClass().getComponentType())))) {
Types.isClassAssignable(lastP.getComponentType(), argValues[lastParamIndex].getClass().getComponentType())))) {
/*
* This is the case that the final argument is
* a null or it contains an array of the component
Expand Down
4 changes: 2 additions & 2 deletions bsh-lambda-300-eee36c8/src/main/java/bsh/Invocable.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public ParameterType collectParamaters(Object base, Object[] params)
protected Object coerceToType(Object param, Class<?> type)
throws Throwable {
Class<?> pClass = Types.getType(param);
if (null == pClass || !type.isAssignableFrom(pClass))
if (null == pClass || !Types.isClassAssignable(type, pClass))
param = Types.castObject(param, type, Types.CAST);
return Primitive.unwrap(param);
}
Expand Down Expand Up @@ -337,7 +337,7 @@ public ParameterType collectParamaters(Object base, Object[] params)
if (getParameterCount() == params.length
&& lastParam != null
&& lastParam.getClass().isArray()
&& getVarArgsComponentType().isAssignableFrom(lastParam.getClass().getComponentType())) {
&& Types.isClassAssignable(getVarArgsComponentType(), lastParam.getClass().getComponentType())) {
isFixedArity = true;
parameters.add(lastParam);
} else if (getParameterCount() == params.length
Expand Down
28 changes: 28 additions & 0 deletions bsh-lambda-300-eee36c8/src/main/java/bsh/Name.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import static bsh.This.Keys.BSHEXTENSIONMETHODRECEIVER;

import java.lang.reflect.Array;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -932,10 +933,37 @@ private Object invokeLocalMethod(

return meth.invoke( args, interpreter, callstack, callerInfo, overrideChild );
}

try {
Object varObj = namespace.getVariable(methodName);
if ( varObj != null && varObj != Primitive.VOID ) {
if ( isCallableFunction(varObj) ) {
return Reflect.invokeObjectMethod(
varObj, "invoke", args, interpreter, callstack, callerInfo );
}
}
} catch ( UtilEvalError e ) { /* fallback to command */ }

// Look for a BeanShell command
return namespace.invokeCommand(methodName, args, interpreter, callstack, callerInfo);
}

/**
* Check if the object is a proxy of our generated functional interfaces.
* Use the marker interface for decoupling from generated names.
*/
private boolean isCallableFunction(Object obj) {
if (obj == null) return false;
Class<?> cls = obj.getClass();
if (Proxy.isProxyClass(cls)) {
for (Class<?> iface : cls.getInterfaces()) {
if (SyntheticInterfaceFactory.BshLambdaMarker.class.isAssignableFrom(iface)) {
return true;
}
}
}
return false;
}

// Static methods that operate on compound ('.' separated) names
// I guess we could move these to StringUtil someday
Expand Down
Loading