diff --git a/java/fury-core/src/main/java/io/fury/codegen/ClosureVisitable.java b/java/fury-core/src/main/java/io/fury/codegen/ClosureVisitable.java
new file mode 100644
index 0000000000..ee77064a60
--- /dev/null
+++ b/java/fury-core/src/main/java/io/fury/codegen/ClosureVisitable.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 The Fury authors
+ * 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 io.fury.codegen;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation used to mark closure may contain child expressions.
+ *
+ * @author chaokunyang
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface ClosureVisitable {}
diff --git a/java/fury-core/src/main/java/io/fury/codegen/Expression.java b/java/fury-core/src/main/java/io/fury/codegen/Expression.java
index 64664b1591..8f21cc1563 100644
--- a/java/fury-core/src/main/java/io/fury/codegen/Expression.java
+++ b/java/fury-core/src/main/java/io/fury/codegen/Expression.java
@@ -19,16 +19,61 @@
package io.fury.codegen;
import static io.fury.codegen.Code.ExprCode;
+import static io.fury.codegen.Code.LiteralValue;
+import static io.fury.codegen.Code.LiteralValue.FalseLiteral;
+import static io.fury.codegen.Code.LiteralValue.TrueLiteral;
+import static io.fury.codegen.CodeGenerator.alignIndent;
+import static io.fury.codegen.CodeGenerator.appendNewlineIfNeeded;
+import static io.fury.codegen.CodeGenerator.indent;
+import static io.fury.type.TypeUtils.BOOLEAN_TYPE;
+import static io.fury.type.TypeUtils.CLASS_TYPE;
+import static io.fury.type.TypeUtils.ITERABLE_TYPE;
+import static io.fury.type.TypeUtils.OBJECT_TYPE;
+import static io.fury.type.TypeUtils.PRIMITIVE_BOOLEAN_TYPE;
+import static io.fury.type.TypeUtils.PRIMITIVE_BYTE_TYPE;
+import static io.fury.type.TypeUtils.PRIMITIVE_INT_TYPE;
+import static io.fury.type.TypeUtils.PRIMITIVE_LONG_TYPE;
+import static io.fury.type.TypeUtils.PRIMITIVE_SHORT_TYPE;
+import static io.fury.type.TypeUtils.PRIMITIVE_VOID_TYPE;
+import static io.fury.type.TypeUtils.STRING_TYPE;
+import static io.fury.type.TypeUtils.boxedType;
+import static io.fury.type.TypeUtils.defaultValue;
+import static io.fury.type.TypeUtils.getArrayType;
+import static io.fury.type.TypeUtils.getElementType;
+import static io.fury.type.TypeUtils.getRawType;
+import static io.fury.type.TypeUtils.getSizeOfPrimitiveType;
+import static io.fury.type.TypeUtils.isPrimitive;
+import static io.fury.type.TypeUtils.maxType;
+import static io.fury.util.Utils.checkArgument;
+import com.google.common.base.Preconditions;
+import com.google.common.primitives.Primitives;
import com.google.common.reflect.TypeToken;
+import io.fury.type.TypeUtils;
+import io.fury.util.Functions;
+import io.fury.util.Platform;
+import io.fury.util.ReflectionUtils;
+import io.fury.util.StringUtils;
+import java.lang.reflect.Array;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* An expression represents a piece of code evaluation logic which can be generated to valid java
* code. Expression can be used to compose complex code logic.
*
- *
TODO refactor expression into Expression and Stmt with a common class Node. Expression will
- * have a value, the stmt won't have value. If/While/For/doWhile/ForEach are statements instead of
- * expression.
+ *
TODO(chaokunyang) refactor expression into Expression and Stmt with a common class Node.
+ * Expression will have a value, the stmt won't have value. If/While/For/doWhile/ForEach are
+ * statements instead of expression.
+ *
+ *
Note that all dependent expression field are marked as non-final, so when split expression
+ * tree into subtrees, we can replace dependent expression fields in subtree root nodes with {@link
+ * Reference}.
*
* @author chaokunyang
*/
@@ -73,4 +118,2325 @@ default boolean nullable() {
// ###########################################################
// ####################### Expressions #######################
// ###########################################################
+
+ /** An expression that have a value as the result of the evaluation. */
+ abstract class ValueExpression implements Expression {
+ // set to others to get a more context-dependent variable name.
+ public String valuePrefix = "value";
+
+ public String isNullPrefix() {
+ return "is" + StringUtils.capitalize(valuePrefix) + "Null";
+ }
+ }
+
+ /**
+ * A ListExpression is a list of expressions. Use last expression's type/nullable/value/IsNull to
+ * represent ListExpression's type/nullable/value/IsNull.
+ */
+ class ListExpression implements Expression {
+ private final List expressions;
+ private Expression last;
+
+ public ListExpression(Expression... expressions) {
+ this(new ArrayList<>(Arrays.asList(expressions)));
+ }
+
+ public ListExpression(List expressions) {
+ this.expressions = expressions;
+ if (!this.expressions.isEmpty()) {
+ this.last = this.expressions.get(this.expressions.size() - 1);
+ }
+ }
+
+ @Override
+ public TypeToken> type() {
+ Preconditions.checkNotNull(last);
+ return last.type();
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ boolean hasCode = false;
+ for (Expression expr : expressions) {
+ ExprCode code = expr.genCode(ctx);
+ if (StringUtils.isNotBlank(code.code())) {
+ appendNewlineIfNeeded(codeBuilder);
+ codeBuilder.append(code.code());
+ hasCode = true;
+ }
+ }
+ ExprCode lastExprCode = last.genCode(ctx);
+ String code = codeBuilder.toString();
+ if (!hasCode) {
+ code = null;
+ }
+ return new ExprCode(code, lastExprCode.isNull(), lastExprCode.value());
+ }
+
+ @Override
+ public boolean nullable() {
+ return last.nullable();
+ }
+
+ public List expressions() {
+ return expressions;
+ }
+
+ public ListExpression add(Expression expr) {
+ Preconditions.checkNotNull(expr);
+ this.expressions.add(expr);
+ this.last = expr;
+ return this;
+ }
+
+ public ListExpression add(Expression expr, Expression... exprs) {
+ add(expr);
+ return addAll(Arrays.asList(exprs));
+ }
+
+ public ListExpression addAll(List exprs) {
+ Preconditions.checkNotNull(exprs);
+ this.expressions.addAll(exprs);
+ if (!exprs.isEmpty()) {
+ this.last = exprs.get(exprs.size() - 1);
+ }
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return expressions.stream().map(Object::toString).collect(Collectors.joining(","));
+ }
+ }
+
+ class Literal implements Expression {
+ public static final Literal True = new Literal(true, PRIMITIVE_BOOLEAN_TYPE);
+ public static final Literal False = new Literal(false, PRIMITIVE_BOOLEAN_TYPE);
+
+ private final Object value;
+ private final TypeToken> type;
+
+ public Literal(String value) {
+ this.value = value;
+ this.type = STRING_TYPE;
+ }
+
+ public Literal(Object value, TypeToken> type) {
+ this.value = value;
+ this.type = type;
+ }
+
+ public static Literal ofByte(short v) {
+ return new Literal(v, PRIMITIVE_BYTE_TYPE);
+ }
+
+ public static Literal ofShort(short v) {
+ return new Literal(v, PRIMITIVE_SHORT_TYPE);
+ }
+
+ public static Literal ofInt(int v) {
+ return new Literal(v, PRIMITIVE_INT_TYPE);
+ }
+
+ public static Literal ofLong(long v) {
+ return new Literal(v, PRIMITIVE_LONG_TYPE);
+ }
+
+ public static Literal ofClass(Class> v) {
+ return new Literal(v, CLASS_TYPE);
+ }
+
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ Class> javaType = getRawType(type);
+ if (isPrimitive(javaType)) {
+ javaType = boxedType(javaType);
+ }
+ if (value == null) {
+ LiteralValue defaultLiteral = new LiteralValue(javaType, defaultValue(javaType));
+ return new ExprCode(null, TrueLiteral, defaultLiteral);
+ } else {
+ if (javaType == String.class) {
+ return new ExprCode(FalseLiteral, new LiteralValue(value));
+ } else if (javaType == Boolean.class || javaType == Integer.class) {
+ return new ExprCode(null, FalseLiteral, new LiteralValue(javaType, value.toString()));
+ } else if (javaType == Float.class) {
+ Float f = (Float) value;
+ if (f.isNaN()) {
+ return new ExprCode(FalseLiteral, new LiteralValue(javaType, "Float.NaN"));
+ } else if (f.equals(Float.POSITIVE_INFINITY)) {
+ return new ExprCode(
+ FalseLiteral, new LiteralValue(javaType, "Float.POSITIVE_INFINITY"));
+ } else if (f.equals(Float.NEGATIVE_INFINITY)) {
+ return new ExprCode(
+ FalseLiteral, new LiteralValue(javaType, "Float.NEGATIVE_INFINITY"));
+ } else {
+ return new ExprCode(FalseLiteral, new LiteralValue(javaType, String.format("%fF", f)));
+ }
+ } else if (javaType == Double.class) {
+ Double d = (Double) value;
+ if (d.isNaN()) {
+ return new ExprCode(FalseLiteral, new LiteralValue(javaType, "Double.NaN"));
+ } else if (d.equals(Double.POSITIVE_INFINITY)) {
+ return new ExprCode(
+ FalseLiteral, new LiteralValue(javaType, "Double.POSITIVE_INFINITY"));
+ } else if (d.equals(Double.NEGATIVE_INFINITY)) {
+ return new ExprCode(
+ FalseLiteral, new LiteralValue(javaType, "Double.NEGATIVE_INFINITY"));
+ } else {
+ return new ExprCode(FalseLiteral, new LiteralValue(javaType, String.format("%fD", d)));
+ }
+ } else if (javaType == Byte.class) {
+ return new ExprCode(
+ FalseLiteral, Code.exprValue(javaType, String.format("(%s)%s", "byte", value)));
+ } else if (javaType == Short.class) {
+ return new ExprCode(
+ FalseLiteral, Code.exprValue(javaType, String.format("(%s)%s", "short", value)));
+ } else if (javaType == Long.class) {
+ return new ExprCode(
+ FalseLiteral,
+ new LiteralValue(javaType, String.format("%dL", ((Number) (value)).longValue())));
+ } else if (isPrimitive(javaType)) {
+ return new ExprCode(FalseLiteral, new LiteralValue(javaType, String.valueOf(value)));
+ } else if (javaType == Class.class) {
+ String v;
+ Class> valueClass = (Class>) value;
+ if (valueClass.isArray()) {
+ v = String.format("%s.class", TypeUtils.getArrayType((Class>) value));
+ } else {
+ v = String.format("%s.class", ((Class>) (value)).getCanonicalName());
+ }
+ return new ExprCode(FalseLiteral, new LiteralValue(javaType, v));
+ } else {
+ throw new UnsupportedOperationException("Unsupported type " + javaType);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (value == null) {
+ return "null";
+ } else {
+ return value.toString();
+ }
+ }
+ }
+
+ class Null implements Expression {
+ private TypeToken> type;
+ private final boolean typedNull;
+
+ public Null(TypeToken> type) {
+ this(type, false);
+ }
+
+ public Null(TypeToken> type, boolean typedNull) {
+ this.type = type;
+ this.typedNull = typedNull;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ Class> javaType = getRawType(type);
+ LiteralValue defaultLiteral =
+ new LiteralValue(javaType, typedNull ? String.format("((%s)null)", type) : "null");
+ return new ExprCode(null, TrueLiteral, defaultLiteral);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(%s)(null)", getRawType(type).getSimpleName());
+ }
+ }
+
+ /** A Reference is a variable/field that can be accessed in the expression's CodegenContext. */
+ class Reference implements Expression {
+ private final String name;
+ private final TypeToken> type;
+ private final boolean nullable;
+
+ public Reference(String name) {
+ this(name, OBJECT_TYPE);
+ }
+
+ public Reference(String name, TypeToken> type) {
+ this.name = name;
+ this.type = type;
+ this.nullable = false;
+ }
+
+ public Reference(String name, TypeToken> type, boolean nullable) {
+ this.name = name;
+ this.type = type;
+ this.nullable = nullable;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ if (nullable) {
+ String isNull = ctx.newName("isNull");
+ String code =
+ StringUtils.format(
+ "boolean ${isNull} = ${name} == null;", "isNull", isNull, "name", name);
+ return new ExprCode(
+ code, Code.isNullVariable(isNull), Code.variable(getRawType(type), name));
+ } else {
+ return new ExprCode(FalseLiteral, Code.variable(getRawType(type), name));
+ }
+ }
+
+ @Override
+ public boolean nullable() {
+ return nullable;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+ class Empty implements Expression {
+
+ @Override
+ public TypeToken> type() {
+ return PRIMITIVE_VOID_TYPE;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ return new ExprCode("");
+ }
+ }
+
+ class Block implements Expression {
+ private final String code;
+
+ public Block(String code) {
+ this.code = code;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return PRIMITIVE_VOID_TYPE;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ return new ExprCode(code, null, null);
+ }
+ }
+
+ class ForceEvaluate implements Expression {
+ private Expression expression;
+
+ public ForceEvaluate(Expression expression) {
+ this.expression = expression;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return expression.type();
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ return expression.doGenCode(ctx);
+ }
+
+ @Override
+ public boolean nullable() {
+ return expression.nullable();
+ }
+
+ @Override
+ public String toString() {
+ return expression.toString();
+ }
+ }
+
+ class FieldValue implements Expression {
+ private Expression targetObject;
+ private String fieldName;
+ private TypeToken> type;
+ private boolean fieldNullable;
+ private final boolean inline;
+
+ public FieldValue(Expression targetObject, String fieldName, TypeToken> type) {
+ this(targetObject, fieldName, type, !type.isPrimitive(), false);
+ }
+
+ public FieldValue(
+ Expression targetObject,
+ String fieldName,
+ TypeToken> type,
+ boolean fieldNullable,
+ boolean inline) {
+ Preconditions.checkArgument(type != null);
+ this.targetObject = targetObject;
+ this.fieldName = fieldName;
+ this.type = type;
+ this.fieldNullable = fieldNullable;
+ this.inline = inline;
+ if (inline) {
+ Preconditions.checkArgument(!fieldNullable);
+ }
+ }
+
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ ExprCode targetExprCode = targetObject.genCode(ctx);
+ if (StringUtils.isNotBlank(targetExprCode.code())) {
+ codeBuilder.append(targetExprCode.code()).append("\n");
+ }
+
+ Class> rawType = getRawType(type);
+ // although isNull is not always used, we place it outside and get freshNames simultaneously
+ // to have same suffix, thus get more readability.
+ String[] freshNames =
+ ctx.newNames(fieldName, "is" + StringUtils.capitalize(fieldName) + "Null");
+ String value = freshNames[0];
+ String isNull = freshNames[1];
+ if (inline) {
+ String inlinedValue =
+ StringUtils.format(
+ "${target}.${fieldName}", "target", targetExprCode.value(), "fieldName", fieldName);
+ return new ExprCode(
+ codeBuilder.toString(), FalseLiteral, Code.variable(rawType, inlinedValue));
+ } else if (fieldNullable) {
+ codeBuilder.append(String.format("boolean %s = false;\n", isNull));
+ String code =
+ StringUtils.format(
+ "${type} ${value} = ${target}.${fieldName};\n",
+ "type",
+ ctx.type(type),
+ "value",
+ value,
+ "defaultValue",
+ defaultValue(rawType),
+ "target",
+ targetExprCode.value(),
+ "fieldName",
+ fieldName);
+ codeBuilder.append(code);
+
+ String nullCode =
+ StringUtils.format(
+ "if (${value} == null) {\n" + " ${isNull} = true;\n" + "}",
+ "value",
+ value,
+ "isNull",
+ isNull);
+ codeBuilder.append(nullCode);
+ return new ExprCode(
+ codeBuilder.toString(), Code.isNullVariable(isNull), Code.variable(rawType, value));
+ } else {
+ String code =
+ StringUtils.format(
+ "${type} ${value} = ${target}.${fieldName};",
+ "type",
+ getRawType(type).getCanonicalName(),
+ "value",
+ value,
+ "target",
+ targetExprCode.value(),
+ "fieldName",
+ fieldName);
+ codeBuilder.append(code);
+ return new ExprCode(
+ codeBuilder.toString(), FalseLiteral, Code.variable(getRawType(type), value));
+ }
+ }
+
+ @Override
+ public boolean nullable() {
+ return fieldNullable;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(%s).%s", targetObject, fieldName);
+ }
+ }
+
+ class SetField implements Expression {
+ private Expression targetObject;
+ private final String fieldName;
+ private Expression fieldValue;
+
+ public SetField(Expression targetObject, String fieldName, Expression fieldValue) {
+ this.targetObject = targetObject;
+ this.fieldName = fieldName;
+ this.fieldValue = fieldValue;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return PRIMITIVE_VOID_TYPE;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ ExprCode targetExprCode = targetObject.genCode(ctx);
+ ExprCode fieldValueExprCode = fieldValue.genCode(ctx);
+ Stream.of(targetExprCode, fieldValueExprCode)
+ .forEach(
+ exprCode -> {
+ if (StringUtils.isNotBlank(exprCode.code())) {
+ codeBuilder.append(exprCode.code()).append('\n');
+ }
+ });
+ // don't check whether ${target} null, place it in if expression
+ String assign =
+ StringUtils.format(
+ "${target}.${fieldName} = ${fieldValue};",
+ "target",
+ targetExprCode.value(),
+ "fieldName",
+ fieldName,
+ "fieldValue",
+ fieldValueExprCode.value());
+ codeBuilder.append(assign);
+ return new ExprCode(codeBuilder.toString(), null, null);
+ }
+
+ public String toString() {
+ return String.format("SetField(%s, %s, %s)", targetObject, fieldName, fieldValue);
+ }
+ }
+
+ /**
+ * An expression to set up a stub in expression tree, so later tree building can replace it with
+ * other expression.
+ */
+ class ReplaceStub implements Expression {
+ private Expression targetObject;
+
+ public ReplaceStub() {}
+
+ public ReplaceStub(Expression targetObject) {
+ this.targetObject = targetObject;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return targetObject != null ? targetObject.type() : PRIMITIVE_VOID_TYPE;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ return targetObject != null ? targetObject.doGenCode(ctx) : new ExprCode("", null, null);
+ }
+
+ @Override
+ public boolean nullable() {
+ return targetObject != null && targetObject.nullable();
+ }
+
+ public void setTargetObject(Expression targetObject) {
+ this.targetObject = targetObject;
+ }
+ }
+
+ class Cast implements Expression {
+ private Expression targetObject;
+ private final String castedValueNamePrefix;
+ private final TypeToken> type;
+ private final boolean inline;
+ private final boolean ignoreUpcast;
+
+ public Cast(Expression targetObject, TypeToken> type) {
+ this(targetObject, type, "castedValue", true, true);
+ }
+
+ public Cast(Expression targetObject, TypeToken> type, String castedValueNamePrefix) {
+ this(targetObject, type, castedValueNamePrefix, false, true);
+ }
+
+ public Cast(
+ Expression targetObject,
+ TypeToken> type,
+ String castedValueNamePrefix,
+ boolean inline,
+ boolean ignoreUpcast) {
+ this.targetObject = targetObject;
+ this.type = type;
+ this.castedValueNamePrefix = castedValueNamePrefix;
+ this.inline = inline;
+ this.ignoreUpcast = ignoreUpcast;
+ checkArgument(ReflectionUtils.isPublic(type), "Type %s is not public", type);
+ }
+
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ if (ignoreUpcast
+ && (targetObject.type().equals(type)
+ || getRawType(type).isAssignableFrom(getRawType((targetObject.type()))))) {
+ return targetObject.genCode(ctx);
+ }
+ StringBuilder codeBuilder = new StringBuilder();
+ Class> rawType = getRawType(type);
+ ExprCode targetExprCode = targetObject.genCode(ctx);
+ if (StringUtils.isNotBlank(targetExprCode.code())) {
+ codeBuilder.append(targetExprCode.code());
+ }
+ // workaround: Cannot cast "java.lang.Object" to "long". then use java auto box/unbox.
+ String withCast = ctx.type(type);
+ if (type.isPrimitive() && !targetObject.type().isPrimitive()) {
+ withCast = ctx.type(boxedType(getRawType(type)));
+ }
+ if (inline) {
+ String value =
+ StringUtils.format(
+ "((${withCast})${target})", "withCast", withCast, "target", targetExprCode.value());
+ return new ExprCode(codeBuilder.toString(), FalseLiteral, Code.variable(rawType, value));
+ } else {
+ String castedValue = ctx.newName(castedValueNamePrefix);
+ if (StringUtils.isNotBlank(targetExprCode.code())) {
+ codeBuilder.append("\n");
+ }
+ String cast =
+ StringUtils.format(
+ "${type} ${castedValue} = (${withCast})${target};",
+ "type",
+ ctx.type(type),
+ "withCast",
+ withCast,
+ "castedValue",
+ castedValue,
+ "target",
+ targetExprCode.value());
+ codeBuilder.append(cast);
+ return new ExprCode(
+ codeBuilder.toString(), targetExprCode.isNull(), Code.variable(rawType, castedValue));
+ }
+ }
+
+ @Override
+ public boolean nullable() {
+ return targetObject.nullable();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(%s)%s", type, targetObject);
+ }
+ }
+
+ class Invoke implements Expression {
+ public Expression targetObject;
+ public final String functionName;
+ public final TypeToken> type;
+ public Expression[] arguments;
+ private String returnNamePrefix;
+ private final boolean returnNullable;
+ private boolean needTryCatch;
+ private boolean inlineCall;
+
+ /** Invoke don't return value, this is a procedure call for side effect. */
+ public Invoke(Expression targetObject, String functionName, Expression... arguments) {
+ this(targetObject, functionName, PRIMITIVE_VOID_TYPE, false, arguments);
+ }
+
+ /** Invoke don't accept arguments. */
+ public Invoke(Expression targetObject, String functionName, TypeToken> type) {
+ this(targetObject, functionName, type, false);
+ }
+
+ /** Invoke don't accept arguments. */
+ public Invoke(
+ Expression targetObject, String functionName, String returnNamePrefix, TypeToken> type) {
+ this(targetObject, functionName, returnNamePrefix, type, false);
+ }
+
+ public Invoke(
+ Expression targetObject, String functionName, TypeToken> type, Expression... arguments) {
+ this(targetObject, functionName, "", type, false, arguments);
+ }
+
+ public Invoke(
+ Expression targetObject,
+ String functionName,
+ TypeToken> type,
+ boolean returnNullable,
+ Expression... arguments) {
+ this(targetObject, functionName, "", type, returnNullable, arguments);
+ }
+
+ public Invoke(
+ Expression targetObject,
+ String functionName,
+ String returnNamePrefix,
+ TypeToken> type,
+ boolean returnNullable,
+ Expression... arguments) {
+ this(
+ targetObject,
+ functionName,
+ returnNamePrefix,
+ type,
+ returnNullable,
+ ReflectionUtils.hasException(getRawType(targetObject.type()), functionName),
+ arguments);
+ }
+
+ public Invoke(
+ Expression targetObject,
+ String functionName,
+ String returnNamePrefix,
+ TypeToken> type,
+ boolean returnNullable,
+ boolean needTryCatch,
+ Expression... arguments) {
+ this.targetObject = targetObject;
+ this.functionName = functionName;
+ this.returnNamePrefix = returnNamePrefix;
+ this.type = type;
+ this.returnNullable = returnNullable;
+ this.arguments = arguments;
+ this.needTryCatch = needTryCatch;
+ }
+
+ public static Invoke inlineInvoke(
+ Expression targetObject, String functionName, TypeToken> type, Expression... arguments) {
+ Invoke invoke = new Invoke(targetObject, functionName, type, false, arguments);
+ invoke.inlineCall = true;
+ return invoke;
+ }
+
+ public static Invoke inlineInvoke(
+ Expression targetObject,
+ String functionName,
+ TypeToken> type,
+ boolean needTryCatch,
+ Expression... arguments) {
+ Invoke invoke =
+ new Invoke(targetObject, functionName, "", type, false, needTryCatch, arguments);
+ invoke.inlineCall = true;
+ return invoke;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ ExprCode targetExprCode = targetObject.genCode(ctx);
+ if (StringUtils.isNotBlank(targetExprCode.code())) {
+ codeBuilder.append(targetExprCode.code());
+ codeBuilder.append("\n");
+ }
+ int len = arguments.length;
+ StringBuilder argsBuilder = new StringBuilder();
+ if (len > 0) {
+ for (int i = 0; i < len; i++) {
+ Expression argExpr = arguments[i];
+ ExprCode argExprCode = argExpr.genCode(ctx);
+ if (StringUtils.isNotBlank(argExprCode.code())) {
+ codeBuilder.append(argExprCode.code()).append("\n");
+ }
+ if (i != 0) {
+ argsBuilder.append(", ");
+ }
+ argsBuilder.append(argExprCode.value());
+ }
+ }
+ if (!inlineCall && type != null && !PRIMITIVE_VOID_TYPE.equals(type)) {
+ Class> rawType = getRawType(type);
+ if (StringUtils.isBlank(returnNamePrefix)) {
+ returnNamePrefix = ctx.namePrefix(getRawType(type));
+ }
+ String[] freshNames = ctx.newNames(returnNamePrefix, returnNamePrefix + "IsNull");
+ String value = freshNames[0];
+ String isNull = freshNames[1];
+ if (returnNullable) {
+ codeBuilder.append(String.format("boolean %s = false;\n", isNull));
+ String callCode =
+ ExpressionUtils.callFunc(
+ ctx.type(type),
+ value,
+ targetExprCode.value().code(),
+ functionName,
+ argsBuilder.toString(),
+ needTryCatch);
+ codeBuilder.append(callCode).append('\n');
+ } else {
+ String callCode =
+ ExpressionUtils.callFunc(
+ ctx.type(type),
+ value,
+ targetExprCode.value().code(),
+ functionName,
+ argsBuilder.toString(),
+ needTryCatch);
+ codeBuilder.append(callCode);
+ }
+
+ if (returnNullable) {
+ String nullCode =
+ StringUtils.format(
+ "if (${value} == null) {\n" + " ${isNull} = true;\n" + "}",
+ "value",
+ value,
+ "isNull",
+ isNull);
+ codeBuilder.append(nullCode);
+ return new ExprCode(
+ codeBuilder.toString(), Code.isNullVariable(isNull), Code.variable(rawType, value));
+ } else {
+ return new ExprCode(codeBuilder.toString(), FalseLiteral, Code.variable(rawType, value));
+ }
+ } else if (inlineCall) {
+ CodeGenerator.stripIfHasLastNewline(codeBuilder);
+ String call =
+ ExpressionUtils.callFunc(
+ targetExprCode.value().code(), functionName, argsBuilder.toString(), needTryCatch);
+ call = call.substring(0, call.length() - 1);
+ return new ExprCode(codeBuilder.toString(), null, Code.variable(getRawType(type), call));
+ } else {
+ String call =
+ ExpressionUtils.callFunc(
+ targetExprCode.value().code(), functionName, argsBuilder.toString(), needTryCatch);
+ codeBuilder.append(call);
+ return new ExprCode(codeBuilder.toString(), null, null);
+ }
+ }
+
+ @Override
+ public boolean nullable() {
+ return returnNullable;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s.%s", targetObject, functionName);
+ }
+ }
+
+ class StaticInvoke implements Expression {
+ private Class> staticObject;
+ private String functionName;
+ private String returnNamePrefix;
+ private TypeToken> type;
+ private Expression[] arguments;
+ private boolean returnNullable;
+
+ public StaticInvoke(Class> staticObject, String functionName, TypeToken> type) {
+ this(staticObject, functionName, type, false);
+ }
+
+ public StaticInvoke(Class> staticObject, String functionName, Expression... arguments) {
+ this(staticObject, functionName, PRIMITIVE_VOID_TYPE, false, arguments);
+ }
+
+ public StaticInvoke(
+ Class> staticObject, String functionName, TypeToken> type, Expression... arguments) {
+ this(staticObject, functionName, "", type, false, arguments);
+ }
+
+ public StaticInvoke(
+ Class> staticObject,
+ String functionName,
+ TypeToken> type,
+ boolean returnNullable,
+ Expression... arguments) {
+ this(staticObject, functionName, "", type, returnNullable, arguments);
+ }
+
+ /**
+ * static invoke method.
+ *
+ * @param staticObject The target of the static call
+ * @param functionName The name of the method to call
+ * @param returnNamePrefix returnNamePrefix
+ * @param type return type of the function call
+ * @param returnNullable When false, indicating the invoked method will always return non-null
+ * value.
+ * @param arguments An optional list of expressions to pass as arguments to the function
+ */
+ public StaticInvoke(
+ Class> staticObject,
+ String functionName,
+ String returnNamePrefix,
+ TypeToken> type,
+ boolean returnNullable,
+ Expression... arguments) {
+ this.staticObject = staticObject;
+ this.functionName = functionName;
+ this.type = type;
+ this.arguments = arguments;
+ this.returnNullable = returnNullable;
+ this.returnNamePrefix = returnNamePrefix;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ int len = arguments.length;
+ StringBuilder argsBuilder = new StringBuilder();
+ if (len > 0) {
+ for (int i = 0; i < len; i++) {
+ Expression argExpr = arguments[i];
+ ExprCode argExprCode = argExpr.genCode(ctx);
+ if (StringUtils.isNotBlank(argExprCode.code())) {
+ codeBuilder.append(argExprCode.code()).append("\n");
+ }
+ if (i != 0) {
+ argsBuilder.append(", ");
+ }
+ argsBuilder.append(argExprCode.value());
+ }
+ }
+
+ if (StringUtils.isBlank(returnNamePrefix)) {
+ returnNamePrefix = ctx.newName(getRawType(type));
+ }
+ boolean needTryCatch = ReflectionUtils.hasException(staticObject, functionName);
+ if (type != null && !PRIMITIVE_VOID_TYPE.equals(type)) {
+ Class> rawType = getRawType(type);
+ String[] freshNames = ctx.newNames(returnNamePrefix, "isNull");
+ String value = freshNames[0];
+ String isNull = freshNames[1];
+ if (returnNullable) {
+ codeBuilder.append(String.format("boolean %s = false;\n", isNull));
+ String callCode =
+ ExpressionUtils.callFunc(
+ ctx.type(type),
+ value,
+ ctx.type(staticObject),
+ functionName,
+ argsBuilder.toString(),
+ needTryCatch);
+ codeBuilder.append(callCode).append('\n');
+ } else {
+ String callCode =
+ ExpressionUtils.callFunc(
+ ctx.type(type),
+ value,
+ ctx.type(staticObject),
+ functionName,
+ argsBuilder.toString(),
+ needTryCatch);
+ codeBuilder.append(callCode);
+ }
+
+ if (returnNullable) {
+ String nullCode =
+ StringUtils.format(
+ "if (${value} == null) {\n" + " ${isNull} = true;\n" + "}",
+ "value",
+ value,
+ "isNull",
+ isNull);
+ codeBuilder.append(nullCode);
+ return new ExprCode(
+ codeBuilder.toString(), Code.isNullVariable(isNull), Code.variable(rawType, value));
+ } else {
+ return new ExprCode(codeBuilder.toString(), FalseLiteral, Code.variable(rawType, value));
+ }
+ } else {
+ String call =
+ ExpressionUtils.callFunc(
+ ctx.type(staticObject), functionName, argsBuilder.toString(), needTryCatch);
+ codeBuilder.append(call);
+ return new ExprCode(codeBuilder.toString(), null, null);
+ }
+ }
+
+ @Override
+ public boolean nullable() {
+ return returnNullable;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s.%s", staticObject, functionName);
+ }
+ }
+
+ class NewInstance implements Expression {
+ private TypeToken> type;
+ private String unknownClassName;
+ private List arguments;
+ private Expression outerPointer;
+ private final boolean needOuterPointer;
+
+ /**
+ * Create an expr which create a new object instance.
+ *
+ * @param interfaceType object declared in this type. actually the type is {@code
+ * unknownClassName}
+ * @param unknownClassName unknownClassName that's unknown in compile-time
+ */
+ public NewInstance(
+ TypeToken> interfaceType, String unknownClassName, Expression... arguments) {
+ this(interfaceType, Arrays.asList(arguments), null);
+ this.unknownClassName = unknownClassName;
+ check();
+ }
+
+ public NewInstance(TypeToken> type, Expression... arguments) {
+ this(type, Arrays.asList(arguments), null);
+ check();
+ }
+
+ private NewInstance(TypeToken> type, List arguments, Expression outerPointer) {
+ this.type = type;
+ this.outerPointer = outerPointer;
+ this.arguments = arguments;
+ this.needOuterPointer =
+ getRawType(type).isMemberClass() && !Modifier.isStatic(getRawType(type).getModifiers());
+ if (needOuterPointer && (outerPointer == null)) {
+ String msg =
+ String.format("outerPointer can't be null when %s is instance inner class", type);
+ throw new CodegenException(msg);
+ }
+ }
+
+ private void check() {
+ Preconditions.checkArgument(
+ !type.isArray(), "Please use " + NewArray.class + " to create array.");
+ if (unknownClassName == null && arguments.size() > 0) {
+ // If unknownClassName is not null, we don't have actual type object,
+ // we assume we can create instance of unknownClassName.
+ // If arguments size is 0, we can always create instance of class, even by
+ // unsafe.allocateInstance
+ boolean anyMatchParamCount =
+ Stream.of(getRawType(type).getConstructors())
+ .anyMatch(c -> c.getParameterCount() == arguments.size());
+ if (!anyMatchParamCount) {
+ String msg =
+ String.format(
+ "%s doesn't have a public constructor that take %d params",
+ type, arguments.size());
+ throw new IllegalArgumentException(msg);
+ }
+ }
+ }
+
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ int len = arguments.size();
+ StringBuilder argsBuilder = new StringBuilder();
+ if (len > 0) {
+ for (int i = 0; i < len; i++) {
+ Expression argExpr = arguments.get(i);
+ ExprCode argExprCode = argExpr.genCode(ctx);
+ if (StringUtils.isNotBlank(argExprCode.code())) {
+ codeBuilder.append(argExprCode.code()).append("\n");
+ }
+ if (i != 0) {
+ argsBuilder.append(", ");
+ }
+ argsBuilder.append(argExprCode.value());
+ }
+ }
+
+ Class> rawType = getRawType(type);
+ String type = ctx.type(rawType);
+ String clzName = unknownClassName;
+ if (clzName == null) {
+ clzName = type;
+ }
+ if (needOuterPointer) {
+ // "${gen.value}.new ${cls.getSimpleName}($argString)"
+ throw new UnsupportedOperationException();
+ } else {
+ String value = ctx.newName(rawType);
+ // class don't have a public no-arg constructor
+ if (arguments.isEmpty() && !ReflectionUtils.hasPublicNoArgConstructor(rawType)) {
+ // janino doesn't generics, so we cast manually.
+ String instance = ctx.newName("instance");
+ String code =
+ ExpressionUtils.callFunc(
+ "Object",
+ instance,
+ ctx.type(Platform.class),
+ "newInstance",
+ clzName + ".class",
+ false);
+ codeBuilder.append(code).append('\n');
+ String cast =
+ StringUtils.format(
+ "${clzName} ${value} = (${clzName})${instance};",
+ "clzName",
+ clzName,
+ "value",
+ value,
+ "instance",
+ instance);
+ codeBuilder.append(cast);
+ } else {
+ String code =
+ StringUtils.format(
+ "${clzName} ${value} = new ${clzName}(${args});",
+ "clzName",
+ clzName,
+ "value",
+ value,
+ "args",
+ argsBuilder.toString());
+ codeBuilder.append(code);
+ }
+ return new ExprCode(codeBuilder.toString(), null, Code.variable(rawType, value));
+ }
+ }
+
+ @Override
+ public boolean nullable() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("newInstance(%s)", type);
+ }
+ }
+
+ class NewArray implements Expression {
+ private TypeToken> type;
+ private Expression[] elements;
+
+ private int numDimensions;
+ private Class> elemType;
+
+ private Expression dim;
+
+ private Expression dims;
+
+ public NewArray(Class> elemType, Expression dim) {
+ this.numDimensions = 1;
+ this.elemType = elemType;
+ this.dim = dim;
+ type = TypeToken.of(Array.newInstance(elemType, 1).getClass());
+ }
+
+ /**
+ * Dynamic created array doesn't have generic type info, so we don't pass in TypeToken.
+ *
+ * @param elemType elemType
+ * @param numDimensions numDimensions
+ * @param dims an int[] represent dims
+ */
+ public NewArray(Class> elemType, int numDimensions, Expression dims) {
+ this.numDimensions = numDimensions;
+ this.elemType = elemType;
+ this.dims = dims;
+
+ int[] stubSizes = new int[numDimensions];
+ for (int i = 0; i < numDimensions; i++) {
+ stubSizes[i] = 1;
+ }
+ type = TypeToken.of(Array.newInstance(elemType, stubSizes).getClass());
+ }
+
+ public NewArray(TypeToken> type, Expression... elements) {
+ this.type = type;
+ this.elements = elements;
+ this.numDimensions = 1;
+ }
+
+ /** ex: new int[3][][]. */
+ public static NewArray newArrayWithFirstDim(
+ Class> elemType, int numDimensions, Expression firstDim) {
+ NewArray array = new NewArray(elemType, firstDim);
+ array.numDimensions = numDimensions;
+ return array;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ Class> rawType = getRawType(type);
+ String arrayType = getArrayType(rawType);
+ String value = ctx.newName("arr");
+ if (dims != null) {
+ // multi-dimension array
+ ExprCode dimsExprCode = dims.genCode(ctx);
+ if (StringUtils.isNotBlank(dimsExprCode.code())) {
+ codeBuilder.append(dimsExprCode.code()).append('\n');
+ }
+ // "${arrType} ${value} = new ${elementType}[$?][$?]...
+ codeBuilder
+ .append(arrayType)
+ .append(' ')
+ .append(value)
+ .append(" = new ")
+ .append(ctx.type(elemType));
+ for (int i = 0; i < numDimensions; i++) {
+ // dims is dimensions array, which store size of per dim.
+ String idim = StringUtils.format("${dims}[${i}]", "dims", dimsExprCode.value(), "i", i);
+ codeBuilder.append('[').append(idim).append("]");
+ }
+ codeBuilder.append(';');
+ } else if (dim != null) {
+ ExprCode dimExprCode = dim.genCode(ctx);
+ if (StringUtils.isNotBlank(dimExprCode.code())) {
+ codeBuilder.append(dimExprCode.code()).append('\n');
+ }
+ if (numDimensions > 1) {
+ // multi-dimension array
+ // "${arrType} ${value} = new ${elementType}[$?][][][]...
+ codeBuilder
+ .append(arrayType)
+ .append(' ')
+ .append(value)
+ .append(" = new ")
+ .append(ctx.type(elemType));
+ codeBuilder.append('[').append(dimExprCode.value()).append(']');
+ for (int i = 1; i < numDimensions; i++) {
+ codeBuilder.append('[').append("]");
+ }
+ codeBuilder.append(';');
+ } else {
+ // one-dimension array
+ String code =
+ StringUtils.format(
+ "${type} ${value} = new ${elemType}[${dim}];",
+ "type",
+ arrayType,
+ "elemType",
+ ctx.type(elemType),
+ "value",
+ value,
+ "dim",
+ dimExprCode.value());
+ codeBuilder.append(code);
+ }
+ } else {
+ // create array with init value
+ int len = elements.length;
+ StringBuilder argsBuilder = new StringBuilder();
+ if (len > 0) {
+ for (int i = 0; i < len; i++) {
+ Expression argExpr = elements[i];
+ ExprCode argExprCode = argExpr.genCode(ctx);
+ if (StringUtils.isNotBlank(argExprCode.code())) {
+ codeBuilder.append(argExprCode.code()).append("\n");
+ }
+ if (i != 0) {
+ argsBuilder.append(", ");
+ }
+ argsBuilder.append(argExprCode.value());
+ }
+ }
+
+ String code =
+ StringUtils.format(
+ "${type} ${value} = new ${type} {${args}};",
+ "type",
+ arrayType,
+ "value",
+ value,
+ "args",
+ argsBuilder.toString());
+ codeBuilder.append(code);
+ }
+
+ return new ExprCode(codeBuilder.toString(), null, Code.variable(rawType, value));
+ }
+ }
+
+ class AssignArrayElem implements Expression {
+ private Expression targetArray;
+ private Expression value;
+ private Expression[] indexes;
+
+ public AssignArrayElem(Expression targetArray, Expression value, Expression... indexes) {
+ this.targetArray = targetArray;
+ this.value = value;
+ this.indexes = indexes;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return PRIMITIVE_VOID_TYPE;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ ExprCode targetExprCode = targetArray.genCode(ctx);
+ ExprCode valueCode = value.genCode(ctx);
+ Stream.of(targetExprCode, valueCode)
+ .forEach(
+ exprCode -> {
+ if (StringUtils.isNotBlank(exprCode.code())) {
+ codeBuilder.append(exprCode.code()).append('\n');
+ }
+ });
+
+ ExprCode[] indexExprCodes = new ExprCode[indexes.length];
+ for (int i = 0; i < indexes.length; i++) {
+ Expression indexExpr = indexes[i];
+ ExprCode indexExprCode = indexExpr.genCode(ctx);
+ indexExprCodes[i] = indexExprCode;
+ if (StringUtils.isNotBlank(indexExprCode.code())) {
+ codeBuilder.append(indexExprCode.code()).append("\n");
+ }
+ }
+ codeBuilder.append(targetExprCode.value());
+ for (int i = 0; i < indexes.length; i++) {
+ codeBuilder.append('[').append(indexExprCodes[i].value()).append(']');
+ }
+ codeBuilder.append(" = ").append(valueCode.value()).append(";");
+ return new ExprCode(codeBuilder.toString(), FalseLiteral, null);
+ }
+ }
+
+ class If implements Expression {
+ private Expression predicate;
+ private Expression trueExpr;
+ private Expression falseExpr;
+ private TypeToken> type;
+ private boolean nullable;
+
+ public If(Expression predicate, Expression trueExpr) {
+ this.predicate = predicate;
+ this.trueExpr = trueExpr;
+ this.nullable = false;
+ this.type = PRIMITIVE_VOID_TYPE;
+ }
+
+ public If(
+ Expression predicate,
+ Expression trueExpr,
+ Expression falseExpr,
+ boolean nullable,
+ TypeToken> type) {
+ this.predicate = predicate;
+ this.trueExpr = trueExpr;
+ this.falseExpr = falseExpr;
+ this.nullable = nullable;
+ this.type = type;
+ }
+
+ public If(Expression predicate, Expression trueExpr, Expression falseExpr, boolean nullable) {
+ this(predicate, trueExpr, falseExpr);
+ this.nullable = nullable;
+ }
+
+ /** if predicate eval to null, take predicate as false. */
+ public If(Expression predicate, Expression trueExpr, Expression falseExpr) {
+ this.predicate = predicate;
+ this.trueExpr = trueExpr;
+ this.falseExpr = falseExpr;
+
+ if (trueExpr.type() == falseExpr.type()) {
+ if (trueExpr.type() != null && !PRIMITIVE_VOID_TYPE.equals(trueExpr.type())) {
+ type = trueExpr.type();
+ } else {
+ type = PRIMITIVE_VOID_TYPE;
+ }
+ } else {
+ if (trueExpr.type() != null && falseExpr.type() != null) {
+ if (Primitives.isWrapperType(getRawType(trueExpr.type()))
+ && trueExpr.type().equals(falseExpr.type().wrap())) {
+ type = trueExpr.type();
+ } else if (Primitives.isWrapperType(getRawType(falseExpr.type()))
+ && falseExpr.type().equals(trueExpr.type().wrap())) {
+ type = falseExpr.type();
+ } else if (trueExpr.type().isSupertypeOf(falseExpr.type())) {
+ type = trueExpr.type();
+ } else if (falseExpr.type().isSupertypeOf(trueExpr.type())) {
+ type = falseExpr.type();
+ } else if (PRIMITIVE_VOID_TYPE.equals(trueExpr.type())
+ || PRIMITIVE_VOID_TYPE.equals(falseExpr.type())) {
+ type = PRIMITIVE_VOID_TYPE;
+ } else {
+ type = OBJECT_TYPE;
+ }
+ } else {
+ type = PRIMITIVE_VOID_TYPE;
+ }
+ }
+ nullable = !PRIMITIVE_VOID_TYPE.equals(type) && !type.isPrimitive();
+ }
+
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ ExprCode condEval = predicate.doGenCode(ctx);
+ ExprCode trueEval = trueExpr.doGenCode(ctx);
+ StringBuilder codeBuilder = new StringBuilder();
+ if (StringUtils.isNotBlank(condEval.code())) {
+ codeBuilder.append(condEval.code()).append('\n');
+ }
+ String cond;
+ if (condEval.isNull() != null && !"false".equals(condEval.isNull().code())) {
+ // indicate condEval.isNull() is a variable. "false" is a java keyword, thus is not a
+ // variable
+ cond =
+ StringUtils.format(
+ "!${condEvalIsNull} && ${condEvalValue}",
+ "condEvalIsNull",
+ condEval.isNull(),
+ "condEvalValue",
+ condEval.value());
+ } else {
+ cond = StringUtils.format("${condEvalValue}", "condEvalValue", condEval.value());
+ }
+
+ if (!PRIMITIVE_VOID_TYPE.equals(type.unwrap())) {
+ ExprCode falseEval = falseExpr.doGenCode(ctx);
+ Preconditions.checkArgument(trueEval.isNull() != null || falseEval.isNull() != null);
+ Preconditions.checkNotNull(trueEval.value());
+ Preconditions.checkNotNull(falseEval.value());
+ Class> rawType = getRawType(type);
+ String[] freshNames = ctx.newNames(rawType, "isNull");
+ String value = freshNames[0];
+ String isNull = freshNames[1];
+ codeBuilder.append(String.format("%s %s;\n", ctx.type(type), value));
+ String ifCode;
+ if (nullable) {
+ codeBuilder.append(String.format("boolean %s = false;\n", isNull));
+ String trueEvalIsNull;
+ if (trueEval.isNull() == null) {
+ trueEvalIsNull = "false";
+ } else {
+ trueEvalIsNull = trueEval.isNull().code();
+ }
+ String falseEvalIsNull;
+ if (falseEval.isNull() == null) {
+ falseEvalIsNull = "false";
+ } else {
+ falseEvalIsNull = falseEval.isNull().code();
+ }
+ ifCode =
+ StringUtils.format(
+ ""
+ + "if (${cond}) {\n"
+ + " ${trueEvalCode}\n"
+ + " ${isNull} = ${trueEvalIsNull};\n"
+ + " ${value} = ${trueEvalValue};\n"
+ + "} else {\n"
+ + " ${falseEvalCode}\n"
+ + " ${isNull} = ${falseEvalIsNull};\n"
+ + " ${value} = ${falseEvalValue};\n"
+ + "}",
+ "isNull",
+ isNull,
+ "value",
+ value,
+ "cond",
+ cond,
+ "trueEvalCode",
+ alignIndent(trueEval.code()),
+ "trueEvalIsNull",
+ trueEvalIsNull,
+ "trueEvalValue",
+ trueEval.value(),
+ "falseEvalCode",
+ alignIndent(falseEval.code()),
+ "falseEvalIsNull",
+ falseEvalIsNull,
+ "falseEvalValue",
+ falseEval.value());
+ } else {
+ ifCode =
+ StringUtils.format(
+ ""
+ + "if (${cond}) {\n"
+ + " ${trueEvalCode}\n"
+ + " ${value} = ${trueEvalValue};\n"
+ + "} else {\n"
+ + " ${falseEvalCode}\n"
+ + " ${value} = ${falseEvalValue};\n"
+ + "}",
+ "cond",
+ cond,
+ "value",
+ value,
+ "trueEvalCode",
+ alignIndent(trueEval.code()),
+ "trueEvalValue",
+ trueEval.value(),
+ "falseEvalCode",
+ alignIndent(falseEval.code()),
+ "falseEvalValue",
+ falseEval.value());
+ }
+ codeBuilder.append(StringUtils.stripBlankLines(ifCode));
+ return new ExprCode(
+ codeBuilder.toString(), Code.isNullVariable(isNull), Code.variable(rawType, value));
+ } else {
+ String ifCode;
+ if (falseExpr != null) {
+ ExprCode falseEval = falseExpr.doGenCode(ctx);
+ ifCode =
+ StringUtils.format(
+ "if (${cond}) {\n"
+ + " ${trueEvalCode}\n"
+ + "} else {\n"
+ + " ${falseEvalCode}\n"
+ + "}",
+ "cond",
+ cond,
+ "trueEvalCode",
+ alignIndent(trueEval.code()),
+ "falseEvalCode",
+ alignIndent(falseEval.code()));
+ } else {
+ ifCode =
+ StringUtils.format(
+ "if (${cond}) {\n" + " ${trueEvalCode}\n" + "}",
+ "cond",
+ cond,
+ "trueEvalCode",
+ alignIndent(trueEval.code()));
+ }
+ codeBuilder.append(ifCode);
+ return new ExprCode(codeBuilder.toString());
+ }
+ }
+
+ @Override
+ public boolean nullable() {
+ return nullable;
+ }
+
+ @Override
+ public String toString() {
+ if (falseExpr != null) {
+ return String.format("if (%s) %s else %s", predicate, trueExpr, falseExpr);
+ } else {
+ return String.format("if (%s) %s", predicate, trueExpr);
+ }
+ }
+ }
+
+ class IsNull implements Expression {
+ private Expression expr;
+
+ public IsNull(Expression expr) {
+ this.expr = expr;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return PRIMITIVE_BOOLEAN_TYPE;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ ExprCode targetExprCode = expr.genCode(ctx);
+ Preconditions.checkNotNull(targetExprCode.isNull());
+ return new ExprCode(targetExprCode.code(), FalseLiteral, targetExprCode.isNull());
+ }
+
+ @Override
+ public boolean nullable() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("IsNull(%s)", expr);
+ }
+ }
+
+ class Not implements Expression {
+ private Expression target;
+
+ public Not(Expression target) {
+ this.target = target;
+ Preconditions.checkArgument(
+ target.type() == PRIMITIVE_BOOLEAN_TYPE || target.type() == BOOLEAN_TYPE);
+ }
+
+ @Override
+ public TypeToken> type() {
+ return target.type();
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ ExprCode targetExprCode = target.genCode(ctx);
+ // whether need to check null for BOOLEAN_TYPE. The question is what to do when target.value
+ // is null.
+ String value = String.format("(!%s)", targetExprCode.value());
+ return new ExprCode(targetExprCode.code(), FalseLiteral, Code.variable(boolean.class, value));
+ }
+
+ @Override
+ public boolean nullable() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("!(%s)", target);
+ }
+ }
+
+ class BinaryOperator extends ValueExpression {
+ private final boolean inline;
+ private final String operator;
+ private final TypeToken> type;
+ private Expression left;
+ private Expression right;
+
+ public BinaryOperator(String operator, Expression left, Expression right) {
+ this(false, operator, left, right);
+ }
+
+ public BinaryOperator(boolean inline, String operator, Expression left, Expression right) {
+ this.inline = inline;
+ this.operator = operator;
+ this.left = left;
+ this.right = right;
+ if (isPrimitive(getRawType(left.type()))) {
+ Preconditions.checkArgument(isPrimitive(getRawType(right.type())));
+ type =
+ getSizeOfPrimitiveType(left.type()) > getSizeOfPrimitiveType(right.type())
+ ? left.type()
+ : right.type();
+ } else {
+ if (left.type().isSupertypeOf(right.type())) {
+ type = left.type();
+ } else if (left.type().isSubtypeOf(right.type())) {
+ type = right.type();
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Arguments type %s vs %s inconsistent", left.type(), right.type()));
+ }
+ }
+ }
+
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ StringBuilder arith = new StringBuilder();
+ Expression[] operands = new Expression[] {left, right};
+ for (int i = 0; i < operands.length; i++) {
+ Expression operand = operands[i];
+ ExprCode code = operand.genCode(ctx);
+ if (StringUtils.isNotBlank(code.code())) {
+ appendNewlineIfNeeded(codeBuilder);
+ codeBuilder.append(code.code());
+ }
+ if (i != operands.length - 1) {
+ arith.append(code.value()).append(' ').append(operator).append(' ');
+ } else {
+ arith.append(code.value());
+ }
+ }
+
+ if (inline) {
+ String value = String.format("(%s)", arith);
+ String code = StringUtils.isBlank(codeBuilder) ? null : codeBuilder.toString();
+ return new ExprCode(code, FalseLiteral, Code.variable(getRawType(type), value));
+ } else {
+ appendNewlineIfNeeded(codeBuilder);
+ String value = ctx.newName(valuePrefix);
+ String valueExpr =
+ StringUtils.format(
+ "${type} ${value} = ${arith};",
+ "type",
+ ctx.type(type),
+ "value",
+ value,
+ "arith",
+ arith);
+ codeBuilder.append(valueExpr);
+ return new ExprCode(
+ codeBuilder.toString(), FalseLiteral, Code.variable(getRawType(type), value));
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s %s %s", left, operator, right);
+ }
+ }
+
+ class Comparator extends BinaryOperator {
+ public Comparator(String operator, Expression left, Expression right, boolean inline) {
+ super(inline, operator, left, right);
+ }
+
+ @Override
+ public TypeToken> type() {
+ return PRIMITIVE_BOOLEAN_TYPE;
+ }
+ }
+
+ class Arithmetic extends BinaryOperator {
+ public Arithmetic(String operator, Expression left, Expression right) {
+ super(operator, left, right);
+ }
+
+ public Arithmetic(boolean inline, String operator, Expression left, Expression right) {
+ super(inline, operator, left, right);
+ }
+ }
+
+ class Add extends Arithmetic {
+ public Add(Expression left, Expression right) {
+ super("+", left, right);
+ }
+
+ public Add(boolean inline, Expression left, Expression right) {
+ super(inline, "+", left, right);
+ }
+ }
+
+ class Subtract extends Arithmetic {
+ public Subtract(Expression left, Expression right) {
+ super("-", left, right);
+ }
+
+ public Subtract(boolean inline, Expression left, Expression right) {
+ super(inline, "-", left, right);
+ }
+ }
+
+ class BitAnd extends Arithmetic {
+ public BitAnd(Expression left, Expression right) {
+ super(true, "&", left, right);
+ }
+
+ public BitAnd(boolean inline, Expression left, Expression right) {
+ super(inline, "&", left, right);
+ }
+ }
+
+ class BitOr extends Arithmetic {
+
+ public BitOr(Expression left, Expression right) {
+ this(true, left, right);
+ }
+
+ public BitOr(boolean inline, Expression left, Expression right) {
+ super(inline, "|", left, right);
+ }
+ }
+
+ class BitShift extends Arithmetic {
+
+ public BitShift(String operator, Expression operand, int numBits) {
+ this(true, operator, operand, new Literal(numBits, PRIMITIVE_INT_TYPE));
+ }
+
+ public BitShift(boolean inline, String operator, Expression left, Expression right) {
+ super(inline, operator, left, right);
+ }
+ }
+
+ class LogicalOperator extends BinaryOperator {
+
+ public LogicalOperator(String operator, Expression left, Expression right) {
+ super(operator, left, right);
+ }
+
+ public LogicalOperator(boolean inline, String operator, Expression left, Expression right) {
+ super(inline, operator, left, right);
+ }
+ }
+
+ class LogicalAnd extends LogicalOperator {
+ public LogicalAnd(Expression left, Expression right) {
+ super(true, "&&", left, right);
+ }
+
+ public LogicalAnd(boolean inline, Expression left, Expression right) {
+ super(inline, "&", left, right);
+ }
+ }
+
+ class While implements Expression {
+ private final BinaryOperator predicate;
+ private Expression action;
+ private Expression[] cutPoints;
+
+ /**
+ * Create a while-block with specified predict and while body.
+ *
+ * @param predicate predicate must be inline.
+ */
+ public While(BinaryOperator predicate, Expression action) {
+ this(predicate, action, new Expression[0]);
+ }
+
+ public While(BinaryOperator predicate, Functions.SerializableSupplier action) {
+ this(
+ predicate,
+ action.get(),
+ Functions.extractCapturedVariables(action, o -> o instanceof Expression)
+ .toArray(new Expression[0]));
+ }
+
+ public While(BinaryOperator predicate, Expression action, Expression[] cutPoints) {
+ this.predicate = predicate;
+ this.action = action;
+ this.cutPoints = cutPoints;
+ Preconditions.checkArgument(predicate.inline, predicate);
+ }
+
+ @Override
+ public TypeToken> type() {
+ return PRIMITIVE_VOID_TYPE;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ for (Expression cutPoint : cutPoints) {
+ ExprCode code = cutPoint.genCode(ctx);
+ if (StringUtils.isNotBlank(code.code())) {
+ appendNewlineIfNeeded(codeBuilder);
+ codeBuilder.append(code.code()).append("\n");
+ }
+ }
+ ExprCode predicateExprCode = predicate.genCode(ctx);
+ if (StringUtils.isNotBlank(predicateExprCode.code())) {
+ codeBuilder.append(predicateExprCode.code());
+ appendNewlineIfNeeded(codeBuilder);
+ }
+ ExprCode actionExprCode = action.genCode(ctx);
+ String whileCode =
+ StringUtils.format(
+ "while (${predicate}) {\n" + "${action}\n" + "}",
+ "predicate",
+ predicateExprCode.value(),
+ "action",
+ indent(actionExprCode.code()));
+ codeBuilder.append(whileCode);
+ return new ExprCode(codeBuilder.toString(), FalseLiteral, null);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("while(%s) {%s}", predicate, action);
+ }
+ }
+
+ class ForEach implements Expression {
+ private Expression inputObject;
+
+ @ClosureVisitable
+ private final Functions.SerializableBiFunction action;
+
+ private final TypeToken> elementType;
+
+ /**
+ * inputObject.type() must be multi-dimension array or Collection, not allowed to be primitive
+ * array
+ */
+ public ForEach(
+ Expression inputObject,
+ Functions.SerializableBiFunction action) {
+ this.inputObject = inputObject;
+ this.action = action;
+ TypeToken elementType;
+ if (inputObject.type().isArray()) {
+ elementType = inputObject.type().getComponentType();
+ } else {
+ elementType = getElementType(inputObject.type());
+ }
+ this.elementType = ReflectionUtils.getPublicSuperType(elementType);
+ }
+
+ public ForEach(
+ Expression inputObject,
+ TypeToken> beanType,
+ Functions.SerializableBiFunction action) {
+ this.inputObject = inputObject;
+ this.action = action;
+ this.elementType = beanType;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return PRIMITIVE_VOID_TYPE;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ ExprCode targetExprCode = inputObject.genCode(ctx);
+ if (StringUtils.isNotBlank(targetExprCode.code())) {
+ codeBuilder.append(targetExprCode.code()).append("\n");
+ }
+ String i = ctx.newName("i");
+ String elemValue = ctx.newName("elemValue");
+ Expression elementExpr =
+ action.apply(new Literal(i), new Reference(elemValue, elementType, false));
+ ExprCode elementExprCode = elementExpr.genCode(ctx);
+
+ if (inputObject.type().isArray()) {
+ String code =
+ StringUtils.format(
+ ""
+ + "int ${len} = ${arr}.length;\n"
+ + "int ${i} = 0;\n"
+ + "while (${i} < ${len}) {\n"
+ + " ${elemType} ${elemValue} = ${arr}[${i}];\n"
+ + " ${elementExprCode}\n"
+ + " ${i}++;\n"
+ + "}",
+ "arr",
+ targetExprCode.value(),
+ "len",
+ ctx.newName("len"),
+ "i",
+ i,
+ "elemType",
+ ctx.type(elementType),
+ "elemValue",
+ elemValue,
+ "i",
+ i,
+ "elementExprCode",
+ alignIndent(elementExprCode.code()));
+ codeBuilder.append(code);
+ return new ExprCode(codeBuilder.toString(), null, null);
+ } else {
+ // don't use forloop for List, because it may be LinkedList.
+ Preconditions.checkArgument(
+ ITERABLE_TYPE.isSupertypeOf(inputObject.type()),
+ "Unsupported type " + inputObject.type());
+ String elemTypeCast = "";
+ if (getRawType(elementType) != Object.class) {
+ // elementType may be unresolved generic`E`
+ elemTypeCast = "(" + ctx.type(elementType) + ")";
+ }
+ String code =
+ StringUtils.format(
+ ""
+ + "java.util.Iterator ${iter} = ${input}.iterator();\n"
+ + "int ${i} = 0;\n"
+ + "while (${iter}.hasNext()) {\n"
+ + " ${elemType} ${elemValue} = ${elemTypeCast}${iter}.next();\n"
+ + " ${elementExprCode}\n"
+ + " ${i}++;\n"
+ + "}",
+ "iter",
+ ctx.newName("iter"),
+ "i",
+ i,
+ "input",
+ targetExprCode.value().code(),
+ "elemType",
+ ctx.type(elementType),
+ "elemTypeCast",
+ elemTypeCast,
+ "elemValue",
+ elemValue,
+ "elementExprCode",
+ alignIndent(elementExprCode.code()));
+ codeBuilder.append(code);
+ return new ExprCode(codeBuilder.toString(), null, null);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ForEach(%s, %s)", inputObject, action);
+ }
+ }
+
+ class ZipForEach implements Expression {
+ private Expression left;
+ private Expression right;
+
+ @ClosureVisitable
+ private final Functions.SerializableTriFunction
+ action;
+
+ public ZipForEach(
+ Expression left,
+ Expression right,
+ Functions.SerializableTriFunction action) {
+ this.left = left;
+ this.right = right;
+ this.action = action;
+ Preconditions.checkArgument(
+ left.type().isArray() || TypeToken.of(Collection.class).isSupertypeOf(left.type()));
+ Preconditions.checkArgument(
+ right.type().isArray() || TypeToken.of(Collection.class).isSupertypeOf(right.type()));
+ if (left.type().isArray()) {
+ Preconditions.checkArgument(
+ right.type().isArray(), "Should both be array or neither be array");
+ }
+ }
+
+ @Override
+ public TypeToken> type() {
+ return PRIMITIVE_VOID_TYPE;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ ExprCode leftExprCode = left.genCode(ctx);
+ ExprCode rightExprCode = right.genCode(ctx);
+ Stream.of(leftExprCode, rightExprCode)
+ .forEach(
+ exprCode -> {
+ if (StringUtils.isNotBlank(exprCode.code())) {
+ codeBuilder.append(exprCode.code()).append('\n');
+ }
+ });
+ String i = ctx.newName("i");
+ String leftElemValue = ctx.newName("leftElemValue");
+ String rightElemValue = ctx.newName("rightElemValue");
+ TypeToken> leftElemType;
+ if (left.type().isArray()) {
+ leftElemType = left.type().getComponentType();
+ } else {
+ leftElemType = getElementType(left.type());
+ }
+ leftElemType = ReflectionUtils.getPublicSuperType(leftElemType);
+ TypeToken> rightElemType;
+ if (right.type().isArray()) {
+ rightElemType = right.type().getComponentType();
+ } else {
+ rightElemType = getElementType(right.type());
+ }
+ rightElemType = ReflectionUtils.getPublicSuperType(rightElemType);
+ Expression elemExpr =
+ action.apply(
+ new Literal(i),
+ new Reference(leftElemValue, leftElemType, true),
+ // elemValue nullability check use isNullAt inside action, so elemValueRef'nullable is
+ // false.
+ new Reference(rightElemValue, rightElemType, false));
+ ExprCode elementExprCode = elemExpr.genCode(ctx);
+
+ if (left.type().isArray()) {
+ String code =
+ StringUtils.format(
+ ""
+ + "int ${len} = ${leftArr}.length;\n"
+ + "int ${i} = 0;\n"
+ + "while (${i} < ${len}) {\n"
+ + " ${leftElemType} ${leftElemValue} = ${leftArr}[${i}];\n"
+ + " ${rightElemType} ${rightElemValue} = ${rightArr}[${i}];\n"
+ + " ${elementExprCode}\n"
+ + " ${i}++;\n"
+ + "}",
+ "leftArr",
+ leftExprCode.value(),
+ "len",
+ ctx.newName("len"),
+ "i",
+ i,
+ "leftElemType",
+ ctx.type(leftElemType),
+ "leftElemValue",
+ leftElemValue,
+ "rightElemType",
+ ctx.type(rightElemType),
+ "rightElemValue",
+ rightElemValue,
+ "rightArr",
+ rightExprCode.value(),
+ "elementExprCode",
+ alignIndent(elementExprCode.code()));
+ codeBuilder.append(code);
+ return new ExprCode(codeBuilder.toString(), null, null);
+ } else {
+ // CHECKSTYLE.OFF:LineLength
+ String code =
+ StringUtils.format(
+ ""
+ + "java.util.Iterator ${leftIter} = ${leftInput}.iterator();\n"
+ + "java.util.Iterator ${rightIter} = ${rightInput}.iterator();\n"
+ + "int ${i} = 0;\n"
+ + "while (${leftIter}.hasNext() && ${rightIter}.hasNext()) {\n"
+ + " ${leftElemType} ${leftElemValue} = (${leftElemType})${leftIter}.next();\n"
+ + " ${rightElemType} ${rightElemValue} = (${rightElemType})${rightIter}.next();\n"
+ + " ${elementExprCode}\n"
+ + " ${i}++;\n"
+ + "}",
+ "leftIter",
+ // CHECKSTYLE.ON:LineLength
+ ctx.newName("leftIter"),
+ "leftInput",
+ leftExprCode.value(),
+ "rightIter",
+ ctx.newName("rightIter"),
+ "rightInput",
+ rightExprCode.value(),
+ "i",
+ i,
+ "leftElemType",
+ ctx.type(leftElemType),
+ "leftElemValue",
+ leftElemValue,
+ "rightElemType",
+ ctx.type(rightElemType),
+ "rightElemValue",
+ rightElemValue,
+ "elementExprCode",
+ alignIndent(elementExprCode.code()));
+ codeBuilder.append(code);
+ return new ExprCode(codeBuilder.toString(), null, null);
+ }
+ }
+ }
+
+ class ForLoop implements Expression {
+ public Expression start;
+ public Expression end;
+ public Expression step;
+
+ @ClosureVisitable public final Functions.SerializableFunction action;
+
+ public ForLoop(
+ Expression start,
+ Expression end,
+ Expression step,
+ Functions.SerializableFunction action) {
+ this.start = start;
+ this.end = end;
+ this.step = step;
+ this.action = action;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return PRIMITIVE_VOID_TYPE;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ Class> maxType = maxType(getRawType(start.type()), getRawType(end.type()));
+ Preconditions.checkArgument(maxType.isPrimitive());
+ StringBuilder codeBuilder = new StringBuilder();
+ String i = ctx.newName("i");
+ Reference iref = new Reference(i, TypeToken.of(maxType));
+ Expression loopAction = action.apply(iref);
+ ExprCode startExprCode = start.genCode(ctx);
+ ExprCode endExprCode = end.genCode(ctx);
+ ExprCode stepExprCode = step.genCode(ctx);
+ ExprCode actionExprCode = loopAction.genCode(ctx);
+ Stream.of(startExprCode, endExprCode, stepExprCode)
+ .forEach(
+ exprCode -> {
+ if (StringUtils.isNotBlank(exprCode.code())) {
+ codeBuilder.append(exprCode.code()).append('\n');
+ }
+ });
+
+ String forCode =
+ StringUtils.format(
+ ""
+ + "for (${type} ${i} = ${start}; ${i} < ${end}; ${i}+=${step}) {\n"
+ + "${actionCode}\n"
+ + "}",
+ "type",
+ maxType.toString(),
+ "i",
+ i,
+ "start",
+ startExprCode.value(),
+ "end",
+ endExprCode.value(),
+ "step",
+ stepExprCode.value(),
+ "actionCode",
+ indent(actionExprCode.code()));
+ codeBuilder.append(forCode);
+ return new ExprCode(codeBuilder.toString(), null, null);
+ }
+ }
+
+ /**
+ * build list from iterable.
+ *
+ * Since value is declared to be {@code List}, no need to cast in other expression
+ * that need List
+ */
+ class ListFromIterable implements Expression {
+ private final TypeToken elementType;
+ private Expression inputObject;
+ private final TypeToken> type;
+
+ public ListFromIterable(Expression inputObject) {
+ this.inputObject = inputObject;
+ Preconditions.checkArgument(
+ getRawType(inputObject.type()) == Iterable.class,
+ "wrong type of inputObject, get " + inputObject.type());
+ elementType = getElementType(inputObject.type());
+ this.type = inputObject.type().getSubtype(List.class);
+ }
+
+ /** Returns inputObject.type(), not {@code List}. */
+ @Override
+ public TypeToken> type() {
+ return type;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ ExprCode targetExprCode = inputObject.genCode(ctx);
+ if (StringUtils.isNotBlank(targetExprCode.code())) {
+ codeBuilder.append(targetExprCode.code()).append("\n");
+ }
+ String iter = ctx.newName("iter");
+ String list = ctx.newName("list");
+ String elemValue = ctx.newName("elemValue");
+
+ Class rawType = getRawType(elementType);
+ // janino don't support Type inference for generic instance creation
+ String code =
+ StringUtils.format(
+ ""
+ + "java.util.List<${className}> ${list} = new java.util.ArrayList<${className}>();\n"
+ + "java.util.Iterator ${iter} = ${iterable}.iterator();\n"
+ + "while (${iter}.hasNext()) {\n"
+ + " ${className} ${elemValue} = (${className})${iter}.next();\n"
+ + " ${list}.add(${elemValue});\n"
+ + "}",
+ "className",
+ ctx.type(rawType),
+ "list",
+ list,
+ "iter",
+ iter,
+ "iterable",
+ targetExprCode.value(),
+ "elemValue",
+ elemValue);
+ codeBuilder.append(code);
+ return new ExprCode(codeBuilder.toString(), FalseLiteral, Code.variable(rawType, list));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s(%s)", getClass().getSimpleName(), inputObject);
+ }
+ }
+
+ class Return implements Expression {
+ private Expression expression;
+
+ public Return(Expression expression) {
+ this.expression = expression;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return expression.type();
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ ExprCode targetExprCode = expression.genCode(ctx);
+ if (StringUtils.isNotBlank(targetExprCode.code())) {
+ codeBuilder.append(targetExprCode.code()).append('\n');
+ }
+ codeBuilder.append("return ").append(targetExprCode.value()).append(';');
+ return new ExprCode(codeBuilder.toString(), null, null);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("return %s", expression);
+ }
+ }
+
+ class Assign implements Expression {
+ private Expression from;
+ private Expression to;
+
+ public Assign(Expression from, Expression to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ @Override
+ public TypeToken> type() {
+ return PRIMITIVE_VOID_TYPE;
+ }
+
+ @Override
+ public ExprCode doGenCode(CodegenContext ctx) {
+ StringBuilder codeBuilder = new StringBuilder();
+ ExprCode fromExprCode = from.genCode(ctx);
+ ExprCode toExprCode = to.genCode(ctx);
+ Stream.of(fromExprCode, toExprCode)
+ .forEach(
+ exprCode -> {
+ if (StringUtils.isNotBlank(exprCode.code())) {
+ codeBuilder.append(exprCode.code()).append('\n');
+ }
+ });
+ String assign =
+ StringUtils.format(
+ "${from} = ${to};", "from", fromExprCode.value(), "to", toExprCode.value());
+ codeBuilder.append(assign);
+ return new ExprCode(codeBuilder.toString(), null, null);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s = %s", from, to);
+ }
+ }
}
diff --git a/java/fury-core/src/main/java/io/fury/codegen/ExpressionUtils.java b/java/fury-core/src/main/java/io/fury/codegen/ExpressionUtils.java
new file mode 100644
index 0000000000..b8038f0d67
--- /dev/null
+++ b/java/fury-core/src/main/java/io/fury/codegen/ExpressionUtils.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2023 The Fury authors
+ * 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 io.fury.codegen;
+
+import static io.fury.codegen.Expression.Arithmetic;
+import static io.fury.codegen.Expression.Comparator;
+import static io.fury.codegen.Expression.IsNull;
+import static io.fury.codegen.Expression.Literal;
+import static io.fury.codegen.Expression.NewArray;
+import static io.fury.codegen.Expression.Not;
+import static io.fury.codegen.Expression.StaticInvoke;
+import static io.fury.type.TypeUtils.getRawType;
+
+import com.google.common.base.Preconditions;
+import com.google.common.reflect.TypeToken;
+import io.fury.codegen.Expression.Cast;
+import io.fury.codegen.Expression.Null;
+import io.fury.util.Functions;
+import io.fury.util.StringUtils;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Expression utils to create expression and code in a more convenient way.
+ *
+ * @author chaokunyang
+ */
+@SuppressWarnings("UnstableApiUsage")
+public class ExpressionUtils {
+
+ public static Expression newObjectArray(Expression... expressions) {
+ return new NewArray(TypeToken.of(Object[].class), expressions);
+ }
+
+ public static Expression valueOf(TypeToken> type, Expression value) {
+ return new StaticInvoke(getRawType(type), "valueOf", type, false, value);
+ }
+
+ public static IsNull isNull(Expression target) {
+ return new IsNull(target);
+ }
+
+ public static Expression notNull(Expression target) {
+ return new Not(new IsNull(target));
+ }
+
+ public static Expression eqNull(Expression target) {
+ Preconditions.checkArgument(!target.type().isPrimitive());
+ return eq(target, new Null(target.type()));
+ }
+
+ public static Not not(Expression target) {
+ return new Not(target);
+ }
+
+ public static Literal nullValue(TypeToken> type) {
+ return new Literal(null, type);
+ }
+
+ public static Literal literalStr(String value) {
+ value = String.format("\"%s\"", value);
+ return new Literal(value);
+ }
+
+ public static Comparator eq(Expression left, Expression right) {
+ return new Comparator("==", left, right, true);
+ }
+
+ public static Comparator eq(Expression left, Expression right, String valuePrefix) {
+ Comparator comparator = new Comparator("==", left, right, false);
+ comparator.valuePrefix = valuePrefix;
+ return comparator;
+ }
+
+ public static Comparator neq(Expression left, Expression right) {
+ return new Comparator("!=", left, right, true);
+ }
+
+ public static Comparator egt(Expression left, Expression right) {
+ return new Comparator(">=", left, right, true);
+ }
+
+ public static Comparator egt(Expression left, Expression right, String valuePrefix) {
+ Comparator comparator = new Comparator(">=", left, right, false);
+ comparator.valuePrefix = valuePrefix;
+ return comparator;
+ }
+
+ public static Comparator lessThan(Expression left, Expression right) {
+ return new Comparator("<", left, right, true);
+ }
+
+ public static Arithmetic add(Expression left, Expression right) {
+ return new Arithmetic(true, "+", left, right);
+ }
+
+ public static Arithmetic add(Expression left, Expression right, String valuePrefix) {
+ Arithmetic arithmetic = new Arithmetic(true, "+", left, right);
+ arithmetic.valuePrefix = valuePrefix;
+ return arithmetic;
+ }
+
+ public static Arithmetic subtract(Expression left, Expression right) {
+ return new Arithmetic(true, "-", left, right);
+ }
+
+ public static Arithmetic subtract(Expression left, Expression right, String valuePrefix) {
+ Arithmetic arithmetic = new Arithmetic(true, "-", left, right);
+ arithmetic.valuePrefix = valuePrefix;
+ return arithmetic;
+ }
+
+ public static Cast cast(Expression value, TypeToken> typeToken) {
+ return new Cast(value, typeToken);
+ }
+
+ static String callFunc(
+ String type,
+ String resultVal,
+ String target,
+ String functionName,
+ String args,
+ boolean needTryCatch) {
+ if (needTryCatch) {
+ return StringUtils.format(
+ "${type} ${value};\n"
+ + "try {\n"
+ + " ${value} = ${target}.${functionName}(${args});\n"
+ + "} catch (Exception e) {\n"
+ + " throw new RuntimeException(e);\n"
+ + "}",
+ "type",
+ type,
+ "value",
+ resultVal,
+ "target",
+ target,
+ "functionName",
+ functionName,
+ "args",
+ args);
+ } else {
+ return StringUtils.format(
+ "${type} ${value} = ${target}.${functionName}(${args});",
+ "type",
+ type,
+ "value",
+ resultVal,
+ "target",
+ target,
+ "functionName",
+ functionName,
+ "args",
+ args);
+ }
+ }
+
+ static String callFunc(String target, String functionName, String args, boolean needTryCatch) {
+ if (needTryCatch) {
+ return StringUtils.format(
+ "try {\n"
+ + " ${target}.${functionName}(${args});\n"
+ + "} catch (Exception e) {\n"
+ + " throw new RuntimeException(e);\n"
+ + "}",
+ "target",
+ target,
+ "functionName",
+ functionName,
+ "args",
+ args);
+ } else {
+ return StringUtils.format(
+ "${target}.${functionName}(${args});",
+ "target",
+ target,
+ "functionName",
+ functionName,
+ "args",
+ args);
+ }
+ }
+
+ public static List extractCapturedExpressions(Serializable closure) {
+ List expressions = new ArrayList<>();
+ Functions.extractCapturedVariables(
+ closure,
+ capturedArg -> {
+ if (capturedArg instanceof Expression) {
+ // FIXME may need to check list/container values types?
+ expressions.add((Expression) capturedArg);
+ } else if (capturedArg instanceof Expression[]) {
+ Collections.addAll(Arrays.asList((Expression[]) capturedArg));
+ }
+ return false;
+ });
+ return expressions;
+ }
+}