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

io.fury.format.encoder.BaseBinaryEncoderBuilder Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2023 The Fury Authors
 *
 * Licensed 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.format.encoder;

import static io.fury.type.TypeUtils.getRawType;

import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeToken;
import io.fury.builder.CodecBuilder;
import io.fury.codegen.CodeGenerator;
import io.fury.codegen.CodegenContext;
import io.fury.codegen.Expression;
import io.fury.codegen.Expression.Arithmetic;
import io.fury.codegen.Expression.AssignArrayElem;
import io.fury.codegen.Expression.Cast;
import io.fury.codegen.Expression.FieldValue;
import io.fury.codegen.Expression.ForEach;
import io.fury.codegen.Expression.If;
import io.fury.codegen.Expression.Invoke;
import io.fury.codegen.Expression.ListExpression;
import io.fury.codegen.Expression.ListFromIterable;
import io.fury.codegen.Expression.Literal;
import io.fury.codegen.Expression.NewArray;
import io.fury.codegen.Expression.NewInstance;
import io.fury.codegen.Expression.Reference;
import io.fury.codegen.Expression.StaticInvoke;
import io.fury.codegen.Expression.ZipForEach;
import io.fury.codegen.ExpressionUtils;
import io.fury.format.row.ArrayData;
import io.fury.format.row.Getters;
import io.fury.format.row.MapData;
import io.fury.format.row.Row;
import io.fury.format.row.binary.BinaryArray;
import io.fury.format.row.binary.BinaryRow;
import io.fury.format.row.binary.BinaryUtils;
import io.fury.format.row.binary.writer.BinaryArrayWriter;
import io.fury.format.row.binary.writer.BinaryRowWriter;
import io.fury.format.row.binary.writer.BinaryWriter;
import io.fury.format.type.DataTypes;
import io.fury.memory.MemoryBuffer;
import io.fury.type.TypeUtils;
import io.fury.util.DateTimeUtils;
import io.fury.util.ReflectionUtils;
import io.fury.util.StringUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.Schema;

/**
 * Base encoder builder for {@link Row}, {@link ArrayData} and {@link MapData}.
 *
 * @author chaokunyang
 */
@SuppressWarnings("UnstableApiUsage")
public abstract class BaseBinaryEncoderBuilder extends CodecBuilder {
  protected static final String REFERENCES_NAME = "references";
  protected static final TypeToken SCHEMA_TYPE = TypeToken.of(Schema.class);
  protected static final TypeToken ARROW_FIELD_TYPE = TypeToken.of(Field.class);
  protected static TypeToken schemaTypeToken = TypeToken.of(Schema.class);
  protected static TypeToken writerTypeToken = TypeToken.of(BinaryWriter.class);
  protected static TypeToken rowWriterTypeToken =
      TypeToken.of(BinaryRowWriter.class);
  protected static TypeToken arrayWriterTypeToken =
      TypeToken.of(BinaryArrayWriter.class);
  protected static TypeToken rowTypeToken = TypeToken.of(Row.class);
  protected static TypeToken binaryRowTypeToken = TypeToken.of(BinaryRow.class);
  protected static TypeToken binaryArrayTypeToken = TypeToken.of(BinaryArray.class);

  protected final Map, Reference> arrayWriterMap = new HashMap<>();
  protected final Map, Reference> beanEncoderMap = new HashMap<>();
  // We need to call beanEncoder's rowWriter.reset() before write a corresponding nested bean every
  // time.
  // Outermost beanEncoder's rowWriter.reset() should be called outside generated code before
  // writer an outermost bean every time.
  protected final Map, Reference> rowWriterMap = new HashMap<>();

  public BaseBinaryEncoderBuilder(CodegenContext context, Class beanClass) {
    this(context, TypeToken.of(beanClass));
  }

  public BaseBinaryEncoderBuilder(CodegenContext context, TypeToken beanType) {
    super(context, beanType);
    ctx.reserveName(REFERENCES_NAME);

    ctx.addImport(BinaryRow.class.getPackage().getName() + ".*");
    ctx.addImport(BinaryWriter.class.getPackage().getName() + ".*");
    ctx.addImport(Schema.class.getPackage().getName() + ".*");
  }

