org.apache.calcite.linq4j.tree.FunctionExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of calcite-linq4j Show documentation
Show all versions of calcite-linq4j Show documentation
Calcite APIs for LINQ (Language-Integrated Query) in Java
/*
* 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 org.apache.calcite.linq4j.tree;
import org.apache.calcite.linq4j.function.Function;
import org.apache.calcite.linq4j.function.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static java.util.Objects.requireNonNull;
/**
* Represents a strongly typed lambda expression as a data structure in the form
* of an expression tree. This class cannot be inherited.
*
* @param Function type
*/
public final class FunctionExpression>
extends LambdaExpression {
public final @Nullable F function;
public final @Nullable BlockStatement body;
public final List parameterList;
private @Nullable F dynamicFunction;
/** Cached hash code for the expression. */
private int hash;
private FunctionExpression(Class type, @Nullable F function, @Nullable BlockStatement body,
List parameterList) {
super(ExpressionType.Lambda, type);
assert type != null : "type should not be null";
assert function != null || body != null
: "both function and body should not be null";
assert parameterList != null : "parameterList should not be null";
this.function = function;
this.body = body;
this.parameterList = parameterList;
}
public FunctionExpression(F function) {
this((Class) function.getClass(), function, null, ImmutableList.of());
}
public FunctionExpression(Class type, BlockStatement body,
List parameters) {
this(type, null, body, parameters);
}
@Override public Expression accept(Shuttle shuttle) {
shuttle = shuttle.preVisit(this);
BlockStatement body = this.body == null ? null : this.body.accept(shuttle);
return shuttle.visit(this, body);
}
@Override public R accept(Visitor visitor) {
return visitor.visit(this);
}
public Invokable compile() {
return args -> {
final Evaluator evaluator = new Evaluator();
for (int i = 0; i < args.length; i++) {
evaluator.push(parameterList.get(i), args[i]);
}
return evaluator.evaluate(requireNonNull(body, "body"));
};
}
public F getFunction() {
if (function != null) {
return function;
}
if (dynamicFunction == null) {
final Invokable x = compile();
ClassLoader classLoader = requireNonNull(requireNonNull(getClass().getClassLoader()));
//noinspection unchecked
dynamicFunction = (F) Proxy.newProxyInstance(classLoader,
new Class[]{Types.toClass(type)}, (proxy, method, args) -> x.dynamicInvoke(args));
}
return dynamicFunction;
}
@Override void accept(ExpressionWriter writer, int lprec, int rprec) {
// "new Function1() {
// public Result apply(T1 p1, ...) {
//
// }
// // bridge method
// public Object apply(Object p1, ...) {
// return apply((T1) p1, ...);
// }
// }
//
// if any arguments are primitive there is an extra bridge method:
//
// new Function1() {
// public double apply(double p1, int p2) {
//
// }
// // box bridge method
// public Double apply(Double p1, Integer p2) {
// return apply(p1.doubleValue(), p2.intValue());
// }
// // bridge method
// public Object apply(Object p1, Object p2) {
// return apply((Double) p1, (Integer) p2);
// }
List params = new ArrayList<>();
List bridgeParams = new ArrayList<>();
List bridgeArgs = new ArrayList<>();
List boxBridgeParams = new ArrayList<>();
List boxBridgeArgs = new ArrayList<>();
for (ParameterExpression parameterExpression : parameterList) {
final Type parameterType = parameterExpression.getType();
final Type parameterBoxType = Types.box(parameterType);
final String parameterBoxTypeName = Types.className(parameterBoxType);
params.add(parameterExpression.declString());
bridgeParams.add(parameterExpression.declString(Object.class));
bridgeArgs.add("(" + parameterBoxTypeName + ") "
+ parameterExpression.name);
boxBridgeParams.add(parameterExpression.declString(parameterBoxType));
boxBridgeArgs.add(parameterExpression.name
+ (Primitive.is(parameterType)
? "." + requireNonNull(Primitive.of(parameterType)).primitiveName + "Value()"
: ""));
}
requireNonNull(body, "body");
Type bridgeResultType = Functions.FUNCTION_RESULT_TYPES.get(this.type);
if (bridgeResultType == null) {
bridgeResultType = body.getType();
}
Type resultType2 = bridgeResultType;
if (bridgeResultType == Object.class
&& !params.equals(bridgeParams)
&& !(body.getType() instanceof TypeVariable)) {
resultType2 = body.getType();
}
String methodName = getAbstractMethodName();
writer.append("new ")
.append(type)
.append("()")
.begin(" {\n")
.append("public ")
.append(Types.className(resultType2))
.list(" " + methodName + "(",
", ", ") ", params)
.append(Blocks.toFunctionBlock(body));
// Generate an intermediate bridge method if at least one parameter is
// primitive.
final String bridgeResultTypeName =
isAbstractMethodPrimitive()
? Types.className(bridgeResultType)
: Types.className(Types.box(bridgeResultType));
if (!boxBridgeParams.equals(params)) {
writer
.append("public ")
.append(bridgeResultTypeName)
.list(" " + methodName + "(", ", ", ") ", boxBridgeParams)
.begin("{\n")
.list("return " + methodName + "(\n", ",\n", ");\n", boxBridgeArgs)
.end("}\n");
}
// Generate a bridge method. Argument types are looser (as if every
// type parameter is set to 'Object').
//
// Skip the bridge method if there are no arguments. It would have the
// same overload as the regular method.
if (!bridgeParams.equals(params)) {
writer
.append("public ")
.append(bridgeResultTypeName)
.list(" " + methodName + "(", ", ", ") ", bridgeParams)
.begin("{\n")
.list("return " + methodName + "(\n", ",\n", ");\n", bridgeArgs)
.end("}\n");
}
writer.end("}\n");
}
private boolean isAbstractMethodPrimitive() {
Method method = getAbstractMethod();
return Primitive.is(method.getReturnType());
}
private String getAbstractMethodName() {
final Method abstractMethod = getAbstractMethod();
return abstractMethod.getName();
}
private Method getAbstractMethod() {
if (type instanceof Class
&& ((Class) type).isInterface()) {
final List declaredMethods =
Lists.newArrayList(((Class) type).getDeclaredMethods());
declaredMethods.removeIf(m -> (m.getModifiers() & 0x00001000) != 0);
if (declaredMethods.size() == 1) {
return declaredMethods.get(0);
}
}
throw new IllegalStateException("Method not found, type = " + type);
}
@Override public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
FunctionExpression that = (FunctionExpression) o;
if (body != null ? !body.equals(that.body) : that.body != null) {
return false;
}
if (function != null ? !function.equals(that.function) : that.function
!= null) {
return false;
}
if (!parameterList.equals(that.parameterList)) {
return false;
}
return true;
}
@Override public int hashCode() {
int result = hash;
if (result == 0) {
result = Objects.hash(nodeType, type, function, body, parameterList);
if (result == 0) {
result = 1;
}
hash = result;
}
return result;
}
/** Function that can be invoked with a variable number of arguments. */
public interface Invokable {
@Nullable Object dynamicInvoke(@Nullable Object... args);
}
}