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

com.landawn.abacus.util.RowDataSet Maven / Gradle / Ivy

Go to download

A general programming library in Java/Android. It's easy to learn and simple to use with concise and powerful APIs.

There is a newer version: 5.2.4
Show newest version
/*
 * Copyright (c) 2015, Haiyang Li.
 *
 * 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 com.landawn.abacus.util;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import java.util.stream.Collector;

import com.landawn.abacus.annotation.SuppressFBWarnings;
import com.landawn.abacus.exception.UncheckedIOException;
import com.landawn.abacus.parser.JSONParser;
import com.landawn.abacus.parser.JSONSerializationConfig;
import com.landawn.abacus.parser.JSONSerializationConfig.JSC;
import com.landawn.abacus.parser.KryoParser;
import com.landawn.abacus.parser.ParserFactory;
import com.landawn.abacus.parser.ParserUtil;
import com.landawn.abacus.parser.ParserUtil.BeanInfo;
import com.landawn.abacus.parser.ParserUtil.PropInfo;
import com.landawn.abacus.parser.XMLConstants;
import com.landawn.abacus.parser.XMLParser;
import com.landawn.abacus.parser.XMLSerializationConfig;
import com.landawn.abacus.parser.XMLSerializationConfig.XSC;
import com.landawn.abacus.type.Type;
import com.landawn.abacus.util.Fn.Factory;
import com.landawn.abacus.util.Fn.Fnn;
import com.landawn.abacus.util.NoCachingNoUpdating.DisposableObjArray;
import com.landawn.abacus.util.Tuple.Tuple2;
import com.landawn.abacus.util.Tuple.Tuple3;
import com.landawn.abacus.util.u.Optional;
import com.landawn.abacus.util.function.IndexedConsumer;
import com.landawn.abacus.util.stream.Collectors;
import com.landawn.abacus.util.stream.IntStream;
import com.landawn.abacus.util.stream.ObjIteratorEx;
import com.landawn.abacus.util.stream.Stream;

/**
 * It's a row DataSet from logic aspect. But each column is stored in a list.
 *
 * @author Haiyang Li
 * @since 0.8
 */
@SuppressWarnings({ "java:S1192", "java:S1854" })
public class RowDataSet implements DataSet, Cloneable {

    static final char PROP_NAME_SEPARATOR = '.';

    static final String NULL_STRING = "null".intern();

    static final char[] NULL_CHAR_ARRAY = NULL_STRING.toCharArray();

    static final String TRUE = Boolean.TRUE.toString().intern();

    static final char[] TRUE_CHAR_ARRAY = TRUE.toCharArray();

    static final String FALSE = Boolean.FALSE.toString().intern();

    static final char[] FALSE_CHAR_ARRAY = FALSE.toCharArray();

    static final Set> SUPPORTED_COUNT_COLUMN_TYPES = N.asSet((Class) int.class, Integer.class, long.class, Long.class, float.class, Float.class,
            double.class, Double.class);

    /**
     * Field CACHED_PROP_NAMES. (value is ""cachedPropNames"")
     */
    public static final String CACHED_PROP_NAMES = "cachedPropNames";

    private static final String ROW = "row";

    private static final JSONParser jsonParser = ParserFactory.createJSONParser();

    private static final XMLParser xmlParser = ParserFactory.isXMLAvailable() ? ParserFactory.createXMLParser() : null;

    private static final KryoParser kryoParser = ParserFactory.isKryoAvailable() ? ParserFactory.createKryoParser() : null;

    private static final JSONSerializationConfig jsc = JSC.create().setDateTimeFormat(DateTimeFormat.ISO_8601_TIMESTAMP);

    private static final XMLSerializationConfig xsc = XSC.create().setDateTimeFormat(DateTimeFormat.ISO_8601_TIMESTAMP);

    private static final Type strType = N.typeOf(String.class);

    List _columnNameList; //NOSONAR

    List> _columnList; //NOSONAR

    Map _columnIndexMap; //NOSONAR

    int[] _columnIndexes; //NOSONAR

    int _currentRowNum = 0; //NOSONAR

    boolean _isFrozen = false; //NOSONAR

    Properties _properties; //NOSONAR

    transient int modCount = 0; //NOSONAR

    // For Kryo
    protected RowDataSet() {
    }

    /**
     *
     *
     * @param columnNameList
     * @param columnList
     */
    public RowDataSet(final List columnNameList, final List> columnList) {
        this(columnNameList, columnList, null);
    }

    /**
     *
     *
     * @param columnNameList
     * @param columnList
     * @param properties
     */
    public RowDataSet(final List columnNameList, final List> columnList, final Properties properties) {
        N.checkArgNotNull(columnNameList);
        N.checkArgNotNull(columnList);

        N.checkArgument(!N.hasDuplicates(columnNameList), "Dupliated column names: {}", columnNameList);
        N.checkArgument(columnNameList.size() == columnList.size(), "the size of column name list: {} is different from the size of column list: {}",
                columnNameList.size(), columnList.size());

        final int size = columnList.size() == 0 ? 0 : columnList.get(0).size();

        for (List column : columnList) {
            N.checkArgument(column.size() == size, "All columns in the specified 'columnList' must have same size.");
        }

        this._columnNameList = columnNameList;

        this._columnList = columnList;

        this._properties = properties;
    }

    //    @Override
    //    public String beanName() {
    //        return _beanName;
    //    }
    //
    //    @SuppressWarnings("unchecked")
    //    @Override
    //    public  Class beanClass() {
    //        return (Class) _beanClass;
    //    }

    /**
     * Column name list.
     *
     * @return
     */
    @SuppressWarnings("deprecation")
    @Override
    public ImmutableList columnNameList() {
        // return _columnNameList;

        return ImmutableList.wrap(_columnNameList);
    }

    /**
     * Gets the column name.
     *
     * @param columnIndex
     * @return
     */
    @Override
    public String getColumnName(final int columnIndex) {
        return _columnNameList.get(columnIndex);
    }

    /**
     * Gets the column index.
     *
     * @param columnName
     * @return
     */
    @Override
    public int getColumnIndex(final String columnName) {
        if (_columnIndexMap == null) {
            _columnIndexMap = new HashMap<>();

            int i = 0;
            for (String e : _columnNameList) {
                _columnIndexMap.put(e, i++);
            }
        }

        Integer columnIndex = _columnIndexMap.get(columnName);

        if (columnIndex == null /* && NameUtil.isCanonicalName(_beanName, columnName)*/) {
            columnIndex = _columnIndexMap.get(NameUtil.getSimpleName(columnName));
        }

        return (columnIndex == null) ? -1 : columnIndex;
    }

    /**
     * Gets the column indexes.
     *
     * @param columnNames
     * @return
     */
    @Override
    public int[] getColumnIndexes(final Collection columnNames) {
        if (N.isNullOrEmpty(columnNames)) {
            return N.EMPTY_INT_ARRAY;
        }

        int[] columnIndexes = new int[columnNames.size()];
        int i = 0;

        for (String columnName : columnNames) {
            columnIndexes[i++] = getColumnIndex(columnName);
        }

        return columnIndexes;
    }

    /**
     *
     * @param columnName
     * @return true, if successful
     */
    @Override
    public boolean containsColumn(final String columnName) {
        return getColumnIndex(columnName) >= 0;
    }

    /**
     * Contains all columns.
     *
     * @param columnNames
     * @return true, if successful
     */
    @Override
    public boolean containsAllColumns(final Collection columnNames) {
        for (String columnName : columnNames) {
            if (!containsColumn(columnName)) {
                return false;
            }
        }

        return true;
    }

    /**
     *
     * @param columnName
     * @param newColumnName
     */
    @Override
    public void renameColumn(final String columnName, final String newColumnName) {
        checkFrozen();

        int idx = checkColumnName(columnName);

        if (columnName.equals(newColumnName)) {
            // ignore.
        } else {
            if (_columnNameList.contains(newColumnName)) {
                throw new IllegalArgumentException("The new property name is already included: " + _columnNameList + ". ");
            }

            if (_columnIndexMap != null) {
                _columnIndexMap.put(newColumnName, _columnIndexMap.remove(_columnNameList.get(idx)));
            }

            _columnNameList.set(idx, newColumnName);
        }

        modCount++;
    }

    /**
     *
     * @param oldNewNames
     */
    @Override
    public void renameColumns(final Map oldNewNames) {
        checkFrozen();

        if (N.hasDuplicates(oldNewNames.values())) {
            throw new IllegalArgumentException("Duplicated new column names: " + oldNewNames.values());
        }

        for (Map.Entry entry : oldNewNames.entrySet()) {
            checkColumnName(entry.getKey());

            if (_columnNameList.contains(entry.getValue()) && !entry.getKey().equals(entry.getValue())) {
                throw new IllegalArgumentException("The new property name is already included: " + _columnNameList + ". ");
            }
        }

        for (Map.Entry entry : oldNewNames.entrySet()) {
            renameColumn(entry.getKey(), entry.getValue());
        }
    }

    /**
     *
     * @param 
     * @param columnName
     * @param func
     * @throws E the e
     */
    @Override
    public  void renameColumn(final String columnName, final Throwables.Function func) throws E {
        renameColumn(columnName, func.apply(columnName));
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param func
     * @throws E the e
     */
    @Override
    public  void renameColumns(final Collection columnNames, final Throwables.Function func) throws E {
        checkColumnName(columnNames);

        final Map map = N.newHashMap(columnNames.size());

        for (String columnName : columnNames) {
            map.put(columnName, func.apply(columnName));
        }

        renameColumns(map);
    }

    /**
     *
     * @param 
     * @param func
     * @throws E the e
     */
    @Override
    public  void renameColumns(final Throwables.Function func) throws E {
        renameColumns(_columnNameList, func);
    }

    /**
     *
     * @param columnName
     * @param newPosition
     */
    @Override
    public void moveColumn(final String columnName, int newPosition) {
        checkFrozen();

        int idx = checkColumnName(columnName);

        if (newPosition < 0 || newPosition >= _columnNameList.size()) {
            throw new IllegalArgumentException("The new column index must be >= 0 and < " + _columnNameList.size());
        }

        if (idx == newPosition) {
            // ignore.
        } else {
            _columnNameList.add(newPosition, _columnNameList.remove(idx));
            _columnList.add(newPosition, _columnList.remove(idx));

            _columnIndexMap = null;
            _columnIndexes = null;
        }

        modCount++;
    }

    /**
     *
     * @param columnNameNewPositionMap
     */
    @Override
    public void moveColumns(Map columnNameNewPositionMap) {
        checkFrozen();

        final List> entries = new ArrayList<>(columnNameNewPositionMap.size());

        for (Map.Entry entry : columnNameNewPositionMap.entrySet()) {
            checkColumnName(entry.getKey());

            if (entry.getValue().intValue() < 0 || entry.getValue().intValue() >= _columnNameList.size()) {
                throw new IllegalArgumentException("The new column index must be >= 0 and < " + _columnNameList.size());
            }

            entries.add(entry);
        }

        N.sort(entries, (Comparator>) (o1, o2) -> Integer.compare(o1.getValue(), o2.getValue()));

        for (Map.Entry entry : entries) {
            int currentColumnIndex = checkColumnName(entry.getKey());

            if (currentColumnIndex == entry.getValue().intValue()) {
                // ignore.
            } else {
                _columnNameList.add(entry.getValue(), _columnNameList.remove(currentColumnIndex));
                _columnList.add(entry.getValue(), _columnList.remove(currentColumnIndex));

                _columnIndexMap = null;
            }
        }

        modCount++;
    }

    /**
     *
     * @param columnNameA
     * @param columnNameB
     */
    @Override
    public void swapColumns(String columnNameA, String columnNameB) {
        checkFrozen();

        int columnIndexA = checkColumnName(columnNameA);
        int columnIndexB = checkColumnName(columnNameB);

        if (columnNameA.equals(columnNameB)) {
            return;
        }

        final String tmpColumnNameA = _columnNameList.get(columnIndexA);
        _columnNameList.set(columnIndexA, _columnNameList.get(columnIndexB));
        _columnNameList.set(columnIndexB, tmpColumnNameA);

        final List tmpColumnA = _columnList.get(columnIndexA);
        _columnList.set(columnIndexA, _columnList.get(columnIndexB));
        _columnList.set(columnIndexB, tmpColumnA);

        if (N.notNullOrEmpty(_columnIndexMap)) {
            _columnIndexMap.put(columnNameA, columnIndexB);
            _columnIndexMap.put(columnNameB, columnIndexA);
        }

        modCount++;
    }

    /**
     *
     * @param rowIndex
     * @param newRowIndex
     */
    @Override
    public void moveRow(int rowIndex, int newRowIndex) {
        checkFrozen();

        this.checkRowNum(rowIndex);
        this.checkRowNum(newRowIndex);

        if (rowIndex == newRowIndex) {
            return;
        }

        for (List column : _columnList) {
            column.add(newRowIndex, column.remove(rowIndex));
        }

        modCount++;
    }

    /**
     *
     * @param rowIndexA
     * @param rowIndexB
     */
    @Override
    public void swapRows(int rowIndexA, int rowIndexB) {
        checkFrozen();

        this.checkRowNum(rowIndexA);
        this.checkRowNum(rowIndexB);

        if (rowIndexA == rowIndexB) {
            return;
        }

        Object tmp = null;

        for (List column : _columnList) {
            tmp = column.get(rowIndexA);
            column.set(rowIndexA, column.get(rowIndexB));
            column.set(rowIndexB, tmp);
        }

        modCount++;
    }

    /**
     *
     * @param 
     * @param rowIndex
     * @param columnIndex
     * @return
     */
    @Override
    public  T get(final int rowIndex, final int columnIndex) {
        return (T) _columnList.get(columnIndex).get(rowIndex);
    }

    //    /**
    //     *
    //     * @param 
    //     * @param targetType
    //     * @param rowIndex
    //     * @param columnIndex
    //     * @return
    //     */
    //    @Override
    //    public  T get(final Class targetType, final int rowIndex, final int columnIndex) {
    //        T rt = (T) _columnList.get(columnIndex).get(rowIndex);
    //
    //        return (rt == null) ? N.defaultValueOf(targetType) : rt;
    //    }

    /**
     *
     * @param rowIndex
     * @param columnIndex
     * @param element
     */
    @Override
    public void set(final int rowIndex, final int columnIndex, final Object element) {
        checkFrozen();

        _columnList.get(columnIndex).set(rowIndex, element);

        modCount++;
    }

    /**
     * Checks if is null.
     *
     * @param rowIndex
     * @param columnIndex
     * @return true, if is null
     */
    @Override
    public boolean isNull(final int rowIndex, final int columnIndex) {
        return get(rowIndex, columnIndex) == null;
    }

    /**
     *
     * @param 
     * @param columnIndex
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public  T get(final int columnIndex) {
        return (T) _columnList.get(columnIndex).get(_currentRowNum);
    }

    //    /**
    //     *
    //     * @param 
    //     * @param targetType
    //     * @param columnIndex
    //     * @return
    //     */
    //    @Override
    //    public  T get(final Class targetType, final int columnIndex) {
    //        T rt = get(columnIndex);
    //
    //        return (rt == null) ? N.defaultValueOf(targetType) : rt;
    //    }

    /**
     *
     * @param 
     * @param columnName
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public  T get(final String columnName) {
        return (T) get(checkColumnName(columnName));
    }

    //    /**
    //     *
    //     * @param 
    //     * @param targetType
    //     * @param columnName
    //     * @return
    //     */
    //    @Override
    //    public  T get(final Class targetType, final String columnName) {
    //        return get(targetType, checkColumnName(columnName));
    //    }
    //
    //    /**
    //     * Gets the or default.
    //     *
    //     * @param 
    //     * @param columnIndex
    //     * @param defaultValue
    //     * @return
    //     */
    //    @Override
    //    public  T getOrDefault(int columnIndex, T defaultValue) {
    //        return columnIndex < 0 ? defaultValue : (T) get(columnIndex);
    //    }
    //
    //    /**
    //     * Gets the or default.
    //     *
    //     * @param 
    //     * @param columnName
    //     * @param defaultValue
    //     * @return
    //     */
    //    @Override
    //    public  T getOrDefault(final String columnName, T defaultValue) {
    //        return getOrDefault(getColumnIndex(columnName), defaultValue);
    //    }

    /**
     * Gets the boolean.
     *
     * @param columnIndex
     * @return
     */
    @Override
    public boolean getBoolean(final int columnIndex) {
        Boolean rt = get(columnIndex);

        return (rt == null) ? false : rt;
    }

    /**
     * Gets the boolean.
     *
     * @param columnName
     * @return
     */
    @Override
    public boolean getBoolean(final String columnName) {
        return getBoolean(checkColumnName(columnName));
    }

    /**
     * Gets the char.
     *
     * @param columnIndex
     * @return
     */
    @Override
    public char getChar(final int columnIndex) {
        Character rt = get(columnIndex);

        return (rt == null) ? 0 : rt;
    }

    /**
     * Gets the char.
     *
     * @param columnName
     * @return
     */
    @Override
    public char getChar(final String columnName) {
        return getChar(checkColumnName(columnName));
    }

    /**
     * Gets the byte.
     *
     * @param columnIndex
     * @return
     */
    @Override
    public byte getByte(final int columnIndex) {
        Number rt = get(columnIndex);

        return (rt == null) ? 0 : rt.byteValue();
    }

    /**
     * Gets the byte.
     *
     * @param columnName
     * @return
     */
    @Override
    public byte getByte(final String columnName) {
        return getByte(checkColumnName(columnName));
    }

    /**
     * Gets the short.
     *
     * @param columnIndex
     * @return
     */
    @Override
    public short getShort(final int columnIndex) {
        Number rt = get(columnIndex);

        return (rt == null) ? 0 : rt.shortValue();
    }

    /**
     * Gets the short.
     *
     * @param columnName
     * @return
     */
    @Override
    public short getShort(final String columnName) {
        return getShort(checkColumnName(columnName));
    }

    /**
     * Gets the int.
     *
     * @param columnIndex
     * @return
     */
    @Override
    public int getInt(final int columnIndex) {
        Number rt = get(columnIndex);

        return (rt == null) ? 0 : rt.intValue();
    }

    /**
     * Gets the int.
     *
     * @param columnName
     * @return
     */
    @Override
    public int getInt(final String columnName) {
        return getInt(checkColumnName(columnName));
    }

    /**
     * Gets the long.
     *
     * @param columnIndex
     * @return
     */
    @Override
    public long getLong(final int columnIndex) {
        Number rt = get(columnIndex);

        return (rt == null) ? 0L : rt.longValue();
    }

    /**
     * Gets the long.
     *
     * @param columnName
     * @return
     */
    @Override
    public long getLong(final String columnName) {
        return getLong(checkColumnName(columnName));
    }

    /**
     * Gets the float.
     *
     * @param columnIndex
     * @return
     */
    @Override
    public float getFloat(final int columnIndex) {
        Number rt = get(columnIndex);

        return (rt == null) ? 0f : Numbers.toFloat(rt);
    }

    /**
     * Gets the float.
     *
     * @param columnName
     * @return
     */
    @Override
    public float getFloat(final String columnName) {
        return getFloat(checkColumnName(columnName));
    }

    /**
     * Gets the double.
     *
     * @param columnIndex
     * @return
     */
    @Override
    public double getDouble(final int columnIndex) {
        Number rt = get(columnIndex);

        return (rt == null) ? 0d : Numbers.toDouble(rt);
    }

    /**
     * Gets the double.
     *
     * @param columnName
     * @return
     */
    @Override
    public double getDouble(final String columnName) {
        return getDouble(checkColumnName(columnName));
    }

    /**
     * Checks if is null.
     *
     * @param columnIndex
     * @return true, if is null
     */
    @Override
    public boolean isNull(final int columnIndex) {
        return get(columnIndex) == null;
    }

    /**
     * Checks if is null.
     *
     * @param columnName
     * @return true, if is null
     */
    @Override
    public boolean isNull(final String columnName) {
        return get(columnName) == null;
    }

    /**
     *
     * @param columnIndex
     * @param value
     */
    @Override
    public void set(final int columnIndex, final Object value) {
        checkFrozen();

        _columnList.get(columnIndex).set(_currentRowNum, value);

        modCount++;
    }

    /**
     *
     * @param columnName
     * @param value
     */
    @Override
    public void set(final String columnName, final Object value) {
        set(checkColumnName(columnName), value);
    }

    /**
     * Gets the column.
     *
     * @param 
     * @param columnIndex
     * @return
     */
    @SuppressWarnings({ "unchecked", "rawtypes", "deprecation" })
    @Override
    public  ImmutableList getColumn(final int columnIndex) {
        // return (List) _columnList.get(columnIndex);
        return ImmutableList.wrap((List) _columnList.get(columnIndex));
    }

    /**
     * Gets the column.
     *
     * @param 
     * @param columnName
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public  ImmutableList getColumn(final String columnName) {
        return getColumn(checkColumnName(columnName));
    }

    /**
     * Copy of column.
     *
     * @param 
     * @param columnName
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public  List copyOfColumn(final String columnName) {
        return new ArrayList<>((List) _columnList.get(checkColumnName(columnName)));
    }

    /**
     * Adds the column.
     *
     * @param columnName
     * @param column
     */
    @Override
    public void addColumn(final String columnName, final List column) {
        addColumn(_columnList.size(), columnName, column);
    }

    /**
     * Adds the column.
     *
     * @param columnIndex
     * @param columnName
     * @param column
     */
    @Override
    public void addColumn(final int columnIndex, final String columnName, final List column) {
        checkFrozen();

        if (columnIndex < 0 || columnIndex > _columnNameList.size()) {
            throw new IllegalArgumentException("Invalid column index: " + columnIndex + ". It must be >= 0 and <= " + _columnNameList.size());
        }

        if (containsColumn(columnName)) {
            throw new IllegalArgumentException("Column(" + columnName + ") is already included in this DataSet.");
        }

        if (N.notNullOrEmpty(column) && column.size() != size()) {
            throw new IllegalArgumentException("The specified column size[" + column.size() + "] must be same as the this DataSet size[" + size() + "]. ");
        }

        _columnNameList.add(columnIndex, columnName);

        if (N.isNullOrEmpty(column)) {
            _columnList.add(columnIndex, N.repeat(null, size()));
        } else {
            _columnList.add(columnIndex, new ArrayList<>(column));
        }

        updateColumnIndex(columnIndex, columnName);

        modCount++;
    }

    /**
     * Adds the column.
     *
     * @param 
     * @param 
     * @param newColumnName
     * @param fromColumnName
     * @param func
     * @throws E the e
     */
    @Override
    public  void addColumn(String newColumnName, String fromColumnName, Throwables.Function func) throws E {
        addColumn(_columnList.size(), newColumnName, fromColumnName, func);
    }

    /**
     * Adds the column.
     *
     * @param 
     * @param 
     * @param columnIndex
     * @param newColumnName
     * @param fromColumnName
     * @param func
     * @throws E the e
     */
    @Override
    public  void addColumn(int columnIndex, final String newColumnName, String fromColumnName, Throwables.Function func)
            throws E {
        checkFrozen();

        if (columnIndex < 0 || columnIndex > _columnNameList.size()) {
            throw new IllegalArgumentException("Invalid column index: " + columnIndex + ". It must be >= 0 and <= " + _columnNameList.size());
        }

        if (containsColumn(newColumnName)) {
            throw new IllegalArgumentException("Column(" + newColumnName + ") is already included in this DataSet.");
        }

        final List newColumn = new ArrayList<>(size());
        final Throwables.Function mapper2 = (Throwables.Function) func;
        final List column = _columnList.get(checkColumnName(fromColumnName));

        for (Object val : column) {
            newColumn.add(mapper2.apply(val));
        }

        _columnNameList.add(columnIndex, newColumnName);
        _columnList.add(columnIndex, newColumn);

        updateColumnIndex(columnIndex, newColumnName);

        modCount++;
    }

    /**
     * Adds the column.
     *
     * @param 
     * @param newColumnName
     * @param fromColumnNames
     * @param func
     * @throws E the e
     */
    @Override
    public  void addColumn(String newColumnName, Collection fromColumnNames,
            Throwables.Function func) throws E {
        addColumn(_columnList.size(), newColumnName, fromColumnNames, func);
    }

    /**
     * Adds the column.
     *
     * @param 
     * @param columnIndex
     * @param newColumnName
     * @param fromColumnNames
     * @param func
     * @throws E the e
     */
    @Override
    public  void addColumn(int columnIndex, final String newColumnName, Collection fromColumnNames,
            Throwables.Function func) throws E {
        checkFrozen();

        if (containsColumn(newColumnName)) {
            throw new IllegalArgumentException("Column(" + newColumnName + ") is already included in this DataSet.");
        }

        final int size = size();
        final int[] fromColumnIndexes = checkColumnName(fromColumnNames);
        final Throwables.Function mapper2 = (Throwables.Function) func;
        final List newColumn = new ArrayList<>(size);
        final Object[] row = new Object[fromColumnIndexes.length];
        final DisposableObjArray disposableArray = DisposableObjArray.wrap(row);

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            for (int i = 0, len = fromColumnIndexes.length; i < len; i++) {
                row[i] = _columnList.get(fromColumnIndexes[i]).get(rowIndex);
            }

            newColumn.add(mapper2.apply(disposableArray));
        }

        _columnNameList.add(columnIndex, newColumnName);
        _columnList.add(columnIndex, newColumn);

        updateColumnIndex(columnIndex, newColumnName);

        modCount++;
    }

    /**
     * Update column index.
     *
     * @param columnIndex
     * @param newColumnName
     */
    private void updateColumnIndex(int columnIndex, final String newColumnName) {
        if (_columnIndexMap != null && columnIndex == _columnIndexMap.size()) {
            _columnIndexMap.put(newColumnName, columnIndex);
        } else {
            _columnIndexMap = null;
        }

        if (_columnIndexes != null && columnIndex == _columnIndexes.length) {
            _columnIndexes = N.copyOf(_columnIndexes, _columnIndexes.length + 1);
            _columnIndexes[columnIndex] = columnIndex;
        } else {
            _columnIndexes = null;
        }
    }

    /**
     * Adds the column.
     *
     * @param 
     * @param newColumnName
     * @param fromColumnNames
     * @param func
     * @throws E the e
     */
    @Override
    public  void addColumn(String newColumnName, Tuple2 fromColumnNames, Throwables.BiFunction func) throws E {
        addColumn(_columnList.size(), newColumnName, fromColumnNames, func);
    }

    /**
     * Adds the column.
     *
     * @param 
     * @param columnIndex
     * @param newColumnName
     * @param fromColumnNames
     * @param func
     * @throws E the e
     */
    @Override
    public  void addColumn(int columnIndex, final String newColumnName, Tuple2 fromColumnNames,
            Throwables.BiFunction func) throws E {
        checkFrozen();

        if (containsColumn(newColumnName)) {
            throw new IllegalArgumentException("Column(" + newColumnName + ") is already included in this DataSet.");
        }

        final int size = size();
        final List column1 = _columnList.get(checkColumnName(fromColumnNames._1));
        final List column2 = _columnList.get(checkColumnName(fromColumnNames._2));

        @SuppressWarnings("rawtypes")
        final Throwables.BiFunction mapper2 = (Throwables.BiFunction) func;
        final List newColumn = new ArrayList<>(size());

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            newColumn.add(mapper2.apply(column1.get(rowIndex), column2.get(rowIndex)));
        }

        _columnNameList.add(columnIndex, newColumnName);
        _columnList.add(columnIndex, newColumn);

        updateColumnIndex(columnIndex, newColumnName);