  public String codecClassName(Class beanClass) {
    return codecClassName(beanClass, "");
  }

  public String codecClassName(Class beanClass, String prefix) {
    String name =
        ReflectionUtils.getClassNameWithoutPackage(beanClass)
            + prefix
            + codecSuffix()
            + CodeGenerator.getClassUniqueId(beanClass);
    return name.replace("$", "_");
  }

  protected String codecSuffix() {
    return "RowCodec";
  }

  public String codecQualifiedClassName(Class beanClass) {
    return CodeGenerator.getPackage(beanClass) + "." + codecClassName(beanClass);
  }

  public String codecQualifiedClassName(Class beanClass, String prefix) {
    return CodeGenerator.getPackage(beanClass) + "." + codecClassName(beanClass, prefix);
  }

  /**
   * Return an expression for serializing an object of given type to row format representation. The
   * inputObject will be written to position ordinal of row/array using given 
   * writer
   */
  protected Expression serializeFor(
      Expression ordinal,
      Expression inputObject,
      Expression writer,
      TypeToken typeToken,
      Expression arrowField) {
    Class rawType = getRawType(typeToken);
    if (TypeUtils.isPrimitive(rawType)) {
      return new ListExpression(
          // notNull is by default, no need to call setNotNullAt
          new Invoke(writer, "write", ordinal, inputObject));
    } else if (TypeUtils.isBoxed(rawType)) {
      // janino support autoboxing and unboxing, so we don't need to call intValue/longValue....
      return setValueOrNull(writer, ordinal, inputObject, inputObject);
    } else if (rawType == BigDecimal.class) {
      return setValueOrNull(writer, ordinal, inputObject, inputObject);
    } else if (rawType == java.math.BigInteger.class) {
      Invoke value = new Invoke(inputObject, "toByteArray", TypeToken.of(byte[].class));
      return setValueOrNull(writer, ordinal, inputObject, value);
    } else if (rawType == java.time.LocalDate.class) {
      StaticInvoke value =
          new StaticInvoke(
              DateTimeUtils.class,
              "localDateToDays",
              TypeUtils.PRIMITIVE_INT_TYPE,
              false,
              inputObject);
      return setValueOrNull(writer, ordinal, inputObject, value);
    } else if (rawType == java.sql.Date.class) {
      StaticInvoke value =
          new StaticInvoke(
              DateTimeUtils.class,
              "fromJavaDate",
              TypeUtils.PRIMITIVE_INT_TYPE,
              false,
              inputObject);
      return setValueOrNull(writer, ordinal, inputObject, value);
    } else if (rawType == java.sql.Timestamp.class) {
      StaticInvoke value =
          new StaticInvoke(
              DateTimeUtils.class,
              "fromJavaTimestamp",
              TypeUtils.PRIMITIVE_LONG_TYPE,
              false,
              inputObject);
      return setValueOrNull(writer, ordinal, inputObject, value);
    } else if (rawType == java.time.Instant.class) {
      StaticInvoke value =
          new StaticInvoke(
              DateTimeUtils.class,
              "instantToMicros",
              TypeUtils.PRIMITIVE_LONG_TYPE,
              false,
              inputObject);
      return setValueOrNull(writer, ordinal, inputObject, value);
    } else if (rawType == String.class) {
      return setValueOrNull(writer, ordinal, inputObject, inputObject);
    } else if (rawType.isEnum()) {
      Invoke value = new Invoke(inputObject, "name", TypeUtils.STRING_TYPE);
      return setValueOrNull(writer, ordinal, inputObject, value);
    } else if (rawType.isArray() || TypeUtils.ITERABLE_TYPE.isSupertypeOf(typeToken)) {
      // place outer writer operations here, because map key/value arrays need to call
      // serializeForArray,
      // but don't setOffsetAndSize for array.
      Invoke offset =
          new Invoke(writer, "writerIndex", "writerIndex", TypeUtils.PRIMITIVE_INT_TYPE);
      Expression serializeArray = serializeForArray(inputObject, writer, typeToken, arrowField);
      Arithmetic size =
          ExpressionUtils.subtract(
              new Invoke(writer, "writerIndex", "writerIndex", TypeUtils.PRIMITIVE_INT_TYPE),
              offset);
      Invoke setOffsetAndSize = new Invoke(writer, "setOffsetAndSize", ordinal, offset, size);
      ListExpression expression =
          new ListExpression(offset, serializeArray, size, setOffsetAndSize);
      return new If(
          ExpressionUtils.eqNull(inputObject),
          new Invoke(writer, "setNullAt", ordinal),
          expression);
    } else if (TypeUtils.MAP_TYPE.isSupertypeOf(typeToken)) {
      return serializeForMap(ordinal, writer, inputObject, typeToken, arrowField);
    } else if (TypeUtils.isBean(rawType)) {
      return serializeForBean(ordinal, writer, inputObject, typeToken, arrowField);
    } else {
      return serializeForObject(ordinal, writer, inputObject);
    }
  }

