blob: 8158eef8d5f84c69f83099b0010dd01dda34a669 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hudi.cli;
import org.apache.hudi.common.util.Option;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Table to be rendered. This class takes care of ordering rows and limiting before renderer renders it.
*/
public class Table implements Iterable<List<String>> {
// Header for this table
private final TableHeader rowHeader;
// User-specified conversions before rendering
private final Map<String, Function<Object, String>> fieldNameToConverterMap;
// Option attribute to track sorting field
private final Option<String> orderingFieldNameOptional;
// Whether sorting has to be in descending order (by default : optional)
private final Option<Boolean> isDescendingOptional;
// Limit the number of entries rendered
private final Option<Integer> limitOptional;
// Raw list of rows
private final List<List<Comparable>> rawRows;
// Flag to determine if all the rows have been added
private boolean finishedAdding = false;
// Rows ready for Rendering
private List<List<String>> renderRows;
public Table(TableHeader rowHeader, Map<String, Function<Object, String>> fieldNameToConverterMap,
Option<String> orderingFieldNameOptional, Option<Boolean> isDescendingOptional, Option<Integer> limitOptional) {
this.rowHeader = rowHeader;
this.fieldNameToConverterMap = fieldNameToConverterMap;
this.orderingFieldNameOptional = orderingFieldNameOptional;
this.isDescendingOptional = isDescendingOptional;
this.limitOptional = limitOptional;
this.rawRows = new ArrayList<>();
}
/**
* Main API to add row to the table.
*
* @param row Row
*/
public Table add(List<Comparable> row) {
if (finishedAdding) {
throw new IllegalStateException("Container already marked done for adding. No more entries can be added.");
}
if (rowHeader.getFieldNames().size() != row.size()) {
throw new IllegalArgumentException("Incorrect number of fields in row. Expected: "
+ rowHeader.getFieldNames().size() + ", Got: " + row.size() + ", Row: " + row);
}
this.rawRows.add(new ArrayList<>(row));
return this;
}
/**
* Add all rows.
*
* @param rows Rows to be added
* @return
*/
public Table addAll(List<List<Comparable>> rows) {
rows.forEach(this::add);
return this;
}
/**
* Add all rows.
*
* @param rows Rows to be added
* @return
*/
public Table addAllRows(List<Comparable[]> rows) {
rows.forEach(r -> add(Arrays.asList(r)));
return this;
}
/**
* API to let the table know writing is over and reading is going to start.
*/
public Table flip() {
this.finishedAdding = true;
sortAndLimit();
return this;
}
/**
* Sorting of rows by a specified field.
*
* @return
*/
private List<List<Comparable>> orderRows() {
return orderingFieldNameOptional.map(orderingColumnName -> {
return rawRows.stream().sorted((row1, row2) -> {
Comparable fieldForRow1 = row1.get(rowHeader.indexOf(orderingColumnName));
Comparable fieldForRow2 = row2.get(rowHeader.indexOf(orderingColumnName));
int cmpRawResult = fieldForRow1.compareTo(fieldForRow2);
return isDescendingOptional.map(isDescending -> isDescending ? -1 * cmpRawResult : cmpRawResult).orElse(cmpRawResult);
}).collect(Collectors.toList());
}).orElse(rawRows);
}
/**
* Prepares for rendering. Rows are sorted and limited
*/
private void sortAndLimit() {
this.renderRows = new ArrayList<>();
final int limit = this.limitOptional.orElse(rawRows.size());
final List<List<Comparable>> orderedRows = orderRows();
renderRows = orderedRows.stream().limit(limit).map(row -> IntStream.range(0, rowHeader.getNumFields()).mapToObj(idx -> {
String fieldName = rowHeader.get(idx);
if (fieldNameToConverterMap.containsKey(fieldName)) {
return fieldNameToConverterMap.get(fieldName).apply(row.get(idx));
}
Object v = row.get(idx);
return v == null ? "null" : v.toString();
}).collect(Collectors.toList())).collect(Collectors.toList());
}
@Override
public Iterator<List<String>> iterator() {
if (!finishedAdding) {
throw new IllegalStateException("Container must be flipped before reading the data");
}
return renderRows.iterator();
}
@Override
public void forEach(Consumer<? super List<String>> action) {
if (!finishedAdding) {
throw new IllegalStateException("Container must be flipped before reading the data");
}
renderRows.forEach(action);
}
public List<String> getFieldNames() {
return rowHeader.getFieldNames();
}
public List<List<String>> getRenderRows() {
return renderRows;
}
}