        modCount++;
    }

    /**
     * Adds the column.
     *
     * @param 
     * @param newColumnName
     * @param fromColumnNames
     * @param func
     * @throws E the e
     */
    @Override
    public  void addColumn(String newColumnName, Tuple3 fromColumnNames,
            Throwables.TriFunction func) throws E {
        addColumn(_columnList.size(), newColumnName, fromColumnNames, func);
    }

    /**
     * Adds the column.
     *
     * @param 
     * @param columnIndex
     * @param newColumnName
     * @param fromColumnNames
     * @param func
     * @throws E the e
     */
    @Override
    public  void addColumn(int columnIndex, final String newColumnName, Tuple3 fromColumnNames,
            Throwables.TriFunction func) throws E {
        checkFrozen();

        if (containsColumn(newColumnName)) {
            throw new IllegalArgumentException("Column(" + newColumnName + ") is already included in this DataSet.");
        }

        final int size = size();
        final List column1 = _columnList.get(checkColumnName(fromColumnNames._1));
        final List column2 = _columnList.get(checkColumnName(fromColumnNames._2));
        final List column3 = _columnList.get(checkColumnName(fromColumnNames._3));
        @SuppressWarnings("rawtypes")
        final Throwables.TriFunction mapper2 = (Throwables.TriFunction) func;
        final List newColumn = new ArrayList<>(size());

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            newColumn.add(mapper2.apply(column1.get(rowIndex), column2.get(rowIndex), column3.get(rowIndex)));
        }

        _columnNameList.add(columnIndex, newColumnName);
        _columnList.add(columnIndex, newColumn);

        updateColumnIndex(columnIndex, newColumnName);

        modCount++;
    }

    /**
     * Removes the column.
     *
     * @param 
     * @param columnName
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public  List removeColumn(final String columnName) {
        checkFrozen();

        final int columnIndex = checkColumnName(columnName);

        _columnIndexMap = null;
        _columnIndexes = null;

        _columnNameList.remove(columnIndex);
        final List removedColumn = _columnList.remove(columnIndex);

        modCount++;

        return (List) removedColumn;
    }

    /**
     * Removes the columns.
     *
     * @param columnNames
     */
    @Override
    public void removeColumns(final Collection columnNames) {
        checkFrozen();

        final int[] columnIndexes = checkColumnName(columnNames);
        N.sort(columnIndexes);

        for (int i = 0, len = columnIndexes.length; i < len; i++) {
            _columnNameList.remove(columnIndexes[i] - i);
            _columnList.remove(columnIndexes[i] - i);
        }

        _columnIndexMap = null;
        _columnIndexes = null;

        modCount++;
    }

    /**
     * Removes the columns.
     *
     * @param 
     * @param filter
     * @throws E the e
     */
    @Override
    public  void removeColumns(Throwables.Predicate filter) throws E {
        removeColumns(N.filter(_columnNameList, filter));
    }

    //    /**
    //     * Removes the columns if.
    //     *
    //     * @param 
    //     * @param filter
    //     * @throws E the e
    //     */
    //    @Deprecated
    //    @Override
    //    public  void removeColumnsIf(Predicate filter) throws E {
    //        removeColumns(filter);
    //    }

    /**
     *
     * @param columnName
     * @param targetType
     */
    @Override
    public void convertColumn(final String columnName, final Class targetType) {
        checkFrozen();

        convertColumnType(checkColumnName(columnName), targetType);
    }

    /**
     *
     * @param columnTargetTypes
     */
    @Override
    public void convertColumns(final Map> columnTargetTypes) {
        checkFrozen();

        checkColumnName(columnTargetTypes.keySet());

        for (Map.Entry> entry : columnTargetTypes.entrySet()) {
            convertColumnType(checkColumnName(entry.getKey()), entry.getValue());
        }
    }

    /**
     *
     *
     * @param 
     * @param columnName
     * @param func
     * @throws E the e
     */
    @Override
    public  void updateColumn(final String columnName, final Throwables.Function func) throws E {
        checkFrozen();

        final Throwables.Function func2 = (Throwables.Function) func;
        final List column = _columnList.get(checkColumnName(columnName));

        for (int i = 0, len = size(); i < len; i++) {
            column.set(i, func2.apply(column.get(i)));
        }

        modCount++;
    }

    /**
     *
     *
     * @param 
     * @param columnNames
     * @param func
     * @throws E the e
     */
    @Override
    public  void updateColumns(final Collection columnNames, final Throwables.Function func) throws E {
        checkColumnName(columnNames);

        final Throwables.Function func2 = (Throwables.Function) func;

        for (String columnName : columnNames) {
            final List column = _columnList.get(checkColumnName(columnName));

            for (int i = 0, len = size(); i < len; i++) {
                column.set(i, func2.apply(column.get(i)));
            }
        }

        modCount++;
    }

    /**
     * Convert column type.
     *
     * @param columnIndex
     * @param targetType
     */
    private void convertColumnType(final int columnIndex, final Class targetType) {
        final List column = _columnList.get(columnIndex);

        Object newValue = null;
        for (int i = 0, len = size(); i < len; i++) {
            newValue = N.convert(column.get(i), targetType);

            column.set(i, newValue);
        }

        modCount++;
    }

    /**
     *
     * @param columnNames
     * @param newColumnName
     * @param newColumnClass
     */
    @Override
    public void combineColumns(final Collection columnNames, final String newColumnName, final Class newColumnClass) {
        checkFrozen();

        final List newColumn = toList(newColumnClass, columnNames, 0, size());

        removeColumns(columnNames);

        addColumn(newColumnName, newColumn);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param newColumnName
     * @param combineFunc
     * @throws E the e
     */
    @Override
    public  void combineColumns(Collection columnNames, final String newColumnName,
            Throwables.Function combineFunc) throws E {
        addColumn(newColumnName, columnNames, combineFunc);

        removeColumns(columnNames);
    }

    /**
     *
     * @param 
     * @param columnNameFilter
     * @param newColumnName
     * @param newColumnClass
     * @throws E the e
     */
    @Override
    public  void combineColumns(Throwables.Predicate columnNameFilter, final String newColumnName, Class newColumnClass)
            throws E {
        combineColumns(N.filter(_columnNameList, columnNameFilter), newColumnName, newColumnClass);
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNameFilter
     * @param newColumnName
     * @param combineFunc
     * @throws E the e
     * @throws E2 the e2
     */
    @Override
    public  void combineColumns(Throwables.Predicate columnNameFilter, final String newColumnName,
            Throwables.Function combineFunc) throws E, E2 {
        combineColumns(N.filter(_columnNameList, columnNameFilter), newColumnName, combineFunc);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param newColumnName
     * @param combineFunc
     * @throws E the e
     */
    @Override
    public  void combineColumns(Tuple2 columnNames, final String newColumnName,
            Throwables.BiFunction combineFunc) throws E {
        addColumn(newColumnName, columnNames, combineFunc);

        removeColumns(Arrays.asList(columnNames._1, columnNames._2));
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param newColumnName
     * @param combineFunc
     * @throws E the e
     */
    @Override
    public  void combineColumns(Tuple3 columnNames, final String newColumnName,
            Throwables.TriFunction combineFunc) throws E {
        addColumn(newColumnName, columnNames, combineFunc);

        removeColumns(Arrays.asList(columnNames._1, columnNames._2, columnNames._3));
    }

    /**
     *
     * @param 
     * @param 
     * @param columnName
     * @param newColumnNames
     * @param divideFunc
     * @throws E the e
     */
    @Override
    public  void divideColumn(final String columnName, Collection newColumnNames,
            Throwables.Function, E> divideFunc) throws E {
        checkFrozen();

        final int columnIndex = this.checkColumnName(columnName);

        if (N.isNullOrEmpty(newColumnNames)) {
            throw new IllegalArgumentException("New column names can't be null or empty.");
        }

        if (!N.disjoint(_columnNameList, newColumnNames)) {
            throw new IllegalArgumentException("Column names: " + N.intersection(_columnNameList, newColumnNames) + " already are included in this data set.");
        }

        @SuppressWarnings("rawtypes")
        final Throwables.Function, E> divideFunc2 = (Throwables.Function) divideFunc;
        final int newColumnsLen = newColumnNames.size();
        final List> newColumns = new ArrayList<>(newColumnsLen);

        for (int i = 0; i < newColumnsLen; i++) {
            newColumns.add(new ArrayList<>(size()));
        }

        final List column = _columnList.get(columnIndex);

        for (Object val : column) {
            final List newVals = divideFunc2.apply(val);

            for (int i = 0; i < newColumnsLen; i++) {
                newColumns.get(i).add(newVals.get(i));
            }
        }

        _columnNameList.remove(columnIndex);
        _columnNameList.addAll(columnIndex, newColumnNames);

        _columnList.remove(columnIndex);
        _columnList.addAll(columnIndex, newColumns);

        _columnIndexMap = null;
        _columnIndexes = null;

        modCount++;
    }

    /**
     *
     * @param 
     * @param 
     * @param columnName
     * @param newColumnNames
     * @param output
     * @throws E the e
     */
    @Override
    public  void divideColumn(final String columnName, Collection newColumnNames, Throwables.BiConsumer output)
            throws E {
        checkFrozen();

        final int columnIndex = this.checkColumnName(columnName);

        if (N.isNullOrEmpty(newColumnNames)) {
            throw new IllegalArgumentException("New column names can't be null or empty.");
        }

        if (!N.disjoint(_columnNameList, newColumnNames)) {
            throw new IllegalArgumentException("Column names: " + N.intersection(_columnNameList, newColumnNames) + " already are included in this data set.");
        }

        @SuppressWarnings("rawtypes")
        final Throwables.BiConsumer output2 = (Throwables.BiConsumer) output;
        final int newColumnsLen = newColumnNames.size();
        final List> newColumns = new ArrayList<>(newColumnsLen);

        for (int i = 0; i < newColumnsLen; i++) {
            newColumns.add(new ArrayList<>(size()));
        }

        final List column = _columnList.get(columnIndex);
        final Object[] tmp = new Object[newColumnsLen];

        for (Object val : column) {
            output2.accept(val, tmp);

            for (int i = 0; i < newColumnsLen; i++) {
                newColumns.get(i).add(tmp[i]);
            }
        }

        _columnNameList.remove(columnIndex);
        _columnNameList.addAll(columnIndex, newColumnNames);

        _columnList.remove(columnIndex);
        _columnList.addAll(columnIndex, newColumns);

        _columnIndexMap = null;
        _columnIndexes = null;

        modCount++;
    }

    /**
     *
     * @param 
     * @param 
     * @param columnName
     * @param newColumnNames
     * @param output
     * @throws E the e
     */
    @Override
    public  void divideColumn(final String columnName, final Tuple2 newColumnNames,
            final Throwables.BiConsumer, E> output) throws E {
        checkFrozen();

        final int columnIndex = this.checkColumnName(columnName);
        this.checkNewColumnName(newColumnNames._1);
        this.checkNewColumnName(newColumnNames._2);

        @SuppressWarnings("rawtypes")
        final Throwables.BiConsumer, E> output2 = (Throwables.BiConsumer) output;
        final List newColumn1 = new ArrayList<>(size());
        final List newColumn2 = new ArrayList<>(size());

        final List column = _columnList.get(columnIndex);
        final Pair tmp = new Pair<>();

        for (Object val : column) {
            output2.accept(val, tmp);

            newColumn1.add(tmp.left);
            newColumn2.add(tmp.right);
        }

        _columnNameList.remove(columnIndex);
        _columnNameList.addAll(columnIndex, Arrays.asList(newColumnNames._1, newColumnNames._2));

        _columnList.remove(columnIndex);
        _columnList.addAll(columnIndex, Arrays.asList(newColumn1, newColumn2));

        _columnIndexMap = null;
        _columnIndexes = null;

        modCount++;
    }

    /**
     *
     * @param 
     * @param 
     * @param columnName
     * @param newColumnNames
     * @param output
     * @throws E the e
     */
    @Override
    public  void divideColumn(final String columnName, final Tuple3 newColumnNames,
            final Throwables.BiConsumer, E> output) throws E {
        checkFrozen();

        final int columnIndex = this.checkColumnName(columnName);
        this.checkNewColumnName(newColumnNames._1);
        this.checkNewColumnName(newColumnNames._2);
        this.checkNewColumnName(newColumnNames._3);

        @SuppressWarnings("rawtypes")
        final Throwables.BiConsumer, E> output2 = (Throwables.BiConsumer) output;
        final List newColumn1 = new ArrayList<>(size());
        final List newColumn2 = new ArrayList<>(size());
        final List newColumn3 = new ArrayList<>(size());

        final List column = _columnList.get(columnIndex);
        final Triple tmp = new Triple<>();

        for (Object val : column) {
            output2.accept(val, tmp);

            newColumn1.add(tmp.left);
            newColumn2.add(tmp.middle);
            newColumn3.add(tmp.right);
        }

        _columnNameList.remove(columnIndex);
        _columnNameList.addAll(columnIndex, Arrays.asList(newColumnNames._1, newColumnNames._2, newColumnNames._3));

        _columnList.remove(columnIndex);
        _columnList.addAll(columnIndex, Arrays.asList(newColumn1, newColumn2, newColumn3));

        _columnIndexMap = null;
        _columnIndexes = null;

        modCount++;
    }

    /**
     * Adds the row.
     *
     * @param row
     */
    @Override
    public void addRow(final Object row) {
        addRow(size(), row);
    }

    /**
     * Adds the row.
     *
     * @param rowIndex
     * @param row
     */
    @Override
    public void addRow(final int rowIndex, final Object row) {
        checkFrozen();

        if ((rowIndex < 0) || (rowIndex > size())) {
            throw new IllegalArgumentException("Invalid row index: " + rowIndex + ". It must be >= 0 and <= " + size());
        }

        final Class rowClass = row.getClass();
        final Type rowType = N.typeOf(rowClass);

        if (rowType.isObjectArray()) {
            final Object[] a = (Object[]) row;

            if (a.length < this._columnNameList.size()) {
                throw new IllegalArgumentException(
                        "The size of array (" + a.length + ") is less than the size of column (" + this._columnNameList.size() + ")");
            }

            if (rowIndex == size()) {
                for (int i = 0, len = this._columnNameList.size(); i < len; i++) {
                    _columnList.get(i).add(a[i]);
                }
            } else {
                for (int i = 0, len = this._columnNameList.size(); i < len; i++) {
                    _columnList.get(i).add(rowIndex, a[i]);
                }
            }
        } else if (rowType.isCollection()) {
            final Collection c = (Collection) row;

            if (c.size() < this._columnNameList.size()) {
                throw new IllegalArgumentException(
                        "The size of collection (" + c.size() + ") is less than the size of column (" + this._columnNameList.size() + ")");
            }

            final Iterator it = c.iterator();

            if (rowIndex == size()) {
                for (int i = 0, len = this._columnNameList.size(); i < len; i++) {
                    _columnList.get(i).add(it.next());
                }
            } else {
                for (int i = 0, len = this._columnNameList.size(); i < len; i++) {
                    _columnList.get(i).add(rowIndex, it.next());
                }
            }
        } else if (rowType.isMap()) {
            final Map map = (Map) row;
            final Object[] a = new Object[this._columnNameList.size()];

            int idx = 0;
            for (String columnName : this._columnNameList) {
                a[idx] = map.get(columnName);

                if (a[idx] == null && !map.containsKey(columnName)) {
                    throw new IllegalArgumentException("Column (" + columnName + ") is not found in map (" + map.keySet() + ")");
                }

                idx++;
            }

            if (rowIndex == size()) {
                for (int i = 0, len = this._columnNameList.size(); i < len; i++) {
                    _columnList.get(i).add(a[i]);
                }
            } else {
                for (int i = 0, len = this._columnNameList.size(); i < len; i++) {
                    _columnList.get(i).add(rowIndex, a[i]);
                }
            }
        } else if (rowType.isBean()) {
            final BeanInfo beanInfo = ParserUtil.getBeanInfo(rowClass);
            final Object[] a = new Object[this._columnNameList.size()];
            PropInfo propInfo = null;
            int idx = 0;

            for (String columnName : this._columnNameList) {
                propInfo = beanInfo.getPropInfo(columnName);

                if (propInfo == null) {
                    throw new IllegalArgumentException("Column (" + columnName + ") is not found in bean (" + rowClass + ")");
                }

                a[idx++] = propInfo.getPropValue(row);
            }

            if (rowIndex == size()) {
                for (int i = 0, len = this._columnNameList.size(); i < len; i++) {
                    _columnList.get(i).add(a[i]);
                }
            } else {
                for (int i = 0, len = this._columnNameList.size(); i < len; i++) {
                    _columnList.get(i).add(rowIndex, a[i]);
                }
            }
        } else {
            throw new IllegalArgumentException(
                    "Unsupported row type: " + ClassUtil.getCanonicalClassName(rowClass) + ". Only Array, List/Set, Map and bean class are supported");
        }

        modCount++;
    }

    /**
     * Removes the row.
     *
     * @param rowIndex
     */
    @Override
    public void removeRow(final int rowIndex) {
        checkFrozen();

        this.checkRowNum(rowIndex);

        for (int i = 0, len = this._columnList.size(); i < len; i++) {
            _columnList.get(i).remove(rowIndex);
        }

        modCount++;
    }

    /**
     * Removes the rows.
     *
     * @param indices
     */
    @Override
    @SafeVarargs
    public final void removeRows(int... indices) {
        checkFrozen();

        for (int rowIndex : indices) {
            this.checkRowNum(rowIndex);
        }

        for (int i = 0, len = this._columnList.size(); i < len; i++) {
            N.deleteAll(_columnList.get(i), indices);
        }

        modCount++;
    }

    /**
     * Removes the row range.
     *
     * @param inclusiveFromRowIndex
     * @param exclusiveToRowIndex
     */
    @Override
    public void removeRowRange(int inclusiveFromRowIndex, int exclusiveToRowIndex) {
        checkFrozen();

        this.checkRowIndex(inclusiveFromRowIndex, exclusiveToRowIndex);

        for (int i = 0, len = this._columnList.size(); i < len; i++) {
            _columnList.get(i).subList(inclusiveFromRowIndex, exclusiveToRowIndex).clear();
        }

        modCount++;
    }

    /**
     *
     * @param 
     * @param rowIndex
     * @param func
     * @throws E the e
     */
    @Override
    public  void updateRow(int rowIndex, Throwables.Function func) throws E {
        checkFrozen();

        this.checkRowNum(rowIndex);

        final Throwables.Function func2 = (Throwables.Function) func;

        for (List column : _columnList) {
            column.set(rowIndex, func2.apply(column.get(rowIndex)));
        }

        modCount++;
    }

    /**
     *
     * @param 
     * @param indices
     * @param func
     * @throws E the e
     */
    @Override
    public  void updateRows(int[] indices, Throwables.Function func) throws E {
        checkFrozen();

        for (int rowIndex : indices) {
            this.checkRowNum(rowIndex);
        }

        final Throwables.Function func2 = (Throwables.Function) func;

        for (List column : _columnList) {
            for (int rowIndex : indices) {
                column.set(rowIndex, func2.apply(column.get(rowIndex)));
            }
        }

        modCount++;
    }

    /**
     *
     * @param 
     * @param func
     * @throws E the e
     */
    @Override
    public  void updateAll(Throwables.Function func) throws E {
        checkFrozen();

        final Throwables.Function func2 = (Throwables.Function) func;
        final int size = size();

        for (List column : _columnList) {
            for (int i = 0; i < size; i++) {
                column.set(i, func2.apply(column.get(i)));
            }
        }

        modCount++;
    }

    /**
     *
     * @param 
     * @param predicate
     * @param newValue
     * @throws E the e
     */
    @Override
    public  void replaceIf(Throwables.Predicate predicate, Object newValue) throws E {
        checkFrozen();

        @SuppressWarnings("rawtypes")
        final Throwables.Predicate Predicate2 = (Throwables.Predicate) predicate;
        final int size = size();
        Object val = null;

        for (List column : _columnList) {
            for (int i = 0; i < size; i++) {
                val = column.get(i);

                column.set(i, Predicate2.test(val) ? newValue : val);
            }
        }

        modCount++;
    }

    /**
     * Current row num.
     *
     * @return
     */
    @Override
    public int currentRowNum() {
        return _currentRowNum;
    }

    /**
     *
     * @param rowNum
     * @return
     */
    @Override
    public DataSet absolute(final int rowNum) {
        checkRowNum(rowNum);

        _currentRowNum = rowNum;

        return this;
    }

    /**
     * Gets the row.
     *
     * @param rowIndex
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public Object[] getRow(final int rowIndex) {
        return getRow(Object[].class, rowIndex);
    }

    /**
     * Gets the row.
     *
     * @param 
     * @param rowClass
     * @param rowIndex
     * @return
     */
    @Override
    public  T getRow(final Class rowClass, final int rowIndex) {
        return getRow(rowClass, _columnNameList, rowIndex);
    }

    /**
     * Gets the row.
     *
     * @param 
     * @param rowClass
     * @param columnNames
     * @param rowIndex
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public  T getRow(final Class rowClass, final Collection columnNames, final int rowIndex) {
        return getRow(rowClass, columnNames, rowIndex, null);
    }

    /**
     * Gets the row.
     *
     * @param 
     * @param rowIndex
     * @param rowSupplier
     * @return
     */
    @Override
    public  T getRow(int rowIndex, IntFunction rowSupplier) {
        return getRow(_columnNameList, rowIndex, rowSupplier);
    }

    /**
     * Gets the row.
     *
     * @param 
     * @param columnNames
     * @param rowIndex
     * @param rowSupplier
     * @return
     */
    @Override
    public  T getRow(Collection columnNames, int rowIndex, IntFunction rowSupplier) {
        return getRow(null, columnNames, rowIndex, rowSupplier);
    }

    private  T getRow(Class rowClass, Collection columnNames, int rowIndex, IntFunction rowSupplier) {
        checkRowNum(rowIndex);

        columnNames = N.isNullOrEmpty(columnNames) ? this._columnNameList : columnNames;
        final int[] columnIndexes = checkColumnName(columnNames);
        final int columnCount = columnIndexes.length;

        rowClass = rowClass == null ? (Class) rowSupplier.apply(0).getClass() : rowClass;
        final Type rowType = N.typeOf(rowClass);
        final BeanInfo beanInfo = rowType.isBean() ? ParserUtil.getBeanInfo(rowClass) : null;

        rowSupplier = rowSupplier == null && !rowType.isBean() ? this.createRowSupplier(rowType, rowClass) : rowSupplier;

        return getRow(rowType, rowClass, beanInfo, columnNames, columnIndexes, columnCount, rowIndex, null, rowSupplier);
    }

    private  T getRow(final Type rowType, final Class rowClass, final BeanInfo beanInfo, final Collection columnNames,
            final int[] columnIndexes, final int columnCount, final int rowIndex, final Map prefixAndFieldNameMap,
            final IntFunction rowSupplier) {

        if (rowType.isObjectArray()) {
            final Object[] result = (Object[]) rowSupplier.apply(columnCount);

            for (int i = 0; i < columnCount; i++) {
                result[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
            }

            return (T) result;
        } else if (rowType.isCollection()) {
            final Collection result = (Collection) rowSupplier.apply(columnCount);

            for (int columnIndex : columnIndexes) {
                result.add(_columnList.get(columnIndex).get(rowIndex));
            }

            return (T) result;
        } else if (rowType.isMap()) {
            final Map result = (Map) rowSupplier.apply(columnCount);

            for (int columnIndex : columnIndexes) {
                result.put(_columnNameList.get(columnIndex), _columnList.get(columnIndex).get(rowIndex));
            }

            return (T) result;
        } else if (rowType.isBean()) {
            final boolean ignoreUnmatchedProperty = columnNames == this._columnNameList;
            Object result = rowSupplier == null ? beanInfo.createBeanResult() : rowSupplier.apply(columnCount);

            Set mergedPropNames = null;
            String propName = null;
            PropInfo propInfo = null;

            for (int i = 0; i < columnCount; i++) {
                propName = _columnNameList.get(columnIndexes[i]);

                if (mergedPropNames != null && mergedPropNames.contains(propName)) {
                    continue;
                }

                propInfo = beanInfo.getPropInfo(propName);

                if (propInfo != null) {
                    propInfo.setPropValue(result, _columnList.get(columnIndexes[i]).get(rowIndex));
                } else {
                    final int idx = propName.indexOf(PROP_NAME_SEPARATOR);

                    if (idx <= 0) {
                        if (ignoreUnmatchedProperty) {
                            continue;
                        }

                        throw new IllegalArgumentException("Property " + propName + " is not found in class: " + rowClass);
                    }

                    final String realPropName = propName.substring(0, idx);
                    propInfo = getPropInfoByPrefix(beanInfo, realPropName, prefixAndFieldNameMap);

                    if (propInfo == null) {
                        if (ignoreUnmatchedProperty) {
                            continue;
                        } else {
                            throw new IllegalArgumentException("Property " + propName + " is not found in class: " + rowClass);
                        }
                    }

                    final Type propBeanType = propInfo.type.isCollection() ? (Type) propInfo.type.getElementType() : propInfo.type;

                    if (!propBeanType.isBean()) {
                        throw new UnsupportedOperationException("Property: " + propInfo.name + " in class: " + rowClass + " is not a bean type");
                    }

                    final Class propBeanClass = propBeanType.clazz();
                    final BeanInfo propBeanInfo = ParserUtil.getBeanInfo(propBeanClass);
                    final List newTmpColumnNameList = new ArrayList<>();
                    final List> newTmpColumnList = new ArrayList<>();

                    if (mergedPropNames == null) {
                        mergedPropNames = new HashSet<>();
                    }

                    String columnName = null;
                    String newColumnName = null;

                    for (int j = i; j < columnCount; j++) {
                        columnName = this._columnNameList.get(columnIndexes[j]);

                        if (mergedPropNames.contains(columnName)) {
                            continue;
                        }

                        if (columnName.length() > idx && columnName.charAt(idx) == PROP_NAME_SEPARATOR && columnName.startsWith(realPropName)) {
                            newColumnName = columnName.substring(idx + 1);
                            newTmpColumnNameList.add(newColumnName);
                            newTmpColumnList.add(_columnList.get(columnIndexes[j]));

                            mergedPropNames.add(columnName);
                        }
                    }

                    final RowDataSet tmp = new RowDataSet(newTmpColumnNameList, newTmpColumnList);

                    final Object propValue = tmp.getRow(propBeanType, propBeanClass, propBeanInfo, newTmpColumnNameList,
                            tmp.checkColumnName(newTmpColumnNameList), newTmpColumnNameList.size(), rowIndex, prefixAndFieldNameMap, null);

                    if (propInfo.type.isCollection()) {
                        @SuppressWarnings("rawtypes")
                        Collection c = N.newCollection((Class) propInfo.clazz);
                        c.add(propValue);
                        propInfo.setPropValue(result, c);
                    } else {
                        propInfo.setPropValue(result, propValue);
                    }
                }
            }

            if (rowSupplier == null) {
                result = beanInfo.finishBeanResult(result);
            }

            return (T) result;
        } else {
            throw new IllegalArgumentException(
                    "Unsupported row type: " + rowType.clazz().getCanonicalName() + ". Only Array, Collection, Map and bean class are supported");
        }
    }

    /**
     *
     *
     * @return
     */
    @Override
    public Optional firstRow() {
        return firstRow(Object[].class);
    }

    /**
     *
     * @param 
     * @param rowClass
     * @return
     */
    @Override
    public  Optional firstRow(final Class rowClass) {
        return firstRow(rowClass, _columnNameList);
    }

    /**
     *
     * @param 
     * @param rowClass
     * @param columnNames
     * @return
     */
    @Override
    public  Optional firstRow(final Class rowClass, final Collection columnNames) {
        return size() == 0 ? (Optional) Optional.empty() : Optional.of(getRow(rowClass, columnNames, 0));
    }

    /**
     *
     * @param 
     * @param rowSupplier
     * @return
     */
    @Override
    public  Optional firstRow(IntFunction rowSupplier) {
        return firstRow(_columnNameList, rowSupplier);
    }

    /**
     *
     *
     * @param 
     * @param columnNames
     * @param rowSupplier
     * @return
     */
    @Override
    public  Optional firstRow(Collection columnNames, IntFunction rowSupplier) {
        if (size() == 0) {
            return Optional.empty();
        }

        final T row = getRow(columnNames, 0, rowSupplier);

        return Optional.of(row);
    }

    /**
     *
     *
     * @return
     */
    @Override
    public Optional lastRow() {
        return lastRow(Object[].class);
    }

    /**
     *
     * @param 
     * @param rowClass
     * @return
     */
    @Override
    public  Optional lastRow(final Class rowClass) {
        return lastRow(rowClass, _columnNameList);
    }

    /**
     *
     * @param 
     * @param rowClass
     * @param columnNames
     * @return
     */
    @Override
    public  Optional lastRow(final Class rowClass, final Collection columnNames) {
        return size() == 0 ? (Optional) Optional.empty() : Optional.of(getRow(rowClass, columnNames, size() - 1));
    }

    /**
     *
     * @param 
     * @param rowSupplier
     * @return
     */
    @Override
    public  Optional lastRow(IntFunction rowSupplier) {
        return lastRow(_columnNameList, rowSupplier);
    }

    /**
     *
     *
     * @param 
     * @param columnNames
     * @param rowSupplier
     * @return
     */
    @Override
    public  Optional lastRow(Collection columnNames, IntFunction rowSupplier) {
        if (size() == 0) {
            return Optional.empty();
        }

        final T row = getRow(columnNames, size() - 1, rowSupplier);

        return Optional.of(row);
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNameA
     * @param columnNameB
     * @return
     */
    @Override
    public  BiIterator iterator(final String columnNameA, final String columnNameB) {
        return iterator(columnNameA, columnNameB, 0, size());
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNameA
     * @param columnNameB
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public  BiIterator iterator(final String columnNameA, final String columnNameB, final int fromRowIndex, final int toRowIndex) {
        this.checkRowIndex(fromRowIndex, toRowIndex);
        final List columnA = _columnList.get(checkColumnName(columnNameA));
        final List columnB = _columnList.get(checkColumnName(columnNameB));

        final IndexedConsumer> output = new IndexedConsumer<>() {
            private final int expectedModCount = modCount;

            @Override
            public void accept(int rowIndex, Pair output) {
                if (modCount != expectedModCount) {
                    throw new ConcurrentModificationException();
                }

                output.set((A) columnA.get(rowIndex), (B) columnB.get(rowIndex));
            }
        };

        return BiIterator.generate(fromRowIndex, toRowIndex, output);
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param columnNameA
     * @param columnNameB
     * @param columnNameC
     * @return
     */
    @Override
    public  TriIterator iterator(final String columnNameA, final String columnNameB, final String columnNameC) {
        return iterator(columnNameA, columnNameB, columnNameC, 0, size());
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param columnNameA
     * @param columnNameB
     * @param columnNameC
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public  TriIterator iterator(final String columnNameA, final String columnNameB, final String columnNameC, final int fromRowIndex,
            final int toRowIndex) {
        this.checkRowIndex(fromRowIndex, toRowIndex);
        final List columnA = _columnList.get(checkColumnName(columnNameA));
        final List columnB = _columnList.get(checkColumnName(columnNameB));
        final List columnC = _columnList.get(checkColumnName(columnNameC));

        final IndexedConsumer> output = new IndexedConsumer<>() {
            private final int expectedModCount = modCount;

            @Override
            public void accept(int rowIndex, Triple output) {
                if (modCount != expectedModCount) {
                    throw new ConcurrentModificationException();
                }

                output.set((A) columnA.get(rowIndex), (B) columnB.get(rowIndex), (C) columnC.get(rowIndex));
            }
        };

        return TriIterator.generate(fromRowIndex, toRowIndex, output);
    }

    /**
     *
     * @param 
     * @param action
     * @throws E the e
     */
    @Override
    public  void forEach(final Throwables.Consumer action) throws E {
        forEach(this._columnNameList, action);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param action
     * @throws E the e
     */
    @Override
    public  void forEach(final Collection columnNames, final Throwables.Consumer action) throws E {
        forEach(columnNames, 0, size(), action);
    }

    /**
     *
     * @param 
     * @param fromRowIndex
     * @param toRowIndex
     * @param action
     * @throws E the e
     */
    @Override
    public  void forEach(final int fromRowIndex, final int toRowIndex, final Throwables.Consumer action)
            throws E {
        forEach(this._columnNameList, fromRowIndex, toRowIndex, action);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param action
     * @throws E the e
     */
    @Override
    public  void forEach(final Collection columnNames, final int fromRowIndex, final int toRowIndex,
            final Throwables.Consumer action) throws E {
        final int[] columnIndexes = checkColumnName(columnNames);
        checkRowIndex(fromRowIndex < toRowIndex ? fromRowIndex : (toRowIndex == -1 ? 0 : toRowIndex), fromRowIndex < toRowIndex ? toRowIndex : fromRowIndex);
        N.checkArgNotNull(action);

        if (size() == 0) {
            return;
        }

        final int columnCount = columnIndexes.length;
        final Object[] row = new Object[columnCount];
        final DisposableObjArray disposableArray = DisposableObjArray.wrap(row);

        if (fromRowIndex <= toRowIndex) {
            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                for (int i = 0; i < columnCount; i++) {
                    row[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
                }

                action.accept(disposableArray);
            }
        } else {
            for (int rowIndex = N.min(size() - 1, fromRowIndex); rowIndex > toRowIndex; rowIndex--) {
                for (int i = 0; i < columnCount; i++) {
                    row[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
                }

                action.accept(disposableArray);
            }
        }
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param action
     * @throws E the e
     */
    @Override
    public  void forEach(Tuple2 columnNames, Throwables.BiConsumer action) throws E {
        forEach(columnNames, 0, size(), action);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param action
     * @throws E the e
     */
    @Override
    public  void forEach(Tuple2 columnNames, int fromRowIndex, int toRowIndex, Throwables.BiConsumer action)
            throws E {
        final List column1 = _columnList.get(checkColumnName(columnNames._1));
        final List column2 = _columnList.get(checkColumnName(columnNames._2));

        checkRowIndex(fromRowIndex < toRowIndex ? fromRowIndex : (toRowIndex == -1 ? 0 : toRowIndex), fromRowIndex < toRowIndex ? toRowIndex : fromRowIndex);
        N.checkArgNotNull(action);

        if (size() == 0) {
            return;
        }

        @SuppressWarnings("rawtypes")
        final Throwables.BiConsumer action2 = (Throwables.BiConsumer) action;

        if (fromRowIndex <= toRowIndex) {
            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                action2.accept(column1.get(rowIndex), column2.get(rowIndex));
            }
        } else {
            for (int rowIndex = N.min(size() - 1, fromRowIndex); rowIndex > toRowIndex; rowIndex--) {
                action2.accept(column1.get(rowIndex), column2.get(rowIndex));
            }
        }
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param action
     * @throws E the e
     */
    @Override
    public  void forEach(Tuple3 columnNames, Throwables.TriConsumer action) throws E {
        forEach(columnNames, 0, size(), action);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param action
     * @throws E the e
     */
    @Override
    public  void forEach(Tuple3 columnNames, int fromRowIndex, int toRowIndex,
            Throwables.TriConsumer action) throws E {
        final List column1 = _columnList.get(checkColumnName(columnNames._1));
        final List column2 = _columnList.get(checkColumnName(columnNames._2));
        final List column3 = _columnList.get(checkColumnName(columnNames._3));

        checkRowIndex(fromRowIndex < toRowIndex ? fromRowIndex : (toRowIndex == -1 ? 0 : toRowIndex), fromRowIndex < toRowIndex ? toRowIndex : fromRowIndex);
        N.checkArgNotNull(action);

        if (size() == 0) {
            return;
        }

        @SuppressWarnings("rawtypes")
        final Throwables.TriConsumer action2 = (Throwables.TriConsumer) action;

        if (fromRowIndex <= toRowIndex) {
            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                action2.accept(column1.get(rowIndex), column2.get(rowIndex), column3.get(rowIndex));
            }
        } else {
            for (int rowIndex = N.min(size() - 1, fromRowIndex); rowIndex > toRowIndex; rowIndex--) {
                action2.accept(column1.get(rowIndex), column2.get(rowIndex), column3.get(rowIndex));
            }
        }
    }

    /**
     *
     *
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public List toList() {
        return toList(Object[].class);
    }

    /**
     *
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public List toList(final int fromRowIndex, final int toRowIndex) {
        return toList(Object[].class, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param 
     * @param rowClass
     * @return
     */
    @Override
    public  List toList(final Class rowClass) {
        return toList(rowClass, 0, size());
    }

    /**
     *
     * @param 
     * @param rowClass
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public  List toList(final Class rowClass, final int fromRowIndex, final int toRowIndex) {
        return toList(rowClass, this._columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param 
     * @param rowClass
     * @param columnNames
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public  List toList(final Class rowClass, final Collection columnNames) {
        return toList(rowClass, columnNames, 0, size());
    }

    /**
     *
     * @param 
     * @param rowClass
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public  List toList(final Class rowClass, Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        return toList(rowClass, columnNames, fromRowIndex, toRowIndex, null, null);
    }

    /**
     *
     * @param 
     * @param rowSupplier
     * @return
     */
    @Override
    public  List toList(IntFunction rowSupplier) {
        return toList(this._columnNameList, rowSupplier);
    }

    /**
     *
     *
     * @param 
     * @param fromRowIndex
     * @param toRowIndex
     * @param rowSupplier
     * @return
     */
    @Override
    public  List toList(int fromRowIndex, int toRowIndex, IntFunction rowSupplier) {
        return toList(this._columnNameList, fromRowIndex, toRowIndex, rowSupplier);
    }

    /**
     *
     *
     * @param 
     * @param columnNames
     * @param rowSupplier
     * @return
     */
    @Override
    public  List toList(Collection columnNames, IntFunction rowSupplier) {
        return toList(columnNames, 0, size(), rowSupplier);
    }

    /**
     *
     *
     * @param 
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param rowSupplier
     * @return
     */
    @Override
    public  List toList(Collection columnNames, int fromRowIndex, int toRowIndex, IntFunction rowSupplier) {
        return toList(null, columnNames, fromRowIndex, toRowIndex, null, rowSupplier);
    }

    private  List toList(Class rowClass, Collection columnNames, int fromRowIndex, int toRowIndex,
            final Map prefixAndFieldNameMap, IntFunction rowSupplier) {
        checkRowIndex(fromRowIndex, toRowIndex);
        columnNames = N.isNullOrEmpty(columnNames) ? this._columnNameList : columnNames;

        rowClass = rowClass == null ? (Class) rowSupplier.apply(0).getClass() : rowClass;
        final Type rowType = N.typeOf(rowClass);

        if (rowType.isBean()) {
            return toEntities(rowClass, ParserUtil.getBeanInfo(rowClass), null, columnNames, fromRowIndex, toRowIndex, prefixAndFieldNameMap, rowSupplier,
                    false, true);
        }

        rowSupplier = rowSupplier == null && !rowType.isBean() ? this.createRowSupplier(rowType, rowClass) : rowSupplier;

        final int[] columnIndexes = checkColumnName(columnNames);
        final int columnCount = columnIndexes.length;
        final int rowCount = toRowIndex - fromRowIndex;

        final List rowList = new ArrayList<>(rowCount);

        if (rowType.isObjectArray()) {
            Object[] row = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                row = (Object[]) rowSupplier.apply(columnCount);

                for (int i = 0; i < columnCount; i++) {
                    row[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
                }

                rowList.add(row);
            }
        } else if (rowType.isList() || rowType.isSet()) {
            Collection row = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                row = (Collection) rowSupplier.apply(columnCount);

                for (int i = 0; i < columnCount; i++) {
                    row.add(_columnList.get(columnIndexes[i]).get(rowIndex));
                }

                rowList.add(row);
            }
        } else if (rowType.isMap()) {
            final String[] mapKeyNames = new String[columnCount];

            for (int i = 0; i < columnCount; i++) {
                mapKeyNames[i] = _columnNameList.get(columnIndexes[i]);
            }

            Map row = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                row = (Map) rowSupplier.apply(columnCount);

                for (int i = 0; i < columnCount; i++) {
                    row.put(mapKeyNames[i], _columnList.get(columnIndexes[i]).get(rowIndex));
                }

                rowList.add(row);
            }
        } else {
            throw new IllegalArgumentException(
                    "Unsupported row type: " + ClassUtil.getCanonicalClassName(rowClass) + ". Only Array, List/Set, Map and bean class are supported");
        }

        return (List) rowList;
    }

    @SuppressWarnings("rawtypes")
    private  IntFunction createRowSupplier(final Type rowType, final Class rowClass) {
        if (rowType.isObjectArray()) {
            final Class componentType = rowClass.getComponentType();
            return cc -> N.newArray(componentType, cc);
        } else if (rowType.isList() || rowType.isSet()) {
            return (IntFunction) Factory.ofCollection((Class) rowClass);

        } else if (rowType.isMap()) {
            return (IntFunction) Factory.ofMap((Class) rowClass);
        } else {
            throw new IllegalArgumentException(
                    "Unsupported row type: " + ClassUtil.getCanonicalClassName(rowClass) + ". Only Array, List/Set, Map and bean class are supported");
        }
    }

    /**
     *
     *
     * @param 
     * @param 
     * @param 
     * @param rowClass
     * @param columnNameFilter
     * @param columnNameConverter
     * @return
     * @throws E
     * @throws E2
     */
    @Override
    public  List toList(final Class rowClass,
            final Throwables.Predicate columnNameFilter, final Throwables.Function columnNameConverter)
            throws E, E2 {
        return toList(rowClass, 0, size(), columnNameFilter, columnNameConverter);
    }

    /**
     *
     *
     * @param 
     * @param 
     * @param 
     * @param rowClass
     * @param fromRowIndex
     * @param toRowIndex
     * @param columnNameFilter
     * @param columnNameConverter
     * @return
     * @throws E
     * @throws E2
     */
    @Override
    public  List toList(final Class rowClass, final int fromRowIndex, final int toRowIndex,
            final Throwables.Predicate columnNameFilter, final Throwables.Function columnNameConverter)
            throws E, E2 {
        checkRowIndex(fromRowIndex, toRowIndex);

        if ((columnNameFilter == null || Objects.equals(columnNameFilter, Fnn.alwaysTrue()))
                && (columnNameConverter == null && Objects.equals(columnNameConverter, Fnn.identity()))) {
            return toList(rowClass, this._columnNameList, fromRowIndex, toRowIndex);
        }

        @SuppressWarnings("rawtypes")
        final Throwables.Predicate columnNameFilterToBeUsed = columnNameFilter == null ? (Throwables.Predicate) Fnn.alwaysTrue()
                : columnNameFilter;
        @SuppressWarnings("rawtypes")
        final Throwables.Function columnNameConverterToBeUsed = columnNameConverter == null ? (Throwables.Function) Fnn.identity()
                : columnNameConverter;

        final List newColumnNameList = new ArrayList<>();
        final List> newColumnList = new ArrayList<>();
        String columnName = null;

        for (int i = 0, columnCount = this._columnNameList.size(); i < columnCount; i++) {
            columnName = _columnNameList.get(i);

            if (columnNameFilterToBeUsed.test(columnName)) {
                newColumnNameList.add(columnNameConverterToBeUsed.apply(columnName));
                newColumnList.add(_columnList.get(i));
            }
        }

        final RowDataSet tmp = new RowDataSet(newColumnNameList, newColumnList);

        return tmp.toList(rowClass, fromRowIndex, toRowIndex);
    }

    /**
     *
     *
     * @param 
     * @param 
     * @param 
     * @param columnNameFilter
     * @param columnNameConverter
     * @param rowSupplier
     * @return
     * @throws E
     * @throws E2
     */
    @Override
    public  List toList(final Throwables.Predicate columnNameFilter,
            final Throwables.Function columnNameConverter, IntFunction rowSupplier) throws E, E2 {
        return toList(0, size(), columnNameFilter, columnNameConverter, rowSupplier);
    }

    /**
     *
     *
     * @param 
     * @param 
     * @param 
     * @param fromRowIndex
     * @param toRowIndex
     * @param columnNameFilter
     * @param columnNameConverter
     * @param rowSupplier
     * @return
     * @throws E
     * @throws E2
     */
    @Override
    public  List toList(final int fromRowIndex, final int toRowIndex,
            final Throwables.Predicate columnNameFilter, final Throwables.Function columnNameConverter,
            IntFunction rowSupplier) throws E, E2 {
        checkRowIndex(fromRowIndex, toRowIndex);

        if ((columnNameFilter == null || Objects.equals(columnNameFilter, Fnn.alwaysTrue()))
                && (columnNameConverter == null && Objects.equals(columnNameConverter, Fnn.identity()))) {
            return toList(this._columnNameList, fromRowIndex, toRowIndex, rowSupplier);
        }

        @SuppressWarnings("rawtypes")
        final Throwables.Predicate columnNameFilterToBeUsed = columnNameFilter == null ? (Throwables.Predicate) Fnn.alwaysTrue()
                : columnNameFilter;
        @SuppressWarnings("rawtypes")
        final Throwables.Function columnNameConverterToBeUsed = columnNameConverter == null ? (Throwables.Function) Fnn.identity()
                : columnNameConverter;

        final List newColumnNameList = new ArrayList<>();
        final List> newColumnList = new ArrayList<>();
        String columnName = null;

        for (int i = 0, columnCount = this._columnNameList.size(); i < columnCount; i++) {
            columnName = _columnNameList.get(i);

            if (columnNameFilterToBeUsed.test(columnName)) {
                newColumnNameList.add(columnNameConverterToBeUsed.apply(columnName));
                newColumnList.add(_columnList.get(i));
            }
        }

        final RowDataSet tmp = new RowDataSet(newColumnNameList, newColumnList);

        return tmp.toList(fromRowIndex, toRowIndex, rowSupplier);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param prefixAndFieldNameMap
     * @return
     */
    @Override
    public  List toEntities(Class beanClass, Map prefixAndFieldNameMap) {
        return toEntities(beanClass, this._columnNameList, 0, size(), prefixAndFieldNameMap);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param fromRowIndex
     * @param toRowIndex
     * @param prefixAndFieldNameMap
     * @return
     */
    @Override
    public  List toEntities(Class beanClass, int fromRowIndex, int toRowIndex, Map prefixAndFieldNameMap) {
        return toEntities(beanClass, this._columnNameList, fromRowIndex, toRowIndex, prefixAndFieldNameMap);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param columnNames
     * @param prefixAndFieldNameMap
     * @return
     */
    @Override
    public  List toEntities(Class beanClass, Collection columnNames, Map prefixAndFieldNameMap) {
        return toEntities(beanClass, columnNames, 0, size(), prefixAndFieldNameMap);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param prefixAndFieldNameMap
     * @return
     */
    @Override
    public  List toEntities(Class beanClass, Collection columnNames, int fromRowIndex, int toRowIndex,
            Map prefixAndFieldNameMap) {
        N.checkArgument(ClassUtil.isBeanClass(beanClass), "{} is not a bean class", beanClass);

        return toList(beanClass, columnNames, fromRowIndex, toRowIndex, prefixAndFieldNameMap, null);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @return
     */
    @Override
    public  List toMergedEntities(final Class beanClass) {
        return toMergedEntities(beanClass, this._columnNameList);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param selectPropNames
     * @return
     */
    @Override
    public  List toMergedEntities(final Class beanClass, final Collection selectPropNames) {
        return toMergedEntities(beanClass, (Collection) null, selectPropNames);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param idPropName
     * @return
     */
    @Override
    public  List toMergedEntities(final Class beanClass, final String idPropName) {
        return toMergedEntities(beanClass, idPropName, this._columnNameList);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param idPropName
     * @param selectPropNames
     * @return
     */
    @Override
    public  List toMergedEntities(final Class beanClass, final String idPropName, final Collection selectPropNames) {
        return toMergedEntities(beanClass, N.asList(idPropName), selectPropNames);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param idPropNames
     * @param selectPropNames
     * @return
     */
    @Override
    public  List toMergedEntities(final Class beanClass, final Collection idPropNames, final Collection selectPropNames) {
        return toMergedEntities(beanClass, idPropNames, selectPropNames, null);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param idPropNames
     * @param selectPropNames
     * @param prefixAndFieldNameMap
     * @return
     */
    @Override
    public  List toMergedEntities(final Class beanClass, final Collection idPropNames, Collection selectPropNames,
            final Map prefixAndFieldNameMap) {
        N.checkArgument(ClassUtil.isBeanClass(beanClass), "{} is not a bean class", beanClass);

        final BeanInfo beanInfo = ParserUtil.getBeanInfo(beanClass);

        Collection idPropNamesToUse = idPropNames;

        if (idPropNames == null) {
            idPropNamesToUse = beanInfo.idPropNameList;

            if (N.isNullOrEmpty(idPropNamesToUse)) {
                throw new IllegalArgumentException("No id property defined in class: " + beanClass);
            }
        }

        N.checkArgNotNull(idPropNamesToUse, "idPropNames");

        if (!this._columnNameList.containsAll(idPropNamesToUse)) {
            final List tmp = new ArrayList<>(idPropNamesToUse.size());
            PropInfo propInfo = null;

            outer: for (String idPropName : idPropNamesToUse) { //NOSONAR
                if (this._columnNameList.contains(idPropName)) {
                    tmp.add(idPropName);
                } else {
                    propInfo = beanInfo.getPropInfo(idPropName);

                    if (propInfo != null && propInfo.columnName.isPresent() && this._columnNameList.contains(propInfo.columnName.get())) {
                        tmp.add(propInfo.columnName.get());
                    } else {
                        for (String columnName : this._columnNameList) {
                            if (columnName.equalsIgnoreCase(idPropName)) {
                                tmp.add(columnName);

                                continue outer;
                            }
                        }

                        if (propInfo != null) {
                            for (String columnName : this._columnNameList) {
                                if (propInfo.equals(beanInfo.getPropInfo(columnName))) {
                                    tmp.add(columnName);

                                    continue outer;
                                }
                            }
                        }

                        tmp.add(idPropName);
                        break;
                    }
                }
            }

            if (this._columnNameList.containsAll(tmp)) {
                idPropNamesToUse = tmp;
            }
        }

        N.checkArgument(this._columnNameList.containsAll(idPropNamesToUse), "Some id properties {} are not found in DataSet: {} for bean {}", idPropNamesToUse,
                this._columnNameList, ClassUtil.getSimpleClassName(beanClass));

        selectPropNames = N.isNullOrEmpty(selectPropNames) ? this._columnNameList : selectPropNames;

        N.checkArgument(N.isNullOrEmpty(selectPropNames) || selectPropNames == this._columnNameList || this._columnNameList.containsAll(selectPropNames),
                "Some select properties {} are not found in DataSet: {} for bean {}", selectPropNames, this._columnNameList,
                ClassUtil.getSimpleClassName(beanClass));

        return toEntities(beanClass, beanInfo, idPropNamesToUse, selectPropNames, 0, size(), prefixAndFieldNameMap, null, true, false);
    }

    @SuppressWarnings("rawtypes")
    private  List toEntities(final Class beanClass, final BeanInfo beanInfo, final Collection idPropNames,
            final Collection columnNames, final int fromRowIndex, final int toRowIndex, final Map prefixAndFieldNameMap,
            final IntFunction rowSupplier, boolean mergeResult, final boolean returnAllList) {
        N.checkArgNotNull(beanClass, "beanClass");
        checkRowIndex(fromRowIndex, toRowIndex);

        if (mergeResult && N.isNullOrEmpty(idPropNames)) {
            throw new IllegalArgumentException("\"idPropNames\" can't be null or empty when \"mergeResult\" is true");
        }

        final int rowCount = toRowIndex - fromRowIndex;
        final int columnCount = columnNames.size();
        final int[] idColumnIndexes = N.isNullOrEmpty(idPropNames) ? N.EMPTY_INT_ARRAY : checkColumnName(idPropNames);
        final boolean ignoreUnmatchedProperty = columnNames == this._columnNameList;

        final Object[] resultEntities = new Object[rowCount];
        final Map idBeanMap = mergeResult ? N.newLinkedHashMap(N.min(64, rowCount)) : N.emptyMap();
        Object bean = null;

        if (N.isNullOrEmpty(idColumnIndexes)) {
            for (int rowIndex = fromRowIndex, i = 0; rowIndex < toRowIndex; rowIndex++, i++) {
                resultEntities[i] = rowSupplier == null ? beanInfo.createBeanResult() : rowSupplier.apply(columnCount);
            }
        } else if (idColumnIndexes.length == 1) {
            final List idColumn = _columnList.get(idColumnIndexes[0]);
            Object rowKey = null;
            Object key = null;

            for (int rowIndex = fromRowIndex, i = 0; rowIndex < toRowIndex; rowIndex++, i++) {
                key = idColumn.get(rowIndex);

                if (key == null) {
                    continue;
                }

                rowKey = getHashKey(key);
                bean = idBeanMap.get(rowKey);

                if (bean == null) {
                    bean = rowSupplier == null ? beanInfo.createBeanResult() : rowSupplier.apply(columnCount);
                    idBeanMap.put(rowKey, bean);
                }

                resultEntities[i] = bean;
            }
        } else {
            final int idColumnCount = idColumnIndexes.length;

            Object[] keyRow = Objectory.createObjectArray(idColumnCount);
            Wrapper rowKey = null;
            boolean isAllKeyNull = true;

            for (int rowIndex = fromRowIndex, i = 0; rowIndex < toRowIndex; rowIndex++, i++) {
                isAllKeyNull = true;

                for (int j = 0; j < idColumnCount; j++) {
                    keyRow[j] = _columnList.get(idColumnIndexes[j]).get(rowIndex);

                    if (keyRow[j] != null) {
                        isAllKeyNull = false;
                    }
                }

                if (isAllKeyNull) {
                    continue;
                }

                rowKey = Wrapper.of(keyRow);
                bean = idBeanMap.get(rowKey);

                if (bean == null) {
                    bean = rowSupplier == null ? beanInfo.createBeanResult() : rowSupplier.apply(columnCount);
                    idBeanMap.put(rowKey, bean);

                    keyRow = Objectory.createObjectArray(idColumnCount);
                }

                resultEntities[i] = bean;
            }

            if (keyRow != null) {
                Objectory.recycle(keyRow);
                keyRow = null;
            }
        }

        List> listPropValuesToDeduplicate = null;

        try {
            final Set mergedPropNames = new HashSet<>();
            List curColumn = null;
            int curColumnIndex = 0;
            PropInfo propInfo = null;

            for (String propName : columnNames) {
                if (mergedPropNames.contains(propName)) {
                    continue;
                }

                curColumnIndex = checkColumnName(propName);
                curColumn = _columnList.get(curColumnIndex);

                propInfo = beanInfo.getPropInfo(propName);

                if (propInfo != null) {
                    boolean isPropValueChecked = false;
                    boolean isPropValueAssignable = false;
                    Object value = null;

                    for (int rowIndex = fromRowIndex, i = 0; rowIndex < toRowIndex; rowIndex++, i++) {
                        if (resultEntities[i] != null) {
                            if (isPropValueChecked) {
                                if (isPropValueAssignable) {
                                    propInfo.setPropValue(resultEntities[i], curColumn.get(rowIndex));
                                } else {
                                    propInfo.setPropValue(resultEntities[i], N.convert(curColumn.get(rowIndex), propInfo.jsonXmlType));
                                }
                            } else {
                                value = curColumn.get(rowIndex);

                                if (value == null) {
                                    propInfo.setPropValue(resultEntities[i], propInfo.jsonXmlType.defaultValue());
                                } else {
                                    if (propInfo.clazz.isAssignableFrom(value.getClass())) {
                                        propInfo.setPropValue(resultEntities[i], value);
                                        isPropValueAssignable = true;
                                    } else {
                                        propInfo.setPropValue(resultEntities[i], N.convert(value, propInfo.jsonXmlType));
                                        isPropValueAssignable = false;
                                    }

                                    isPropValueChecked = true;
                                }
                            }
                        }
                    }

                    mergedPropNames.add(propName);
                } else {
                    final int idx = propName.indexOf(PROP_NAME_SEPARATOR);

                    if (idx <= 0) {
                        if (ignoreUnmatchedProperty) {
                            continue;
                        }

                        throw new IllegalArgumentException("Property " + propName + " is not found in class: " + beanClass);
                    }

                    final String realPropName = propName.substring(0, idx);
                    propInfo = getPropInfoByPrefix(beanInfo, realPropName, prefixAndFieldNameMap);

                    if (propInfo == null) {
                        if (ignoreUnmatchedProperty) {
                            continue;
                        } else {
                            throw new IllegalArgumentException("Property " + propName + " is not found in class: " + beanClass);
                        }
                    }

                    final Type propBeanType = propInfo.type.isCollection() ? propInfo.type.getElementType() : propInfo.type;

                    if (!propBeanType.isBean()) {
                        throw new UnsupportedOperationException("Property: " + propInfo.name + " in class: " + beanClass + " is not a bean type");
                    }

                    final Class propBeanClass = propBeanType.clazz();
                    final BeanInfo propBeanInfo = ParserUtil.getBeanInfo(propBeanClass);
                    final List propEntityIdPropNames = mergeResult ? propBeanInfo.idPropNameList : null;
                    final List newPropEntityIdNames = mergeResult && N.isNullOrEmpty(propEntityIdPropNames) ? new ArrayList<>() : propEntityIdPropNames;
                    final List newTmpColumnNameList = new ArrayList<>();
                    final List> newTmpColumnList = new ArrayList<>();

                    String newColumnName = null;
                    int columnIndex = 0;

                    for (String columnName : columnNames) {
                        if (mergedPropNames.contains(columnName)) {
                            continue;
                        }

                        columnIndex = checkColumnName(columnName);

                        if (columnName.length() > idx && columnName.charAt(idx) == PROP_NAME_SEPARATOR && columnName.startsWith(realPropName)) {
                            newColumnName = columnName.substring(idx + 1);
                            newTmpColumnNameList.add(newColumnName);
                            newTmpColumnList.add(_columnList.get(columnIndex));

                            mergedPropNames.add(columnName);

                            if (mergeResult && N.isNullOrEmpty(propEntityIdPropNames) && newColumnName.indexOf(PROP_NAME_SEPARATOR) < 0) {
                                newPropEntityIdNames.add(newColumnName);
                            }
                        }
                    }

                    final RowDataSet tmp = new RowDataSet(newTmpColumnNameList, newTmpColumnList);

                    final boolean isToMerge = mergeResult && N.notNullOrEmpty(newPropEntityIdNames) && tmp._columnNameList.containsAll(newPropEntityIdNames);

                    final List propValueList = tmp.toEntities(propBeanClass, propBeanInfo, isToMerge ? newPropEntityIdNames : null, tmp._columnNameList,
                            fromRowIndex, toRowIndex, prefixAndFieldNameMap, null, isToMerge, true);

                    if (propInfo.type.isCollection()) {
                        Collection c = null;

                        for (int rowIndex = fromRowIndex, i = 0; rowIndex < toRowIndex; rowIndex++, i++) {
                            if (resultEntities[i] == null || propValueList.get(i) == null) {
                                continue;
                            }

                            c = propInfo.getPropValue(resultEntities[i]);

                            if (c == null) {
                                c = N.newCollection((Class) propInfo.clazz);
                                propInfo.setPropValue(resultEntities[i], c);

                                if (isToMerge && !(c instanceof Set)) {
                                    if (listPropValuesToDeduplicate == null) {
                                        listPropValuesToDeduplicate = new ArrayList<>();
                                    }

                                    listPropValuesToDeduplicate.add(c);
                                }
                            }

                            c.add(propValueList.get(i));
                        }
                    } else {
                        for (int rowIndex = fromRowIndex, i = 0; rowIndex < toRowIndex; rowIndex++, i++) {
                            if (resultEntities[i] == null || propValueList.get(i) == null) {
                                continue;
                            }

                            propInfo.setPropValue(resultEntities[i], propValueList.get(i));
                        }
                    }
                }
            }

            if (N.notNullOrEmpty(listPropValuesToDeduplicate)) {
                for (Collection list : listPropValuesToDeduplicate) {
                    N.removeDuplicates(list);
                }
            }

            final List result = returnAllList || N.isNullOrEmpty(idBeanMap) ? (List) N.asList(resultEntities)
                    : new ArrayList<>((Collection) idBeanMap.values());

            if (rowSupplier == null && N.notNullOrEmpty(result)) {
                for (int i = 0, size = result.size(); i < size; i++) {
                    result.set(i, beanInfo.finishBeanResult(result.get(i)));
                }
            }

            return result;
        } finally {
            if (N.len(idColumnIndexes) > 1 && N.notNullOrEmpty(idBeanMap)) {
                for (Wrapper e : ((Map, Object>) ((Map) idBeanMap)).keySet()) {
                    Objectory.recycle(e.value());
                }
            }
        }
    }

    private PropInfo getPropInfoByPrefix(final BeanInfo beanInfo, final String prefix, final Map prefixAndFieldNameMap) {
        PropInfo propInfo = beanInfo.getPropInfo(prefix);

        if (propInfo == null && N.notNullOrEmpty(prefixAndFieldNameMap) && prefixAndFieldNameMap.containsKey(prefix)) {
            propInfo = beanInfo.getPropInfo(prefixAndFieldNameMap.get(prefix));
        }

        if (propInfo == null) {
            propInfo = beanInfo.getPropInfo(prefix + "s"); // Trying to do something smart?
            final int len = prefix.length() + 1;

            if (propInfo != null && (propInfo.type.isBean() || (propInfo.type.isCollection() && propInfo.type.getElementType().isBean()))
                    && N.noneMatch(this._columnNameList, it -> it.length() > len && it.charAt(len) == '.' && Strings.startsWithIgnoreCase(it, prefix + "s."))) {
                // good
            } else {
                propInfo = beanInfo.getPropInfo(prefix + "es"); // Trying to do something smart?
                final int len2 = prefix.length() + 2;

                if (propInfo != null && (propInfo.type.isBean() || (propInfo.type.isCollection() && propInfo.type.getElementType().isBean())) && N.noneMatch(
                        this._columnNameList, it -> it.length() > len2 && it.charAt(len2) == '.' && Strings.startsWithIgnoreCase(it, prefix + "es."))) {
                    // good
                } else {
                    // Sorry, have done all I can do.
                    propInfo = null;
                }
            }
        }

        return propInfo;
    }

    /**
     *
     * @param  the key type
     * @param  the value type
     * @param keyColumnName
     * @param valueColumnName
     * @return
     */
    @Override
    public  Map toMap(final String keyColumnName, final String valueColumnName) {
        return toMap(keyColumnName, valueColumnName, 0, size());
    }

    /**
     *
     * @param  the key type
     * @param  the value type
     * @param keyColumnName
     * @param valueColumnName
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public  Map toMap(final String keyColumnName, final String valueColumnName, final int fromRowIndex, final int toRowIndex) {
        return toMap(keyColumnName, valueColumnName, fromRowIndex, toRowIndex, (IntFunction>) CommonUtil::newLinkedHashMap);
    }

    /**
     *
     * @param  the key type
     * @param  the value type
     * @param 
     * @param keyColumnName
     * @param valueColumnName
     * @param fromRowIndex
     * @param toRowIndex
     * @param supplier
     * @return
     */
    @Override
    public > M toMap(String keyColumnName, String valueColumnName, int fromRowIndex, int toRowIndex,
            IntFunction supplier) {
        checkRowIndex(fromRowIndex, toRowIndex);

        final int keyColumnIndex = checkColumnName(keyColumnName);
        final int valueColumnIndex = checkColumnName(valueColumnName);

        final M resultMap = supplier.apply(toRowIndex - fromRowIndex);

        for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
            resultMap.put((K) _columnList.get(keyColumnIndex).get(rowIndex), (V) _columnList.get(valueColumnIndex).get(rowIndex));
        }

        return resultMap;
    }

    /**
     *
     * @param  the key type
     * @param  the value type
     * @param rowClass
     * @param keyColumnName
     * @param valueColumnNames
     * @return
     */
    @Override
    public  Map toMap(final Class rowClass, final String keyColumnName, final Collection valueColumnNames) {
        return toMap(rowClass, keyColumnName, valueColumnNames, 0, size());
    }

    /**
     *
     * @param  the key type
     * @param  the value type
     * @param rowClass
     * @param keyColumnName
     * @param valueColumnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public  Map toMap(final Class rowClass, final String keyColumnName, final Collection valueColumnNames,
            final int fromRowIndex, final int toRowIndex) {
        return toMap(rowClass, keyColumnName, valueColumnNames, fromRowIndex, toRowIndex, (IntFunction>) CommonUtil::newLinkedHashMap);
    }

    /**
     *
     * @param  the key type
     * @param  the value type
     * @param 
     * @param rowClass
     * @param keyColumnName
     * @param valueColumnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param supplier
     * @return
     */
    @Override
    public > M toMap(Class rowClass, String keyColumnName, Collection valueColumnNames, int fromRowIndex,
            int toRowIndex, IntFunction supplier) {
        checkRowIndex(fromRowIndex, toRowIndex);

        final int keyColumnIndex = checkColumnName(keyColumnName);
        final int[] valueColumnIndexes = checkColumnName(valueColumnNames);

        final Type valueType = N.typeOf(rowClass);
        final int valueColumnCount = valueColumnIndexes.length;
        final Map resultMap = (Map) supplier.apply(toRowIndex - fromRowIndex);

        if (valueType.isObjectArray()) {
            Object[] value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = N.newArray(rowClass.getComponentType(), valueColumnCount);

                for (int i = 0; i < valueColumnCount; i++) {
                    value[i] = _columnList.get(valueColumnIndexes[i]).get(rowIndex);
                }

                resultMap.put(_columnList.get(keyColumnIndex).get(rowIndex), value);
            }
        } else if (valueType.isList() || valueType.isSet()) {
            final boolean isAbstractRowClass = Modifier.isAbstract(rowClass.getModifiers());
            final Constructor intConstructor = isAbstractRowClass ? null : ClassUtil.getDeclaredConstructor(rowClass, int.class);
            final Constructor constructor = isAbstractRowClass ? null : ClassUtil.getDeclaredConstructor(rowClass);
            Collection value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = (Collection) (isAbstractRowClass ? (valueType.isList() ? new ArrayList<>(valueColumnCount) : N.newHashSet(valueColumnCount))
                        : ((intConstructor == null) ? ClassUtil.invokeConstructor(constructor)
                                : ClassUtil.invokeConstructor(intConstructor, valueColumnCount)));

                for (int columIndex : valueColumnIndexes) {
                    value.add(_columnList.get(columIndex).get(rowIndex));
                }

                resultMap.put(_columnList.get(keyColumnIndex).get(rowIndex), value);
            }
        } else if (valueType.isMap()) {
            final boolean isAbstractRowClass = Modifier.isAbstract(rowClass.getModifiers());
            final Constructor intConstructor = isAbstractRowClass ? null : ClassUtil.getDeclaredConstructor(rowClass, int.class);
            final Constructor constructor = isAbstractRowClass ? null : ClassUtil.getDeclaredConstructor(rowClass);
            Map value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = (Map) (isAbstractRowClass ? N.newHashMap(valueColumnCount)
                        : (intConstructor == null ? ClassUtil.invokeConstructor(constructor) : ClassUtil.invokeConstructor(intConstructor, valueColumnCount)));

                for (int columIndex : valueColumnIndexes) {
                    value.put(_columnNameList.get(columIndex), _columnList.get(columIndex).get(rowIndex));
                }

                resultMap.put(_columnList.get(keyColumnIndex).get(rowIndex), value);
            }
        } else if (valueType.isBean()) {
            final BeanInfo beanInfo = ParserUtil.getBeanInfo(rowClass);
            final boolean ignoreUnmatchedProperty = valueColumnNames == _columnNameList;
            Object value = null;
            String propName = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = beanInfo.createBeanResult();

                for (int columIndex : valueColumnIndexes) {
                    propName = _columnNameList.get(columIndex);
                    beanInfo.setPropValue(value, propName, _columnList.get(columIndex).get(rowIndex), ignoreUnmatchedProperty);
                }

                value = beanInfo.finishBeanResult(value);

                resultMap.put(_columnList.get(keyColumnIndex).get(rowIndex), value);
            }
        } else {
            throw new IllegalArgumentException(
                    "Unsupported row type: " + rowClass.getCanonicalName() + ". Only Array, List/Set, Map and bean class are supported");
        }

        return (M) resultMap;
    }

    /**
     *
     *
     * @param  the key type
     * @param  the value type
     * @param keyColumnName
     * @param valueColumnNames
     * @param rowSupplier
     * @return
     */
    @Override
    public  Map toMap(String keyColumnName, Collection valueColumnNames, IntFunction rowSupplier) {
        return toMap(keyColumnName, valueColumnNames, 0, size(), rowSupplier);
    }

    /**
     *
     *
     * @param  the key type
     * @param  the value type
     * @param keyColumnName
     * @param valueColumnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param rowSupplier
     * @return
     */
    @Override
    public  Map toMap(String keyColumnName, Collection valueColumnNames, int fromRowIndex, int toRowIndex,
            IntFunction rowSupplier) {
        return toMap(keyColumnName, valueColumnNames, fromRowIndex, toRowIndex, rowSupplier, (IntFunction>) CommonUtil::newLinkedHashMap);
    }

    /**
     *
     *
     * @param  the key type
     * @param  the value type
     * @param 
     * @param keyColumnName
     * @param valueColumnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param rowSupplier
     * @param supplier
     * @return
     */
    @Override
    public > M toMap(String keyColumnName, Collection valueColumnNames, int fromRowIndex, int toRowIndex,
            IntFunction rowSupplier, IntFunction supplier) {
        checkRowIndex(fromRowIndex, toRowIndex);

        final int keyColumnIndex = checkColumnName(keyColumnName);
        final int[] valueColumnIndexes = checkColumnName(valueColumnNames);

        final Class rowClass = (Class) rowSupplier.apply(0).getClass();
        final Type valueType = N.typeOf(rowClass);
        final int valueColumnCount = valueColumnIndexes.length;
        final Map resultMap = (Map) supplier.apply(toRowIndex - fromRowIndex);

        if (valueType.isObjectArray()) {
            Object[] value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = (Object[]) rowSupplier.apply(valueColumnCount);

                for (int i = 0; i < valueColumnCount; i++) {
                    value[i] = _columnList.get(valueColumnIndexes[i]).get(rowIndex);
                }

                resultMap.put(_columnList.get(keyColumnIndex).get(rowIndex), value);
            }
        } else if (valueType.isList() || valueType.isSet()) {
            Collection value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = (Collection) rowSupplier.apply(valueColumnCount);

                for (int columIndex : valueColumnIndexes) {
                    value.add(_columnList.get(columIndex).get(rowIndex));
                }

                resultMap.put(_columnList.get(keyColumnIndex).get(rowIndex), value);
            }
        } else if (valueType.isMap()) {
            Map value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = (Map) rowSupplier.apply(valueColumnCount);

                for (int columIndex : valueColumnIndexes) {
                    value.put(_columnNameList.get(columIndex), _columnList.get(columIndex).get(rowIndex));
                }

                resultMap.put(_columnList.get(keyColumnIndex).get(rowIndex), value);
            }
        } else if (valueType.isBean()) {
            final boolean ignoreUnmatchedProperty = valueColumnNames == _columnNameList;
            final BeanInfo beanInfo = ParserUtil.getBeanInfo(rowClass);
            Object value = null;
            String propName = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = rowSupplier.apply(valueColumnCount);

                for (int columIndex : valueColumnIndexes) {
                    propName = _columnNameList.get(columIndex);
                    beanInfo.setPropValue(value, propName, _columnList.get(columIndex).get(rowIndex), ignoreUnmatchedProperty);
                }

                resultMap.put(_columnList.get(keyColumnIndex).get(rowIndex), value);
            }
        } else {
            throw new IllegalArgumentException(
                    "Unsupported row type: " + rowClass.getCanonicalName() + ". Only Array, List/Set, Map and bean class are supported");
        }

        return (M) resultMap;
    }

    /**
     *
     * @param  the key type
     * @param 
     * @param keyColumnName
     * @param valueColumnName
     * @return
     */
    @Override
    public  ListMultimap toMultimap(final String keyColumnName, final String valueColumnName) {
        return toMultimap(keyColumnName, valueColumnName, 0, size());
    }

    /**
     *
     * @param  the key type
     * @param 
     * @param keyColumnName
     * @param valueColumnName
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public  ListMultimap toMultimap(final String keyColumnName, final String valueColumnName, final int fromRowIndex, final int toRowIndex) {
        return toMultimap(keyColumnName, valueColumnName, fromRowIndex, toRowIndex, (IntFunction>) len -> N.newLinkedListMultimap());
    }

    /**
     *
     * @param  the key type
     * @param 
     * @param  the value type
     * @param 
     * @param keyColumnName
     * @param valueColumnName
     * @param fromRowIndex
     * @param toRowIndex
     * @param supplier
     * @return
     */
    @Override
    public , M extends Multimap> M toMultimap(String keyColumnName, String valueColumnName, int fromRowIndex,
            int toRowIndex, IntFunction supplier) {
        checkRowIndex(fromRowIndex, toRowIndex);

        final M resultMap = supplier.apply(toRowIndex - fromRowIndex);

        final int keyColumnIndex = checkColumnName(keyColumnName);
        final int valueColumnIndex = checkColumnName(valueColumnName);

        for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
            resultMap.put((K) _columnList.get(keyColumnIndex).get(rowIndex), (E) _columnList.get(valueColumnIndex).get(rowIndex));
        }

        return resultMap;
    }

    /**
     *
     * @param  the key type
     * @param 
     * @param rowClass
     * @param keyColumnName
     * @param valueColumnNames
     * @return
     */
    @Override
    public  ListMultimap toMultimap(final Class rowClass, final String keyColumnName, final Collection valueColumnNames) {
        return toMultimap(rowClass, keyColumnName, valueColumnNames, 0, size());
    }

    /**
     *
     * @param  the key type
     * @param 
     * @param rowClass
     * @param keyColumnName
     * @param valueColumnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public  ListMultimap toMultimap(final Class rowClass, final String keyColumnName, final Collection valueColumnNames,
            final int fromRowIndex, final int toRowIndex) {
        return toMultimap(rowClass, keyColumnName, valueColumnNames, fromRowIndex, toRowIndex,
                (IntFunction>) len -> N.newLinkedListMultimap());
    }

    /**
     *
     * @param  the key type
     * @param 
     * @param  the value type
     * @param 
     * @param rowClass
     * @param keyColumnName
     * @param valueColumnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param supplier
     * @return
     */
    @Override
    public , M extends Multimap> M toMultimap(Class rowClass, String keyColumnName,
            Collection valueColumnNames, int fromRowIndex, int toRowIndex, IntFunction supplier) {
        checkRowIndex(fromRowIndex, toRowIndex);

        final int keyColumnIndex = checkColumnName(keyColumnName);
        final int[] valueColumnIndexes = checkColumnName(valueColumnNames);

        final Type elementType = N.typeOf(rowClass);
        final int valueColumnCount = valueColumnIndexes.length;

        final M resultMap = supplier.apply(toRowIndex - fromRowIndex);

        if (elementType.isObjectArray()) {
            Object[] value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = N.newArray(rowClass.getComponentType(), valueColumnCount);

                for (int i = 0; i < valueColumnCount; i++) {
                    value[i] = _columnList.get(valueColumnIndexes[i]).get(rowIndex);
                }

                resultMap.put((K) _columnList.get(keyColumnIndex).get(rowIndex), (E) value);
            }
        } else if (elementType.isList() || elementType.isSet()) {
            final boolean isAbstractRowClass = Modifier.isAbstract(rowClass.getModifiers());
            final Constructor intConstructor = isAbstractRowClass ? null : ClassUtil.getDeclaredConstructor(rowClass, int.class);
            final Constructor constructor = isAbstractRowClass ? null : ClassUtil.getDeclaredConstructor(rowClass);
            Collection value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = (Collection) (isAbstractRowClass ? (elementType.isList() ? new ArrayList<>(valueColumnCount) : N.newHashSet(valueColumnCount))
                        : ((intConstructor == null) ? ClassUtil.invokeConstructor(constructor)
                                : ClassUtil.invokeConstructor(intConstructor, valueColumnCount)));

                for (int columIndex : valueColumnIndexes) {
                    value.add(_columnList.get(columIndex).get(rowIndex));
                }

                resultMap.put((K) _columnList.get(keyColumnIndex).get(rowIndex), (E) value);
            }
        } else if (elementType.isMap()) {
            final boolean isAbstractRowClass = Modifier.isAbstract(rowClass.getModifiers());
            final Constructor intConstructor = isAbstractRowClass ? null : ClassUtil.getDeclaredConstructor(rowClass, int.class);
            final Constructor constructor = isAbstractRowClass ? null : ClassUtil.getDeclaredConstructor(rowClass);
            Map value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = (Map) (isAbstractRowClass ? N.newHashMap(valueColumnCount)
                        : (intConstructor == null ? ClassUtil.invokeConstructor(constructor) : ClassUtil.invokeConstructor(intConstructor, valueColumnCount)));

                for (int columIndex : valueColumnIndexes) {
                    value.put(_columnNameList.get(columIndex), _columnList.get(columIndex).get(rowIndex));
                }

                resultMap.put((K) _columnList.get(keyColumnIndex).get(rowIndex), (E) value);
            }
        } else if (elementType.isBean()) {
            final BeanInfo beanInfo = ParserUtil.getBeanInfo(rowClass);
            final boolean ignoreUnmatchedProperty = valueColumnNames == _columnNameList;
            Object value = null;
            String propName = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = beanInfo.createBeanResult();

                for (int columIndex : valueColumnIndexes) {
                    propName = _columnNameList.get(columIndex);
                    beanInfo.setPropValue(value, propName, _columnList.get(columIndex).get(rowIndex), ignoreUnmatchedProperty);
                }

                value = beanInfo.finishBeanResult(value);

                resultMap.put((K) _columnList.get(keyColumnIndex).get(rowIndex), (E) value);
            }
        } else {
            throw new IllegalArgumentException(
                    "Unsupported row type: " + rowClass.getCanonicalName() + ". Only Array, List/Set, Map and bean class are supported");
        }

        return resultMap;
    }

    /**
     *
     *
     * @param  the key type
     * @param 
     * @param keyColumnName
     * @param valueColumnNames
     * @param rowSupplier
     * @return
     */
    @Override
    public  ListMultimap toMultimap(String keyColumnName, Collection valueColumnNames, IntFunction rowSupplier) {
        return toMultimap(keyColumnName, valueColumnNames, 0, size(), rowSupplier);
    }

    /**
     *
     *
     * @param  the key type
     * @param 
     * @param keyColumnName
     * @param valueColumnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param rowSupplier
     * @return
     */
    @Override
    public  ListMultimap toMultimap(String keyColumnName, Collection valueColumnNames, int fromRowIndex, int toRowIndex,
            IntFunction rowSupplier) {
        return toMultimap(keyColumnName, valueColumnNames, fromRowIndex, toRowIndex, rowSupplier,
                (IntFunction>) len -> N.newLinkedListMultimap());
    }

    /**
     *
     *
     * @param  the key type
     * @param 
     * @param  the value type
     * @param 
     * @param keyColumnName
     * @param valueColumnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param rowSupplier
     * @param supplier
     * @return
     */
    @Override
    public , M extends Multimap> M toMultimap(String keyColumnName, Collection valueColumnNames,
            int fromRowIndex, int toRowIndex, IntFunction rowSupplier, IntFunction supplier) {
        checkRowIndex(fromRowIndex, toRowIndex);

        final int keyColumnIndex = checkColumnName(keyColumnName);
        final int[] valueColumnIndexes = checkColumnName(valueColumnNames);

        final Class rowClass = rowSupplier.apply(0).getClass();
        final Type elementType = N.typeOf(rowClass);
        final int valueColumnCount = valueColumnIndexes.length;

        final M resultMap = supplier.apply(toRowIndex - fromRowIndex);

        if (elementType.isObjectArray()) {
            Object[] value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = (Object[]) rowSupplier.apply(valueColumnCount);

                for (int i = 0; i < valueColumnCount; i++) {
                    value[i] = _columnList.get(valueColumnIndexes[i]).get(rowIndex);
                }

                resultMap.put((K) _columnList.get(keyColumnIndex).get(rowIndex), (E) value);
            }
        } else if (elementType.isList() || elementType.isSet()) {
            Collection value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = (Collection) rowSupplier.apply(valueColumnCount);

                for (int columIndex : valueColumnIndexes) {
                    value.add(_columnList.get(columIndex).get(rowIndex));
                }

                resultMap.put((K) _columnList.get(keyColumnIndex).get(rowIndex), (E) value);
            }
        } else if (elementType.isMap()) {
            Map value = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = (Map) rowSupplier.apply(valueColumnCount);

                for (int columIndex : valueColumnIndexes) {
                    value.put(_columnNameList.get(columIndex), _columnList.get(columIndex).get(rowIndex));
                }

                resultMap.put((K) _columnList.get(keyColumnIndex).get(rowIndex), (E) value);
            }
        } else if (elementType.isBean()) {
            final boolean ignoreUnmatchedProperty = valueColumnNames == _columnNameList;
            final BeanInfo beanInfo = ParserUtil.getBeanInfo(rowClass);
            Object value = null;
            String propName = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                value = rowSupplier.apply(valueColumnCount);

                for (int columIndex : valueColumnIndexes) {
                    propName = _columnNameList.get(columIndex);
                    beanInfo.setPropValue(value, propName, _columnList.get(columIndex).get(rowIndex), ignoreUnmatchedProperty);
                }

                resultMap.put((K) _columnList.get(keyColumnIndex).get(rowIndex), (E) value);
            }
        } else {
            throw new IllegalArgumentException(
                    "Unsupported row type: " + rowClass.getCanonicalName() + ". Only Array, List/Set, Map and bean class are supported");
        }

        return resultMap;
    }

    /**
     *
     *
     * @return
     */
    @Override
    public String toJSON() {
        return toJSON(0, size());
    }

    /**
     *
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public String toJSON(final int fromRowIndex, final int toRowIndex) {
        return toJSON(this._columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public String toJSON(final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        final BufferedJSONWriter writer = Objectory.createBufferedJSONWriter();

        try {
            toJSON(writer, columnNames, fromRowIndex, toRowIndex);

            return writer.toString();
        } finally {
            Objectory.recycle(writer);
        }
    }

    /**
     *
     * @param output
     */
    @Override
    public void toJSON(final File output) {
        toJSON(output, 0, size());
    }

    /**
     *
     * @param output
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toJSON(final File output, final int fromRowIndex, final int toRowIndex) {
        toJSON(output, this._columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @throws UncheckedIOException the unchecked IO exception
     */
    @Override
    public void toJSON(final File output, final Collection columnNames, final int fromRowIndex, final int toRowIndex) throws UncheckedIOException {
        OutputStream os = null;

        try {
            IOUtil.createNewFileIfNotExists(output);

            os = IOUtil.newFileOutputStream(output);

            toJSON(os, columnNames, fromRowIndex, toRowIndex);

            os.flush();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            IOUtil.close(os);
        }
    }

    /**
     *
     * @param output
     */
    @Override
    public void toJSON(final OutputStream output) {
        toJSON(output, 0, size());
    }

    /**
     *
     * @param output
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toJSON(final OutputStream output, final int fromRowIndex, final int toRowIndex) {
        toJSON(output, this._columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @throws UncheckedIOException the unchecked IO exception
     */
    @Override
    public void toJSON(final OutputStream output, final Collection columnNames, final int fromRowIndex, final int toRowIndex)
            throws UncheckedIOException {
        final BufferedJSONWriter writer = Objectory.createBufferedJSONWriter(output);

        try {
            toJSON(writer, columnNames, fromRowIndex, toRowIndex);

            writer.flush();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            Objectory.recycle(writer);
        }
    }

    /**
     *
     * @param output
     */
    @Override
    public void toJSON(final Writer output) {
        toJSON(output, 0, size());
    }

    /**
     *
     * @param output
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toJSON(final Writer output, final int fromRowIndex, final int toRowIndex) {
        toJSON(output, this._columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @throws UncheckedIOException the unchecked IO exception
     */
    @Override
    public void toJSON(final Writer output, final Collection columnNames, final int fromRowIndex, final int toRowIndex) throws UncheckedIOException {
        checkRowIndex(fromRowIndex, toRowIndex);

        if (N.isNullOrEmpty(columnNames)) {
            try {
                IOUtil.write(output, "[]");
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }

            return;
        }

        final int[] columnIndexes = checkColumnName(columnNames);
        final int columnCount = columnIndexes.length;

        final char[][] charArrayOfColumnNames = new char[columnCount][];

        for (int i = 0; i < columnCount; i++) {
            charArrayOfColumnNames[i] = ("\"" + _columnNameList.get(columnIndexes[i]) + "\"").toCharArray();
        }

        final boolean isBufferedWriter = output instanceof BufferedJSONWriter;
        final BufferedJSONWriter bw = isBufferedWriter ? (BufferedJSONWriter) output : Objectory.createBufferedJSONWriter(output);

        try {
            bw.write(WD._BRACKET_L);

            Type type = null;
            Object element = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                if (rowIndex > fromRowIndex) {
                    bw.write(Strings.ELEMENT_SEPARATOR_CHAR_ARRAY);
                }

                bw.write(WD._BRACE_L);

                for (int i = 0; i < columnCount; i++) {
                    element = _columnList.get(columnIndexes[i]).get(rowIndex);

                    type = element == null ? null : N.typeOf(element.getClass());

                    if (i > 0) {
                        bw.write(Strings.ELEMENT_SEPARATOR_CHAR_ARRAY);
                    }

                    bw.write(charArrayOfColumnNames[i]);
                    bw.write(WD._COLON);

                    if (type == null) {
                        bw.write(NULL_CHAR_ARRAY);
                    } else if (type.isSerializable()) {
                        type.writeCharacter(bw, element, jsc);
                    } else {
                        // jsonParser.serialize(bw, element, jsc);

                        try { //NOSONAR
                            jsonParser.serialize(bw, element, jsc);
                        } catch (Exception e) {
                            // ignore.

                            strType.writeCharacter(bw, N.toString(element), jsc);
                        }
                    }
                }

                bw.write(WD._BRACE_R);
            }

            bw.write(WD._BRACKET_R);

            bw.flush();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            if (!isBufferedWriter) {
                Objectory.recycle(bw);
            }
        }
    }

    /**
     *
     *
     * @return
     */
    @Override
    public String toXML() {
        return toXML(ROW);
    }

    /**
     *
     * @param rowElementName
     * @return
     */
    @Override
    public String toXML(final String rowElementName) {
        return toXML(rowElementName, 0, size());
    }

    /**
     *
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public String toXML(final int fromRowIndex, final int toRowIndex) {
        return toXML(ROW, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param rowElementName
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public String toXML(final String rowElementName, final int fromRowIndex, final int toRowIndex) {
        return toXML(rowElementName, this._columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public String toXML(final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        return toXML(ROW, columnNames, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param rowElementName
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public String toXML(final String rowElementName, final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        final BufferedXMLWriter writer = Objectory.createBufferedXMLWriter();

        try {
            toXML(writer, rowElementName, columnNames, fromRowIndex, toRowIndex);

            return writer.toString();
        } finally {
            Objectory.recycle(writer);
        }
    }

    /**
     *
     * @param output
     */
    @Override
    public void toXML(final File output) {
        toXML(output, 0, size());
    }

    /**
     *
     * @param output
     * @param rowElementName
     */
    @Override
    public void toXML(final File output, final String rowElementName) {
        toXML(output, rowElementName, 0, size());
    }

    /**
     *
     * @param output
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toXML(final File output, final int fromRowIndex, final int toRowIndex) {
        toXML(output, ROW, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param rowElementName
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toXML(final File output, final String rowElementName, final int fromRowIndex, final int toRowIndex) {
        toXML(output, rowElementName, this._columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toXML(final File output, final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        toXML(output, ROW, columnNames, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param rowElementName
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @throws UncheckedIOException the unchecked IO exception
     */
    @Override
    public void toXML(final File output, final String rowElementName, final Collection columnNames, final int fromRowIndex, final int toRowIndex)
            throws UncheckedIOException {
        OutputStream os = null;

        try {
            IOUtil.createNewFileIfNotExists(output);

            os = IOUtil.newFileOutputStream(output);

            toXML(os, rowElementName, columnNames, fromRowIndex, toRowIndex);

            os.flush();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            IOUtil.close(os);
        }
    }

    /**
     *
     * @param output
     */
    @Override
    public void toXML(final OutputStream output) {
        toXML(output, 0, size());
    }

    /**
     *
     * @param output
     * @param rowElementName
     */
    @Override
    public void toXML(final OutputStream output, final String rowElementName) {
        toXML(output, rowElementName, 0, size());
    }

    /**
     *
     * @param output
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toXML(final OutputStream output, final int fromRowIndex, final int toRowIndex) {
        toXML(output, ROW, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param rowElementName
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toXML(final OutputStream output, final String rowElementName, final int fromRowIndex, final int toRowIndex) {
        toXML(output, rowElementName, this._columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toXML(final OutputStream output, final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        toXML(output, ROW, columnNames, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param rowElementName
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @throws UncheckedIOException the unchecked IO exception
     */
    @Override
    public void toXML(final OutputStream output, final String rowElementName, final Collection columnNames, final int fromRowIndex,
            final int toRowIndex) throws UncheckedIOException {
        final BufferedXMLWriter writer = Objectory.createBufferedXMLWriter(output);

        try {
            toXML(writer, rowElementName, columnNames, fromRowIndex, toRowIndex);

            writer.flush();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            Objectory.recycle(writer);
        }
    }

    /**
     *
     * @param output
     */
    @Override
    public void toXML(final Writer output) {
        toXML(output, 0, size());
    }

    /**
     *
     * @param output
     * @param rowElementName
     */
    @Override
    public void toXML(final Writer output, final String rowElementName) {
        toXML(output, rowElementName, 0, size());
    }

    /**
     *
     * @param output
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toXML(final Writer output, final int fromRowIndex, final int toRowIndex) {
        toXML(output, ROW, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param rowElementName
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toXML(final Writer output, final String rowElementName, final int fromRowIndex, final int toRowIndex) {
        toXML(output, rowElementName, this._columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toXML(final Writer output, final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        toXML(output, ROW, columnNames, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param output
     * @param rowElementName
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @throws UncheckedIOException the unchecked IO exception
     */
    @Override
    public void toXML(final Writer output, final String rowElementName, final Collection columnNames, final int fromRowIndex, final int toRowIndex)
            throws UncheckedIOException {
        checkRowIndex(fromRowIndex, toRowIndex);

        if (N.isNullOrEmpty(columnNames)) {
            try {
                IOUtil.write(output, XMLConstants.DATA_SET_ELE_START);
                IOUtil.write(output, XMLConstants.DATA_SET_ELE_END);
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }

            return;
        }

        final int[] columnIndexes = checkColumnName(columnNames);

        final int columnCount = columnIndexes.length;

        final char[] rowElementNameHead = ("<" + rowElementName + ">").toCharArray();
        final char[] rowElementNameTail = ("").toCharArray();

        final char[][] charArrayOfColumnNames = new char[columnCount][];

        for (int i = 0; i < columnCount; i++) {
            charArrayOfColumnNames[i] = _columnNameList.get(columnIndexes[i]).toCharArray();
        }

        final boolean isBufferedWriter = output instanceof BufferedXMLWriter;
        final BufferedXMLWriter bw = isBufferedWriter ? (BufferedXMLWriter) output : Objectory.createBufferedXMLWriter(output);

        try {
            bw.write(XMLConstants.DATA_SET_ELE_START);

            Type type = null;
            Object element = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                bw.write(rowElementNameHead);

                for (int i = 0; i < columnCount; i++) {
                    element = _columnList.get(columnIndexes[i]).get(rowIndex);

                    type = element == null ? null : N.typeOf(element.getClass());

                    bw.write(WD._LESS_THAN);
                    bw.write(charArrayOfColumnNames[i]);
                    bw.write(WD._GREATER_THAN);

                    if (type == null) {
                        bw.write(NULL_CHAR_ARRAY);
                    } else if (type.isSerializable()) {
                        type.writeCharacter(bw, element, xsc);
                    } else {
                        // xmlParser.serialize(bw, element, xsc);

                        try { //NOSONAR
                            xmlParser.serialize(bw, element, xsc);
                        } catch (Exception e) {
                            // ignore.

                            strType.writeCharacter(bw, N.toString(element), xsc);
                        }
                    }

                    bw.write(WD._LESS_THAN);
                    bw.write(WD._SLASH);
                    bw.write(charArrayOfColumnNames[i]);
                    bw.write(WD._GREATER_THAN);
                }

                bw.write(rowElementNameTail);
            }

            bw.write(XMLConstants.DATA_SET_ELE_END);

            bw.flush();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            if (!isBufferedWriter) {
                Objectory.recycle(bw);
            }
        }
    }

    /**
     *
     *
     * @return
     */
    @Override
    public String toCSV() {
        return toCSV(this.columnNameList(), 0, size());
    }

    /**
     *
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public String toCSV(final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        return toCSV(columnNames, fromRowIndex, toRowIndex, true, true);
    }

    /**
     *
     * @param writeTitle
     * @param quoted
     * @return
     */
    @Override
    public String toCSV(final boolean writeTitle, final boolean quoted) {
        return toCSV(columnNameList(), 0, size(), writeTitle, quoted);
    }

    /**
     *
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param writeTitle
     * @param quoted
     * @return
     */
    @Override
    public String toCSV(final Collection columnNames, final int fromRowIndex, final int toRowIndex, final boolean writeTitle, final boolean quoted) {
        final BufferedWriter bw = Objectory.createBufferedWriter();

        try {
            toCSV(bw, columnNames, fromRowIndex, toRowIndex, writeTitle, quoted);

            return bw.toString();
        } finally {
            Objectory.recycle(bw);
        }
    }

    /**
     *
     * @param output
     */
    @Override
    public void toCSV(final File output) {
        toCSV(output, _columnNameList, 0, size());
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toCSV(final File output, final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        toCSV(output, columnNames, fromRowIndex, toRowIndex, true, true);
    }

    /**
     *
     * @param output
     * @param writeTitle
     * @param quoted
     */
    @Override
    public void toCSV(final File output, final boolean writeTitle, final boolean quoted) {
        toCSV(output, _columnNameList, 0, size(), writeTitle, quoted);
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param writeTitle
     * @param quoted
     */
    @Override
    public void toCSV(final File output, final Collection columnNames, final int fromRowIndex, final int toRowIndex, final boolean writeTitle,
            final boolean quoted) {
        OutputStream os = null;

        try {
            IOUtil.createNewFileIfNotExists(output);

            os = IOUtil.newFileOutputStream(output);

            toCSV(os, columnNames, fromRowIndex, toRowIndex, writeTitle, quoted);

            os.flush();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            IOUtil.close(os);
        }
    }

    /**
     *
     * @param output
     */
    @Override
    public void toCSV(final OutputStream output) {
        toCSV(output, _columnNameList, 0, size());
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toCSV(final OutputStream output, final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        toCSV(output, columnNames, fromRowIndex, toRowIndex, true, true);
    }

    /**
     *
     * @param output
     * @param writeTitle
     * @param quoted
     */
    @Override
    public void toCSV(final OutputStream output, final boolean writeTitle, final boolean quoted) {
        toCSV(output, _columnNameList, 0, size(), writeTitle, quoted);
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param writeTitle
     * @param quoted
     * @throws UncheckedIOException the unchecked IO exception
     */
    @Override
    public void toCSV(final OutputStream output, final Collection columnNames, final int fromRowIndex, final int toRowIndex, final boolean writeTitle,
            final boolean quoted) throws UncheckedIOException {
        Writer writer = null;

        try {
            writer = new OutputStreamWriter(output);

            toCSV(writer, columnNames, fromRowIndex, toRowIndex, writeTitle, quoted);

            writer.flush();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    /**
     *
     * @param output
     */
    @Override
    public void toCSV(final Writer output) {
        toCSV(output, _columnNameList, 0, size());
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     */
    @Override
    public void toCSV(final Writer output, final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        toCSV(output, columnNames, fromRowIndex, toRowIndex, true, true);
    }

    /**
     *
     * @param output
     * @param writeTitle
     * @param quoted
     */
    @Override
    public void toCSV(final Writer output, final boolean writeTitle, final boolean quoted) {
        toCSV(output, _columnNameList, 0, size(), writeTitle, quoted);
    }

    /**
     *
     * @param output
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param writeTitle
     * @param quoted
     * @throws UncheckedIOException the unchecked IO exception
     */
    @SuppressWarnings("deprecation")
    @Override
    public void toCSV(final Writer output, final Collection columnNames, final int fromRowIndex, final int toRowIndex, final boolean writeTitle,
            final boolean quoted) throws UncheckedIOException {
        checkRowIndex(fromRowIndex, toRowIndex);

        if (N.isNullOrEmpty(columnNames)) {
            return;
        }

        final int[] columnIndexes = checkColumnName(columnNames);
        final int columnCount = columnIndexes.length;

        final JSONSerializationConfig config = JSC.create();
        config.setDateTimeFormat(DateTimeFormat.ISO_8601_TIMESTAMP);

        if (quoted) {
            config.quoteMapKey(true);
            config.quotePropName(true);
            config.setCharQuotation(WD._QUOTATION_D);
            config.setStringQuotation(WD._QUOTATION_D);
        } else {
            config.quoteMapKey(false);
            config.quotePropName(false);
            config.noCharQuotation();
        }

        final boolean isBufferedWriter = output instanceof BufferedJSONWriter;
        final BufferedJSONWriter bw = isBufferedWriter ? (BufferedJSONWriter) output : Objectory.createBufferedJSONWriter(output);

        try {
            if (writeTitle) {
                for (int i = 0; i < columnCount; i++) {
                    if (i > 0) {
                        bw.write(Strings.ELEMENT_SEPARATOR_CHAR_ARRAY);
                    }

                    // bw.write(getColumnName(columnIndexes[i]));
                    strType.writeCharacter(bw, getColumnName(columnIndexes[i]), config);
                }

                bw.write(IOUtil.LINE_SEPARATOR);
            }

            Type type = null;
            Object element = null;

            for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                if (rowIndex > fromRowIndex) {
                    bw.write(IOUtil.LINE_SEPARATOR);
                }

                for (int i = 0; i < columnCount; i++) {
                    if (i > 0) {
                        bw.write(Strings.ELEMENT_SEPARATOR_CHAR_ARRAY);
                    }

                    element = _columnList.get(columnIndexes[i]).get(rowIndex);

                    if (element == null) {
                        bw.write(NULL_CHAR_ARRAY);
                    } else {
                        type = N.typeOf(element.getClass());

                        if (type.isSerializable()) {
                            type.writeCharacter(bw, element, config);
                        } else {
                            strType.writeCharacter(bw, jsonParser.serialize(element, config), config);
                        }
                    }
                }
            }

            bw.flush();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            if (!isBufferedWriter) {
                Objectory.recycle(bw);
            }
        }
    }

    //    /**
    //     *
    //     * @param columnName
    //     * @return
    //     */
    //    @Override
    //    public DataSet groupBy(final String columnName) {
    //        return groupBy(columnName, NULL_PARAM_INDICATOR_1);
    //    }

    /**
     *
     * @param 
     * @param columnName
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param collector
     * @return
     */
    @Override
    public  DataSet groupBy(final String columnName, String aggregateResultColumnName, String aggregateOnColumnName, final Collector collector) {
        return groupBy(columnName, NULL_PARAM_INDICATOR_1, aggregateResultColumnName, aggregateOnColumnName, collector);
    }

    /**
     *
     *
     * @param columnName
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowClass
     * @return
     */
    @Override
    public DataSet groupBy(String columnName, String aggregateResultColumnName, Collection aggregateOnColumnNames, Class rowClass) {
        final List keyColumn = getColumn(columnName);
        final List valueColumn = toList(rowClass, aggregateOnColumnNames);

        final Map> map = N.newLinkedHashMap(N.min(9, size()));
        final List keyList = new ArrayList<>(N.min(9, size()));
        Object key = null;
        List val = null;

        for (int i = 0, size = keyColumn.size(); i < size; i++) {
            key = getHashKey(keyColumn.get(i));
            val = map.get(key);

            if (val == null) {
                val = new ArrayList<>();
                map.put(key, val);

                keyList.add(keyColumn.get(i));
            }

            val.add(valueColumn.get(i));
        }

        final List newColumnNameList = N.asList(columnName, aggregateResultColumnName);
        final List> newColumnList = N.asList(keyList, new ArrayList<>(map.values()));

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param columnName
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param collector
     * @return
     */
    @Override
    public DataSet groupBy(final String columnName, String aggregateResultColumnName, Collection aggregateOnColumnNames,
            final Collector collector) {
        return groupBy(columnName, aggregateResultColumnName, aggregateOnColumnNames, CLONE, collector);
    }

    /**
     *
     * @param 
     * @param 
     * @param columnName
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowMapper
     * @param collector
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet groupBy(final String columnName, String aggregateResultColumnName, Collection aggregateOnColumnNames,
            final Throwables.Function rowMapper, final Collector collector) throws E {
        return groupBy(columnName, NULL_PARAM_INDICATOR_1, aggregateResultColumnName, aggregateOnColumnNames, rowMapper, collector);
    }

    /**
     *
     * @param 
     * @param 
     * @param columnName
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param func
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet groupBy(final String columnName, String aggregateResultColumnName, String aggregateOnColumnName,
            final Throwables.Function, ?, E> func) throws E {
        return groupBy(columnName, NULL_PARAM_INDICATOR_1, aggregateResultColumnName, aggregateOnColumnName, func);
    }

    /**
     *
     * @param  the key type
     * @param 
     * @param columnName
     * @param keyMapper
     * @return
     * @throws E the e
     */
    private  DataSet groupBy(final String columnName, Throwables.Function keyMapper) throws E {
        final int columnIndex = checkColumnName(columnName);

        final int size = size();
        final int newColumnCount = 1;
        final List newColumnNameList = new ArrayList<>(newColumnCount);
        newColumnNameList.add(columnName);

        final List> newColumnList = new ArrayList<>(newColumnCount);

        for (int i = 0; i < newColumnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final Throwables.Function keyMapper2 = (Throwables.Function) (keyMapper == null ? Fn.identity() : keyMapper);
        final List keyColumn = newColumnList.get(0);

        final Set keySet = N.newHashSet();
        final List groupByColumn = _columnList.get(columnIndex);
        Object value = null;

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            value = groupByColumn.get(rowIndex);

            if (keySet.add(getHashKey(keyMapper2.apply(value)))) {
                keyColumn.add(value);
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param  the key type
     * @param 
     * @param 
     * @param columnName
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param collector
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet groupBy(final String columnName, Throwables.Function keyMapper, String aggregateResultColumnName,
            String aggregateOnColumnName, final Collector collector) throws E {
        final int columnIndex = checkColumnName(columnName);
        final int aggOnColumnIndex = checkColumnName(aggregateOnColumnName);

        if (N.equals(columnName, aggregateResultColumnName)) {
            throw new IllegalArgumentException("Duplicated Property name: " + aggregateResultColumnName);
        }

        final int size = size();
        final int newColumnCount = 2;
        final List newColumnNameList = new ArrayList<>(newColumnCount);
        newColumnNameList.add(columnName);
        newColumnNameList.add(aggregateResultColumnName);

        final List> newColumnList = new ArrayList<>(newColumnCount);

        for (int i = 0; i < newColumnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final Throwables.Function keyMapper2 = (Throwables.Function) (keyMapper == null ? Fn.identity() : keyMapper);
        final List keyColumn = newColumnList.get(0);
        final List aggResultColumn = newColumnList.get(1);
        final Supplier supplier = (Supplier) collector.supplier();
        final BiConsumer accumulator = (BiConsumer) collector.accumulator();
        final Function finisher = (Function) collector.finisher();

        final Map keyRowIndexMap = new HashMap<>();
        final List groupByColumn = _columnList.get(columnIndex);
        final List aggOnColumn = _columnList.get(aggOnColumnIndex);
        Object key = null;
        Object value = null;
        Integer collectorRowIndex = -1;

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            value = groupByColumn.get(rowIndex);
            key = getHashKey(keyMapper2.apply(value));

            collectorRowIndex = keyRowIndexMap.get(key);

            if (collectorRowIndex == null) {
                collectorRowIndex = aggResultColumn.size();
                keyRowIndexMap.put(key, collectorRowIndex);
                keyColumn.add(value);
                aggResultColumn.add(supplier.get());
            }

            accumulator.accept(aggResultColumn.get(collectorRowIndex), aggOnColumn.get(rowIndex));
        }

        for (int i = 0, len = aggResultColumn.size(); i < len; i++) {
            aggResultColumn.set(i, finisher.apply(aggResultColumn.get(i)));
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param  the key type
     * @param 
     * @param 
     * @param 
     * @param columnName
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param func
     * @return
     * @throws E the e
     * @throws E2 the e2
     */
    @Override
    public  DataSet groupBy(final String columnName, Throwables.Function keyMapper,
            String aggregateResultColumnName, String aggregateOnColumnName, final Throwables.Function, ?, E2> func) throws E, E2 {
        final RowDataSet result = (RowDataSet) groupBy(columnName, keyMapper, aggregateResultColumnName, aggregateOnColumnName, Collectors.toList());
        final List column = result._columnList.get(result.getColumnIndex(aggregateResultColumnName));

        for (int i = 0, len = column.size(); i < len; i++) {
            column.set(i, func.apply(Stream.of((List) column.get(i))));
        }

        return result;
    }

    /**
     *
     *
     * @param 
     * @param 
     * @param columnName
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowClass
     * @return
     * @throws E
     */
    @Override
    public  DataSet groupBy(String columnName, Throwables.Function keyMapper, String aggregateResultColumnName,
            Collection aggregateOnColumnNames, Class rowClass) throws E {
        final Throwables.Function keyMapper2 = (Throwables.Function) (keyMapper == null ? Fn.identity() : keyMapper);

        final List keyColumn = getColumn(columnName);
        final List valueColumn = toList(rowClass, aggregateOnColumnNames);

        final Map> map = N.newLinkedHashMap(N.min(9, size()));
        final List keyList = new ArrayList<>(N.min(9, size()));
        Object key = null;
        List val = null;

        for (int i = 0, size = keyColumn.size(); i < size; i++) {
            key = getHashKey(keyMapper2.apply(keyColumn.get(i)));
            val = map.get(key);

            if (val == null) {
                val = new ArrayList<>();
                map.put(key, val);

                keyList.add(keyColumn.get(i));
            }

            val.add(valueColumn.get(i));
        }

        final List newColumnNameList = N.asList(columnName, aggregateResultColumnName);
        final List> newColumnList = N.asList(keyList, new ArrayList<>(map.values()));

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    private static final Throwables.Function CLONE = DisposableObjArray::clone;
    private static final Throwables.Function NULL_PARAM_INDICATOR_1 = null;
    private static final Throwables.Function NULL_PARAM_INDICATOR_2 = null;

    /**
     *
     * @param  the key type
     * @param 
     * @param columnName
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param collector
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet groupBy(final String columnName, Throwables.Function keyMapper, String aggregateResultColumnName,
            Collection aggregateOnColumnNames, final Collector collector) throws E {
        return groupBy(columnName, keyMapper, aggregateResultColumnName, aggregateOnColumnNames, CLONE, collector);
    }

    /**
     *
     * @param  the key type
     * @param 
     * @param 
     * @param 
     * @param columnName
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowMapper
     * @param collector
     * @return
     * @throws E the e
     * @throws E2 the e2
     */
    @Override
    public  DataSet groupBy(final String columnName, Throwables.Function keyMapper,
            String aggregateResultColumnName, Collection aggregateOnColumnNames, final Throwables.Function rowMapper,
            final Collector collector) throws E, E2 {
        final int columnIndex = checkColumnName(columnName);
        final int[] aggOnColumnIndexes = checkColumnName(aggregateOnColumnNames);

        if (N.equals(columnName, aggregateResultColumnName)) {
            throw new IllegalArgumentException("Duplicated Property name: " + aggregateResultColumnName);
        }

        N.checkArgNotNull(rowMapper, "rowMapper");
        N.checkArgNotNull(collector, "collector");

        final int size = size();
        final int aggOnColumnCount = aggOnColumnIndexes.length;
        final int newColumnCount = 2;
        final List newColumnNameList = new ArrayList<>(newColumnCount);
        newColumnNameList.add(columnName);
        newColumnNameList.add(aggregateResultColumnName);

        final List> newColumnList = new ArrayList<>(newColumnCount);

        for (int i = 0; i < newColumnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final Throwables.Function keyMapper2 = (Throwables.Function) (keyMapper == null ? Fn.identity() : keyMapper);
        final List keyColumn = newColumnList.get(0);
        final List aggResultColumn = newColumnList.get(1);
        final Supplier supplier = (Supplier) collector.supplier();
        final BiConsumer accumulator = (BiConsumer) collector.accumulator();
        final Function finisher = (Function) collector.finisher();

        final Map keyRowIndexMap = new HashMap<>();
        final List groupByColumn = _columnList.get(columnIndex);
        final Object[] aggRow = new Object[aggOnColumnCount];
        final DisposableObjArray disposableArray = DisposableObjArray.wrap(aggRow);
        Object key = null;
        Object value = null;
        Integer collectorRowIndex = -1;

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            value = groupByColumn.get(rowIndex);
            key = getHashKey(keyMapper2.apply(value));

            collectorRowIndex = keyRowIndexMap.get(key);

            if (collectorRowIndex == null) {
                collectorRowIndex = aggResultColumn.size();
                keyRowIndexMap.put(key, collectorRowIndex);
                keyColumn.add(value);
                aggResultColumn.add(supplier.get());
            }

            for (int i = 0; i < aggOnColumnCount; i++) {
                aggRow[i] = _columnList.get(aggOnColumnIndexes[i]).get(rowIndex);
            }

            accumulator.accept(aggResultColumn.get(collectorRowIndex), rowMapper.apply(disposableArray));
        }

        for (int i = 0, len = aggResultColumn.size(); i < len; i++) {
            aggResultColumn.set(i, finisher.apply(aggResultColumn.get(i)));
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param columnNames
     * @return
     */
    @Override
    public DataSet groupBy(final Collection columnNames) {
        return groupBy(columnNames, NULL_PARAM_INDICATOR_2);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param collector
     * @return
     */
    @Override
    public  DataSet groupBy(Collection columnNames, String aggregateResultColumnName, String aggregateOnColumnName,
            final Collector collector) {
        return groupBy(columnNames, NULL_PARAM_INDICATOR_2, aggregateResultColumnName, aggregateOnColumnName, collector);
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param func
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet groupBy(Collection columnNames, String aggregateResultColumnName, String aggregateOnColumnName,
            final Throwables.Function, ?, E> func) throws E {
        return groupBy(columnNames, NULL_PARAM_INDICATOR_2, aggregateResultColumnName, aggregateOnColumnName, func);
    }

    /**
     *
     *
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowClass
     * @return
     */
    @Override
    public DataSet groupBy(Collection columnNames, String aggregateResultColumnName, Collection aggregateOnColumnNames, Class rowClass) {
        N.checkArgNotNullOrEmpty(columnNames, "columnNames");
        N.checkArgNotNullOrEmpty(aggregateOnColumnNames, "aggregateOnColumnNames");

        if (columnNames.size() == 1) {
            return groupBy(columnNames.iterator().next(), aggregateResultColumnName, aggregateOnColumnNames, rowClass);
        }

        final int size = size();
        final int[] columnIndexes = checkColumnName(columnNames);
        final int columnCount = columnIndexes.length;
        final int newColumnCount = columnIndexes.length + 1;
        final List newColumnNameList = N.newArrayList(newColumnCount);
        newColumnNameList.addAll(columnNames);
        newColumnNameList.add(aggregateResultColumnName);

        final List> newColumnList = new ArrayList<>(newColumnCount);

        for (int i = 0; i < columnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        if (size == 0) {
            newColumnList.add(new ArrayList<>());

            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final List valueColumnList = toList(rowClass, aggregateOnColumnNames);

        final Map, List> keyRowMap = N.newLinkedHashMap(N.min(9, size()));

        Object[] keyRow = Objectory.createObjectArray(columnCount);
        Wrapper key = null;
        List val = null;

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            for (int i = 0; i < newColumnCount; i++) {
                keyRow[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
            }

            key = Wrapper.of(keyRow);
            val = keyRowMap.get(key);

            if (val == null) {
                val = new ArrayList<>();
                keyRowMap.put(key, val);

                for (int i = 0; i < newColumnCount; i++) {
                    newColumnList.get(i).add(keyRow[i]);
                }

                keyRow = Objectory.createObjectArray(columnCount);
            }

            val.add(valueColumnList.get(rowIndex));
        }

        if (keyRow != null) {
            Objectory.recycle(keyRow);
            keyRow = null;
        }

        for (Wrapper e : keyRowMap.keySet()) {
            Objectory.recycle(e.value());
        }

        newColumnList.add(new ArrayList<>(keyRowMap.values()));

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param collector
     * @return
     */
    @Override
    public DataSet groupBy(Collection columnNames, String aggregateResultColumnName, Collection aggregateOnColumnNames,
            final Collector collector) {
        return groupBy(columnNames, aggregateResultColumnName, aggregateOnColumnNames, CLONE, collector);
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowMapper
     * @param collector
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet groupBy(Collection columnNames, String aggregateResultColumnName, Collection aggregateOnColumnNames,
            final Throwables.Function rowMapper, final Collector collector) throws E {
        return groupBy(columnNames, NULL_PARAM_INDICATOR_2, aggregateResultColumnName, aggregateOnColumnNames, rowMapper, collector);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param keyMapper
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet groupBy(final Collection columnNames, final Throwables.Function keyMapper)
            throws E {
        N.checkArgNotNullOrEmpty(columnNames, "columnNames");

        final boolean isNullOrIdentityKeyMapper = keyMapper == null || keyMapper == Fn.identity();

        if (columnNames.size() == 1 && isNullOrIdentityKeyMapper) {
            return this.groupBy(columnNames.iterator().next(), keyMapper);
        }

        final int size = size();
        final int[] columnIndexes = checkColumnName(columnNames);
        final int columnCount = columnIndexes.length;
        final int newColumnCount = columnIndexes.length;
        final List newColumnNameList = N.newArrayList(columnNames);
        final List> newColumnList = new ArrayList<>(newColumnCount);

        for (int i = 0; i < newColumnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final Set keyRowSet = N.newHashSet();
        Object[] keyRow = Objectory.createObjectArray(columnCount);
        final DisposableObjArray disposableArray = DisposableObjArray.wrap(keyRow);

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            for (int i = 0; i < newColumnCount; i++) {
                keyRow[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
            }

            if (isNullOrIdentityKeyMapper) {
                if (keyRowSet.add(Wrapper.of(keyRow))) {
                    for (int i = 0; i < newColumnCount; i++) {
                        newColumnList.get(i).add(keyRow[i]);
                    }

                    keyRow = Objectory.createObjectArray(columnCount);
                }
            } else if (keyRowSet.add(getHashKey(keyMapper.apply(disposableArray)))) {
                for (int i = 0; i < newColumnCount; i++) {
                    newColumnList.get(i).add(keyRow[i]);
                }
            }
        }

        if (keyRow != null) {
            Objectory.recycle(keyRow);
            keyRow = null;
        }

        if (isNullOrIdentityKeyMapper) {
            @SuppressWarnings("rawtypes")
            final Set> tmp = (Set) keyRowSet;

            for (Wrapper e : tmp) {
                Objectory.recycle(e.value());
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param collector
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet groupBy(Collection columnNames, final Throwables.Function keyMapper,
            String aggregateResultColumnName, String aggregateOnColumnName, final Collector collector) throws E {
        N.checkArgNotNullOrEmpty(columnNames, "columnNames");

        if (N.notNullOrEmpty(columnNames) && columnNames.contains(aggregateResultColumnName)) {
            throw new IllegalArgumentException("Duplicated Property name: " + aggregateResultColumnName);
        }

        final boolean isNullOrIdentityKeyMapper = keyMapper == null || keyMapper == Fn.identity();

        if (columnNames.size() == 1 && isNullOrIdentityKeyMapper) {
            return groupBy(columnNames.iterator().next(), keyMapper, aggregateResultColumnName, aggregateOnColumnName, collector);
        }

        final int size = size();
        final int[] columnIndexes = checkColumnName(columnNames);
        final int aggOnColumnIndex = checkColumnName(aggregateOnColumnName);
        final int columnCount = columnIndexes.length;
        final int newColumnCount = columnIndexes.length + 1;
        final List newColumnNameList = new ArrayList<>(columnNames);
        newColumnNameList.add(aggregateResultColumnName);
        final List> newColumnList = new ArrayList<>(newColumnCount);

        for (int i = 0; i < newColumnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final Supplier supplier = (Supplier) collector.supplier();
        final BiConsumer accumulator = (BiConsumer) collector.accumulator();
        final Function finisher = (Function) collector.finisher();

        final List aggResultColumn = newColumnList.get(newColumnList.size() - 1);
        final List aggOnColumn = _columnList.get(aggOnColumnIndex);
        final Map keyRowIndexMap = new HashMap<>();
        Object[] keyRow = Objectory.createObjectArray(columnCount);
        final DisposableObjArray disposableArray = DisposableObjArray.wrap(keyRow);
        Object key = null;
        Integer collectorRowIndex = -1;

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            for (int i = 0; i < columnCount; i++) {
                keyRow[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
            }

            key = isNullOrIdentityKeyMapper ? Wrapper.of(keyRow) : getHashKey(keyMapper.apply(disposableArray));
            collectorRowIndex = keyRowIndexMap.get(key);

            if (collectorRowIndex == null) {
                collectorRowIndex = aggResultColumn.size();
                keyRowIndexMap.put(key, collectorRowIndex);
                aggResultColumn.add(supplier.get());

                for (int i = 0; i < columnCount; i++) {
                    newColumnList.get(i).add(keyRow[i]);
                }

                keyRow = Objectory.createObjectArray(columnCount);
            }

            accumulator.accept(aggResultColumn.get(collectorRowIndex), aggOnColumn.get(rowIndex));
        }

        for (int i = 0, len = aggResultColumn.size(); i < len; i++) {
            aggResultColumn.set(i, finisher.apply(aggResultColumn.get(i)));
        }

        if (keyRow != null) {
            Objectory.recycle(keyRow);
            keyRow = null;
        }

        if (isNullOrIdentityKeyMapper) {
            @SuppressWarnings("rawtypes")
            final Set> tmp = (Set) keyRowIndexMap.keySet();

            for (Wrapper e : tmp) {
                Objectory.recycle(e.value());
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param func
     * @return
     * @throws E the e
     * @throws E2 the e2
     */
    @Override
    public  DataSet groupBy(Collection columnNames,
            Throwables.Function keyMapper, String aggregateResultColumnName, String aggregateOnColumnName,
            final Throwables.Function, ?, E2> func) throws E, E2 {
        final RowDataSet result = (RowDataSet) groupBy(columnNames, keyMapper, aggregateResultColumnName, aggregateOnColumnName, Collectors.toList());
        final List column = result._columnList.get(result.getColumnIndex(aggregateResultColumnName));

        for (int i = 0, len = column.size(); i < len; i++) {
            column.set(i, func.apply(Stream.of((List) column.get(i))));
        }

        return result;
    }

    /**
     *
     *
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowClass
     * @return
     * @throws E
     */
    @Override
    public  DataSet groupBy(Collection columnNames, Throwables.Function keyMapper,
            String aggregateResultColumnName, Collection aggregateOnColumnNames, Class rowClass) throws E {
        N.checkArgNotNullOrEmpty(columnNames, "columnNames");
        N.checkArgNotNullOrEmpty(aggregateOnColumnNames, "aggregateOnColumnNames");

        final boolean isNullOrIdentityKeyMapper = keyMapper == null || keyMapper == Fn.identity();

        if (isNullOrIdentityKeyMapper) {
            if (columnNames.size() == 1) {
                return groupBy(columnNames.iterator().next(), aggregateResultColumnName, aggregateOnColumnNames, rowClass);
            }
            return groupBy(columnNames, aggregateResultColumnName, aggregateOnColumnNames, rowClass);
        }

        final int size = size();
        final int[] columnIndexes = checkColumnName(columnNames);
        final int columnCount = columnIndexes.length;
        final int newColumnCount = columnIndexes.length + 1;
        final List newColumnNameList = N.newArrayList(newColumnCount);
        newColumnNameList.addAll(columnNames);
        newColumnNameList.add(aggregateResultColumnName);

        final List> newColumnList = new ArrayList<>(newColumnCount);

        for (int i = 0; i < columnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        if (size == 0) {
            newColumnList.add(new ArrayList<>());

            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final List valueColumnList = toList(rowClass, aggregateOnColumnNames);

        final Map> keyRowMap = N.newLinkedHashMap();
        final Object[] keyRow = Objectory.createObjectArray(columnCount);
        final DisposableObjArray keyDisposableArray = DisposableObjArray.wrap(keyRow);

        Object key = null;
        List val = null;

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            for (int i = 0; i < newColumnCount; i++) {
                keyRow[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
            }

            key = getHashKey(keyMapper.apply(keyDisposableArray));
            val = keyRowMap.get(key);

            if (val == null) {
                val = new ArrayList<>();
                keyRowMap.put(key, val);

                for (int i = 0; i < newColumnCount; i++) {
                    newColumnList.get(i).add(keyRow[i]);
                }
            }

            val.add(valueColumnList.get(rowIndex));
        }

        if (keyRow != null) {
            Objectory.recycle(keyRow);
        }

        newColumnList.add(new ArrayList<>(keyRowMap.values()));

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param collector
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet groupBy(Collection columnNames, Throwables.Function keyMapper,
            String aggregateResultColumnName, Collection aggregateOnColumnNames, final Collector collector) throws E {
        return groupBy(aggregateOnColumnNames, keyMapper, aggregateResultColumnName, aggregateOnColumnNames, CLONE, collector);
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowMapper
     * @param collector
     * @return
     * @throws E the e
     * @throws E2 the e2
     */
    @Override
    public  DataSet groupBy(Collection columnNames,
            Throwables.Function keyMapper, String aggregateResultColumnName, Collection aggregateOnColumnNames,
            final Throwables.Function rowMapper, final Collector collector) throws E, E2 {
        N.checkArgNotNullOrEmpty(columnNames, "columnNames");

        if (N.notNullOrEmpty(columnNames) && columnNames.contains(aggregateResultColumnName)) {
            throw new IllegalArgumentException("Duplicated Property name: " + aggregateResultColumnName);
        }

        N.checkArgNotNull(rowMapper, "rowMapper");
        N.checkArgNotNull(collector, "collector");

        final boolean isNullOrIdentityKeyMapper = keyMapper == null || keyMapper == Fn.identity();

        if (columnNames.size() == 1 && isNullOrIdentityKeyMapper) {
            return groupBy(columnNames.iterator().next(), keyMapper, aggregateResultColumnName, aggregateOnColumnNames, rowMapper, collector);
        }

        final int size = size();
        final int[] columnIndexes = checkColumnName(columnNames);
        final int[] aggOnColumnIndexes = checkColumnName(aggregateOnColumnNames);
        final int columnCount = columnIndexes.length;
        final int newColumnCount = columnIndexes.length + 1;
        final List newColumnNameList = new ArrayList<>(columnNames);
        newColumnNameList.add(aggregateResultColumnName);

        final List> newColumnList = new ArrayList<>(newColumnCount);

        for (int i = 0; i < newColumnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final Supplier supplier = (Supplier) collector.supplier();
        final BiConsumer accumulator = (BiConsumer) collector.accumulator();
        final Function finisher = (Function) collector.finisher();

        final int aggOnColumnCount = aggOnColumnIndexes.length;
        final List aggResultColumn = newColumnList.get(newColumnList.size() - 1);
        final Map keyRowIndexMap = new HashMap<>();
        Object[] keyRow = Objectory.createObjectArray(columnCount);
        final DisposableObjArray keyDisposableArray = DisposableObjArray.wrap(keyRow);
        final Object[] aggOnRow = new Object[aggOnColumnCount];
        final DisposableObjArray aggOnRowDisposableArray = DisposableObjArray.wrap(aggOnRow);
        Object key = null;
        Integer collectorRowIndex = -1;

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            for (int i = 0; i < columnCount; i++) {
                keyRow[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
            }

            key = isNullOrIdentityKeyMapper ? Wrapper.of(keyRow) : getHashKey(keyMapper.apply(keyDisposableArray));
            collectorRowIndex = keyRowIndexMap.get(key);

            if (collectorRowIndex == null) {
                collectorRowIndex = aggResultColumn.size();
                keyRowIndexMap.put(key, collectorRowIndex);
                aggResultColumn.add(supplier.get());

                for (int i = 0; i < columnCount; i++) {
                    newColumnList.get(i).add(keyRow[i]);
                }

                keyRow = Objectory.createObjectArray(columnCount);
            }

            for (int i = 0; i < aggOnColumnCount; i++) {
                aggOnRow[i] = _columnList.get(aggOnColumnIndexes[i]).get(rowIndex);
            }

            accumulator.accept(aggResultColumn.get(collectorRowIndex), rowMapper.apply(aggOnRowDisposableArray));
        }

        for (int i = 0, len = aggResultColumn.size(); i < len; i++) {
            aggResultColumn.set(i, finisher.apply(aggResultColumn.get(i)));
        }

        if (keyRow != null) {
            Objectory.recycle(keyRow);
            keyRow = null;
        }

        if (isNullOrIdentityKeyMapper) {
            @SuppressWarnings("rawtypes")
            final Set> tmp = (Set) keyRowIndexMap.keySet();

            for (Wrapper e : tmp) {
                Objectory.recycle(e.value());
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    private static final com.landawn.abacus.util.function.Predicate> NOT_EMPTY_FILTER = CommonUtil::notNullOrEmpty;

    /**
     *
     * @param columnNames
     * @return
     */
    @Override
    public Stream rollup(final Collection columnNames) {
        return Stream.of(Iterables.rollup(columnNames)).reversed().filter(NOT_EMPTY_FILTER).map(this::groupBy);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param collector
     * @return
     */
    @Override
    public  Stream rollup(final Collection columnNames, final String aggregateResultColumnName, final String aggregateOnColumnName,
            final Collector collector) {
        return Stream.of(Iterables.rollup(columnNames))
                .reversed()
                .filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> groupBy(columnNames1, aggregateResultColumnName, aggregateOnColumnName, collector));
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param func
     * @return
     */
    @Override
    public  Stream rollup(final Collection columnNames, final String aggregateResultColumnName,
            final String aggregateOnColumnName, final Throwables.Function, ?, E> func) {
        return Stream.of(Iterables.rollup(columnNames))
                .reversed()
                .filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try.call((Callable) () -> groupBy(columnNames1, aggregateResultColumnName, aggregateOnColumnName, func)));
    }

    /**
     *
     *
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowClass
     * @return
     */
    @Override
    public Stream rollup(Collection columnNames, String aggregateResultColumnName, Collection aggregateOnColumnNames,
            Class rowClass) {
        return Stream.of(Iterables.rollup(columnNames))
                .reversed()
                .filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try.call((Callable) () -> groupBy(columnNames1, aggregateResultColumnName, aggregateOnColumnNames, rowClass)));
    }

    /**
     *
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param collector
     * @return
     */
    @Override
    public Stream rollup(final Collection columnNames, final String aggregateResultColumnName, final Collection aggregateOnColumnNames,
            final Collector collector) {
        return rollup(columnNames, aggregateResultColumnName, aggregateOnColumnNames, CLONE, collector);
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowMapper
     * @param collector
     * @return
     */
    @Override
    public  Stream rollup(final Collection columnNames, final String aggregateResultColumnName,
            final Collection aggregateOnColumnNames, final Throwables.Function rowMapper,
            final Collector collector) {
        return Stream.of(Iterables.rollup(columnNames))
                .reversed()
                .filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try
                        .call((Callable) () -> groupBy(columnNames1, aggregateResultColumnName, aggregateOnColumnNames, rowMapper, collector)));
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param keyMapper
     * @return
     */
    @Override
    public  Stream rollup(final Collection columnNames,
            final Throwables.Function keyMapper) {
        return Stream.of(Iterables.rollup(columnNames))
                .reversed()
                .filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try.call((Callable) () -> groupBy(columnNames1, keyMapper)));
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param collector
     * @return
     */
    @Override
    public  Stream rollup(final Collection columnNames,
            final Throwables.Function keyMapper, final String aggregateResultColumnName, final String aggregateOnColumnName,
            final Collector collector) {
        return Stream.of(Iterables.rollup(columnNames))
                .reversed()
                .filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try
                        .call((Callable) () -> groupBy(columnNames1, keyMapper, aggregateResultColumnName, aggregateOnColumnName, collector)));
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param func
     * @return
     */
    @Override
    public  Stream rollup(final Collection columnNames,
            final Throwables.Function keyMapper, final String aggregateResultColumnName, final String aggregateOnColumnName,
            final Throwables.Function, ?, E2> func) {
        return Stream.of(Iterables.rollup(columnNames))
                .reversed()
                .filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try
                        .call((Callable) () -> groupBy(columnNames1, keyMapper, aggregateResultColumnName, aggregateOnColumnName, func)));
    }

    /**
     *
     *
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowClass
     * @return
     */
    @Override
    public  Stream rollup(Collection columnNames, Throwables.Function keyMapper,
            String aggregateResultColumnName, Collection aggregateOnColumnNames, Class rowClass) {
        return Stream.of(Iterables.rollup(columnNames))
                .reversed()
                .filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try
                        .call((Callable) () -> groupBy(columnNames1, keyMapper, aggregateResultColumnName, aggregateOnColumnNames, rowClass)));
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param collector
     * @return
     */
    @Override
    public  Stream rollup(final Collection columnNames,
            final Throwables.Function keyMapper, final String aggregateResultColumnName,
            final Collection aggregateOnColumnNames, final Collector collector) {
        return rollup(columnNames, keyMapper, aggregateResultColumnName, aggregateOnColumnNames, CLONE, collector);
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowMapper
     * @param collector
     * @return
     */
    @Override
    public  Stream rollup(final Collection columnNames,
            final Throwables.Function keyMapper, final String aggregateResultColumnName,
            final Collection aggregateOnColumnNames, final Throwables.Function rowMapper,
            final Collector collector) {
        return Stream.of(Iterables.rollup(columnNames))
                .reversed()
                .filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try.call(
                        (Callable) () -> groupBy(columnNames1, keyMapper, aggregateResultColumnName, aggregateOnColumnNames, rowMapper, collector)));
    }

    /**
     *
     * @param columnNames
     * @return
     */
    @Override
    public Stream cube(final Collection columnNames) {
        return cubeSet(columnNames).filter(NOT_EMPTY_FILTER).map(this::groupBy);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param collector
     * @return
     */
    @Override
    public  Stream cube(final Collection columnNames, final String aggregateResultColumnName, final String aggregateOnColumnName,
            final Collector collector) {
        return cubeSet(columnNames).filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> groupBy(columnNames1, aggregateResultColumnName, aggregateOnColumnName, collector));
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param func
     * @return
     */
    @Override
    public  Stream cube(final Collection columnNames, final String aggregateResultColumnName,
            final String aggregateOnColumnName, final Throwables.Function, ?, E> func) {
        return cubeSet(columnNames).filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try.call((Callable) () -> groupBy(columnNames1, aggregateResultColumnName, aggregateOnColumnName, func)));
    }

    /**
     *
     *
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowClass
     * @return
     */
    @Override
    public Stream cube(Collection columnNames, String aggregateResultColumnName, Collection aggregateOnColumnNames,
            Class rowClass) {
        return cubeSet(columnNames).filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try.call((Callable) () -> groupBy(columnNames1, aggregateResultColumnName, aggregateOnColumnNames, rowClass)));
    }

    /**
     *
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param collector
     * @return
     */
    @Override
    public Stream cube(final Collection columnNames, final String aggregateResultColumnName, final Collection aggregateOnColumnNames,
            final Collector collector) {
        return cube(columnNames, aggregateResultColumnName, aggregateOnColumnNames, CLONE, collector);
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNames
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowMapper
     * @param collector
     * @return
     */
    @Override
    public  Stream cube(final Collection columnNames, final String aggregateResultColumnName,
            final Collection aggregateOnColumnNames, final Throwables.Function rowMapper,
            final Collector collector) {
        return cubeSet(columnNames).filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try
                        .call((Callable) () -> groupBy(columnNames1, aggregateResultColumnName, aggregateOnColumnNames, rowMapper, collector)));
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param keyMapper
     * @return
     */
    @Override
    public  Stream cube(final Collection columnNames,
            final Throwables.Function keyMapper) {
        return cubeSet(columnNames).filter(NOT_EMPTY_FILTER).map(columnNames1 -> Try.call((Callable) () -> groupBy(columnNames1, keyMapper)));
    }

    /**
     *
     * @param 
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param collector
     * @return
     */
    @Override
    public  Stream cube(final Collection columnNames,
            final Throwables.Function keyMapper, final String aggregateResultColumnName, final String aggregateOnColumnName,
            final Collector collector) {
        return cubeSet(columnNames).filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try
                        .call((Callable) () -> groupBy(columnNames1, keyMapper, aggregateResultColumnName, aggregateOnColumnName, collector)));
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnName
     * @param func
     * @return
     */
    @Override
    public  Stream cube(final Collection columnNames,
            final Throwables.Function keyMapper, final String aggregateResultColumnName, final String aggregateOnColumnName,
            final Throwables.Function, ?, E2> func) {
        return cubeSet(columnNames).filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try
                        .call((Callable) () -> groupBy(columnNames1, keyMapper, aggregateResultColumnName, aggregateOnColumnName, func)));
    }

    /**
     *
     *
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowClass
     * @return
     */
    @Override
    public  Stream cube(Collection columnNames, Throwables.Function keyMapper,
            String aggregateResultColumnName, Collection aggregateOnColumnNames, Class rowClass) {
        return cubeSet(columnNames).filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try
                        .call((Callable) () -> groupBy(columnNames1, keyMapper, aggregateResultColumnName, aggregateOnColumnNames, rowClass)));
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param collector
     * @return
     */
    @Override
    public  Stream cube(final Collection columnNames,
            final Throwables.Function keyMapper, final String aggregateResultColumnName,
            final Collection aggregateOnColumnNames, final Collector collector) {
        return cube(columnNames, keyMapper, aggregateResultColumnName, aggregateOnColumnNames, CLONE, collector);
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param columnNames
     * @param keyMapper
     * @param aggregateResultColumnName
     * @param aggregateOnColumnNames
     * @param rowMapper
     * @param collector
     * @return
     */
    @Override
    public  Stream cube(final Collection columnNames,
            final Throwables.Function keyMapper, final String aggregateResultColumnName,
            final Collection aggregateOnColumnNames, final Throwables.Function rowMapper,
            final Collector collector) {
        return cubeSet(columnNames).filter(NOT_EMPTY_FILTER)
                .map(columnNames1 -> Try.call(
                        (Callable) () -> groupBy(columnNames1, keyMapper, aggregateResultColumnName, aggregateOnColumnNames, rowMapper, collector)));
    }

    private static final com.landawn.abacus.util.function.Function, Integer> TO_SIZE_FUNC = Set::size;

    private static final com.landawn.abacus.util.function.Consumer>> REVERSE_ACTION = CommonUtil::reverse;

    /**
     *
     * @param columnNames
     * @return
     */
    private Stream> cubeSet(final Collection columnNames) {
        return Stream.of(Iterables.powerSet(N.newLinkedHashSet(columnNames)))
                .groupByToEntry(TO_SIZE_FUNC)
                .values()
                .onEach(REVERSE_ACTION)
                .flatmap(Fn.>> identity())
                .reversed();
    }

    /**
     *
     * @param columnName
     */
    @Override
    public void sortBy(final String columnName) {
        sortBy(columnName, Comparators.naturalOrder());
    }

    /**
     *
     * @param 
     * @param columnName
     * @param cmp
     */
    @Override
    public  void sortBy(final String columnName, final Comparator cmp) {
        sort(columnName, cmp, false);
    }

    /**
     *
     * @param columnNames
     */
    @Override
    public void sortBy(final Collection columnNames) {
        sortBy(columnNames, Comparators.OBJECT_ARRAY_COMPARATOR);
    }

    /**
     *
     * @param columnNames
     * @param cmp
     */
    @Override
    public void sortBy(final Collection columnNames, final Comparator cmp) {
        sort(columnNames, cmp, false);
    }

    /**
     *
     * @param columnNames
     * @param keyMapper
     */
    @SuppressWarnings("rawtypes")
    @Override
    public void sortBy(Collection columnNames, Function keyMapper) {
        sort(columnNames, keyMapper, false);
    }

    /**
     * Parallel sort by.
     *
     * @param columnName
     */
    @Override
    public void parallelSortBy(final String columnName) {
        parallelSortBy(columnName, Comparators.naturalOrder());
    }

    /**
     * Parallel sort by.
     *
     * @param 
     * @param columnName
     * @param cmp
     */
    @Override
    public  void parallelSortBy(final String columnName, final Comparator cmp) {
        sort(columnName, cmp, true);
    }

    /**
     * Parallel sort by.
     *
     * @param columnNames
     */
    @Override
    public void parallelSortBy(final Collection columnNames) {
        parallelSortBy(columnNames, Comparators.OBJECT_ARRAY_COMPARATOR);
    }

    /**
     * Parallel sort by.
     *
     * @param columnNames
     * @param cmp
     */
    @Override
    public void parallelSortBy(final Collection columnNames, final Comparator cmp) {
        sort(columnNames, cmp, true);
    }

    /**
     * Parallel sort by.
     *
     * @param columnNames
     * @param keyMapper
     */
    @SuppressWarnings("rawtypes")
    @Override
    public void parallelSortBy(Collection columnNames, Function keyMapper) {
        sort(columnNames, keyMapper, true);
    }

    /**
     *
     * @param 
     * @param columnName
     * @param cmp
     * @param isParallelSort
     */
    private  void sort(final String columnName, final Comparator cmp, final boolean isParallelSort) {
        checkFrozen();

        final int columnIndex = checkColumnName(columnName);
        final int size = size();

        if (size == 0) {
            return;
        }

        // TODO too many array objects are created.
        final Indexed[] arrayOfPair = new Indexed[size];
        final List orderByColumn = _columnList.get(columnIndex);

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            arrayOfPair[rowIndex] = Indexed.of(orderByColumn.get(rowIndex), rowIndex);
        }

        final Comparator> pairCmp = createComparatorForIndexedObject(cmp);

        sort(arrayOfPair, pairCmp, isParallelSort);
    }

    @SuppressWarnings("rawtypes")
    private static Comparator> createComparatorForIndexedObject(final Comparator cmp) {
        Comparator> pairCmp = null;

        if (cmp != null) {
            final Comparator cmp2 = (Comparator) cmp;
            pairCmp = (a, b) -> cmp2.compare(a.value(), b.value());
        } else {
            final Comparator> tmp = (a, b) -> N.compare(a.value(), b.value());
            pairCmp = (Comparator) tmp;
        }

        return pairCmp;
    }

    private static Comparator> createComparatorForIndexedObjectArray(final Comparator cmp) {
        Comparator> pairCmp = null;

        if (cmp != null) {
            final Comparator cmp2 = (Comparator) cmp;
            pairCmp = (a, b) -> cmp2.compare(a.value(), b.value());
        } else {
            pairCmp = (a, b) -> Comparators.OBJECT_ARRAY_COMPARATOR.compare(a.value(), b.value());
        }

        return pairCmp;
    }

    /**
     *
     * @param columnNames
     * @param cmp
     * @param isParallelSort
     */
    private void sort(final Collection columnNames, final Comparator cmp, final boolean isParallelSort) {
        checkFrozen();

        final int[] columnIndexes = checkColumnName(columnNames);
        final int size = size();

        if (size == 0) {
            return;
        }

        final int sortByColumnCount = columnIndexes.length;
        // TODO too many array objects are created.
        final Indexed[] arrayOfPair = new Indexed[size];

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            arrayOfPair[rowIndex] = Indexed.of(Objectory.createObjectArray(sortByColumnCount), rowIndex);
        }

        for (int i = 0; i < sortByColumnCount; i++) {
            final List orderByColumn = _columnList.get(columnIndexes[i]);

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                arrayOfPair[rowIndex].value()[i] = orderByColumn.get(rowIndex);
            }
        }

        final Comparator> pairCmp = createComparatorForIndexedObjectArray(cmp);

        sort(arrayOfPair, pairCmp, isParallelSort);

        for (Indexed p : arrayOfPair) {
            Objectory.recycle(p.value());
        }
    }

    /**
     *
     * @param columnNames
     * @param keyMapper
     * @param isParallelSort
     */
    @SuppressWarnings("rawtypes")
    private void sort(final Collection columnNames, final Function keyMapper,
            final boolean isParallelSort) {
        checkFrozen();

        final int[] columnIndexes = checkColumnName(columnNames);
        final int size = size();

        if (size == 0) {
            return;
        }

        final int sortByColumnCount = columnIndexes.length;
        final Indexed[] arrayOfPair = new Indexed[size];

        final Object[] sortByRow = new Object[sortByColumnCount];
        final DisposableObjArray disposableArray = DisposableObjArray.wrap(sortByRow);

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            for (int i = 0; i < sortByColumnCount; i++) {
                sortByRow[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
            }

            arrayOfPair[rowIndex] = Indexed.of((Comparable) keyMapper.apply(disposableArray), rowIndex);
        }

        final Comparator> pairCmp = Comparators.comparingBy(Indexed::value);

        sort(arrayOfPair, pairCmp, isParallelSort);
    }

    /**
     *
     * @param 
     * @param arrayOfPair
     * @param pairCmp
     * @param isParallelSort
     */
    private  void sort(final Indexed[] arrayOfPair, final Comparator> pairCmp, final boolean isParallelSort) {
        if (isParallelSort) {
            N.parallelSort(arrayOfPair, pairCmp);
        } else {
            N.sort(arrayOfPair, pairCmp);
        }

        final int size = size();
        final int columnCount = _columnNameList.size();
        final Set ordered = N.newHashSet(size);
        final Object[] tempRow = new Object[columnCount];

        for (int i = 0, index = 0; i < size; i++) {
            index = arrayOfPair[i].index();

            if ((index != i) && !ordered.contains(i)) {
                for (int j = 0; j < columnCount; j++) {
                    tempRow[j] = _columnList.get(j).get(i);
                }

                int previous = i;
                int next = index;

                do {
                    for (int j = 0; j < columnCount; j++) {
                        _columnList.get(j).set(previous, _columnList.get(j).get(next));
                    }

                    ordered.add(next);

                    previous = next;
                    next = arrayOfPair[next].index();
                } while (next != i);

                for (int j = 0; j < columnCount; j++) {
                    _columnList.get(j).set(previous, tempRow[j]);
                }

                ordered.add(i);
            }
        }

        modCount++;
    }

    /**
     *
     * @param columnName
     * @param n
     * @return
     */
    @Override
    public DataSet topBy(final String columnName, final int n) {
        return topBy(columnName, n, Comparators.naturalOrder());
    }

    /**
     *
     * @param 
     * @param columnName
     * @param n
     * @param cmp
     * @return
     */
    @Override
    public  DataSet topBy(final String columnName, final int n, final Comparator cmp) {
        if (n < 1) {
            throw new IllegalArgumentException("'n' can not be less than 1");
        }

        final int columnIndex = checkColumnName(columnName);
        final int size = size();

        if (n >= size) {
            return this.copy();
        }

        final Comparator> pairCmp = createComparatorForIndexedObject(cmp);

        final List orderByColumn = _columnList.get(columnIndex);

        return top(n, pairCmp, orderByColumn::get);
    }

    /**
     *
     * @param columnNames
     * @param n
     * @return
     */
    @Override
    public DataSet topBy(final Collection columnNames, final int n) {
        return topBy(columnNames, n, Comparators.OBJECT_ARRAY_COMPARATOR);
    }

    /**
     *
     * @param columnNames
     * @param n
     * @param cmp
     * @return
     */
    @Override
    public DataSet topBy(final Collection columnNames, final int n, final Comparator cmp) {
        if (n < 1) {
            throw new IllegalArgumentException("'n' can not be less than 1");
        }

        final int[] sortByColumnIndexes = checkColumnName(columnNames);
        final int size = size();

        if (n >= size) {
            return this.copy();
        }

        final Comparator> pairCmp = createComparatorForIndexedObjectArray(cmp);

        final List keyRowList = new ArrayList<>(n);
        final int sortByColumnCount = sortByColumnIndexes.length;

        final DataSet result = top(n, pairCmp, rowIndex -> {
            final Object[] keyRow = Objectory.createObjectArray(sortByColumnCount);
            keyRowList.add(keyRow);

            for (int i = 0; i < sortByColumnCount; i++) {
                keyRow[i] = _columnList.get(sortByColumnIndexes[i]).get(rowIndex);
            }

            return keyRow;
        });

        for (Object[] a : keyRowList) {
            Objectory.recycle(a);
        }

        return result;
    }

    /**
     *
     * @param columnNames
     * @param n
     * @param keyMapper
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public DataSet topBy(final Collection columnNames, final int n, final Function keyMapper) {
        if (n < 1) {
            throw new IllegalArgumentException("'n' can not be less than 1");
        }

        final int[] columnIndexes = checkColumnName(columnNames);
        final int size = size();

        if (n >= size) {
            return this.copy();
        }

        final Comparator> pairCmp = Comparators.comparingBy(Indexed::value);

        final int sortByColumnCount = columnIndexes.length;
        final Object[] keyRow = new Object[sortByColumnCount];
        final DisposableObjArray disposableObjArray = DisposableObjArray.wrap(keyRow);

        return top(n, pairCmp, rowIndex -> {

            for (int i = 0; i < sortByColumnCount; i++) {
                keyRow[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
            }

            return keyMapper.apply(disposableObjArray);
        });
    }

    /**
     *
     * @param 
     * @param n
     * @param pairCmp
     * @param keyFunc
     * @return
     */
    private  DataSet top(final int n, final Comparator> pairCmp, final IntFunction keyFunc) {
        final int size = size();
        final Queue> heap = new PriorityQueue<>(n, pairCmp);
        Indexed pair = null;

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            pair = Indexed.of(keyFunc.apply(rowIndex), rowIndex);

            if (heap.size() >= n) {
                if (pairCmp.compare(heap.peek(), pair) < 0) {
                    heap.poll();
                    heap.add(pair);
                }
            } else {
                heap.offer(pair);
            }
        }

        final Indexed[] arrayOfPair = heap.toArray(new Indexed[heap.size()]);

        N.sort(arrayOfPair, (Comparator>) (o1, o2) -> o1.index() - o2.index());

        final int columnCount = _columnNameList.size();
        final List newColumnNameList = new ArrayList<>(_columnNameList);
        final List> newColumnList = new ArrayList<>(columnCount);

        for (int i = 0; i < columnCount; i++) {
            newColumnList.add(new ArrayList<>(arrayOfPair.length));
        }

        int rowIndex = 0;
        for (Indexed e : arrayOfPair) {
            rowIndex = e.index();

            for (int i = 0; i < columnCount; i++) {
                newColumnList.get(i).add(_columnList.get(i).get(rowIndex));
            }
        }

        final Properties newProperties = N.isNullOrEmpty(_properties) ? null : _properties.copy();

        return new RowDataSet(newColumnNameList, newColumnList, newProperties);
    }

    /**
     *
     *
     * @return
     */
    @Override
    public DataSet distinct() {
        return distinctBy(this._columnNameList);
    }

    /**
     *
     * @param columnName
     * @return
     */
    @Override
    public DataSet distinctBy(final String columnName) {
        return distinctBy(columnName, Fn.identity());
    }

    /**
     *
     * @param  the key type
     * @param 
     * @param columnName
     * @param keyMapper
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet distinctBy(final String columnName, final Throwables.Function keyMapper) throws E {
        final int columnIndex = checkColumnName(columnName);

        final int size = size();
        final int columnCount = _columnNameList.size();
        final List newColumnNameList = new ArrayList<>(_columnNameList);
        final List> newColumnList = new ArrayList<>(columnCount);

        for (int i = 0; i < columnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        final Properties newProperties = N.isNullOrEmpty(_properties) ? null : _properties.copy();

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList, newProperties);
        }

        final Throwables.Function keyMapper2 = (Throwables.Function) keyMapper;
        final Set rowSet = N.newHashSet();
        Object key = null;
        Object value = null;

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            value = _columnList.get(columnIndex).get(rowIndex);
            key = getHashKey(keyMapper2 == null ? value : keyMapper2.apply(value));

            if (rowSet.add(key)) {
                for (int i = 0; i < columnCount; i++) {
                    newColumnList.get(i).add(_columnList.get(i).get(rowIndex));
                }
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList, newProperties);
    }

    /**
     *
     * @param columnNames
     * @return
     */
    @Override
    public DataSet distinctBy(final Collection columnNames) {
        return distinctBy(columnNames, NULL_PARAM_INDICATOR_2);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param keyMapper
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet distinctBy(final Collection columnNames, final Throwables.Function keyMapper)
            throws E {
        final boolean isNullOrIdentityKeyMapper = keyMapper == null || keyMapper == Fn.identity();

        if (columnNames.size() == 1 && isNullOrIdentityKeyMapper) {
            return distinctBy(columnNames.iterator().next());
        }

        final int size = size();
        final int[] columnIndexes = checkColumnName(columnNames);

        final int columnCount = _columnNameList.size();
        final List newColumnNameList = new ArrayList<>(_columnNameList);
        final List> newColumnList = new ArrayList<>(columnCount);

        for (int i = 0; i < columnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        final Properties newProperties = N.isNullOrEmpty(_properties) ? null : _properties.copy();

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList, newProperties);
        }

        final Set rowSet = N.newHashSet();
        Object[] row = Objectory.createObjectArray(columnCount);
        DisposableObjArray disposableArray = DisposableObjArray.wrap(row);

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            for (int i = 0, len = columnIndexes.length; i < len; i++) {
                row[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
            }

            if (isNullOrIdentityKeyMapper) {
                if (rowSet.add(Wrapper.of(row))) {
                    for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
                        newColumnList.get(columnIndex).add(_columnList.get(columnIndex).get(rowIndex));
                    }

                    row = Objectory.createObjectArray(columnCount);
                }
            } else if (rowSet.add(getHashKey(keyMapper.apply(disposableArray)))) {
                for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
                    newColumnList.get(columnIndex).add(_columnList.get(columnIndex).get(rowIndex));
                }
            }
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        if (isNullOrIdentityKeyMapper) {
            @SuppressWarnings("rawtypes")
            final Set> tmp = (Set) rowSet;

            for (Wrapper e : tmp) {
                Objectory.recycle(e.value());
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList, newProperties);
    }

    /**
     *
     * @param 
     * @param filter
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(final Throwables.Predicate filter) throws E {
        return filter(filter, size());
    }

    /**
     *
     * @param 
     * @param filter
     * @param max
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(Throwables.Predicate filter, int max) throws E {
        return filter(0, size(), filter);
    }

    /**
     *
     * @param 
     * @param fromRowIndex
     * @param toRowIndex
     * @param filter
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(final int fromRowIndex, final int toRowIndex, final Throwables.Predicate filter)
            throws E {
        return filter(fromRowIndex, toRowIndex, filter, size());
    }

    /**
     *
     * @param 
     * @param fromRowIndex
     * @param toRowIndex
     * @param filter
     * @param max
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(int fromRowIndex, int toRowIndex, Throwables.Predicate filter, int max)
            throws E {
        return filter(this._columnNameList, fromRowIndex, toRowIndex, filter, max);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param filter
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(Tuple2 columnNames, Throwables.BiPredicate filter) throws E {
        return filter(columnNames, filter, size());
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param filter
     * @param max
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(Tuple2 columnNames, Throwables.BiPredicate filter, int max) throws E {
        return filter(columnNames, 0, size(), filter, max);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param filter
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(Tuple2 columnNames, int fromRowIndex, int toRowIndex, Throwables.BiPredicate filter)
            throws E {
        return filter(columnNames, fromRowIndex, toRowIndex, filter, size());
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param filter
     * @param max
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(Tuple2 columnNames, int fromRowIndex, int toRowIndex, Throwables.BiPredicate filter,
            int max) throws E {
        final List column1 = _columnList.get(checkColumnName(columnNames._1));
        final List column2 = _columnList.get(checkColumnName(columnNames._2));
        checkRowIndex(fromRowIndex, toRowIndex);
        N.checkArgNotNull(filter);

        @SuppressWarnings("rawtypes")
        final Throwables.BiPredicate filter2 = (Throwables.BiPredicate) filter;
        final int size = size();
        final int columnCount = _columnNameList.size();
        final List newColumnNameList = new ArrayList<>(_columnNameList);
        final List> newColumnList = new ArrayList<>(columnCount);

        for (int i = 0; i < columnCount; i++) {
            newColumnList.add(new ArrayList<>(N.min(max, (size == 0) ? 0 : ((int) (size * 0.8) + 1))));
        }

        final Properties newProperties = N.isNullOrEmpty(_properties) ? null : _properties.copy();

        if (size == 0 || max == 0) {
            return new RowDataSet(newColumnNameList, newColumnList, newProperties);
        }

        int count = max;

        for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
            if (filter2.test(column1.get(rowIndex), column2.get(rowIndex))) {
                if (--count < 0) {
                    break;
                }

                for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
                    newColumnList.get(columnIndex).add(_columnList.get(columnIndex).get(rowIndex));
                }
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList, newProperties);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param filter
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(Tuple3 columnNames, Throwables.TriPredicate filter) throws E {
        return filter(columnNames, filter, size());
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param filter
     * @param max
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(Tuple3 columnNames, Throwables.TriPredicate filter, int max) throws E {
        return filter(columnNames, 0, size(), filter, max);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param filter
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(Tuple3 columnNames, int fromRowIndex, int toRowIndex,
            Throwables.TriPredicate filter) throws E {
        return filter(columnNames, fromRowIndex, toRowIndex, filter, size());
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param filter
     * @param max
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(final Tuple3 columnNames, final int fromRowIndex, final int toRowIndex,
            final Throwables.TriPredicate filter, final int max) throws E {
        final List column1 = _columnList.get(checkColumnName(columnNames._1));
        final List column2 = _columnList.get(checkColumnName(columnNames._2));
        final List column3 = _columnList.get(checkColumnName(columnNames._3));

        checkRowIndex(fromRowIndex, toRowIndex);
        N.checkArgNotNull(filter);

        @SuppressWarnings("rawtypes")
        final Throwables.TriPredicate filter2 = (Throwables.TriPredicate) filter;
        final int size = size();
        final int columnCount = _columnNameList.size();
        final List newColumnNameList = new ArrayList<>(_columnNameList);
        final List> newColumnList = new ArrayList<>(columnCount);

        for (int i = 0; i < columnCount; i++) {
            newColumnList.add(new ArrayList<>(N.min(max, (size == 0) ? 0 : ((int) (size * 0.8) + 1))));
        }

        final Properties newProperties = N.isNullOrEmpty(_properties) ? null : _properties.copy();

        if (size == 0 || max == 0) {
            return new RowDataSet(newColumnNameList, newColumnList, newProperties);
        }

        int count = max;

        for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
            if (filter2.test(column1.get(rowIndex), column2.get(rowIndex), column3.get(rowIndex))) {
                if (--count < 0) {
                    break;
                }

                for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
                    newColumnList.get(columnIndex).add(_columnList.get(columnIndex).get(rowIndex));
                }
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList, newProperties);
    }

    /**
     *
     * @param 
     * @param 
     * @param columnName
     * @param filter
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(final String columnName, final Throwables.Predicate filter) throws E {
        return filter(columnName, filter, size());
    }

    /**
     *
     * @param 
     * @param 
     * @param columnName
     * @param filter
     * @param max
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(final String columnName, Throwables.Predicate filter, int max) throws E {
        return filter(columnName, 0, size(), filter, max);
    }

    /**
     *
     * @param 
     * @param 
     * @param columnName
     * @param fromRowIndex
     * @param toRowIndex
     * @param filter
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(final String columnName, final int fromRowIndex, final int toRowIndex,
            final Throwables.Predicate filter) throws E {
        return filter(columnName, fromRowIndex, toRowIndex, filter, size());
    }

    /**
     *
     * @param 
     * @param 
     * @param columnName
     * @param fromRowIndex
     * @param toRowIndex
     * @param filter
     * @param max
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(final String columnName, int fromRowIndex, int toRowIndex, Throwables.Predicate filter, int max)
            throws E {
        final int filterColumnIndex = checkColumnName(columnName);
        checkRowIndex(fromRowIndex, toRowIndex);
        N.checkArgNotNull(filter, "filter");
        N.checkArgNotNegative(max, "max");

        final int size = size();
        final int columnCount = _columnNameList.size();
        final List newColumnNameList = new ArrayList<>(_columnNameList);
        final List> newColumnList = new ArrayList<>(columnCount);

        for (int i = 0; i < columnCount; i++) {
            newColumnList.add(new ArrayList<>(N.min(max, (size == 0) ? 0 : ((int) (size * 0.8) + 1))));
        }

        final Properties newProperties = N.isNullOrEmpty(_properties) ? null : _properties.copy();

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList, newProperties);
        }

        for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
            if (filter.test((T) _columnList.get(filterColumnIndex).get(rowIndex))) {
                if (--max < 0) {
                    break;
                }

                for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
                    newColumnList.get(columnIndex).add(_columnList.get(columnIndex).get(rowIndex));
                }
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList, newProperties);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param filter
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(final Collection columnNames, final Throwables.Predicate filter)
            throws E {
        return filter(columnNames, filter, size());
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param filter
     * @param max
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(Collection columnNames, Throwables.Predicate filter, int max) throws E {
        return filter(columnNames, 0, size(), filter, max);
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param filter
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(final Collection columnNames, final int fromRowIndex, final int toRowIndex,
            final Throwables.Predicate filter) throws E {
        return filter(columnNames, fromRowIndex, toRowIndex, filter, size());
    }

    /**
     *
     * @param 
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param filter
     * @param max
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet filter(Collection columnNames, int fromRowIndex, int toRowIndex,
            Throwables.Predicate filter, int max) throws E {
        final int[] filterColumnIndexes = checkColumnName(columnNames);
        checkRowIndex(fromRowIndex, toRowIndex);
        N.checkArgNotNull(filter, "filter");
        N.checkArgNotNegative(max, "max");

        final int size = size();
        final int columnCount = _columnNameList.size();
        final List newColumnNameList = new ArrayList<>(_columnNameList);
        final List> newColumnList = new ArrayList<>(columnCount);

        for (int i = 0; i < columnCount; i++) {
            newColumnList.add(new ArrayList<>(N.min(max, (size == 0) ? 0 : ((int) (size * 0.8) + 1))));
        }

        final Properties newProperties = N.isNullOrEmpty(_properties) ? null : _properties.copy();

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList, newProperties);
        }

        final int filterColumnCount = filterColumnIndexes.length;
        final Object[] values = new Object[filterColumnCount];
        final DisposableObjArray disposableArray = DisposableObjArray.wrap(values);

        for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
            for (int i = 0; i < filterColumnCount; i++) {
                values[i] = _columnList.get(filterColumnIndexes[i]).get(rowIndex);
            }

            if (filter.test(disposableArray)) {
                if (--max < 0) {
                    break;
                }

                for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
                    newColumnList.get(columnIndex).add(_columnList.get(columnIndex).get(rowIndex));
                }
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList, newProperties);
    }

    /**
     *
     * @param 
     * @param fromColumnName
     * @param func
     * @param newColumnName
     * @param copyingColumnName
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet map(final String fromColumnName, final Throwables.Function func, final String newColumnName,
            final String copyingColumnName) throws E {
        return map(fromColumnName, func, newColumnName, Array.asList(copyingColumnName));
    }

    /**
     *
     * @param 
     * @param fromColumnName
     * @param func
     * @param newColumnName
     * @param copyingColumnNames
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet map(final String fromColumnName, final Throwables.Function func, final String newColumnName,
            final Collection copyingColumnNames) throws E {
        N.checkArgNotNull(func, "func");
        final int fromColumnIndex = checkColumnName(fromColumnName);
        final int[] copyingColumnIndices = N.isNullOrEmpty(copyingColumnNames) ? N.EMPTY_INT_ARRAY : checkColumnName(copyingColumnNames);

        final Throwables.Function mapper = (Throwables.Function) func;
        final int size = size();
        final int copyingColumnCount = copyingColumnIndices.length;

        final List mappedColumn = new ArrayList<>(size);

        for (Object val : _columnList.get(fromColumnIndex)) {
            mappedColumn.add(mapper.apply(val));
        }

        final List newColumnNameList = new ArrayList<>(copyingColumnCount + 1);
        newColumnNameList.add(newColumnName);

        final List> newColumnList = new ArrayList<>(copyingColumnCount + 1);
        newColumnList.add(mappedColumn);

        if (N.notNullOrEmpty(copyingColumnNames)) {
            newColumnNameList.addAll(copyingColumnNames);

            for (int columnIndex : copyingColumnIndices) {
                newColumnList.add(new ArrayList<>(_columnList.get(columnIndex)));
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param 
     * @param fromColumnNames
     * @param func
     * @param newColumnName
     * @param copyingColumnNames
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet map(final Tuple2 fromColumnNames, final Throwables.BiFunction func,
            final String newColumnName, final Collection copyingColumnNames) throws E {
        N.checkArgNotNull(func, "func");
        final List fromColumn1 = _columnList.get(checkColumnName(fromColumnNames._1));
        final List fromColumn2 = _columnList.get(checkColumnName(fromColumnNames._2));
        final int[] copyingColumnIndices = N.isNullOrEmpty(copyingColumnNames) ? N.EMPTY_INT_ARRAY : checkColumnName(copyingColumnNames);

        final Throwables.BiFunction mapper = (Throwables.BiFunction) func;
        final int size = size();
        final int copyingColumnCount = copyingColumnIndices.length;

        final List mappedColumn = new ArrayList<>(size);

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            mappedColumn.add(mapper.apply(fromColumn1.get(rowIndex), fromColumn2.get(rowIndex)));
        }

        final List newColumnNameList = new ArrayList<>(copyingColumnCount + 1);
        newColumnNameList.add(newColumnName);

        final List> newColumnList = new ArrayList<>(copyingColumnCount + 1);
        newColumnList.add(mappedColumn);

        if (N.notNullOrEmpty(copyingColumnNames)) {
            newColumnNameList.addAll(copyingColumnNames);

            for (int columnIndex : copyingColumnIndices) {
                newColumnList.add(new ArrayList<>(_columnList.get(columnIndex)));
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param 
     * @param fromColumnNames
     * @param func
     * @param newColumnName
     * @param copyingColumnNames
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet map(final Tuple3 fromColumnNames, final Throwables.TriFunction func,
            final String newColumnName, final Collection copyingColumnNames) throws E {
        N.checkArgNotNull(func, "func");
        final List fromColumn1 = _columnList.get(checkColumnName(fromColumnNames._1));
        final List fromColumn2 = _columnList.get(checkColumnName(fromColumnNames._2));
        final List fromColumn3 = _columnList.get(checkColumnName(fromColumnNames._3));

        final int[] copyingColumnIndices = N.isNullOrEmpty(copyingColumnNames) ? N.EMPTY_INT_ARRAY : checkColumnName(copyingColumnNames);

        final Throwables.TriFunction mapper = (Throwables.TriFunction) func;
        final int size = size();
        final int copyingColumnCount = copyingColumnIndices.length;

        final List mappedColumn = new ArrayList<>(size);

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            mappedColumn.add(mapper.apply(fromColumn1.get(rowIndex), fromColumn2.get(rowIndex), fromColumn3.get(rowIndex)));
        }

        final List newColumnNameList = new ArrayList<>(copyingColumnCount + 1);
        newColumnNameList.add(newColumnName);

        final List> newColumnList = new ArrayList<>(copyingColumnCount + 1);
        newColumnList.add(mappedColumn);

        if (N.notNullOrEmpty(copyingColumnNames)) {
            newColumnNameList.addAll(copyingColumnNames);

            for (int columnIndex : copyingColumnIndices) {
                newColumnList.add(new ArrayList<>(_columnList.get(columnIndex)));
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param 
     * @param fromColumnNames
     * @param func
     * @param newColumnName
     * @param copyingColumnNames
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet map(final Collection fromColumnNames, final Throwables.Function func,
            final String newColumnName, final Collection copyingColumnNames) throws E {
        N.checkArgNotNull(func, "func");
        final int[] fromColumnIndices = checkColumnName(fromColumnNames);
        final int[] copyingColumnIndices = N.isNullOrEmpty(copyingColumnNames) ? N.EMPTY_INT_ARRAY : checkColumnName(copyingColumnNames);

        final Throwables.Function mapper = (Throwables.Function) func;
        final int size = size();
        final int fromColumnCount = fromColumnIndices.length;
        final int copyingColumnCount = copyingColumnIndices.length;

        final List mappedColumn = new ArrayList<>(size);
        final Object[] tmpRow = new Object[fromColumnCount];
        final DisposableObjArray disposableArray = DisposableObjArray.wrap(tmpRow);

        for (int rowIndex = 0; rowIndex < size; rowIndex++) {
            for (int i = 0; i < fromColumnCount; i++) {
                tmpRow[i] = _columnList.get(fromColumnIndices[i]).get(rowIndex);
            }

            mappedColumn.add(mapper.apply(disposableArray));
        }

        final List newColumnNameList = new ArrayList<>(copyingColumnCount + 1);
        newColumnNameList.add(newColumnName);

        final List> newColumnList = new ArrayList<>(copyingColumnCount + 1);
        newColumnList.add(mappedColumn);

        if (N.notNullOrEmpty(copyingColumnNames)) {
            newColumnNameList.addAll(copyingColumnNames);

            for (int columnIndex : copyingColumnIndices) {
                newColumnList.add(new ArrayList<>(_columnList.get(columnIndex)));
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param 
     * @param fromColumnName
     * @param func
     * @param newColumnName
     * @param copyingColumnName
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet flatMap(final String fromColumnName, final Throwables.Function, E> func,
            final String newColumnName, final String copyingColumnName) throws E {
        return flatMap(fromColumnName, func, newColumnName, Array.asList(copyingColumnName));
    }

    /**
     *
     * @param 
     * @param fromColumnName
     * @param func
     * @param newColumnName
     * @param copyingColumnNames
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet flatMap(final String fromColumnName, final Throwables.Function, E> func,
            final String newColumnName, final Collection copyingColumnNames) throws E {
        N.checkArgNotNull(func, "func");
        final int fromColumnIndex = checkColumnName(fromColumnName);
        final int[] copyingColumnIndices = N.isNullOrEmpty(copyingColumnNames) ? N.EMPTY_INT_ARRAY : checkColumnName(copyingColumnNames);

        final Throwables.Function, E> mapper = (Throwables.Function, E>) func;
        final int size = size();
        final int copyingColumnCount = copyingColumnIndices.length;

        final List mappedColumn = new ArrayList<>(size);

        final List newColumnNameList = new ArrayList<>(copyingColumnCount + 1);
        newColumnNameList.add(newColumnName);

        final List> newColumnList = new ArrayList<>(copyingColumnCount + 1);
        newColumnList.add(mappedColumn);

        if (N.isNullOrEmpty(copyingColumnNames)) {
            Collection c = null;

            for (Object val : _columnList.get(fromColumnIndex)) {
                c = mapper.apply(val);

                if (N.notNullOrEmpty(c)) {
                    mappedColumn.addAll(c);
                }
            }
        } else {
            newColumnNameList.addAll(copyingColumnNames);

            for (int i = 0; i < copyingColumnCount; i++) {
                newColumnList.add(new ArrayList<>(size));
            }

            final List fromColumn = _columnList.get(fromColumnIndex);
            Collection c = null;
            List copyingColumn = null;
            Object val = null;

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                c = mapper.apply(fromColumn.get(rowIndex));

                if (N.notNullOrEmpty(c)) {
                    mappedColumn.addAll(c);

                    for (int i = 0; i < copyingColumnCount; i++) {
                        val = _columnList.get(copyingColumnIndices[i]).get(rowIndex);
                        copyingColumn = newColumnList.get(i + 1);

                        for (int j = 0, len = c.size(); j < len; j++) {
                            copyingColumn.add(val);
                        }
                    }
                }
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param 
     * @param fromColumnNames
     * @param func
     * @param newColumnName
     * @param copyingColumnNames
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet flatMap(final Tuple2 fromColumnNames,
            final Throwables.BiFunction, E> func, final String newColumnName, final Collection copyingColumnNames)
            throws E {
        N.checkArgNotNull(func, "func");
        final List fromColumn1 = _columnList.get(checkColumnName(fromColumnNames._1));
        final List fromColumn2 = _columnList.get(checkColumnName(fromColumnNames._2));

        final int[] copyingColumnIndices = N.isNullOrEmpty(copyingColumnNames) ? N.EMPTY_INT_ARRAY : checkColumnName(copyingColumnNames);

        final Throwables.BiFunction, E> mapper = (Throwables.BiFunction, E>) func;
        final int size = size();
        final int copyingColumnCount = copyingColumnIndices.length;

        final List mappedColumn = new ArrayList<>(size);

        final List newColumnNameList = new ArrayList<>(copyingColumnCount + 1);
        newColumnNameList.add(newColumnName);

        final List> newColumnList = new ArrayList<>(copyingColumnCount + 1);
        newColumnList.add(mappedColumn);

        if (N.isNullOrEmpty(copyingColumnNames)) {
            Collection c = null;

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                c = mapper.apply(fromColumn1.get(rowIndex), fromColumn2.get(rowIndex));

                if (N.notNullOrEmpty(c)) {
                    mappedColumn.addAll(c);
                }
            }
        } else {
            newColumnNameList.addAll(copyingColumnNames);

            for (int i = 0; i < copyingColumnCount; i++) {
                newColumnList.add(new ArrayList<>(size));
            }

            Collection c = null;
            List copyingColumn = null;
            Object val = null;

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                c = mapper.apply(fromColumn1.get(rowIndex), fromColumn2.get(rowIndex));

                if (N.notNullOrEmpty(c)) {
                    mappedColumn.addAll(c);

                    for (int i = 0; i < copyingColumnCount; i++) {
                        val = _columnList.get(copyingColumnIndices[i]).get(rowIndex);
                        copyingColumn = newColumnList.get(i + 1);

                        for (int j = 0, len = c.size(); j < len; j++) {
                            copyingColumn.add(val);
                        }
                    }
                }
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param 
     * @param fromColumnNames
     * @param func
     * @param newColumnName
     * @param copyingColumnNames
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet flatMap(final Tuple3 fromColumnNames,
            final Throwables.TriFunction, E> func, final String newColumnName, final Collection copyingColumnNames)
            throws E {
        N.checkArgNotNull(func, "func");
        final List fromColumn1 = _columnList.get(checkColumnName(fromColumnNames._1));
        final List fromColumn2 = _columnList.get(checkColumnName(fromColumnNames._2));
        final List fromColumn3 = _columnList.get(checkColumnName(fromColumnNames._3));

        final int[] copyingColumnIndices = N.isNullOrEmpty(copyingColumnNames) ? N.EMPTY_INT_ARRAY : checkColumnName(copyingColumnNames);

        final Throwables.TriFunction, E> mapper = (Throwables.TriFunction, E>) func;
        final int size = size();
        final int copyingColumnCount = copyingColumnIndices.length;

        final List mappedColumn = new ArrayList<>(size);

        final List newColumnNameList = new ArrayList<>(copyingColumnCount + 1);
        newColumnNameList.add(newColumnName);

        final List> newColumnList = new ArrayList<>(copyingColumnCount + 1);
        newColumnList.add(mappedColumn);

        if (N.isNullOrEmpty(copyingColumnNames)) {
            Collection c = null;

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                c = mapper.apply(fromColumn1.get(rowIndex), fromColumn2.get(rowIndex), fromColumn3.get(rowIndex));

                if (N.notNullOrEmpty(c)) {
                    mappedColumn.addAll(c);
                }
            }
        } else {
            newColumnNameList.addAll(copyingColumnNames);

            for (int i = 0; i < copyingColumnCount; i++) {
                newColumnList.add(new ArrayList<>(size));
            }

            Collection c = null;
            List copyingColumn = null;
            Object val = null;

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                c = mapper.apply(fromColumn1.get(rowIndex), fromColumn2.get(rowIndex), fromColumn3.get(rowIndex));

                if (N.notNullOrEmpty(c)) {
                    mappedColumn.addAll(c);

                    for (int i = 0; i < copyingColumnCount; i++) {
                        val = _columnList.get(copyingColumnIndices[i]).get(rowIndex);
                        copyingColumn = newColumnList.get(i + 1);

                        for (int j = 0, len = c.size(); j < len; j++) {
                            copyingColumn.add(val);
                        }
                    }
                }
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param 
     * @param fromColumnNames
     * @param func
     * @param newColumnName
     * @param copyingColumnNames
     * @return
     * @throws E the e
     */
    @Override
    public  DataSet flatMap(final Collection fromColumnNames,
            final Throwables.Function, E> func, final String newColumnName,
            final Collection copyingColumnNames) throws E {
        N.checkArgNotNull(func, "func");
        final int[] fromColumnIndices = checkColumnName(fromColumnNames);
        final int[] copyingColumnIndices = N.isNullOrEmpty(copyingColumnNames) ? N.EMPTY_INT_ARRAY : checkColumnName(copyingColumnNames);

        final Throwables.Function, E> mapper = (Throwables.Function, E>) func;
        final int size = size();
        final int fromColumnCount = fromColumnIndices.length;
        final int copyingColumnCount = copyingColumnIndices.length;

        final List mappedColumn = new ArrayList<>(size);

        final List newColumnNameList = new ArrayList<>(copyingColumnCount + 1);
        newColumnNameList.add(newColumnName);

        final List> newColumnList = new ArrayList<>(copyingColumnCount + 1);
        newColumnList.add(mappedColumn);

        final Object[] tmpRow = new Object[fromColumnCount];
        final DisposableObjArray disposableArray = DisposableObjArray.wrap(tmpRow);

        if (N.isNullOrEmpty(copyingColumnNames)) {
            Collection c = null;

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                for (int j = 0; j < fromColumnCount; j++) {
                    tmpRow[j] = _columnList.get(fromColumnIndices[j]).get(rowIndex);
                }

                c = mapper.apply(disposableArray);

                if (N.notNullOrEmpty(c)) {
                    mappedColumn.addAll(c);
                }
            }
        } else {
            newColumnNameList.addAll(copyingColumnNames);

            for (int i = 0; i < copyingColumnCount; i++) {
                newColumnList.add(new ArrayList<>(size));
            }

            Collection c = null;
            List copyingColumn = null;
            Object val = null;

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                for (int j = 0; j < fromColumnCount; j++) {
                    tmpRow[j] = _columnList.get(fromColumnIndices[j]).get(rowIndex);
                }

                c = mapper.apply(disposableArray);

                if (N.notNullOrEmpty(c)) {
                    mappedColumn.addAll(c);

                    for (int i = 0; i < copyingColumnCount; i++) {
                        val = _columnList.get(copyingColumnIndices[i]).get(rowIndex);
                        copyingColumn = newColumnList.get(i + 1);

                        for (int j = 0, len = c.size(); j < len; j++) {
                            copyingColumn.add(val);
                        }
                    }
                }
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     *
     * @return
     */
    @Override
    public DataSet copy() {
        return copy(_columnNameList, 0, size());
    }

    /**
     *
     * @param columnNames
     * @return
     */
    @Override
    public DataSet copy(final Collection columnNames) {
        return copy(columnNames, 0, size());
    }

    /**
     *
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public DataSet copy(final int fromRowIndex, final int toRowIndex) {
        return copy(_columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public DataSet copy(final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        return copy(columnNames, fromRowIndex, toRowIndex, true);
    }

    /**
     *
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param copyProperties
     * @return
     */
    private RowDataSet copy(final Collection columnNames, final int fromRowIndex, final int toRowIndex, final boolean copyProperties) {
        checkRowIndex(fromRowIndex, toRowIndex);

        final List newColumnNameList = new ArrayList<>(columnNames);
        final List> newColumnList = new ArrayList<>(newColumnNameList.size());

        if (fromRowIndex == 0 && toRowIndex == size()) {
            for (String columnName : newColumnNameList) {
                newColumnList.add(new ArrayList<>(_columnList.get(checkColumnName(columnName))));
            }
        } else {
            for (String columnName : newColumnNameList) {
                newColumnList.add(new ArrayList<>(_columnList.get(checkColumnName(columnName)).subList(fromRowIndex, toRowIndex)));
            }
        }

        final Properties newProperties = copyProperties && N.notNullOrEmpty(_properties) ? _properties.copy() : null;

        return new RowDataSet(newColumnNameList, newColumnList, newProperties);
    }

    /**
     *
     *
     * @return
     */
    @Override
    public DataSet clone() { //NOSONAR
        return clone(this._isFrozen);
    }

    /**
     *
     * @param freeze
     * @return
     */
    @Override
    public DataSet clone(boolean freeze) { //NOSONAR
        RowDataSet dataSet = null;

        if (kryoParser != null) {
            dataSet = kryoParser.clone(this);
        } else {
            dataSet = jsonParser.deserialize(RowDataSet.class, jsonParser.serialize(this));
        }

        dataSet._isFrozen = freeze;
        return dataSet;
    }

    /**
     *
     * @param right
     * @param columnName
     * @param refColumnName
     * @return
     */
    @Override
    public DataSet innerJoin(final DataSet right, final String columnName, final String refColumnName) {
        final Map onColumnNames = N.asMap(columnName, refColumnName);

        return innerJoin(right, onColumnNames);
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @return
     */
    @Override
    public DataSet innerJoin(final DataSet right, final Map onColumnNames) {
        return join(right, onColumnNames, false);
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @param newColumnName
     * @param newColumnClass
     * @return
     */
    @Override
    public DataSet innerJoin(final DataSet right, final Map onColumnNames, final String newColumnName, final Class newColumnClass) {
        return join(right, onColumnNames, newColumnName, newColumnClass, false);
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @param newColumnName
     * @param newColumnClass
     * @param collSupplier
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public DataSet innerJoin(final DataSet right, final Map onColumnNames, final String newColumnName, final Class newColumnClass,
            final IntFunction collSupplier) {
        return join(right, onColumnNames, newColumnName, newColumnClass, collSupplier, false);
    }

    /**
     *
     * @param right
     * @param columnName
     * @param refColumnName
     * @return
     */
    @Override
    public DataSet leftJoin(final DataSet right, final String columnName, final String refColumnName) {
        final Map onColumnNames = N.asMap(columnName, refColumnName);

        return leftJoin(right, onColumnNames);
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @return
     */
    @Override
    public DataSet leftJoin(final DataSet right, final Map onColumnNames) {
        return join(right, onColumnNames, true);
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @param isLeftJoin
     * @return
     */
    private DataSet join(final DataSet right, final Map onColumnNames, final boolean isLeftJoin) {
        checkJoinOnColumnNames(onColumnNames);

        if (onColumnNames.size() == 1) {
            final Map.Entry onColumnEntry = onColumnNames.entrySet().iterator().next();
            final int leftJoinColumnIndex = checkColumnName(onColumnEntry.getKey());
            final int rightJoinColumnIndex = checkRefColumnName(right, onColumnEntry.getValue());
            final List rightColumnNames = getRightColumnNames(right, onColumnEntry.getValue());
            final List newColumnNameList = new ArrayList<>(_columnNameList.size() + rightColumnNames.size());
            final List> newColumnList = new ArrayList<>(_columnNameList.size() + rightColumnNames.size());

            initNewColumnList(newColumnNameList, newColumnList, rightColumnNames);

            final List leftJoinColumn = this.getColumn(leftJoinColumnIndex);
            final List rightJoinColumn = right.getColumn(rightJoinColumnIndex);
            final Map> joinColumnRightRowIndexMap = new HashMap<>();
            Object hashKey = null;

            for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
                hashKey = getHashKey(rightJoinColumn.get(rightRowIndex));
                putRowIndex(joinColumnRightRowIndexMap, hashKey, rightRowIndex);
            }

            final int[] rightColumnIndexes = right.getColumnIndexes(rightColumnNames);
            List rightRowIndexList = null;

            for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
                hashKey = getHashKey(leftJoinColumn.get(leftRowIndex));
                rightRowIndexList = joinColumnRightRowIndexMap.get(hashKey);

                join(newColumnList, right, isLeftJoin, leftRowIndex, rightRowIndexList, rightColumnIndexes);
            }

            return new RowDataSet(newColumnNameList, newColumnList);
        }
        final int[] leftJoinColumnIndexes = new int[onColumnNames.size()];
        final int[] rightJoinColumnIndexes = new int[onColumnNames.size()];
        final List rightColumnNames = new ArrayList<>(right.columnNameList());

        initColumnIndexes(leftJoinColumnIndexes, rightJoinColumnIndexes, right, onColumnNames, rightColumnNames);

        final List newColumnNameList = new ArrayList<>(_columnNameList.size() + rightColumnNames.size());
        final List> newColumnList = new ArrayList<>(_columnNameList.size() + rightColumnNames.size());

        initNewColumnList(newColumnNameList, newColumnList, rightColumnNames);

        final Map> joinColumnRightRowIndexMap = new ArrayHashMap<>();
        Object[] row = null;

        for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
            row = row == null ? Objectory.createObjectArray(rightJoinColumnIndexes.length) : row;

            for (int i = 0, len = rightJoinColumnIndexes.length; i < len; i++) {
                row[i] = right.get(rightRowIndex, rightJoinColumnIndexes[i]);
            }

            row = putRowIndex(joinColumnRightRowIndexMap, row, rightRowIndex);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        final int[] rightColumnIndexes = right.getColumnIndexes(rightColumnNames);
        row = Objectory.createObjectArray(leftJoinColumnIndexes.length);
        List rightRowIndexList = null;

        for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
            for (int i = 0, len = leftJoinColumnIndexes.length; i < len; i++) {
                row[i] = this.get(leftRowIndex, leftJoinColumnIndexes[i]);
            }

            rightRowIndexList = joinColumnRightRowIndexMap.get(row);

            join(newColumnList, right, isLeftJoin, leftRowIndex, rightRowIndexList, rightColumnIndexes);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        for (Object[] a : joinColumnRightRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param isLeftJoin
     * @param leftRowIndex
     * @param rightRowIndexList
     * @param rightColumnIndexes
     */
    private void join(final List> newColumnList, final DataSet right, final boolean isLeftJoin, int leftRowIndex, List rightRowIndexList,
            final int[] rightColumnIndexes) {
        if (N.notNullOrEmpty(rightRowIndexList)) {
            for (int rightRowIndex : rightRowIndexList) {
                for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                    newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
                }

                for (int i = 0, leftColumnLength = _columnNameList.size(), rightColumnLength = rightColumnIndexes.length; i < rightColumnLength; i++) {
                    newColumnList.get(leftColumnLength + i).add(right.get(rightRowIndex, rightColumnIndexes[i]));
                }
            }
        } else if (isLeftJoin) {
            for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
            }

            for (int i = 0, leftColumnLength = _columnNameList.size(), rightColumnLength = rightColumnIndexes.length; i < rightColumnLength; i++) {
                newColumnList.get(leftColumnLength + i).add(null);
            }
        }
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @param newColumnName
     * @param newColumnClass
     * @param isLeftJoin
     * @return
     */
    private DataSet join(final DataSet right, final Map onColumnNames, final String newColumnName, final Class newColumnClass,
            final boolean isLeftJoin) {
        checkJoinOnColumnNames(onColumnNames);
        checkNewColumnName(newColumnName);

        if (onColumnNames.size() == 1) {
            final Map.Entry onColumnEntry = onColumnNames.entrySet().iterator().next();
            final int leftJoinColumnIndex = checkColumnName(onColumnEntry.getKey());
            final int rightJoinColumnIndex = checkRefColumnName(right, onColumnEntry.getValue());
            final List newColumnNameList = new ArrayList<>(_columnNameList.size() + 1);
            final List> newColumnList = new ArrayList<>(_columnNameList.size() + 1);

            initNewColumnList(newColumnNameList, newColumnList, newColumnName);

            final List leftJoinColumn = this.getColumn(leftJoinColumnIndex);
            final List rightJoinColumn = right.getColumn(rightJoinColumnIndex);
            final Map> joinColumnRightRowIndexMap = new HashMap<>();
            Object hashKey = null;

            for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
                hashKey = getHashKey(rightJoinColumn.get(rightRowIndex));
                putRowIndex(joinColumnRightRowIndexMap, hashKey, rightRowIndex);
            }

            final int newColumnIndex = newColumnList.size() - 1;
            List rightRowIndexList = null;

            for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
                hashKey = getHashKey(leftJoinColumn.get(leftRowIndex));
                rightRowIndexList = joinColumnRightRowIndexMap.get(hashKey);

                join(newColumnList, right, isLeftJoin, newColumnClass, newColumnIndex, leftRowIndex, rightRowIndexList);
            }

            return new RowDataSet(newColumnNameList, newColumnList);
        }
        final int[] leftJoinColumnIndexes = new int[onColumnNames.size()];
        final int[] rightJoinColumnIndexes = new int[onColumnNames.size()];

        initColumnIndexes(leftJoinColumnIndexes, rightJoinColumnIndexes, right, onColumnNames);

        final List newColumnNameList = new ArrayList<>(_columnNameList.size() + 1);
        final List> newColumnList = new ArrayList<>(_columnNameList.size() + 1);
        initNewColumnList(newColumnNameList, newColumnList, newColumnName);

        final Map> joinColumnRightRowIndexMap = new ArrayHashMap<>();
        Object[] row = null;

        for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
            row = row == null ? Objectory.createObjectArray(rightJoinColumnIndexes.length) : row;

            for (int i = 0, len = rightJoinColumnIndexes.length; i < len; i++) {
                row[i] = right.get(rightRowIndex, rightJoinColumnIndexes[i]);
            }

            row = putRowIndex(joinColumnRightRowIndexMap, row, rightRowIndex);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        final int newColumnIndex = newColumnList.size() - 1;
        List rightRowIndexList = null;
        row = Objectory.createObjectArray(leftJoinColumnIndexes.length);

        for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
            for (int i = 0, len = leftJoinColumnIndexes.length; i < len; i++) {
                row[i] = this.get(leftRowIndex, leftJoinColumnIndexes[i]);
            }

            rightRowIndexList = joinColumnRightRowIndexMap.get(row);

            join(newColumnList, right, isLeftJoin, newColumnClass, newColumnIndex, leftRowIndex, rightRowIndexList);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        for (Object[] a : joinColumnRightRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param isLeftJoin
     * @param newColumnClass
     * @param newColumnIndex
     * @param leftRowIndex
     * @param rightRowIndexList
     */
    private void join(final List> newColumnList, final DataSet right, final boolean isLeftJoin, final Class newColumnClass,
            final int newColumnIndex, int leftRowIndex, List rightRowIndexList) {
        if (N.notNullOrEmpty(rightRowIndexList)) {
            for (int rightRowIndex : rightRowIndexList) {
                for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                    newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
                }

                newColumnList.get(newColumnIndex).add(right.getRow(newColumnClass, rightRowIndex));
            }
        } else if (isLeftJoin) {
            for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
            }

            newColumnList.get(newColumnIndex).add(null);
        }
    }

    /**
     * Check join on column names.
     *
     * @param onColumnNames
     */
    private void checkJoinOnColumnNames(final Map onColumnNames) {
        if (N.isNullOrEmpty(onColumnNames)) {
            throw new IllegalArgumentException("The joining column names can't be null or empty");
        }
    }

    /**
     * Check ref column name.
     *
     * @param right
     * @param refColumnName
     * @return
     */
    private int checkRefColumnName(final DataSet right, final String refColumnName) {
        final int rightJoinColumnIndex = right.getColumnIndex(refColumnName);

        if (rightJoinColumnIndex < 0) {
            throw new IllegalArgumentException("The specified column: " + refColumnName + " is not included in the right DataSet " + right.columnNameList());
        }

        return rightJoinColumnIndex;
    }

    /**
     * Check new column name.
     *
     * @param newColumnName
     */
    private void checkNewColumnName(final String newColumnName) {
        if (this.containsColumn(newColumnName)) {
            throw new IllegalArgumentException("The new column: " + newColumnName + " is already included in this DataSet: " + _columnNameList);
        }
    }

    /**
     * Gets the right column names.
     *
     * @param right
     * @param refColumnName
     * @return
     */
    private List getRightColumnNames(final DataSet right, final String refColumnName) {
        final List rightColumnNames = new ArrayList<>(right.columnNameList());

        if (this.containsColumn(refColumnName)) {
            rightColumnNames.remove(refColumnName);
        }

        return rightColumnNames;
    }

    /**
     * Inits the column indexes.
     *
     * @param leftJoinColumnIndexes
     * @param rightJoinColumnIndexes
     * @param right
     * @param onColumnNames
     * @param rightColumnNames
     */
    private void initColumnIndexes(final int[] leftJoinColumnIndexes, final int[] rightJoinColumnIndexes, final DataSet right,
            final Map onColumnNames, final List rightColumnNames) {
        int i = 0;
        for (Map.Entry entry : onColumnNames.entrySet()) {
            leftJoinColumnIndexes[i] = checkColumnName(entry.getKey());
            rightJoinColumnIndexes[i] = right.getColumnIndex(entry.getValue());

            if (rightJoinColumnIndexes[i] < 0) {
                throw new IllegalArgumentException(
                        "The specified column: " + entry.getValue() + " is not included in the right DataSet " + right.columnNameList());
            }

            if (entry.getKey().equals(entry.getValue())) {
                rightColumnNames.remove(entry.getValue());
            }

            i++;
        }
    }

    /**
     * Inits the column indexes.
     *
     * @param leftJoinColumnIndexes
     * @param rightJoinColumnIndexes
     * @param right
     * @param onColumnNames
     */
    private void initColumnIndexes(final int[] leftJoinColumnIndexes, final int[] rightJoinColumnIndexes, final DataSet right,
            final Map onColumnNames) {
        int i = 0;
        for (Map.Entry entry : onColumnNames.entrySet()) {
            leftJoinColumnIndexes[i] = checkColumnName(entry.getKey());
            rightJoinColumnIndexes[i] = right.getColumnIndex(entry.getValue());

            if (rightJoinColumnIndexes[i] < 0) {
                throw new IllegalArgumentException(
                        "The specified column: " + entry.getValue() + " is not included in the right DataSet " + right.columnNameList());
            }

            i++;
        }
    }

    /**
     * Inits the new column list.
     *
     * @param newColumnNameList
     * @param newColumnList
     * @param rightColumnNames
     */
    private void initNewColumnList(final List newColumnNameList, final List> newColumnList, final List rightColumnNames) {
        for (int i = 0, len = _columnNameList.size(); i < len; i++) {
            newColumnNameList.add(_columnNameList.get(i));
            newColumnList.add(new ArrayList<>());
        }

        for (String rightColumnName : rightColumnNames) {
            if (this.containsColumn(rightColumnName)) {
                throw new IllegalArgumentException(
                        "The column in right DataSet: " + rightColumnName + " is already included in this DataSet: " + _columnNameList);
            }

            newColumnNameList.add(rightColumnName);
            newColumnList.add(new ArrayList<>());
        }
    }

    /**
     * Inits the new column list.
     *
     * @param newColumnNameList
     * @param newColumnList
     * @param newColumnName
     */
    private void initNewColumnList(final List newColumnNameList, final List> newColumnList, final String newColumnName) {
        for (int i = 0, len = _columnNameList.size(); i < len; i++) {
            newColumnNameList.add(_columnNameList.get(i));
            newColumnList.add(new ArrayList<>());
        }

        newColumnNameList.add(newColumnName);
        newColumnList.add(new ArrayList<>());
    }

    /**
     * Put row index.
     *
     * @param joinColumnRightRowIndexMap
     * @param hashKey
     * @param rightRowIndex
     */
    private void putRowIndex(final Map> joinColumnRightRowIndexMap, Object hashKey, int rightRowIndex) {
        List rightRowIndexList = joinColumnRightRowIndexMap.get(hashKey);

        if (rightRowIndexList == null) {
            joinColumnRightRowIndexMap.put(hashKey, N.asList(rightRowIndex));
        } else {
            rightRowIndexList.add(rightRowIndex);
        }
    }

    /**
     * Put row index.
     *
     * @param joinColumnRightRowIndexMap
     * @param row
     * @param rightRowIndex
     * @return
     */
    private Object[] putRowIndex(final Map> joinColumnRightRowIndexMap, Object[] row, int rightRowIndex) {
        List rightRowIndexList = joinColumnRightRowIndexMap.get(row);

        if (rightRowIndexList == null) {
            joinColumnRightRowIndexMap.put(row, N.asList(rightRowIndex));
            row = null;
        } else {
            rightRowIndexList.add(rightRowIndex);
        }

        return row;
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @param newColumnName
     * @param newColumnClass
     * @return
     */
    @Override
    public DataSet leftJoin(final DataSet right, final Map onColumnNames, final String newColumnName, final Class newColumnClass) {
        return join(right, onColumnNames, newColumnName, newColumnClass, true);
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @param newColumnName
     * @param newColumnClass
     * @param collSupplier
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public DataSet leftJoin(final DataSet right, final Map onColumnNames, final String newColumnName, final Class newColumnClass,
            final IntFunction collSupplier) {
        return join(right, onColumnNames, newColumnName, newColumnClass, collSupplier, true);
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @param newColumnName
     * @param newColumnClass
     * @param collSupplier
     * @param isLeftJoin
     * @return
     */
    @SuppressWarnings("rawtypes")
    private DataSet join(final DataSet right, final Map onColumnNames, final String newColumnName, final Class newColumnClass,
            final IntFunction collSupplier, final boolean isLeftJoin) {
        checkJoinOnColumnNames(onColumnNames);
        checkNewColumnName(newColumnName);
        N.checkArgNotNull(collSupplier);

        if (onColumnNames.size() == 1) {
            final Map.Entry onColumnEntry = onColumnNames.entrySet().iterator().next();
            final int leftJoinColumnIndex = checkColumnName(onColumnEntry.getKey());
            final int rightJoinColumnIndex = checkRefColumnName(right, onColumnEntry.getValue());

            final List newColumnNameList = new ArrayList<>(_columnNameList.size() + 1);
            final List> newColumnList = new ArrayList<>(_columnNameList.size() + 1);

            initNewColumnList(newColumnNameList, newColumnList, newColumnName);

            final List leftJoinColumn = this.getColumn(leftJoinColumnIndex);
            final List rightJoinColumn = right.getColumn(rightJoinColumnIndex);
            final Map> joinColumnRightRowIndexMap = new HashMap<>();
            List rightRowIndexList = null;
            Object hashKey = null;

            for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
                hashKey = getHashKey(rightJoinColumn.get(rightRowIndex));
                putRowIndex(joinColumnRightRowIndexMap, hashKey, rightRowIndex);
            }

            final int newColumnIndex = newColumnList.size() - 1;

            for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
                hashKey = getHashKey(leftJoinColumn.get(leftRowIndex));
                rightRowIndexList = joinColumnRightRowIndexMap.get(hashKey);

                join(newColumnList, right, isLeftJoin, newColumnClass, collSupplier, newColumnIndex, leftRowIndex, rightRowIndexList);
            }

            return new RowDataSet(newColumnNameList, newColumnList);
        }
        final int[] leftJoinColumnIndexes = new int[onColumnNames.size()];
        final int[] rightJoinColumnIndexes = new int[onColumnNames.size()];

        initColumnIndexes(leftJoinColumnIndexes, rightJoinColumnIndexes, right, onColumnNames);

        final List newColumnNameList = new ArrayList<>(_columnNameList.size() + 1);
        final List> newColumnList = new ArrayList<>(_columnNameList.size() + 1);
        initNewColumnList(newColumnNameList, newColumnList, newColumnName);

        final Map> joinColumnRightRowIndexMap = new ArrayHashMap<>();
        List rightRowIndexList = null;
        Object[] row = null;

        for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
            row = row == null ? Objectory.createObjectArray(rightJoinColumnIndexes.length) : row;

            for (int i = 0, len = rightJoinColumnIndexes.length; i < len; i++) {
                row[i] = right.get(rightRowIndex, rightJoinColumnIndexes[i]);
            }

            row = putRowIndex(joinColumnRightRowIndexMap, row, rightRowIndex);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        final int newColumnIndex = newColumnList.size() - 1;
        row = Objectory.createObjectArray(leftJoinColumnIndexes.length);

        for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
            for (int i = 0, len = leftJoinColumnIndexes.length; i < len; i++) {
                row[i] = get(leftRowIndex, leftJoinColumnIndexes[i]);
            }

            rightRowIndexList = joinColumnRightRowIndexMap.get(row);

            join(newColumnList, right, isLeftJoin, newColumnClass, collSupplier, newColumnIndex, leftRowIndex, rightRowIndexList);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        for (Object[] a : joinColumnRightRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param isLeftJoin
     * @param newColumnClass
     * @param collSupplier
     * @param newColumnIndex
     * @param leftRowIndex
     * @param rightRowIndexList
     */
    @SuppressWarnings("rawtypes")
    private void join(final List> newColumnList, final DataSet right, final boolean isLeftJoin, final Class newColumnClass,
            final IntFunction collSupplier, final int newColumnIndex, int leftRowIndex, List rightRowIndexList) {
        if (N.notNullOrEmpty(rightRowIndexList)) {
            for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
            }

            final Collection coll = collSupplier.apply(rightRowIndexList.size());

            for (int rightRowIndex : rightRowIndexList) {
                coll.add(right.getRow(newColumnClass, rightRowIndex));
            }

            newColumnList.get(newColumnIndex).add(coll);
        } else if (isLeftJoin) {
            for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
            }

            newColumnList.get(newColumnIndex).add(null);
        }
    }

    /**
     *
     * @param right
     * @param columnName
     * @param refColumnName
     * @return
     */
    @Override
    public DataSet rightJoin(final DataSet right, final String columnName, final String refColumnName) {
        final Map onColumnNames = N.asMap(columnName, refColumnName);

        return rightJoin(right, onColumnNames);
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @return
     */
    @Override
    public DataSet rightJoin(final DataSet right, final Map onColumnNames) {
        checkJoinOnColumnNames(onColumnNames);

        if (onColumnNames.size() == 0) {
            final Map.Entry onColumnEntry = onColumnNames.entrySet().iterator().next();
            final int leftJoinColumnIndex = checkColumnName(onColumnEntry.getKey());
            final int rightJoinColumnIndex = checkRefColumnName(right, onColumnEntry.getValue());
            final List leftColumnNames = getLeftColumnNamesForRightJoin(onColumnEntry.getValue());
            final List rightColumnNames = right.columnNameList();

            final List newColumnNameList = new ArrayList<>(leftColumnNames.size() + rightColumnNames.size());
            final List> newColumnList = new ArrayList<>(leftColumnNames.size() + rightColumnNames.size());

            initNewColumnListForRightJoin(newColumnNameList, newColumnList, right, leftColumnNames, rightColumnNames);

            final List leftJoinColumn = this.getColumn(leftJoinColumnIndex);
            final List rightJoinColumn = right.getColumn(rightJoinColumnIndex);
            final Map> joinColumnLeftRowIndexMap = new HashMap<>();
            List leftRowIndexList = null;
            Object hashKey = null;

            for (int leftRowIndex = 0, leftDataSetSize = this.size(); leftRowIndex < leftDataSetSize; leftRowIndex++) {
                hashKey = getHashKey(leftJoinColumn.get(leftRowIndex));
                putRowIndex(joinColumnLeftRowIndexMap, hashKey, leftRowIndex);
            }

            final int[] leftColumnIndexes = this.getColumnIndexes(leftColumnNames);
            final int[] rightColumnIndexes = right.getColumnIndexes(rightColumnNames);

            for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
                hashKey = getHashKey(rightJoinColumn.get(rightRowIndex));
                leftRowIndexList = joinColumnLeftRowIndexMap.get(hashKey);

                rightJoin(newColumnList, right, rightRowIndex, rightColumnIndexes, leftColumnIndexes, leftRowIndexList);
            }

            return new RowDataSet(newColumnNameList, newColumnList);
        }
        final List leftColumnNames = new ArrayList<>(_columnNameList);
        final List rightColumnNames = right.columnNameList();
        final int[] leftJoinColumnIndexes = new int[onColumnNames.size()];
        final int[] rightJoinColumnIndexes = new int[onColumnNames.size()];

        initColumnIndexesForRightJoin(leftJoinColumnIndexes, rightJoinColumnIndexes, right, onColumnNames, leftColumnNames);

        final List newColumnNameList = new ArrayList<>(leftColumnNames.size() + rightColumnNames.size());
        final List> newColumnList = new ArrayList<>(leftColumnNames.size() + rightColumnNames.size());

        initNewColumnListForRightJoin(newColumnNameList, newColumnList, right, leftColumnNames, rightColumnNames);

        final Map> joinColumnLeftRowIndexMap = new ArrayHashMap<>();
        Object[] row = null;

        for (int leftRowIndex = 0, leftDataSetSize = this.size(); leftRowIndex < leftDataSetSize; leftRowIndex++) {
            row = row == null ? Objectory.createObjectArray(leftJoinColumnIndexes.length) : row;

            for (int i = 0, len = leftJoinColumnIndexes.length; i < len; i++) {
                row[i] = this.get(leftRowIndex, leftJoinColumnIndexes[i]);
            }

            row = putRowIndex(joinColumnLeftRowIndexMap, row, leftRowIndex);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        final int[] leftColumnIndexes = this.getColumnIndexes(leftColumnNames);
        final int[] rightColumnIndexes = right.getColumnIndexes(rightColumnNames);
        row = Objectory.createObjectArray(rightJoinColumnIndexes.length);
        List leftRowIndexList = null;

        for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
            for (int i = 0, len = rightJoinColumnIndexes.length; i < len; i++) {
                row[i] = right.get(rightRowIndex, rightJoinColumnIndexes[i]);
            }

            leftRowIndexList = joinColumnLeftRowIndexMap.get(row);

            rightJoin(newColumnList, right, rightRowIndex, rightColumnIndexes, leftColumnIndexes, leftRowIndexList);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        for (Object[] a : joinColumnLeftRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param rightRowIndex
     * @param rightColumnIndexes
     * @param leftColumnIndexes
     * @param leftRowIndexList
     */
    private void rightJoin(final List> newColumnList, final DataSet right, int rightRowIndex, final int[] rightColumnIndexes,
            final int[] leftColumnIndexes, List leftRowIndexList) {
        if (N.notNullOrEmpty(leftRowIndexList)) {
            for (int leftRowIndex : leftRowIndexList) {
                for (int i = 0, leftColumnLength = leftColumnIndexes.length; i < leftColumnLength; i++) {
                    newColumnList.get(i).add(this.get(leftRowIndex, leftColumnIndexes[i]));
                }

                for (int i = 0, leftColumnLength = leftColumnIndexes.length, rightColumnLength = rightColumnIndexes.length; i < rightColumnLength; i++) {
                    newColumnList.get(i + leftColumnLength).add(right.get(rightRowIndex, rightColumnIndexes[i]));
                }
            }
        } else {
            for (int i = 0, leftColumnLength = leftColumnIndexes.length; i < leftColumnLength; i++) {
                newColumnList.get(i).add(null);
            }

            for (int i = 0, leftColumnLength = leftColumnIndexes.length, rightColumnLength = rightColumnIndexes.length; i < rightColumnLength; i++) {
                newColumnList.get(i + leftColumnLength).add(right.get(rightRowIndex, rightColumnIndexes[i]));
            }
        }
    }

    /**
     * Inits the column indexes for right join.
     *
     * @param leftJoinColumnIndexes
     * @param rightJoinColumnIndexes
     * @param right
     * @param onColumnNames
     * @param leftColumnNames
     */
    private void initColumnIndexesForRightJoin(final int[] leftJoinColumnIndexes, final int[] rightJoinColumnIndexes, final DataSet right,
            final Map onColumnNames, final List leftColumnNames) {
        int i = 0;
        for (Map.Entry entry : onColumnNames.entrySet()) {
            leftJoinColumnIndexes[i] = checkColumnName(entry.getKey());
            rightJoinColumnIndexes[i] = right.getColumnIndex(entry.getValue());

            if (rightJoinColumnIndexes[i] < 0) {
                throw new IllegalArgumentException(
                        "The specified column: " + entry.getValue() + " is not included in the right DataSet " + right.columnNameList());
            }

            if (entry.getKey().equals(entry.getValue())) {
                leftColumnNames.remove(entry.getKey());
            }

            i++;
        }
    }

    /**
     * Inits the new column list for right join.
     *
     * @param newColumnNameList
     * @param newColumnList
     * @param right
     * @param leftColumnNames
     * @param rightColumnNames
     */
    private void initNewColumnListForRightJoin(final List newColumnNameList, final List> newColumnList, final DataSet right,
            final List leftColumnNames, final List rightColumnNames) {
        for (String leftColumnName : leftColumnNames) {
            if (right.containsColumn(leftColumnName)) {
                throw new IllegalArgumentException(
                        "The column in this DataSet: " + leftColumnName + " is already included in right DataSet: " + rightColumnNames);
            }

            newColumnNameList.add(leftColumnName);
            newColumnList.add(new ArrayList<>());
        }

        for (String rightColumnName : rightColumnNames) {
            newColumnNameList.add(rightColumnName);
            newColumnList.add(new ArrayList<>());
        }
    }

    /**
     * Gets the left column names for right join.
     *
     * @param refColumnName
     * @return
     */
    private List getLeftColumnNamesForRightJoin(final String refColumnName) {
        final List leftColumnNames = new ArrayList<>(_columnNameList);

        if (this.containsColumn(refColumnName)) {
            leftColumnNames.remove(refColumnName);
        }

        return leftColumnNames;
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @param newColumnName
     * @param newColumnClass
     * @return
     */
    @Override
    public DataSet rightJoin(final DataSet right, final Map onColumnNames, final String newColumnName, final Class newColumnClass) {
        checkJoinOnColumnNames(onColumnNames);
        checkNewColumnName(newColumnName);

        if (onColumnNames.size() == 1) {
            final Map.Entry onColumnEntry = onColumnNames.entrySet().iterator().next();
            final int leftJoinColumnIndex = checkColumnName(onColumnEntry.getKey());
            final int rightJoinColumnIndex = checkRefColumnName(right, onColumnEntry.getValue());

            final List leftColumnNames = new ArrayList<>(_columnNameList);
            final List newColumnNameList = new ArrayList<>(leftColumnNames.size() + 1);
            final List> newColumnList = new ArrayList<>(leftColumnNames.size() + 1);

            initNewColumnListForRightJoin(newColumnNameList, newColumnList, leftColumnNames, newColumnName);

            final List leftJoinColumn = this.getColumn(leftJoinColumnIndex);
            final List rightJoinColumn = right.getColumn(rightJoinColumnIndex);
            final Map> joinColumnLeftRowIndexMap = new HashMap<>();
            Object hashKey = null;

            for (int leftRowIndex = 0, leftDataSetSize = this.size(); leftRowIndex < leftDataSetSize; leftRowIndex++) {
                hashKey = getHashKey(leftJoinColumn.get(leftRowIndex));
                putRowIndex(joinColumnLeftRowIndexMap, hashKey, leftRowIndex);
            }

            final int newColumnIndex = newColumnList.size() - 1;
            final int[] leftColumnIndexes = this.getColumnIndexes(leftColumnNames);
            List leftRowIndexList = null;

            for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
                hashKey = getHashKey(rightJoinColumn.get(rightRowIndex));
                leftRowIndexList = joinColumnLeftRowIndexMap.get(hashKey);

                rightJoin(newColumnList, right, newColumnClass, newColumnIndex, rightRowIndex, leftRowIndexList, leftColumnIndexes);
            }

            return new RowDataSet(newColumnNameList, newColumnList);
        }
        final List leftColumnNames = new ArrayList<>(_columnNameList);
        final int[] leftJoinColumnIndexes = new int[onColumnNames.size()];
        final int[] rightJoinColumnIndexes = new int[onColumnNames.size()];

        initColumnIndexes(leftJoinColumnIndexes, rightJoinColumnIndexes, right, onColumnNames);

        final List newColumnNameList = new ArrayList<>(leftColumnNames.size() + 1);
        final List> newColumnList = new ArrayList<>(leftColumnNames.size() + 1);

        initNewColumnListForRightJoin(newColumnNameList, newColumnList, leftColumnNames, newColumnName);

        final Map> joinColumnLeftRowIndexMap = new ArrayHashMap<>();
        Object[] row = null;

        for (int leftRowIndex = 0, leftDataSetSize = this.size(); leftRowIndex < leftDataSetSize; leftRowIndex++) {
            row = row == null ? Objectory.createObjectArray(leftJoinColumnIndexes.length) : row;

            for (int i = 0, len = leftJoinColumnIndexes.length; i < len; i++) {
                row[i] = this.get(leftRowIndex, leftJoinColumnIndexes[i]);
            }

            row = putRowIndex(joinColumnLeftRowIndexMap, row, leftRowIndex);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        final int newColumnIndex = newColumnList.size() - 1;
        final int[] leftColumnIndexes = this.getColumnIndexes(leftColumnNames);
        row = Objectory.createObjectArray(rightJoinColumnIndexes.length);
        List leftRowIndexList = null;

        for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
            for (int i = 0, len = rightJoinColumnIndexes.length; i < len; i++) {
                row[i] = right.get(rightRowIndex, rightJoinColumnIndexes[i]);
            }

            leftRowIndexList = joinColumnLeftRowIndexMap.get(row);

            rightJoin(newColumnList, right, newColumnClass, newColumnIndex, rightRowIndex, leftRowIndexList, leftColumnIndexes);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        for (Object[] a : joinColumnLeftRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param newColumnClass
     * @param newColumnIndex
     * @param rightRowIndex
     * @param leftRowIndexList
     * @param leftColumnIndexes
     */
    private void rightJoin(final List> newColumnList, final DataSet right, final Class newColumnClass, final int newColumnIndex,
            int rightRowIndex, List leftRowIndexList, final int[] leftColumnIndexes) {
        if (N.notNullOrEmpty(leftRowIndexList)) {
            for (int leftRowIndex : leftRowIndexList) {
                for (int i = 0, leftColumnLength = leftColumnIndexes.length; i < leftColumnLength; i++) {
                    newColumnList.get(i).add(this.get(leftRowIndex, leftColumnIndexes[i]));
                }

                newColumnList.get(newColumnIndex).add(right.getRow(newColumnClass, rightRowIndex));
            }
        } else {
            for (int i = 0, leftColumnLength = leftColumnIndexes.length; i < leftColumnLength; i++) {
                newColumnList.get(i).add(null);
            }

            newColumnList.get(newColumnIndex).add(right.getRow(newColumnClass, rightRowIndex));
        }
    }

    /**
     * Inits the new column list for right join.
     *
     * @param newColumnNameList
     * @param newColumnList
     * @param leftColumnNames
     * @param newColumnName
     */
    private void initNewColumnListForRightJoin(final List newColumnNameList, final List> newColumnList, final List leftColumnNames,
            final String newColumnName) {
        for (String leftColumnName : leftColumnNames) {
            newColumnNameList.add(leftColumnName);
            newColumnList.add(new ArrayList<>());
        }

        newColumnNameList.add(newColumnName);
        newColumnList.add(new ArrayList<>());
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @param newColumnName
     * @param newColumnClass
     * @param collSupplier
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public DataSet rightJoin(final DataSet right, final Map onColumnNames, final String newColumnName, final Class newColumnClass,
            final IntFunction collSupplier) {
        checkJoinOnColumnNames(onColumnNames);
        checkNewColumnName(newColumnName);
        N.checkArgNotNull(collSupplier);

        if (onColumnNames.size() == 1) {
            final Map.Entry onColumnEntry = onColumnNames.entrySet().iterator().next();
            final int leftJoinColumnIndex = checkColumnName(onColumnEntry.getKey());
            final int rightJoinColumnIndex = checkRefColumnName(right, onColumnEntry.getValue());

            final List leftColumnNames = new ArrayList<>(_columnNameList);
            final List newColumnNameList = new ArrayList<>(leftColumnNames.size() + 1);
            final List> newColumnList = new ArrayList<>(leftColumnNames.size() + 1);

            initNewColumnListForRightJoin(newColumnNameList, newColumnList, leftColumnNames, newColumnName);

            final List leftJoinColumn = this.getColumn(leftJoinColumnIndex);
            final List rightJoinColumn = right.getColumn(rightJoinColumnIndex);
            final Map> joinColumnLeftRowIndexMap = new HashMap<>();
            Object hashKey = null;

            for (int leftRowIndex = 0, leftDataSetSize = this.size(); leftRowIndex < leftDataSetSize; leftRowIndex++) {
                hashKey = getHashKey(leftJoinColumn.get(leftRowIndex));
                putRowIndex(joinColumnLeftRowIndexMap, hashKey, leftRowIndex);
            }

            final Map> joinColumnRightRowIndexMap = new LinkedHashMap<>();

            for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
                hashKey = getHashKey(rightJoinColumn.get(rightRowIndex));
                putRowIndex(joinColumnRightRowIndexMap, hashKey, rightRowIndex);
            }

            final int newColumnIndex = newColumnList.size() - 1;
            final int[] leftColumnIndexes = this.getColumnIndexes(leftColumnNames);
            List leftRowIndexList = null;
            List rightRowIndexList = null;

            for (Map.Entry> rightRowIndexEntry : joinColumnRightRowIndexMap.entrySet()) {
                leftRowIndexList = joinColumnLeftRowIndexMap.get(rightRowIndexEntry.getKey());
                rightRowIndexList = rightRowIndexEntry.getValue();

                rightJoin(newColumnList, right, newColumnClass, collSupplier, newColumnIndex, leftColumnIndexes, leftRowIndexList, rightRowIndexList);
            }

            return new RowDataSet(newColumnNameList, newColumnList);
        }
        final List leftColumnNames = new ArrayList<>(_columnNameList);
        final int[] leftJoinColumnIndexes = new int[onColumnNames.size()];
        final int[] rightJoinColumnIndexes = new int[onColumnNames.size()];

        initColumnIndexes(leftJoinColumnIndexes, rightJoinColumnIndexes, right, onColumnNames);

        final List newColumnNameList = new ArrayList<>(leftColumnNames.size() + 1);
        final List> newColumnList = new ArrayList<>(leftColumnNames.size() + 1);

        initNewColumnListForRightJoin(newColumnNameList, newColumnList, leftColumnNames, newColumnName);

        final Map> joinColumnLeftRowIndexMap = new ArrayHashMap<>();
        Object[] row = null;

        for (int leftRowIndex = 0, leftDataSetSize = this.size(); leftRowIndex < leftDataSetSize; leftRowIndex++) {
            row = row == null ? Objectory.createObjectArray(leftJoinColumnIndexes.length) : row;

            for (int i = 0, len = leftJoinColumnIndexes.length; i < len; i++) {
                row[i] = this.get(leftRowIndex, leftJoinColumnIndexes[i]);
            }

            row = putRowIndex(joinColumnLeftRowIndexMap, row, leftRowIndex);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        final Map> joinColumnRightRowIndexMap = new ArrayHashMap<>(LinkedHashMap.class);

        for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
            row = row == null ? Objectory.createObjectArray(rightJoinColumnIndexes.length) : row;

            for (int i = 0, len = rightJoinColumnIndexes.length; i < len; i++) {
                row[i] = right.get(rightRowIndex, rightJoinColumnIndexes[i]);
            }

            row = putRowIndex(joinColumnRightRowIndexMap, row, rightRowIndex);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        final int newColumnIndex = newColumnList.size() - 1;
        final int[] leftColumnIndexes = this.getColumnIndexes(leftColumnNames);
        List leftRowIndexList = null;
        List rightRowIndexList = null;

        for (Map.Entry> rightRowIndexEntry : joinColumnRightRowIndexMap.entrySet()) {
            leftRowIndexList = joinColumnLeftRowIndexMap.get(rightRowIndexEntry.getKey());
            rightRowIndexList = rightRowIndexEntry.getValue();

            rightJoin(newColumnList, right, newColumnClass, collSupplier, newColumnIndex, leftColumnIndexes, leftRowIndexList, rightRowIndexList);
        }

        for (Object[] a : joinColumnLeftRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        for (Object[] a : joinColumnRightRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param newColumnClass
     * @param collSupplier
     * @param newColumnIndex
     * @param leftColumnIndexes
     * @param leftRowIndexList
     * @param rightRowIndexList
     */
    @SuppressWarnings("rawtypes")
    private void rightJoin(final List> newColumnList, final DataSet right, final Class newColumnClass,
            final IntFunction collSupplier, final int newColumnIndex, final int[] leftColumnIndexes, List leftRowIndexList,
            List rightRowIndexList) {
        if (N.notNullOrEmpty(leftRowIndexList)) {
            for (int leftRowIndex : leftRowIndexList) {
                for (int i = 0, leftColumnLength = leftColumnIndexes.length; i < leftColumnLength; i++) {
                    newColumnList.get(i).add(this.get(leftRowIndex, leftColumnIndexes[i]));
                }

                final Collection coll = collSupplier.apply(rightRowIndexList.size());

                for (int righRowIndex : rightRowIndexList) {
                    coll.add(right.getRow(newColumnClass, righRowIndex));
                }

                newColumnList.get(newColumnIndex).add(coll);
            }
        } else {
            for (int i = 0, leftColumnLength = leftColumnIndexes.length; i < leftColumnLength; i++) {
                newColumnList.get(i).add(null);
            }

            final Collection coll = collSupplier.apply(rightRowIndexList.size());

            for (int righRowIndex : rightRowIndexList) {
                coll.add(right.getRow(newColumnClass, righRowIndex));
            }

            newColumnList.get(newColumnIndex).add(coll);
        }
    }

    /**
     *
     * @param right
     * @param columnName
     * @param refColumnName
     * @return
     */
    @Override
    public DataSet fullJoin(final DataSet right, final String columnName, final String refColumnName) {
        final Map onColumnNames = N.asMap(columnName, refColumnName);

        return fullJoin(right, onColumnNames);
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @return
     */
    @Override
    public DataSet fullJoin(final DataSet right, final Map onColumnNames) {
        checkJoinOnColumnNames(onColumnNames);

        if (onColumnNames.size() == 1) {
            final Map.Entry onColumnEntry = onColumnNames.entrySet().iterator().next();
            final int leftJoinColumnIndex = checkColumnName(onColumnEntry.getKey());
            final int rightJoinColumnIndex = checkRefColumnName(right, onColumnEntry.getValue());
            final List rightColumnNames = getRightColumnNames(right, onColumnEntry.getValue());

            final List newColumnNameList = new ArrayList<>(_columnNameList.size() + rightColumnNames.size());
            final List> newColumnList = new ArrayList<>(_columnNameList.size() + rightColumnNames.size());

            initNewColumnList(newColumnNameList, newColumnList, rightColumnNames);

            final List leftJoinColumn = this.getColumn(leftJoinColumnIndex);
            final List rightJoinColumn = right.getColumn(rightJoinColumnIndex);
            final Map> joinColumnRightRowIndexMap = new HashMap<>();
            List rightRowIndexList = null;
            Object hashKey = null;

            for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
                hashKey = getHashKey(rightJoinColumn.get(rightRowIndex));
                putRowIndex(joinColumnRightRowIndexMap, hashKey, rightRowIndex);
            }

            final int[] rightColumnIndexes = right.getColumnIndexes(rightColumnNames);
            final Set joinColumnLeftRowIndexSet = N.newHashSet();

            for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
                hashKey = getHashKey(leftJoinColumn.get(leftRowIndex));
                rightRowIndexList = joinColumnRightRowIndexMap.get(hashKey);

                fullJoin(newColumnList, right, leftRowIndex, rightRowIndexList, rightColumnIndexes);

                joinColumnLeftRowIndexSet.add(hashKey);
            }

            for (Map.Entry> rightRowIndexEntry : joinColumnRightRowIndexMap.entrySet()) {
                if (!joinColumnLeftRowIndexSet.contains(rightRowIndexEntry.getKey())) {
                    fullJoin(newColumnList, right, rightRowIndexEntry.getValue(), rightColumnIndexes);
                }
            }

            return new RowDataSet(newColumnNameList, newColumnList);
        }
        final int[] leftJoinColumnIndexes = new int[onColumnNames.size()];
        final int[] rightJoinColumnIndexes = new int[onColumnNames.size()];
        final List rightColumnNames = new ArrayList<>(right.columnNameList());

        initColumnIndexes(leftJoinColumnIndexes, rightJoinColumnIndexes, right, onColumnNames, rightColumnNames);

        final List newColumnNameList = new ArrayList<>(_columnNameList.size() + rightColumnNames.size());
        final List> newColumnList = new ArrayList<>(_columnNameList.size() + rightColumnNames.size());
        initNewColumnList(newColumnNameList, newColumnList, rightColumnNames);

        final Map> joinColumnRightRowIndexMap = new ArrayHashMap<>(LinkedHashMap.class);
        List rightRowIndexList = null;
        Object[] row = null;

        for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
            row = row == null ? Objectory.createObjectArray(rightJoinColumnIndexes.length) : row;

            for (int i = 0, len = rightJoinColumnIndexes.length; i < len; i++) {
                row[i] = right.get(rightRowIndex, rightJoinColumnIndexes[i]);
            }

            row = putRowIndex(joinColumnRightRowIndexMap, row, rightRowIndex);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        final int[] rightColumnIndexes = right.getColumnIndexes(rightColumnNames);
        final Map joinColumnLeftRowIndexMap = new ArrayHashMap<>();

        for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
            row = row == null ? Objectory.createObjectArray(rightJoinColumnIndexes.length) : row;

            for (int i = 0, len = leftJoinColumnIndexes.length; i < len; i++) {
                row[i] = this.get(leftRowIndex, leftJoinColumnIndexes[i]);
            }

            rightRowIndexList = joinColumnRightRowIndexMap.get(row);

            fullJoin(newColumnList, right, leftRowIndex, rightRowIndexList, rightColumnIndexes);

            if (!joinColumnLeftRowIndexMap.containsKey(row)) {
                joinColumnLeftRowIndexMap.put(row, leftRowIndex);
                row = null;
            }
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        for (Map.Entry> rightRowIndexEntry : joinColumnRightRowIndexMap.entrySet()) {
            if (!joinColumnLeftRowIndexMap.containsKey(rightRowIndexEntry.getKey())) {
                fullJoin(newColumnList, right, rightRowIndexEntry.getValue(), rightColumnIndexes);
            }
        }

        for (Object[] a : joinColumnRightRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        for (Object[] a : joinColumnLeftRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param rightRowIndexList
     * @param rightColumnIndexes
     */
    private void fullJoin(final List> newColumnList, final DataSet right, List rightRowIndexList, final int[] rightColumnIndexes) {
        for (int rightRowIndex : rightRowIndexList) {
            for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                newColumnList.get(i).add(null);
            }

            for (int i = 0, leftColumnLength = _columnNameList.size(), rightColumnLength = rightColumnIndexes.length; i < rightColumnLength; i++) {
                newColumnList.get(leftColumnLength + i).add(right.get(rightRowIndex, rightColumnIndexes[i]));
            }
        }
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param leftRowIndex
     * @param rightRowIndexList
     * @param rightColumnIndexes
     */
    private void fullJoin(final List> newColumnList, final DataSet right, int leftRowIndex, List rightRowIndexList,
            final int[] rightColumnIndexes) {
        if (N.notNullOrEmpty(rightRowIndexList)) {
            for (int rightRowIndex : rightRowIndexList) {
                for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                    newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
                }

                for (int i = 0, leftColumnLength = _columnNameList.size(), rightColumnLength = rightColumnIndexes.length; i < rightColumnLength; i++) {
                    newColumnList.get(leftColumnLength + i).add(right.get(rightRowIndex, rightColumnIndexes[i]));
                }
            }
        } else {
            for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
            }

            for (int i = 0, leftColumnLength = _columnNameList.size(), rightColumnLength = rightColumnIndexes.length; i < rightColumnLength; i++) {
                newColumnList.get(leftColumnLength + i).add(null);
            }
        }
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @param newColumnName
     * @param newColumnClass
     * @return
     */
    @Override
    public DataSet fullJoin(final DataSet right, final Map onColumnNames, final String newColumnName, final Class newColumnClass) {
        checkJoinOnColumnNames(onColumnNames);
        checkNewColumnName(newColumnName);

        if (onColumnNames.size() == 1) {
            final Map.Entry onColumnEntry = onColumnNames.entrySet().iterator().next();
            final int leftJoinColumnIndex = checkColumnName(onColumnEntry.getKey());
            final int rightJoinColumnIndex = checkRefColumnName(right, onColumnEntry.getValue());

            final List newColumnNameList = new ArrayList<>(_columnNameList.size() + 1);
            final List> newColumnList = new ArrayList<>(_columnNameList.size() + 1);

            initNewColumnList(newColumnNameList, newColumnList, newColumnName);

            final List leftJoinColumn = this.getColumn(leftJoinColumnIndex);
            final List rightJoinColumn = right.getColumn(rightJoinColumnIndex);
            final Map> joinColumnRightRowIndexMap = new HashMap<>();
            List rightRowIndexList = null;
            Object hashKey = null;

            for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
                hashKey = getHashKey(rightJoinColumn.get(rightRowIndex));
                putRowIndex(joinColumnRightRowIndexMap, hashKey, rightRowIndex);
            }

            final int newColumnIndex = newColumnList.size() - 1;
            final Set joinColumnLeftRowIndexSet = N.newHashSet();

            for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
                hashKey = getHashKey(leftJoinColumn.get(leftRowIndex));
                rightRowIndexList = joinColumnRightRowIndexMap.get(hashKey);

                fullJoin(newColumnList, right, newColumnClass, newColumnIndex, leftRowIndex, rightRowIndexList);

                joinColumnLeftRowIndexSet.add(hashKey);
            }

            for (Map.Entry> rightRowIndexEntry : joinColumnRightRowIndexMap.entrySet()) {
                if (!joinColumnLeftRowIndexSet.contains(rightRowIndexEntry.getKey())) {
                    fullJoin(newColumnList, right, newColumnClass, newColumnIndex, rightRowIndexEntry.getValue());
                }
            }

            return new RowDataSet(newColumnNameList, newColumnList);
        }
        final int[] leftJoinColumnIndexes = new int[onColumnNames.size()];
        final int[] rightJoinColumnIndexes = new int[onColumnNames.size()];

        initColumnIndexes(leftJoinColumnIndexes, rightJoinColumnIndexes, right, onColumnNames);

        final List newColumnNameList = new ArrayList<>(_columnNameList.size() + 1);
        final List> newColumnList = new ArrayList<>(_columnNameList.size() + 1);

        initNewColumnList(newColumnNameList, newColumnList, newColumnName);

        final Map> joinColumnRightRowIndexMap = new ArrayHashMap<>(LinkedHashMap.class);
        List rightRowIndexList = null;
        Object[] row = null;

        for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
            row = row == null ? Objectory.createObjectArray(rightJoinColumnIndexes.length) : row;

            for (int i = 0, len = rightJoinColumnIndexes.length; i < len; i++) {
                row[i] = right.get(rightRowIndex, rightJoinColumnIndexes[i]);
            }

            row = putRowIndex(joinColumnRightRowIndexMap, row, rightRowIndex);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        final int newColumnIndex = newColumnList.size() - 1;
        final Map joinColumnLeftRowIndexMap = new ArrayHashMap<>();

        for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
            row = row == null ? Objectory.createObjectArray(rightJoinColumnIndexes.length) : row;

            for (int i = 0, len = leftJoinColumnIndexes.length; i < len; i++) {
                row[i] = this.get(leftRowIndex, leftJoinColumnIndexes[i]);
            }

            rightRowIndexList = joinColumnRightRowIndexMap.get(row);

            fullJoin(newColumnList, right, newColumnClass, newColumnIndex, leftRowIndex, rightRowIndexList);

            if (!joinColumnLeftRowIndexMap.containsKey(row)) {
                joinColumnLeftRowIndexMap.put(row, leftRowIndex);
                row = null;
            }
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        for (Map.Entry> rightRowIndexEntry : joinColumnRightRowIndexMap.entrySet()) {
            if (!joinColumnLeftRowIndexMap.containsKey(rightRowIndexEntry.getKey())) {
                fullJoin(newColumnList, right, newColumnClass, newColumnIndex, rightRowIndexEntry.getValue());
            }
        }

        for (Object[] a : joinColumnRightRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        for (Object[] a : joinColumnLeftRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param newColumnClass
     * @param newColumnIndex
     * @param rightRowIndexList
     */
    private void fullJoin(final List> newColumnList, final DataSet right, final Class newColumnClass, final int newColumnIndex,
            List rightRowIndexList) {
        for (int rightRowIndex : rightRowIndexList) {
            for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                newColumnList.get(i).add(null);
            }

            newColumnList.get(newColumnIndex).add(right.getRow(newColumnClass, rightRowIndex));
        }
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param newColumnClass
     * @param newColumnIndex
     * @param leftRowIndex
     * @param rightRowIndexList
     */
    private void fullJoin(final List> newColumnList, final DataSet right, final Class newColumnClass, final int newColumnIndex,
            int leftRowIndex, List rightRowIndexList) {
        if (N.notNullOrEmpty(rightRowIndexList)) {
            for (int rightRowIndex : rightRowIndexList) {
                for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                    newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
                }

                newColumnList.get(newColumnIndex).add(right.getRow(newColumnClass, rightRowIndex));
            }
        } else {
            for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
            }

            newColumnList.get(newColumnIndex).add(null);
        }
    }

    /**
     *
     * @param right
     * @param onColumnNames
     * @param newColumnName
     * @param newColumnClass
     * @param collSupplier
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public DataSet fullJoin(final DataSet right, final Map onColumnNames, final String newColumnName, final Class newColumnClass,
            final IntFunction collSupplier) {
        checkJoinOnColumnNames(onColumnNames);
        checkNewColumnName(newColumnName);
        N.checkArgNotNull(collSupplier);

        if (onColumnNames.size() == 1) {
            final Map.Entry onColumnEntry = onColumnNames.entrySet().iterator().next();
            final int leftJoinColumnIndex = checkColumnName(onColumnEntry.getKey());
            final int rightJoinColumnIndex = checkRefColumnName(right, onColumnEntry.getValue());

            final List newColumnNameList = new ArrayList<>(_columnNameList.size() + 1);
            final List> newColumnList = new ArrayList<>(_columnNameList.size() + 1);

            initNewColumnList(newColumnNameList, newColumnList, newColumnName);

            final List leftJoinColumn = this.getColumn(leftJoinColumnIndex);
            final List rightJoinColumn = right.getColumn(rightJoinColumnIndex);
            final Map> joinColumnRightRowIndexMap = new HashMap<>();
            Object hashKey = null;

            for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
                hashKey = getHashKey(rightJoinColumn.get(rightRowIndex));
                putRowIndex(joinColumnRightRowIndexMap, hashKey, rightRowIndex);
            }

            final int newColumnIndex = newColumnList.size() - 1;
            final Set joinColumnLeftRowIndexSet = N.newHashSet();
            List rightRowIndexList = null;

            for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
                hashKey = getHashKey(leftJoinColumn.get(leftRowIndex));
                rightRowIndexList = joinColumnRightRowIndexMap.get(hashKey);

                fullJoin(newColumnList, right, newColumnClass, collSupplier, newColumnIndex, leftRowIndex, rightRowIndexList);

                joinColumnLeftRowIndexSet.add(hashKey);
            }

            for (Map.Entry> rightRowIndexEntry : joinColumnRightRowIndexMap.entrySet()) {
                if (!joinColumnLeftRowIndexSet.contains(rightRowIndexEntry.getKey())) {
                    fullJoin(newColumnList, right, newColumnClass, collSupplier, newColumnIndex, rightRowIndexEntry.getValue());
                }
            }

            return new RowDataSet(newColumnNameList, newColumnList);
        }
        final int[] leftJoinColumnIndexes = new int[onColumnNames.size()];
        final int[] rightJoinColumnIndexes = new int[onColumnNames.size()];

        initColumnIndexes(leftJoinColumnIndexes, rightJoinColumnIndexes, right, onColumnNames);

        final List newColumnNameList = new ArrayList<>(_columnNameList.size() + 1);
        final List> newColumnList = new ArrayList<>(_columnNameList.size() + 1);
        initNewColumnList(newColumnNameList, newColumnList, newColumnName);

        final Map> joinColumnRightRowIndexMap = new ArrayHashMap<>(LinkedHashMap.class);
        List rightRowIndexList = null;
        Object[] row = null;

        for (int rightRowIndex = 0, rightDataSetSize = right.size(); rightRowIndex < rightDataSetSize; rightRowIndex++) {
            row = row == null ? Objectory.createObjectArray(rightJoinColumnIndexes.length) : row;

            for (int i = 0, len = rightJoinColumnIndexes.length; i < len; i++) {
                row[i] = right.get(rightRowIndex, rightJoinColumnIndexes[i]);
            }

            row = putRowIndex(joinColumnRightRowIndexMap, row, rightRowIndex);
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        final int newColumnIndex = newColumnList.size() - 1;
        final Map joinColumnLeftRowIndexMap = new ArrayHashMap<>();

        for (int leftRowIndex = 0, size = size(); leftRowIndex < size; leftRowIndex++) {
            row = row == null ? Objectory.createObjectArray(rightJoinColumnIndexes.length) : row;

            for (int i = 0, len = leftJoinColumnIndexes.length; i < len; i++) {
                row[i] = this.get(leftRowIndex, leftJoinColumnIndexes[i]);
            }

            rightRowIndexList = joinColumnRightRowIndexMap.get(row);

            fullJoin(newColumnList, right, newColumnClass, collSupplier, newColumnIndex, leftRowIndex, rightRowIndexList);

            if (!joinColumnLeftRowIndexMap.containsKey(row)) {
                joinColumnLeftRowIndexMap.put(row, leftRowIndex);
                row = null;
            }
        }

        if (row != null) {
            Objectory.recycle(row);
            row = null;
        }

        for (Map.Entry> rightRowIndexEntry : joinColumnRightRowIndexMap.entrySet()) {
            if (!joinColumnLeftRowIndexMap.containsKey(rightRowIndexEntry.getKey())) {
                fullJoin(newColumnList, right, newColumnClass, collSupplier, newColumnIndex, rightRowIndexEntry.getValue());
            }
        }

        for (Object[] a : joinColumnRightRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        for (Object[] a : joinColumnLeftRowIndexMap.keySet()) {
            Objectory.recycle(a);
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param newColumnClass
     * @param collSupplier
     * @param newColumnIndex
     * @param rightRowIndexList
     */
    @SuppressWarnings("rawtypes")
    private void fullJoin(final List> newColumnList, final DataSet right, final Class newColumnClass,
            final IntFunction collSupplier, final int newColumnIndex, final List rightRowIndexList) {
        for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
            newColumnList.get(i).add(null);
        }

        final Collection coll = collSupplier.apply(rightRowIndexList.size());

        for (int rightRowIndex : rightRowIndexList) {
            coll.add(right.getRow(newColumnClass, rightRowIndex));
        }

        newColumnList.get(newColumnIndex).add(coll);
    }

    /**
     *
     * @param newColumnList
     * @param right
     * @param newColumnClass
     * @param collSupplier
     * @param newColumnIndex
     * @param leftRowIndex
     * @param rightRowIndexList
     */
    @SuppressWarnings("rawtypes")
    private void fullJoin(final List> newColumnList, final DataSet right, final Class newColumnClass,
            final IntFunction collSupplier, final int newColumnIndex, int leftRowIndex, List rightRowIndexList) {
        if (N.notNullOrEmpty(rightRowIndexList)) {
            for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
            }

            final Collection coll = collSupplier.apply(rightRowIndexList.size());

            for (int rightRowIndex : rightRowIndexList) {
                coll.add(right.getRow(newColumnClass, rightRowIndex));
            }

            newColumnList.get(newColumnIndex).add(coll);
        } else {
            for (int i = 0, leftColumnLength = _columnNameList.size(); i < leftColumnLength; i++) {
                newColumnList.get(i).add(_columnList.get(i).get(leftRowIndex));
            }

            newColumnList.get(newColumnIndex).add(null);
        }
    }

    /**
     *
     * @param dataSet
     * @return
     */
    @Override
    public DataSet union(final DataSet dataSet) {
        // final DataSet result = copy(_columnNameList, 0, size(), false);
        // result.merge(dataSet.difference(result));
        // return result;

        return merge(dataSet.difference(this));
    }

    /**
     *
     * @param dataSet
     * @return
     */
    @Override
    public DataSet unionAll(final DataSet dataSet) {
        // final DataSet result = copy(_columnNameList, 0, size(), false);
        // result.merge(dataSet);
        // return result;

        return merge(dataSet);
    }

    /**
     *
     * @param other
     * @return
     */
    @Override
    public DataSet intersect(final DataSet other) {
        final List commonColumnNameList = new ArrayList<>(this._columnNameList);
        commonColumnNameList.retainAll(other.columnNameList());

        if (N.isNullOrEmpty(commonColumnNameList)) {
            throw new IllegalArgumentException("These two DataSets don't have common column names: " + this._columnNameList + ", " + other.columnNameList());
        }

        final int newColumnCount = this.columnNameList().size();
        final List newColumnNameList = new ArrayList<>(this._columnNameList);
        final List> newColumnList = new ArrayList<>(newColumnCount);

        for (int i = 0; i < newColumnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        final int size = size();

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final int commonColumnCount = commonColumnNameList.size();

        if (commonColumnCount == 1) {
            final int columnIndex = this.getColumnIndex(commonColumnNameList.get(0));
            final int otherColumnIndex = other.getColumnIndex(commonColumnNameList.get(0));

            final List column = _columnList.get(columnIndex);
            final Set rowSet = N.newHashSet();

            for (Object e : other.getColumn(otherColumnIndex)) {
                rowSet.add(getHashKey(e));
            }

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                if (rowSet.remove(getHashKey(column.get(rowIndex)))) {
                    for (int i = 0; i < newColumnCount; i++) {
                        newColumnList.get(i).add(_columnList.get(i).get(rowIndex));
                    }
                }
            }
        } else {
            final int[] columnIndexes = this.getColumnIndexes(commonColumnNameList);
            final int[] otherColumnIndexes = other.getColumnIndexes(commonColumnNameList);

            final Set rowSet = new ArrayHashSet<>();
            Object[] row = null;

            for (int otherRowIndex = 0, otherSize = other.size(); otherRowIndex < otherSize; otherRowIndex++) {
                row = row == null ? new Object[commonColumnCount] : row;

                for (int i = 0; i < commonColumnCount; i++) {
                    row[i] = other.get(otherRowIndex, otherColumnIndexes[i]);
                }

                if (rowSet.add(row)) {
                    row = null;
                }
            }

            row = new Object[commonColumnCount];

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                for (int i = 0; i < commonColumnCount; i++) {
                    row[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
                }

                if (rowSet.remove(row)) {
                    for (int i = 0; i < newColumnCount; i++) {
                        newColumnList.get(i).add(_columnList.get(i).get(rowIndex));
                    }
                }
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param other
     * @return
     */
    @Override
    public DataSet intersectAll(final DataSet other) {
        return remove2(other, true);
    }

    /**
     *
     * @param other
     * @return
     */
    @Override
    public DataSet intersection(final DataSet other) {
        return remove(other, true);
    }

    /**
     *
     * @param other
     * @return
     */
    @Override
    public DataSet difference(final DataSet other) {
        return remove(other, false);
    }

    /**
     *
     * @param other
     * @param retain
     * @return
     */
    private DataSet remove(final DataSet other, final boolean retain) {
        final List commonColumnNameList = new ArrayList<>(this._columnNameList);
        commonColumnNameList.retainAll(other.columnNameList());

        if (N.isNullOrEmpty(commonColumnNameList)) {
            throw new IllegalArgumentException("These two DataSets don't have common column names: " + this._columnNameList + ", " + other.columnNameList());
        }

        final int newColumnCount = this.columnNameList().size();
        final List newColumnNameList = new ArrayList<>(this._columnNameList);
        final List> newColumnList = new ArrayList<>(newColumnCount);

        for (int i = 0; i < newColumnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        final int size = size();

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final int commonColumnCount = commonColumnNameList.size();

        if (commonColumnCount == 1) {
            final int columnIndex = this.getColumnIndex(commonColumnNameList.get(0));
            final int otherColumnIndex = other.getColumnIndex(commonColumnNameList.get(0));

            final List column = _columnList.get(columnIndex);
            final Multiset rowSet = new Multiset<>();

            for (Object val : other.getColumn(otherColumnIndex)) {
                rowSet.add(getHashKey(val));
            }

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                if ((rowSet.getAndRemove(getHashKey(column.get(rowIndex))) > 0) == retain) {
                    for (int i = 0; i < newColumnCount; i++) {
                        newColumnList.get(i).add(_columnList.get(i).get(rowIndex));
                    }
                }
            }
        } else {
            final int[] columnIndexes = this.getColumnIndexes(commonColumnNameList);
            final int[] otherColumnIndexes = other.getColumnIndexes(commonColumnNameList);

            final Multiset rowSet = new Multiset<>(ArrayHashMap.class);
            Object[] row = null;

            for (int otherRowIndex = 0, otherSize = other.size(); otherRowIndex < otherSize; otherRowIndex++) {
                row = row == null ? Objectory.createObjectArray(commonColumnCount) : row;

                for (int i = 0; i < commonColumnCount; i++) {
                    row[i] = other.get(otherRowIndex, otherColumnIndexes[i]);
                }

                if (rowSet.getAndAdd(row) == 0) {
                    row = null;
                }
            }

            if (row != null) {
                Objectory.recycle(row);
                row = null;
            }

            row = Objectory.createObjectArray(commonColumnCount);

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                for (int i = 0; i < commonColumnCount; i++) {
                    row[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
                }

                if ((rowSet.getAndRemove(row) > 0) == retain) {
                    for (int i = 0; i < newColumnCount; i++) {
                        newColumnList.get(i).add(_columnList.get(i).get(rowIndex));
                    }
                }
            }

            Objectory.recycle(row);
            row = null;

            for (Object[] a : rowSet) {
                Objectory.recycle(a);
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param dataSet
     * @return
     */
    @Override
    public DataSet symmetricDifference(DataSet dataSet) {
        return this.difference(dataSet).merge(dataSet.difference(this));
    }

    /**
     *
     * @param other
     * @return
     */
    @Override
    public DataSet except(final DataSet other) {
        return remove2(other, false);
    }

    /**
     * Removes the 2.
     *
     * @param other
     * @param retain
     * @return
     */
    private DataSet remove2(final DataSet other, final boolean retain) {
        final List commonColumnNameList = new ArrayList<>(this._columnNameList);
        commonColumnNameList.retainAll(other.columnNameList());

        if (N.isNullOrEmpty(commonColumnNameList)) {
            throw new IllegalArgumentException("These two DataSets don't have common column names: " + this._columnNameList + ", " + other.columnNameList());
        }

        final int newColumnCount = this.columnNameList().size();
        final List newColumnNameList = new ArrayList<>(this._columnNameList);
        final List> newColumnList = new ArrayList<>(newColumnCount);

        for (int i = 0; i < newColumnCount; i++) {
            newColumnList.add(new ArrayList<>());
        }

        final int size = size();

        if (size == 0) {
            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final int commonColumnCount = commonColumnNameList.size();

        if (commonColumnCount == 1) {
            final int columnIndex = this.getColumnIndex(commonColumnNameList.get(0));
            final int otherColumnIndex = other.getColumnIndex(commonColumnNameList.get(0));

            final List column = _columnList.get(columnIndex);
            final Set rowSet = N.newHashSet();

            for (Object e : other.getColumn(otherColumnIndex)) {
                rowSet.add(getHashKey(e));
            }

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                if (rowSet.contains(getHashKey(column.get(rowIndex))) == retain) {
                    for (int i = 0; i < newColumnCount; i++) {
                        newColumnList.get(i).add(_columnList.get(i).get(rowIndex));
                    }
                }
            }
        } else {
            final int[] columnIndexes = this.getColumnIndexes(commonColumnNameList);
            final int[] otherColumnIndexes = other.getColumnIndexes(commonColumnNameList);

            final Set rowSet = new ArrayHashSet<>();
            Object[] row = null;

            for (int otherRowIndex = 0, otherSize = other.size(); otherRowIndex < otherSize; otherRowIndex++) {
                row = row == null ? Objectory.createObjectArray(commonColumnCount) : row;

                for (int i = 0; i < commonColumnCount; i++) {
                    row[i] = other.get(otherRowIndex, otherColumnIndexes[i]);
                }

                if (rowSet.add(row)) {
                    row = null;
                }
            }

            if (row != null) {
                Objectory.recycle(row);
                row = null;
            }

            row = Objectory.createObjectArray(commonColumnCount);

            for (int rowIndex = 0; rowIndex < size; rowIndex++) {
                for (int i = 0; i < commonColumnCount; i++) {
                    row[i] = _columnList.get(columnIndexes[i]).get(rowIndex);
                }

                if (rowSet.contains(row) == retain) {
                    for (int i = 0; i < newColumnCount; i++) {
                        newColumnList.get(i).add(_columnList.get(i).get(rowIndex));
                    }
                }
            }

            Objectory.recycle(row);
            row = null;

            for (Object[] a : rowSet) {
                Objectory.recycle(a);
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param from
     * @return
     */
    @Override
    public DataSet merge(final DataSet from) {
        return merge(from, from.columnNameList(), 0, from.size());
    }

    /**
     *
     * @param from
     * @param columnNames
     * @return
     */
    @Override
    public DataSet merge(final DataSet from, final Collection columnNames) {
        return merge(from, columnNames, 0, from.size());
    }

    /**
     *
     * @param from
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public DataSet merge(final DataSet from, final int fromRowIndex, final int toRowIndex) {
        return merge(from, from.columnNameList(), fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param from
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public DataSet merge(final DataSet from, final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        checkRowIndex(fromRowIndex, toRowIndex, from.size());

        final RowDataSet result = copy(this._columnNameList, 0, size(), true);
        List column = null;

        for (String columnName : columnNames) {
            if (!result.containsColumn(columnName)) {
                if (column == null) {
                    column = new ArrayList<>(size() + (toRowIndex - fromRowIndex));
                    N.fill(column, 0, size(), null);
                }

                result.addColumn(columnName, column);
            }
        }

        for (String columnName : result._columnNameList) {
            column = result._columnList.get(result.getColumnIndex(columnName));
            int columnIndex = from.getColumnIndex(columnName);

            if (columnIndex >= 0) {
                if (fromRowIndex == 0 && toRowIndex == from.size()) {
                    column.addAll(from.getColumn(columnIndex));
                } else {
                    column.addAll(from.getColumn(columnIndex).subList(fromRowIndex, toRowIndex));
                }
            } else {
                for (int i = fromRowIndex; i < toRowIndex; i++) {
                    column.add(null);
                }
            }
        }

        if (N.notNullOrEmpty(from.properties())) {
            result.properties().putAll(from.properties());
        }

        return result;
    }

    /**
     *
     * @param a
     * @param b
     * @return
     */
    @Override
    public DataSet merge(final DataSet a, final DataSet b) {
        return N.merge(this, a, b);
    }

    /**
     *
     *
     * @param dss
     * @return
     */
    @Override
    public DataSet merge(final Collection dss) {
        if (N.isNullOrEmpty(dss)) {
            return N.newEmptyDataSet().merge(this);
        }

        final List dsList = new ArrayList<>(N.size(dss) + 1);
        dsList.add(this);
        dsList.addAll(dss);

        return N.merge(dsList);
    }

    /**
     *
     * @param b
     * @return
     */
    @Override
    public DataSet cartesianProduct(DataSet b) {
        final Collection tmp = N.intersection(this._columnNameList, b.columnNameList());
        if (N.notNullOrEmpty(tmp)) {
            throw new IllegalArgumentException(tmp + " are included in both DataSets: " + this._columnNameList + " : " + b.columnNameList());
        }

        final int aSize = this.size();
        final int bSize = b.size();
        final int aColumnCount = this._columnNameList.size();
        final int bColumnCount = b.columnNameList().size();

        final int newColumnCount = aColumnCount + bColumnCount;
        final int newRowCount = aSize * bSize;

        final List newColumnNameList = new ArrayList<>(newColumnCount);
        newColumnNameList.addAll(_columnNameList);
        newColumnNameList.addAll(b.columnNameList());

        final List> newColumnList = new ArrayList<>();

        for (int i = 0; i < newColumnCount; i++) {
            newColumnList.add(new ArrayList<>(newRowCount));
        }

        if (newRowCount == 0) {
            return new RowDataSet(newColumnNameList, newColumnList);
        }

        final Object[] tmpArray = new Object[bSize];

        for (int rowIndex = 0; rowIndex < aSize; rowIndex++) {
            for (int columnIndex = 0; columnIndex < aColumnCount; columnIndex++) {
                N.fill(tmpArray, this.get(rowIndex, columnIndex));
                newColumnList.get(columnIndex).addAll(Arrays.asList(tmpArray));
            }

            for (int columnIndex = 0; columnIndex < bColumnCount; columnIndex++) {
                newColumnList.get(columnIndex + aColumnCount).addAll(b.getColumn(columnIndex));
            }
        }

        return new RowDataSet(newColumnNameList, newColumnList);
    }

    /**
     *
     * @param chunkSize
     * @return
     */
    @Override
    public Stream split(final int chunkSize) {
        return split(_columnNameList, chunkSize);
    }

    /**
     *
     * @param columnNames
     * @param chunkSize
     * @return
     */
    @Override
    public Stream split(final Collection columnNames, final int chunkSize) {
        checkColumnName(columnNames);
        N.checkArgPositive(chunkSize, "chunkSize");

        final int expectedModCount = modCount;
        final int totalSize = this.size();

        return IntStream.range(0, totalSize, chunkSize).mapToObj(from -> {
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }

            return RowDataSet.this.copy(columnNames, from, from <= totalSize - chunkSize ? from + chunkSize : totalSize);
        });
    }

    /**
     *
     * @param chunkSize
     * @return
     */
    @Override
    public List splitToList(final int chunkSize) {
        return splitToList(_columnNameList, chunkSize);
    }

    /**
     *
     * @param columnNames
     * @param chunkSize
     * @return
     */
    @Override
    public List splitToList(final Collection columnNames, final int chunkSize) {
        N.checkArgPositive(chunkSize, "chunkSize");

        final List res = new ArrayList<>();

        for (int i = 0, totalSize = size(); i < totalSize; i = i <= totalSize - chunkSize ? i + chunkSize : totalSize) {
            res.add(copy(columnNames, i, i <= totalSize - chunkSize ? i + chunkSize : totalSize));
        }

        return res;
    }

    //    /**
    //     *
    //     * @param chunkSize
    //     * @return
    //     * @deprecated replaced by {@link #splitToList(int)}
    //     */
    //    @Deprecated
    //    @Override
    //    public List splitt(int chunkSize) {
    //        return splitToList(chunkSize);
    //    }

    //    /**
    //     *
    //     * @param columnNames
    //     * @param chunkSize
    //     * @return
    //     * @deprecated replaced by {@link #splitToList(Collection, int)}
    //     */
    //    @Deprecated
    //    @Override
    //    public List splitt(Collection columnNames, int chunkSize) {
    //        return splitToList(columnNames, chunkSize);
    //    }

    //    @Override
    //    public  List> split(final Class rowClass, final int size) {
    //        return split(rowClass, _columnNameList, size);
    //    }
    //
    //    @Override
    //    public  List> split(final Class rowClass, final Collection columnNames, final int size) {
    //        return split(rowClass, columnNames, 0, size(), size);
    //    }
    //
    //    @Override
    //    public  List> split(final Class rowClass, final int fromRowIndex, final int toRowIndex, final int size) {
    //        return split(rowClass, _columnNameList, fromRowIndex, toRowIndex, size);
    //    }
    //
    //    @Override
    //    public  List> split(final Class rowClass, final Collection columnNames, final int fromRowIndex, final int toRowIndex,
    //            final int size) {
    //        checkRowIndex(fromRowIndex, toRowIndex);
    //
    //        final List list = this.toList(rowClass, columnNames, fromRowIndex, toRowIndex);
    //
    //        return N.split(list, size);
    //    }
    //
    //    @Override
    //    public  List> split(IntFunction rowSupplier, int size) {
    //        return split(rowSupplier, _columnNameList, size);
    //    }
    //
    //    @Override
    //    public  List> split(IntFunction rowSupplier, Collection columnNames, int size) {
    //        return split(rowSupplier, columnNames, 0, size(), size);
    //    }
    //
    //    @Override
    //    public  List> split(IntFunction rowSupplier, int fromRowIndex, int toRowIndex, int size) {
    //        return split(rowSupplier, _columnNameList, fromRowIndex, toRowIndex, size);
    //    }
    //
    //    @Override
    //    public  List> split(IntFunction rowSupplier, Collection columnNames, int fromRowIndex, int toRowIndex, int size) {
    //        checkRowIndex(fromRowIndex, toRowIndex);
    //
    //        final List list = this.toList(rowSupplier, columnNames, fromRowIndex, toRowIndex);
    //
    //        return N.split(list, size);
    //    }

    /**
     *
     * @param columnNames
     * @return
     */
    @Override
    public DataSet slice(Collection columnNames) {
        return slice(columnNames, 0, size());
    }

    /**
     *
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public DataSet slice(int fromRowIndex, int toRowIndex) {
        return slice(_columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public DataSet slice(Collection columnNames, int fromRowIndex, int toRowIndex) {
        N.checkFromToIndex(fromRowIndex, toRowIndex, size());
        DataSet ds = null;

        if (N.isNullOrEmpty(columnNames)) {
            ds = N.newEmptyDataSet();
        } else {
            final int[] columnIndexes = checkColumnName(columnNames);
            final List newColumnNames = new ArrayList<>(columnNames);
            final List> newColumnList = new ArrayList<>(newColumnNames.size());

            if (fromRowIndex > 0 || toRowIndex < size()) {
                for (int columnIndex : columnIndexes) {
                    newColumnList.add(_columnList.get(columnIndex).subList(fromRowIndex, toRowIndex));
                }
            } else {
                for (int columnIndex : columnIndexes) {
                    newColumnList.add(_columnList.get(columnIndex));
                }
            }

            ds = new RowDataSet(newColumnNames, newColumnList, _properties);
        }

        ds.frozen();
        return ds;
    }

    /**
     *
     * @param pageSize
     * @return
     */
    @Override
    public PaginatedDataSet paginate(final int pageSize) {
        return paginate(_columnNameList, pageSize);
    }

    /**
     *
     *
     * @param columnNames
     * @param pageSize
     * @return
     */
    @Override
    public PaginatedDataSet paginate(final Collection columnNames, final int pageSize) {
        return new PaginatedRowDataSet(N.isNullOrEmpty(columnNames) ? _columnNameList : columnNames, pageSize);
    }

    //    @SuppressWarnings("rawtypes")
    //    @Override
    //    public > Map percentiles(final String columnName) {
    //        if (size() == 0) {
    //            throw new RuntimeException("The size of dataset is 0");
    //        }
    //
    //        final Object[] columns = getColumn(columnName).toArray();
    //
    //        N.sort(columns);
    //
    //        return (Map) N.percentiles(columns);
    //    }
    //
    //    @Override
    //    public  Map percentiles(final String columnName, final Comparator comparator) {
    //        if (size() == 0) {
    //            throw new RuntimeException("The size of dataset is 0");
    //        }
    //
    //        final T[] columns = (T[]) getColumn(columnName).toArray();
    //
    //        N.sort(columns, comparator);
    //
    //        return N.percentiles(columns);
    //    }

    /**
     *
     * @param 
     * @param columnName
     * @return
     */
    @Override
    public  Stream stream(String columnName) {
        return stream(columnName, 0, size());
    }

    /**
     *
     * @param 
     * @param columnName
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public  Stream stream(final String columnName, int fromRowIndex, int toRowIndex) {
        this.checkRowIndex(fromRowIndex, toRowIndex);

        return (Stream) Stream.of(_columnList.get(checkColumnName(columnName)), fromRowIndex, toRowIndex);
    }

    //    @SuppressWarnings("rawtypes")
    //    @Override
    //    public > Map percentiles(final String columnName) {
    //        if (size() == 0) {
    //            throw new RuntimeException("The size of dataset is 0");
    //        }
    //
    //        final Object[] columns = getColumn(columnName).toArray();
    //
    //        N.sort(columns);
    //
    //        return (Map) N.percentiles(columns);
    //    }
    //
    //    @Override
    //    public  Map percentiles(final String columnName, final Comparator comparator) {
    //        if (size() == 0) {
    //            throw new RuntimeException("The size of dataset is 0");
    //        }
    //
    //        final T[] columns = (T[]) getColumn(columnName).toArray();
    //
    //        N.sort(columns, comparator);
    //
    //        return N.percentiles(columns);
    //    }

    //    /**
    //     *
    //     * @param 
    //     * @param rowMapper
    //     * @return
    //     */
    //    @Override
    //    public  Stream stream(final Function rowMapper) {
    //        return stream(0, size(), rowMapper);
    //    }
    //
    //    /**
    //     *
    //     * @param 
    //     * @param fromRowIndex
    //     * @param toRowIndex
    //     * @param rowMapper
    //     * @return
    //     */
    //    @Override
    //    public  Stream stream(int fromRowIndex, int toRowIndex, final Function rowMapper) {
    //        return stream(_columnNameList, fromRowIndex, toRowIndex, rowMapper);
    //    }
    //
    //    /**
    //     *
    //     * @param 
    //     * @param columnNames
    //     * @param rowMapper
    //     * @return
    //     */
    //    @Override
    //    public  Stream stream(Collection columnNames, final Function rowMapper) {
    //        return stream(columnNames, 0, size(), rowMapper);
    //    }
    //
    //    /**
    //     *
    //     * @param 
    //     * @param columnNames
    //     * @param fromRowIndex
    //     * @param toRowIndex
    //     * @param rowMapper
    //     * @return
    //     */
    //    @Override
    //    public  Stream stream(final Collection columnNames, final int fromRowIndex, final int toRowIndex,
    //            final Function rowMapper) {
    //        final int[] columnIndexes = this.checkColumnName(columnNames);
    //        checkRowIndex(fromRowIndex, toRowIndex);
    //        N.checkArgNotNull(rowMapper, "rowMapper");
    //        final int columnCount = columnNames.size();
    //
    //        return Stream.of(new ObjIteratorEx() {
    //            private final Type rowType = N.typeOf(Object[].class);
    //            private Object[] row = new Object[columnCount];
    //            private DisposableObjArray disposableRow = DisposableObjArray.wrap(row);
    //            private final int expectedModCount = modCount;
    //            private int cursor = fromRowIndex;
    //
    //            @Override
    //            public boolean hasNext() {
    //                ConcurrentModification();
    //
    //                return cursor < toRowIndex;
    //            }
    //
    //            @Override
    //            public DisposableObjArray next() {
    //                ConcurrentModification();
    //
    //                if (cursor >= toRowIndex) {
    //                    throw new NoSuchElementException();
    //                }
    //
    //                getRow(rowType, null, row, columnIndexes, columnNames, cursor);
    //
    //                cursor++;
    //
    //                return disposableRow;
    //            }
    //
    //            @Override
    //            public long count() {
    //                ConcurrentModification();
    //
    //                return toRowIndex - cursor;
    //            }
    //
    //            @Override
    //            public void advance(long n) {
    //                N.checkArgNotNegative(n, "n");
    //
    //                ConcurrentModification();
    //
    //                cursor = n > toRowIndex - cursor ? toRowIndex : (int) n + cursor;
    //            }
    //
    //            @Override
    //            public  A[] toArray(A[] a) {
    //                ConcurrentModification();
    //
    //                final List rows = RowDataSet.this.toList(Object[].class, columnNames, cursor, toRowIndex);
    //
    //                a = a.length >= rows.size() ? a : (A[]) N.newArray(a.getClass().getComponentType(), rows.size());
    //
    //                rows.toArray(a);
    //
    //                return a;
    //            }
    //
    //            final void ConcurrentModification() {
    //                if (modCount != expectedModCount) {
    //                    throw new ConcurrentModificationException();
    //                }
    //            }
    //        }).map(rowMapper);
    //    }

    /**
     *
     * @param 
     * @param rowClass
     * @return
     */
    @Override
    public  Stream stream(Class rowClass) {
        return stream(rowClass, 0, size());
    }

    /**
     *
     * @param 
     * @param rowClass
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public  Stream stream(Class rowClass, int fromRowIndex, int toRowIndex) {
        return stream(rowClass, _columnNameList, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param 
     * @param rowClass
     * @param columnNames
     * @return
     */
    @Override
    public  Stream stream(Class rowClass, Collection columnNames) {
        return stream(rowClass, columnNames, 0, size());
    }

    /**
     *
     * @param 
     * @param rowClass
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     */
    @Override
    public  Stream stream(final Class rowClass, final Collection columnNames, final int fromRowIndex, final int toRowIndex) {
        return stream(rowClass, columnNames, fromRowIndex, toRowIndex, null, null);
    }

    /**
     *
     * @param 
     * @param rowSupplier
     * @return
     */
    @Override
    public  Stream stream(IntFunction rowSupplier) {
        return stream(0, size(), rowSupplier);
    }

    /**
     *
     *
     * @param 
     * @param fromRowIndex
     * @param toRowIndex
     * @param rowSupplier
     * @return
     */
    @Override
    public  Stream stream(int fromRowIndex, int toRowIndex, IntFunction rowSupplier) {
        return stream(_columnNameList, fromRowIndex, toRowIndex, rowSupplier);
    }

    /**
     *
     *
     * @param 
     * @param columnNames
     * @param rowSupplier
     * @return
     */
    @Override
    public  Stream stream(Collection columnNames, IntFunction rowSupplier) {
        return stream(columnNames, 0, size(), rowSupplier);
    }

    /**
     *
     *
     * @param 
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param rowSupplier
     * @return
     */
    @Override
    public  Stream stream(final Collection columnNames, final int fromRowIndex, final int toRowIndex,
            final IntFunction rowSupplier) {
        return stream(null, columnNames, fromRowIndex, toRowIndex, null, rowSupplier);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param prefixAndFieldNameMap
     * @return
     */
    @Override
    public  Stream stream(Class beanClass, Map prefixAndFieldNameMap) {
        return stream(beanClass, this._columnNameList, 0, size(), prefixAndFieldNameMap);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param fromRowIndex
     * @param toRowIndex
     * @param prefixAndFieldNameMap
     * @return
     */
    @Override
    public  Stream stream(Class beanClass, int fromRowIndex, int toRowIndex, Map prefixAndFieldNameMap) {
        return stream(beanClass, this._columnNameList, fromRowIndex, toRowIndex, prefixAndFieldNameMap);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param columnNames
     * @param prefixAndFieldNameMap
     * @return
     */
    @Override
    public  Stream stream(Class beanClass, Collection columnNames, Map prefixAndFieldNameMap) {
        return stream(beanClass, columnNames, 0, size(), prefixAndFieldNameMap);
    }

    /**
     *
     *
     * @param 
     * @param beanClass
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @param prefixAndFieldNameMap
     * @return
     */
    @Override
    public  Stream stream(Class beanClass, Collection columnNames, int fromRowIndex, int toRowIndex,
            final Map prefixAndFieldNameMap) {
        N.checkArgument(ClassUtil.isBeanClass(beanClass), "{} is not a bean class", beanClass);

        return stream(beanClass, columnNames, fromRowIndex, toRowIndex, prefixAndFieldNameMap, null);
    }

    private  Stream stream(final Class inputRowClass, final Collection inputColumnNames, final int fromRowIndex,
            final int toRowIndex, final Map prefixAndFieldNameMap, final IntFunction inputRowSupplier) {
        checkRowIndex(fromRowIndex, toRowIndex);

        final Collection columnNames = N.isNullOrEmpty(inputColumnNames) ? this._columnNameList : inputColumnNames;

        N.checkArgument(N.isNullOrEmpty(columnNames) || columnNames == this._columnNameList || this._columnNameList.containsAll(columnNames),
                "Some select properties {} are not found in DataSet: {}", columnNames, this._columnNameList);

        final int[] columnIndexes = checkColumnName(columnNames);
        final int columnCount = columnIndexes.length;

        final Class rowClass = inputRowClass == null ? (Class) inputRowSupplier.apply(0).getClass() : inputRowClass;
        final Type rowType = N.typeOf(rowClass);
        final BeanInfo beanInfo = rowType.isBean() ? ParserUtil.getBeanInfo(rowClass) : null;

        final IntFunction rowSupplier = inputRowSupplier == null && !rowType.isBean() ? this.createRowSupplier(rowType, rowClass)
                : inputRowSupplier;

        return Stream.of(new ObjIteratorEx() {
            private final int expectedModCount = modCount;
            private int cursor = fromRowIndex;

            @Override
            public boolean hasNext() {
                checkConcurrentModification();

                return cursor < toRowIndex;
            }

            @Override
            public T next() {
                checkConcurrentModification();

                if (cursor >= toRowIndex) {
                    throw new NoSuchElementException();
                }

                return getRow(rowType, rowClass, beanInfo, columnNames, columnIndexes, columnCount, cursor++, prefixAndFieldNameMap, rowSupplier);
            }

            @Override
            public long count() {
                checkConcurrentModification();

                return toRowIndex - cursor; //NOSONAR
            }

            @Override
            public void advance(long n) {
                N.checkArgNotNegative(n, "n");

                checkConcurrentModification();

                cursor = n > toRowIndex - cursor ? toRowIndex : (int) n + cursor;
            }

            @Override
            public  A[] toArray(A[] a) {
                checkConcurrentModification();

                final List rows = RowDataSet.this.toList(rowClass, columnNames, cursor, toRowIndex, prefixAndFieldNameMap, rowSupplier);

                a = a.length >= rows.size() ? a : (A[]) N.newArray(a.getClass().getComponentType(), rows.size());

                rows.toArray(a);

                return a;
            }

            final void checkConcurrentModification() {
                if (modCount != expectedModCount) {
                    throw new ConcurrentModificationException();
                }
            }
        });
    }

    //    @Override
    //    public  T getProperty(final String propName) {
    //        return (T) (_properties == null ? null : _properties.get(propName));
    //    }
    //
    //    @Override
    //    public  T setProperty(final String propName, final Object propValue) {
    //        if (_properties == null) {
    //            _properties = new Properties();
    //        }
    //
    //        return (T) _properties.put(propName, propValue);
    //    }
    //
    //    @Override
    //    public  T removeProperty(final String propName) {
    //        if (_properties == null) {
    //            return null;
    //        }
    //
    //        return (T) _properties.remove(propName);
    //    }

    /**
     *
     * @param 
     * @param 
     * @param func
     * @return
     * @throws E the e
     */
    @Override
    public  R apply(Throwables.Function func) throws E {
        return func.apply(this);
    }

    /**
     *
     * @param 
     * @param action
     * @throws E the e
     */
    @Override
    public  void accept(Throwables.Consumer action) throws E {
        action.accept(this);
    }

    /**
     * Freeze.
     */
    @Override
    public void freeze() {
        _isFrozen = true;
    }

    /**
     *
     * @return true, if successful
     */
    @Override
    public boolean frozen() {
        return _isFrozen;
    }

    /**
     * Checks if is empty.
     *
     * @return true, if is empty
     */
    @Override
    public boolean isEmpty() {
        return size() == 0;
    }

    /**
     * Trim to size.
     */
    @Override
    public void trimToSize() {
        if (_columnList instanceof ArrayList) {
            ((ArrayList) _columnList).trimToSize();
        }

        for (List column : _columnList) {
            if (column instanceof ArrayList) {
                ((ArrayList) column).trimToSize();
            }
        }
    }

    /**
     *
     *
     * @return
     */
    @Override
    public int size() {
        return (_columnList.size() == 0) ? 0 : _columnList.get(0).size();
    }

    /**
     * Clear.
     */
    @Override
    public void clear() {
        checkFrozen();

        for (int i = 0; i < _columnList.size(); i++) {
            _columnList.get(i).clear();
        }

        // columnList.clear();
        modCount++;

        // Runtime.getRuntime().gc();
    }

    /**
     *
     *
     * @return
     */
    @Override
    public Properties properties() {
        if (_properties == null) {
            _properties = new Properties<>();
        }

        return _properties;
    }

    //    @Override
    //    public  T getProperty(final String propName) {
    //        return (T) (_properties == null ? null : _properties.get(propName));
    //    }
    //
    //    @Override
    //    public  T setProperty(final String propName, final Object propValue) {
    //        if (_properties == null) {
    //            _properties = new Properties();
    //        }
    //
    //        return (T) _properties.put(propName, propValue);
    //    }
    //
    //    @Override
    //    public  T removeProperty(final String propName) {
    //        if (_properties == null) {
    //            return null;
    //        }
    //
    //        return (T) _properties.remove(propName);
    //    }

    /**
     *
     *
     * @return
     */
    @Override
    public Stream columnNames() {
        return Stream.of(_columnNameList);
    }

    /**
     *
     *
     * @return
     */
    @Override
    public Stream> columns() {
        return IntStream.range(0, this._columnNameList.size()).mapToObj(this::getColumn);
    }

    /**
     *
     *
     * @return
     */
    @Override
    public Map> columnMap() {
        final Map> result = new LinkedHashMap<>();

        for (String columnName : _columnNameList) {
            result.put(columnName, getColumn(columnName));
        }

        return result;
    }

    //    @Override
    //    public DataSetBuilder builder() {
    //        return Builder.of(this);
    //    }

    /**
     *
     * @throws UncheckedIOException the unchecked IO exception
     */
    @Override
    public void println() throws UncheckedIOException {
        println(_columnNameList, 0, size());
    }

    /**
     *
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @throws UncheckedIOException the unchecked IO exception
     */
    @Override
    public void println(Collection columnNames, int fromRowIndex, int toRowIndex) throws UncheckedIOException {
        println(new OutputStreamWriter(System.out), columnNames, fromRowIndex, toRowIndex);
    }

    /**
     *
     * @param 
     * @param outputWriter
     * @return
     * @throws UncheckedIOException the unchecked IO exception
     */
    @Override
    public  W println(final W outputWriter) throws UncheckedIOException {
        return println(outputWriter, _columnNameList, 0, size());
    }

    /**
     *
     * @param 
     * @param outputWriter
     * @param columnNames
     * @param fromRowIndex
     * @param toRowIndex
     * @return
     * @throws UncheckedIOException the unchecked IO exception
     */
    @Override
    public  W println(final W outputWriter, Collection columnNames, int fromRowIndex, int toRowIndex) throws UncheckedIOException {
        final int[] columnIndexes = N.isNullOrEmpty(columnNames) ? N.EMPTY_INT_ARRAY : checkColumnName(columnNames);
        checkRowIndex(fromRowIndex, toRowIndex);
        N.checkArgNotNull(outputWriter, "outputWriter");

        boolean isBufferedWriter = outputWriter instanceof BufferedWriter || outputWriter instanceof java.io.BufferedWriter;
        final Writer bw = isBufferedWriter ? outputWriter : Objectory.createBufferedWriter(outputWriter);
        final int rowLen = toRowIndex - fromRowIndex;
        final int columnLen = columnIndexes.length;

        try {
            if (columnLen == 0) {
                bw.write("+---+");
                bw.write(IOUtil.LINE_SEPARATOR);
                bw.write("|   |");
                bw.write(IOUtil.LINE_SEPARATOR);
                bw.write("+---+");
            } else {
                final List columnNameList = new ArrayList<>(columnNames);
                final List> strColumnList = new ArrayList<>(columnLen);
                final int[] maxColumnLens = new int[columnLen];

                for (int i = 0; i < columnLen; i++) {
                    final List column = _columnList.get(columnIndexes[i]);
                    final List strColumn = new ArrayList<>(rowLen);
                    int maxLen = N.len(columnNameList.get(i));
                    String str = null;

                    for (int rowIndex = fromRowIndex; rowIndex < toRowIndex; rowIndex++) {
                        str = N.toString(column.get(rowIndex));
                        maxLen = N.max(maxLen, N.len(str));
                        strColumn.add(str);
                    }

                    maxColumnLens[i] = maxLen;
                    strColumnList.add(strColumn);
                }

                final char hch = '-';
                final char hchDelta = 2;
                for (int i = 0; i < columnLen; i++) {
                    bw.write('+');

                    bw.write(Strings.repeat(hch, maxColumnLens[i] + hchDelta));
                }

                bw.write('+');

                bw.write(IOUtil.LINE_SEPARATOR);

                for (int i = 0; i < columnLen; i++) {
                    if (i == 0) {
                        bw.write("| ");
                    } else {
                        bw.write(" | ");
                    }

                    bw.write(Strings.padEnd(columnNameList.get(i), maxColumnLens[i]));
                }

                bw.write(" |");

                bw.write(IOUtil.LINE_SEPARATOR);

                for (int i = 0; i < columnLen; i++) {
                    bw.write('+');

                    bw.write(Strings.repeat(hch, maxColumnLens[i] + hchDelta));
                }

                bw.write('+');

                for (int j = 0; j < rowLen; j++) {
                    bw.write(IOUtil.LINE_SEPARATOR);

                    for (int i = 0; i < columnLen; i++) {
                        if (i == 0) {
                            bw.write("| ");
                        } else {
                            bw.write(" | ");
                        }

                        bw.write(Strings.padEnd(strColumnList.get(i).get(j), maxColumnLens[i]));
                    }

                    bw.write(" |");
                }

                if (rowLen == 0) {
                    bw.write(IOUtil.LINE_SEPARATOR);

                    for (int i = 0; i < columnLen; i++) {
                        if (i == 0) {
                            bw.write("| ");
                            bw.write(Strings.padEnd("", maxColumnLens[i]));
                        } else {
                            bw.write(Strings.padEnd("", maxColumnLens[i] + 3));
                        }
                    }

                    bw.write(" |");
                }

                bw.write(IOUtil.LINE_SEPARATOR);

                for (int i = 0; i < columnLen; i++) {
                    bw.write('+');

                    bw.write(Strings.repeat(hch, maxColumnLens[i] + hchDelta));
                }

                bw.write('+');
            }

            bw.write(IOUtil.LINE_SEPARATOR);

            bw.flush();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            if (!isBufferedWriter) {
                Objectory.recycle((BufferedWriter) bw);
            }
        }

        return outputWriter;
    }

    /**
     *
     *
     * @return
     */
    @Override
    public int hashCode() {
        int h = 17;
        h = (h * 31) + _columnNameList.hashCode();
        return (h * 31) + _columnList.hashCode();
    }

    /**
     *
     * @param obj
     * @return true, if successful
     */
    @SuppressFBWarnings
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj instanceof RowDataSet other) {
            return (size() == other.size()) && N.equals(_columnNameList, other._columnNameList) && N.equals(_columnList, other._columnList);
        }

        return false;
    }

    /**
     *
     *
     * @return
     */
    @Override
    public String toString() {
        if (_columnNameList.size() == 0) {
            return "[[]]";
        }

        final BufferedWriter bw = Objectory.createBufferedWriter();

        try {
            toCSV(bw, true, false);

            return bw.toString();
        } finally {
            Objectory.recycle(bw);
        }
    }

    /**
     * Check frozen.
     */
    void checkFrozen() {
        if (_isFrozen) {
            throw new IllegalStateException("This DataSet is frozen, can't modify it.");
        }
    }

    /**
     * Check column name.
     *
     * @param columnName
     * @return
     */
    int checkColumnName(final String columnName) {
        int columnIndex = getColumnIndex(columnName);

        if (columnIndex < 0) {
            throw new IllegalArgumentException("The specified column(" + columnName + ") is not included in this DataSet " + _columnNameList);
        }

        return columnIndex;
    }

    /**
     * Check column name.
     *
     * @param columnNames
     * @return
     */
    int[] checkColumnName(final String... columnNames) {
        if (N.isNullOrEmpty(columnNames)) {
            throw new IllegalArgumentException("The specified columnNames is null or empty");
        }

        final int length = columnNames.length;
        final int[] columnIndexes = new int[length];

        for (int i = 0; i < length; i++) {
            columnIndexes[i] = checkColumnName(columnNames[i]);
        }

        return columnIndexes;
    }

    /**
     * Check column name.
     *
     * @param columnNames
     * @return
     */
    int[] checkColumnName(final Collection columnNames) {
        if (N.isNullOrEmpty(columnNames)) {
            throw new IllegalArgumentException("The specified columnNames is null or empty");
        }

        if (columnNames != this._columnNameList) {
            final int count = columnNames.size();
            final int[] columnNameIndexes = new int[count];
            final Iterator it = columnNames.iterator();

            for (int i = 0; i < count; i++) {
                columnNameIndexes[i] = checkColumnName(it.next());
            }

            return columnNameIndexes;
        }

        if (this._columnIndexes == null) {
            int count = columnNames.size();
            this._columnIndexes = new int[count];

            for (int i = 0; i < count; i++) {
                _columnIndexes[i] = i;
            }
        }

        return _columnIndexes;
    }

    /**
     * Check row num.
     *
     * @param rowNum
     */
    void checkRowNum(final int rowNum) {
        if ((rowNum < 0) || (rowNum >= size())) {
            throw new IllegalArgumentException("Invalid row number: " + rowNum + ". It must be >= 0 and < " + size());
        }
    }

    /**
     * Check row index.
     *
     * @param fromRowIndex
     * @param toRowIndex
     */
    void checkRowIndex(final int fromRowIndex, final int toRowIndex) {
        checkRowIndex(fromRowIndex, toRowIndex, size());
    }

    /**
     * Check row index.
     *
     * @param fromRowIndex
     * @param toRowIndex
     * @param size
     */
    void checkRowIndex(final int fromRowIndex, final int toRowIndex, final int size) {
        if ((fromRowIndex < 0) || (fromRowIndex > toRowIndex) || (toRowIndex > size)) {
            throw new IllegalArgumentException("Invalid fromRowIndex : " + fromRowIndex + " or toRowIndex: " + toRowIndex);
        }
    }

    /**
     * Gets the hash key.
     *
     * @param obj
     * @return
     */
    static Object getHashKey(Object obj) {
        return obj == null || !obj.getClass().isArray() ? obj : Wrapper.of(obj);
    }

    /**
     * The Class PaginatedRowDataSet.
     *
     * @author Haiyang Li
     * @version $Revision: 0.8 $ 07/01/15
     */
    private class PaginatedRowDataSet implements PaginatedDataSet {

        /** The expected mod count. */
        private final int expectedModCount = modCount;

        /** The page pool. */
        private final Map pagePool = new HashMap<>();

        private final Collection columnNames;

        /** The page size. */
        private final int pageSize;

        /** The page count. */
        private final int totalPages;

        /** The current page num. */
        private int currentPageNum;

        /**
         * Instantiates a new paginated row data set.
         *
         * @param pageSize
         */
        private PaginatedRowDataSet(final Collection columnNames, final int pageSize) {
            this.columnNames = columnNames;
            this.pageSize = pageSize;

            this.totalPages = ((RowDataSet.this.size() % pageSize) == 0) ? (RowDataSet.this.size() / pageSize) : ((RowDataSet.this.size() / pageSize) + 1);

            currentPageNum = 0;
        }

        /**
         *
         * @return
         */
        @Override
        public Iterator iterator() {
            return new Itr();
        }

        /**
         * Checks for next.
         *
         * @return true, if successful
         */
        @Override
        public boolean hasNext() {
            return currentPageNum < pageCount();
        }

        /**
         *
         * @return
         */
        @Override
        public DataSet currentPage() {
            return getPage(currentPageNum);
        }

        /**
         *
         * @return
         */
        @Override
        public DataSet nextPage() {
            return absolute(currentPageNum + 1).currentPage();
        }

        /**
         *
         * @return
         */
        @Override
        public DataSet previousPage() {
            return absolute(currentPageNum - 1).currentPage();
        }

        /**
         *
         * @return
         */
        @Override
        public Optional firstPage() {
            return (Optional) (pageCount() == 0 ? Optional.empty() : Optional.of(absolute(0).currentPage()));
        }

        /**
         *
         * @return
         */
        @Override
        public Optional lastPage() {
            return (Optional) (pageCount() == 0 ? Optional.empty() : Optional.of(absolute(pageCount() - 1).currentPage()));
        }

        /**
         * Gets the page.
         *
         * @param pageNum
         * @return
         */
        @Override
        public DataSet getPage(final int pageNum) {
            checkConcurrentModification();
            checkPageNumber(pageNum);

            synchronized (pagePool) {
                DataSet page = pagePool.get(pageNum);

                if (page == null) {
                    int offset = pageNum * pageSize;
                    page = RowDataSet.this.slice(columnNames, offset, Math.min(offset + pageSize, RowDataSet.this.size()));

                    pagePool.put(pageNum, page);
                }

                return page;
            }
        }

        /**
         *
         * @param pageNumber
         * @return
         */
        @Override
        public PaginatedDataSet absolute(final int pageNumber) {
            checkPageNumber(pageNumber);

            currentPageNum = pageNumber;

            return this;
        }

        /**
         * Current page num.
         *
         * @return
         */
        @Override
        public int currentPageNum() {
            return currentPageNum;
        }

        /**
         *
         * @return
         */
        @Override
        public int pageSize() {
            return pageSize;
        }

        /**
         *
         * @return
         */
        @Override
        public int pageCount() {
            return totalPages;
        }

        /**
         *
         * @return
         */
        @Override
        public int totalPages() {
            return totalPages;
        }

        /**
         *
         * @return
         */
        @Override
        public Stream stream() {
            return Stream.of(iterator());
        }

        final void checkConcurrentModification() {
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }

        /**
         * Check page number.
         *
         * @param pageNumber
         */
        private void checkPageNumber(final int pageNumber) {
            if ((pageNumber < 0) || (pageNumber >= pageCount())) {
                throw new IllegalArgumentException(pageNumber + " out of page index [0, " + pageCount() + ")");
            }
        }

        /**
         * The Class Itr.
         */
        private class Itr implements Iterator {

            /** The cursor. */
            int cursor = 0;

            /**
             * Checks for next.
             *
             * @return true, if successful
             */
            @Override
            public boolean hasNext() {
                return cursor < pageCount();
            }

            /**
             *
             * @return
             */
            @Override
            public DataSet next() {
                checkConcurrentModification();

                try {
                    DataSet next = getPage(cursor);
                    cursor++;

                    return next;
                } catch (IndexOutOfBoundsException e) {
                    checkConcurrentModification();
                    throw new NoSuchElementException();
                }
            }

            /**
             * Removes the.
             */
            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        }
    }
}