  /**
   * Returns an expression to write iterable inputObject of type typeToken
   * as {@link BinaryArray} using given writer.
   */
  protected Expression serializeForArray(
      Expression inputObject, Expression writer, TypeToken typeToken, Expression arrowField) {
    return serializeForArray(inputObject, writer, typeToken, arrowField, false);
  }

  protected Expression serializeForArray(
      Expression inputObject,
      Expression writer,
      TypeToken typeToken,
      Expression arrowField,
      boolean reuse) {
    Reference arrayWriter = getOrCreateArrayWriter(typeToken, arrowField, writer, reuse);
    StaticInvoke arrayElementField =
        new StaticInvoke(
            DataTypes.class, "arrayElementField", "elemField", ARROW_FIELD_TYPE, false, arrowField);
    Class rawType = getRawType(typeToken);
    if (rawType.isArray()) {
      FieldValue length = new FieldValue(inputObject, "length", TypeUtils.PRIMITIVE_INT_TYPE);
      Invoke reset = new Invoke(arrayWriter, "reset", length);
      if (rawType.getComponentType().isPrimitive()) {
        return new ListExpression(
            reset, new Invoke(arrayWriter, "fromPrimitiveArray", inputObject), arrayWriter);
      } else {
        ForEach forEach =
            new ForEach(
                inputObject,
                (i, value) ->
                    serializeFor(
                        i,
                        value,
                        arrayWriter,
                        Objects.requireNonNull(typeToken.getComponentType()),
                        arrayElementField));
        return new ListExpression(reset, forEach, arrayWriter);
      }
    } else if (getRawType(typeToken) == Iterable.class) {
      ListFromIterable listFromIterable = new ListFromIterable(inputObject);
      Invoke size = new Invoke(listFromIterable, "size", TypeUtils.PRIMITIVE_INT_TYPE);
      Invoke reset = new Invoke(arrayWriter, "reset", size);
      ForEach forEach =
          new ForEach(
              listFromIterable,
              (i, value) ->
                  serializeFor(
                      i,
                      value,
                      arrayWriter,
                      TypeUtils.getElementType(typeToken),
                      arrayElementField));
      return new ListExpression(reset, forEach, arrayWriter);
    } else { // collection
      Invoke size = new Invoke(inputObject, "size", TypeUtils.PRIMITIVE_INT_TYPE);
      Invoke reset = new Invoke(arrayWriter, "reset", size);
      ForEach forEach =
          new ForEach(
              inputObject,
              (i, value) ->
                  serializeFor(
                      i,
                      value,
                      arrayWriter,
                      TypeUtils.getElementType(typeToken),
                      arrayElementField));
      return new ListExpression(reset, forEach, arrayWriter);
    }
  }

  /**
   * Get or create an ArrayWriter for given type and use writer as parent
   * writer.
   */
  protected Reference getOrCreateArrayWriter(
      TypeToken typeToken, Expression arrayDataType, Expression writer) {
    return getOrCreateArrayWriter(typeToken, arrayDataType, writer, false);
  }

