All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.calcite.linq4j.tree.FunctionExpression Maven / Gradle / Ivy

/*
 * 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(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);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy