/**
   * Sorts each local partition of a data set on the field(s) specified by the field expression in
   * the specified {@link Order} before it is emitted by the output format.</br> <b>Note:
   * Non-composite types can only be sorted on the full element which is specified by a wildcard
   * expression ("*" or "_").</b><br>
   * Data sets of composite types (Tuple or Pojo) can be sorted on multiple fields in different
   * orders by chaining {@link #sortLocalOutput(String, Order)} calls.
   *
   * @param fieldExpression The field expression for the field(s) on which the data set is locally
   *     sorted.
   * @param order The Order in which the specified field(s) are locally sorted.
   * @return This data sink operator with specified output order.
   * @see Order
   */
  public DataSink<T> sortLocalOutput(String fieldExpression, Order order) {

    int numFields;
    int[] fields;
    Order[] orders;

    if (this.type instanceof CompositeType) {
      // compute flat field positions for (nested) sorting fields
      Keys.ExpressionKeys<T> ek;
      try {
        ek = new Keys.ExpressionKeys<T>(new String[] {fieldExpression}, this.type);
      } catch (IllegalArgumentException iae) {
        throw new InvalidProgramException("Invalid specification of field expression.", iae);
      }
      fields = ek.computeLogicalKeyPositions();
      numFields = fields.length;
      orders = new Order[numFields];
      Arrays.fill(orders, order);
    } else {
      fieldExpression = fieldExpression.trim();
      if (!(fieldExpression.equals("*") || fieldExpression.equals("_"))) {
        throw new InvalidProgramException(
            "Output sorting of non-composite types can only be defined on the full type. "
                + "Use a field wildcard for that (\"*\" or \"_\")");
      } else {
        numFields = 1;
        fields = new int[] {0};
        orders = new Order[] {order};
      }
    }

    if (this.sortKeyPositions == null) {
      // set sorting info
      this.sortKeyPositions = fields;
      this.sortOrders = orders;
    } else {
      // append sorting info to existing info
      int oldLength = this.sortKeyPositions.length;
      int newLength = oldLength + numFields;
      this.sortKeyPositions = Arrays.copyOf(this.sortKeyPositions, newLength);
      this.sortOrders = Arrays.copyOf(this.sortOrders, newLength);
      for (int i = 0; i < numFields; i++) {
        this.sortKeyPositions[oldLength + i] = fields[i];
        this.sortOrders[oldLength + i] = orders[i];
      }
    }

    return this;
  }
  /**
   * Sorts each local partition of a {@link org.apache.flink.api.java.tuple.Tuple} data set on the
   * specified field in the specified {@link Order} before it is emitted by the output format.</br>
   * <b>Note: Only tuple data sets can be sorted using integer field indices.</b><br>
   * The tuple data set can be sorted on multiple fields in different orders by chaining {@link
   * #sortLocalOutput(int, Order)} calls.
   *
   * @param field The Tuple field on which the data set is locally sorted.
   * @param order The Order in which the specified Tuple field is locally sorted.
   * @return This data sink operator with specified output order.
   * @see org.apache.flink.api.java.tuple.Tuple
   * @see Order
   */
  public DataSink<T> sortLocalOutput(int field, Order order) {

    if (!this.type.isTupleType()) {
      throw new InvalidProgramException(
          "Specifying order keys via field positions is only valid for tuple data types");
    }
    if (field >= this.type.getArity()) {
      throw new InvalidProgramException("Order key out of tuple bounds.");
    }

    // get flat keys
    Keys.ExpressionKeys<T> ek;
    try {
      ek = new Keys.ExpressionKeys<T>(new int[] {field}, this.type);
    } catch (IllegalArgumentException iae) {
      throw new InvalidProgramException("Invalid specification of field expression.", iae);
    }
    int[] flatKeys = ek.computeLogicalKeyPositions();

    if (this.sortKeyPositions == null) {
      // set sorting info
      this.sortKeyPositions = flatKeys;
      this.sortOrders = new Order[flatKeys.length];
      Arrays.fill(this.sortOrders, order);
    } else {
      // append sorting info to exising info
      int oldLength = this.sortKeyPositions.length;
      int newLength = oldLength + flatKeys.length;
      this.sortKeyPositions = Arrays.copyOf(this.sortKeyPositions, newLength);
      this.sortOrders = Arrays.copyOf(this.sortOrders, newLength);

      for (int i = 0; i < flatKeys.length; i++) {
        this.sortKeyPositions[oldLength + i] = flatKeys[i];
        this.sortOrders[oldLength + i] = order;
      }
    }

    return this;
  }