  protected Reference getOrCreateArrayWriter(
      TypeToken typeToken, Expression arrayDataType, Expression writer, boolean reuse) {
    if (reuse) {
      return (Reference) writer;
    }

    return arrayWriterMap.computeIfAbsent(
        typeToken,
        t -> {
          String name = ctx.newName("arrayWriter");
          ctx.addField(
              ctx.type(BinaryArrayWriter.class),
              name,
              new NewInstance(arrayWriterTypeToken, arrayDataType, writer));
          return new Reference(name, arrayWriterTypeToken, false);
        });
  }

  /**
   * Returns an expression to write map inputObject to position ordinal of
   * row/array using given writer.
   */
  protected Expression serializeForMap(
      Expression ordinal,
      Expression writer,
      Expression inputObject,
      TypeToken typeToken,
      Expression arrowField) {
    StaticInvoke keyArrayField =
        new StaticInvoke(
            DataTypes.class,
            "keyArrayFieldForMap",
            "keyArrayField",
            ARROW_FIELD_TYPE,
            false,
            arrowField);
    StaticInvoke valueArrayField =
        new StaticInvoke(
            DataTypes.class,
            "itemArrayFieldForMap",
            "valueArrayField",
            ARROW_FIELD_TYPE,
            false,
            arrowField);

    @SuppressWarnings("unchecked")
    TypeToken supertype = ((TypeToken>) typeToken).getSupertype(Map.class);
    TypeToken keySetType = supertype.resolveType(TypeUtils.KEY_SET_RETURN_TYPE);
    TypeToken valuesType = supertype.resolveType(TypeUtils.VALUES_RETURN_TYPE);

    Invoke keySet = new Invoke(inputObject, "keySet", keySetType);
    Expression keySerializationExpr = serializeForArray(keySet, writer, keySetType, keyArrayField);

    Invoke values = new Invoke(inputObject, "values", valuesType);
    Expression valueSerializationExpr =
        serializeForArray(values, writer, valuesType, valueArrayField);

    Invoke offset = new Invoke(writer, "writerIndex", "writerIndex", TypeUtils.PRIMITIVE_INT_TYPE);
    // preserve 8 bytes to write the key array numBytes later
    Invoke preserve =
        new Invoke(writer, "writeDirectly", new Literal(-1, TypeUtils.PRIMITIVE_INT_TYPE));
    Invoke writeKeyArrayNumBytes =
        new Invoke(
            writer,
            "writeDirectly",
            offset,
            new Invoke(keySerializationExpr, "size", TypeUtils.PRIMITIVE_INT_TYPE));
    Arithmetic size =
        ExpressionUtils.subtract(
            new Invoke(writer, "writerIndex", "writerIndex", TypeUtils.PRIMITIVE_INT_TYPE), offset);
    Invoke setOffsetAndSize = new Invoke(writer, "setOffsetAndSize", ordinal, offset, size);

    ListExpression expression =
        new ListExpression(
            offset,
            preserve,
            keySerializationExpr,
            writeKeyArrayNumBytes,
            valueSerializationExpr,
            setOffsetAndSize);

    return new If(
        ExpressionUtils.eqNull(inputObject), new Invoke(writer, "setNullAt", ordinal), expression);
  }

  /**
   * Returns an expression to write bean inputObject to position ordinal
   * of row/array using given writer.
   */
  protected Expression serializeForBean(
      Expression ordinal,
      Expression writer,
      Expression inputObject,
      TypeToken typeToken,
      Expression structField) {
    Class rawType = getRawType(typeToken);
    Reference rowWriter;
    Reference beanEncoder = beanEncoderMap.get(typeToken);
    if (beanEncoder == null) {
      // janino generics don't add cast, so this `<${type}>` is only for generated code readability
      StaticInvoke schema =
          new StaticInvoke(
              DataTypes.class, "schemaFromStructField", "schema", SCHEMA_TYPE, false, structField);
      String rowWriterName =
          ctx.newName(StringUtils.uncapitalize(rawType.getSimpleName() + "RowWriter"));
      NewInstance newRowWriter = new NewInstance(rowWriterTypeToken, schema, writer);
      ctx.addField(ctx.type(rowWriterTypeToken), rowWriterName, newRowWriter);

      Preconditions.checkArgument(!codecClassName(rawType).contains("."));
      String encoderName = ctx.newName(StringUtils.uncapitalize(codecClassName(rawType)));
      String encoderClass = codecQualifiedClassName(rawType);
      TypeToken codecTypeToken = TypeToken.of(GeneratedRowEncoder.class);
      NewInstance newEncoder =
          new NewInstance(
              codecTypeToken,
              encoderClass,
              ExpressionUtils.newObjectArray(schema, newRowWriter, furyRef));
      ctx.addField(encoderClass, encoderName, newEncoder);

      rowWriter = new Reference(rowWriterName, rowWriterTypeToken);
      rowWriterMap.put(typeToken, rowWriter);
      beanEncoder = new Reference(encoderName, codecTypeToken);
      beanEncoderMap.put(typeToken, beanEncoder);
    }
    rowWriter = rowWriterMap.get(typeToken);

    Invoke reset = new Invoke(rowWriter, "reset");
    Invoke offset = new Invoke(writer, "writerIndex", "writerIndex", TypeUtils.PRIMITIVE_INT_TYPE);
    Invoke toRow = new Invoke(beanEncoder, "toRow", inputObject);
    Arithmetic size =
        ExpressionUtils.subtract(
            new Invoke(writer, "writerIndex", "writerIndex", TypeUtils.PRIMITIVE_INT_TYPE), offset);
    Invoke setOffsetAndSize = new Invoke(writer, "setOffsetAndSize", ordinal, offset, size);
    ListExpression expression =
        new ListExpression(
            offset,
            reset,
            toRow, // reset will change writerIndex. must call reset and toRow in pair.
            size,
            setOffsetAndSize);

    return new If(
        ExpressionUtils.eqNull(inputObject), new Invoke(writer, "setNullAt", ordinal), expression);
  }

  /**
   * Return an expression to serialize opaque inputObject as binary using fury
   * . When deserialization, using fury to deserialize sliced MemoryBuffer. See {@link
   * BinaryUtils#getElemAccessMethodName(TypeToken)}, {@link Getters#getBuffer(int)}
   */
  protected Expression serializeForObject(
      Expression ordinal, Expression writer, Expression inputObject) {
    Invoke offset = new Invoke(writer, "writerIndex", "writerIndex", TypeUtils.PRIMITIVE_INT_TYPE);
    Invoke buffer = new Invoke(writer, "getBuffer", "buffer", TypeToken.of(MemoryBuffer.class));
    Expression setWriterIndex = new Invoke(buffer, "writerIndex", offset);
    Invoke serialize = new Invoke(furyRef, "serialize", buffer, inputObject);
    Invoke newWriterIndex =
        new Invoke(buffer, "writerIndex", "writerIndex", TypeUtils.PRIMITIVE_INT_TYPE);
    Arithmetic size = ExpressionUtils.subtract(newWriterIndex, offset, "size");
    Expression increaseWriterIndexToAligned =
        new Invoke(writer, "increaseWriterIndexToAligned", size);
    Invoke setOffsetAndSize = new Invoke(writer, "setOffsetAndSize", ordinal, offset, size);
    ListExpression expression =
        new ListExpression(
            offset,
            buffer,
            setWriterIndex,
            serialize,
            increaseWriterIndexToAligned,
            setOffsetAndSize);
    return new If(
        ExpressionUtils.eqNull(inputObject), new Invoke(writer, "setNullAt", ordinal), expression);
  }

  protected Expression setValueOrNull(
      Expression writer, Expression ordinal, Expression inputObject, Expression value) {
    Expression action = new Invoke(writer, "write", ordinal, value);
    return new If(
        ExpressionUtils.eqNull(inputObject), new Invoke(writer, "setNullAt", ordinal), action);
  }

  /**
   * Returns an expression that deserialize value as a java object of type 
   * typeToken.
   */
  protected Expression deserializeFor(Expression value, TypeToken typeToken) {
    Class rawType = getRawType(typeToken);
    if (TypeUtils.isPrimitive(rawType) || TypeUtils.isBoxed(rawType)) {
      return value;
    } else if (rawType == BigDecimal.class) {
      return value;
    } else if (rawType == java.math.BigInteger.class) {
      return new NewInstance(TypeUtils.BIG_INTEGER_TYPE, value);
    } else if (rawType == java.time.LocalDate.class) {
      return new StaticInvoke(
          DateTimeUtils.class, "daysToLocalDate", TypeUtils.LOCAL_DATE_TYPE, false, value);
    } else if (rawType == java.sql.Date.class) {
      return new StaticInvoke(DateTimeUtils.class, "toJavaDate", TypeUtils.DATE_TYPE, false, value);
    } else if (rawType == java.sql.Timestamp.class) {
      return new StaticInvoke(
          DateTimeUtils.class, "toJavaTimestamp", TypeUtils.TIMESTAMP_TYPE, false, value);
    } else if (rawType == java.time.Instant.class) {
      return new StaticInvoke(
          DateTimeUtils.class, "microsToInstant", TypeUtils.INSTANT_TYPE, false, value);
    } else if (rawType == String.class) {
      return value;
    } else if (rawType.isEnum()) {
      return ExpressionUtils.valueOf(typeToken, value);
    } else if (rawType.isArray()) {
      return deserializeForArray(value, typeToken);
    } else if (TypeUtils.ITERABLE_TYPE.isSupertypeOf(typeToken)) {
      return deserializeForCollection(value, typeToken);
    } else if (TypeUtils.MAP_TYPE.isSupertypeOf(typeToken)) {
      return deserializeForMap(value, typeToken);
    } else if (TypeUtils.isBean(rawType)) {
      return deserializeForBean(value, typeToken);
    } else {
      return deserializeForObject(value, typeToken);
    }
  }

  /**
   * Returns an expression that deserialize row as a java bean of type typeToken
   * .
   */
  protected Expression deserializeForBean(Expression row, TypeToken typeToken) {
    Reference beanEncoder = beanEncoderMap.get(typeToken);
    if (beanEncoder == null) {
      throw new IllegalStateException("beanEncoder should have be added in serializeForBean()");
    }
    Invoke beanObj = new Invoke(beanEncoder, "fromRow", TypeUtils.OBJECT_TYPE, false, row);
    return new Cast(beanObj, typeToken, "bean");
  }

  /** Returns an expression that deserialize mapData as a java map. */
  protected Expression deserializeForMap(Expression mapData, TypeToken typeToken) {
    Expression javaMap = newMap(typeToken);
    @SuppressWarnings("unchecked")
    TypeToken supertype = ((TypeToken>) typeToken).getSupertype(Map.class);
    TypeToken keySetType = supertype.resolveType(TypeUtils.KEY_SET_RETURN_TYPE);
    TypeToken keysType = TypeUtils.getCollectionType(keySetType);
    TypeToken valuesType = supertype.resolveType(TypeUtils.VALUES_RETURN_TYPE);
    Expression keyArray = new Invoke(mapData, "keyArray", binaryArrayTypeToken, false);
    Expression valueArray = new Invoke(mapData, "valueArray", binaryArrayTypeToken, false);
    Expression keyJavaArray;
    Expression valueJavaArray;
    if (TypeUtils.ITERABLE_TYPE.isSupertypeOf(keysType)) {
      keyJavaArray = deserializeForCollection(keyArray, keysType);
    } else {
      keyJavaArray = deserializeForArray(keyArray, keysType);
    }
    if (TypeUtils.ITERABLE_TYPE.isSupertypeOf(valuesType)) {
      valueJavaArray = deserializeForCollection(valueArray, valuesType);
    } else {
      valueJavaArray = deserializeForArray(valueArray, valuesType);
    }

    ZipForEach put =
        new ZipForEach(
            keyJavaArray,
            valueJavaArray,
            (i, key, value) ->
                new If(ExpressionUtils.notNull(key), new Invoke(javaMap, "put", key, value)));
    return new ListExpression(javaMap, put, javaMap);
  }

  /** Returns an expression that deserialize arrayData as a java collection. */
  protected Expression deserializeForCollection(Expression arrayData, TypeToken typeToken) {
    Expression collection = newCollection(typeToken);
    try {
      TypeToken elemType = TypeUtils.getElementType(typeToken);
      ArrayDataForEach addElemsOp =
          new ArrayDataForEach(
              arrayData,
              elemType,
              (i, value) -> new Invoke(collection, "add", deserializeFor(value, elemType)),
              i -> new Invoke(collection, "add", ExpressionUtils.nullValue(elemType)));
      return new ListExpression(collection, addElemsOp, collection);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Create a java collection. Create a {@link ArrayList} if typeToken is super class
   * of {@link ArrayList}; Create a {@link HashSet} if typeToken is super class of
   * {@link HashSet}; Create an instance of typeToken otherwise.
   */
  protected Expression newCollection(TypeToken typeToken) {
    Class clazz = getRawType(typeToken);
    Expression collection;
    if (TypeToken.of(clazz).isSupertypeOf(TypeToken.of(ArrayList.class))) {
      collection = new NewInstance(TypeToken.of(ArrayList.class));
    } else if (TypeToken.of(clazz).isSupertypeOf(TypeToken.of(HashSet.class))) {
      collection = new NewInstance(TypeToken.of(HashSet.class));
    } else {
      if (ReflectionUtils.isAbstract(clazz) || clazz.isInterface()) {
        String msg = String.format("class %s can't be abstract or interface", clazz);
        throw new UnsupportedOperationException(msg);
      }
      collection = new NewInstance(typeToken);
    }
    return collection;
  }

  /**
   * Create a java map. Create a {@link HashMap} if typeToken is super class of
   * HashMap; Create an instance of typeToken otherwise.
   */
  protected Expression newMap(TypeToken typeToken) {
    Class clazz = getRawType(typeToken);
    Expression javaMap;
    // use TypeToken.of(clazz) rather typeToken to strip generics.
    if (TypeToken.of(clazz).isSupertypeOf(TypeToken.of(HashMap.class))) {
      javaMap = new NewInstance(TypeToken.of(HashMap.class));
    } else {
      if (ReflectionUtils.isAbstract(clazz) || clazz.isInterface()) {
        String msg = String.format("class %s can't be abstract or interface", clazz);
        throw new UnsupportedOperationException(msg);
      }
      javaMap = new NewInstance(typeToken);
    }
    return javaMap;
  }

  /**
   * Returns an expression to deserialize multi-array from arrayData, and set value to
   * rootJavaArray.
   */
  protected Expression deserializeForMultiDimensionArray(
      Expression arrayData,
      Expression rootJavaArray,
      int numDimensions,
      TypeToken typeToken,
      Expression[] indexes) {
    Preconditions.checkArgument(numDimensions > 1);
    Preconditions.checkArgument(typeToken.isArray());
    TypeToken elemType = typeToken.getComponentType();
    if (numDimensions == 2) {
      return new ArrayDataForEach(
          arrayData,
          elemType,
          (i, value) -> {
            Expression[] newIndexes = Arrays.copyOf(indexes, indexes.length + 1);
            newIndexes[indexes.length] = i;
            Expression elemArr =
                deserializeForArray(value, Objects.requireNonNull(typeToken.getComponentType()));
            return new AssignArrayElem(rootJavaArray, elemArr, newIndexes);
          });
    } else {
      return new ArrayDataForEach(
          arrayData,
          elemType,
          (i, value) -> {
            Expression[] newIndexes = Arrays.copyOf(indexes, indexes.length + 1);
            newIndexes[indexes.length] = i;
            return deserializeForMultiDimensionArray(
                value, rootJavaArray, numDimensions - 1, elemType, newIndexes);
          });
    }
  }

  /**
   * Return an expression that deserialize arrayData. If array is multi-array, forward
   * to {@link BaseBinaryEncoderBuilder#deserializeForMultiDimensionArray}
   */
  protected Expression deserializeForArray(Expression arrayData, TypeToken typeToken) {
    int numDimensions = TypeUtils.getArrayDimensions(typeToken);
    if (numDimensions > 1) {
      // If some dimension's elements is all null, we take outer-most array as null,
      // and don't create multi-array. return an no-ops expression or return null.
      StaticInvoke dimensions =
          new StaticInvoke(
              BinaryArray.class,
              "getDimensions",
              "dims",
              TypeToken.of(int[].class),
              true,
              arrayData,
              new Literal(numDimensions, TypeUtils.INT_TYPE));
      TypeToken innerElemType = TypeUtils.getMultiDimensionArrayElementType(typeToken);
      Class innerElemClass = getRawType(innerElemType);
      Expression rootJavaMultiDimArray = new NewArray(innerElemClass, numDimensions, dimensions);
      Expression op =
          deserializeForMultiDimensionArray(
              arrayData, rootJavaMultiDimArray, numDimensions, typeToken, new Expression[0]);
      // although the value maybe null, we don't use this info, so we set nullability to false.
      return new If(
          ExpressionUtils.notNull(dimensions),
          new ListExpression(rootJavaMultiDimArray, op, rootJavaMultiDimArray),
          ExpressionUtils.nullValue(rootJavaMultiDimArray.type()),
          false);
    } else {
      TypeToken elemType = typeToken.getComponentType();
      Class innerElemClass = getRawType(Objects.requireNonNull(elemType));
      if (byte.class == innerElemClass) {
        return new Invoke(arrayData, "toByteArray", TypeUtils.PRIMITIVE_BYTE_ARRAY_TYPE);
      } else if (boolean.class == innerElemClass) {
        return new Invoke(arrayData, "toBooleanArray", TypeUtils.PRIMITIVE_BOOLEAN_ARRAY_TYPE);
      } else if (short.class == innerElemClass) {
        return new Invoke(arrayData, "toShortArray", TypeUtils.PRIMITIVE_SHORT_ARRAY_TYPE);
      } else if (int.class == innerElemClass) {
        return new Invoke(arrayData, "toIntArray", TypeUtils.PRIMITIVE_INT_ARRAY_TYPE);
      } else if (long.class == innerElemClass) {
        return new Invoke(arrayData, "toLongArray", TypeUtils.PRIMITIVE_LONG_ARRAY_TYPE);
      } else if (float.class == innerElemClass) {
        return new Invoke(arrayData, "toFloatArray", TypeUtils.PRIMITIVE_FLOAT_ARRAY_TYPE);
      } else if (double.class == innerElemClass) {
        return new Invoke(arrayData, "toDoubleArray", TypeUtils.PRIMITIVE_DOUBLE_ARRAY_TYPE);
      } else {
        Invoke dim = new Invoke(arrayData, "numElements", TypeUtils.PRIMITIVE_INT_TYPE);
        NewArray javaArray = new NewArray(innerElemClass, dim);
        ArrayDataForEach op =
            new ArrayDataForEach(
                arrayData,
                elemType,
                (i, value) -> {
                  Expression elemValue = deserializeFor(value, elemType);
                  return new AssignArrayElem(javaArray, elemValue, i);
                });
        // add javaArray at last as expression value
        return new ListExpression(javaArray, op, javaArray);
      }
    }
  }

  /**
   * Using fury to deserialize sliced MemoryBuffer. see {@link
   * BinaryUtils#getElemAccessMethodName(TypeToken)}, {@link Getters#getBuffer(int)}
   */
  protected Expression deserializeForObject(Expression value, TypeToken typeToken) {
    return new Invoke(furyRef, "deserialize", typeToken, value);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy