org.apache.hudi.cli.Table Maven / Gradle / Ivy
The newest version!
/*
* 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> {
// Header for this table
private final TableHeader rowHeader;
// Whether to print row number
private final boolean addRowNo;
// User-specified conversions before rendering
private final Map> fieldNameToConverterMap;
// Option attribute to track sorting field
private final Option orderingFieldNameOptional;
// Whether sorting has to be in descending order (by default : optional)
private final Option isDescendingOptional;
// Limit the number of entries rendered
private final Option limitOptional;
// Raw list of rows
private final List> rawRows;
// Flag to determine if all the rows have been added
private boolean finishedAdding = false;
// Headers ready for rendering
private TableHeader renderHeaders;
// Rows ready for rendering
private List> renderRows;
public Table(
TableHeader rowHeader, Map> fieldNameToConverterMap,
boolean addRowNo, Option orderingFieldNameOptional,
Option isDescendingOptional, Option limitOptional) {
this.rowHeader = rowHeader;
this.addRowNo = addRowNo;
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 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> rows) {
rows.forEach(this::add);
return this;
}
/**
* Add all rows.
*
* @param rows Rows to be added
* @return
*/
public Table addAllRows(List 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> 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());
// Row number is added here if enabled
final List> rawOrderedRows = orderRows();
final List> orderedRows;
if (addRowNo) {
orderedRows = new ArrayList<>();
int rowNo = 0;
for (List row : rawOrderedRows) {
List newRow = new ArrayList<>();
newRow.add(rowNo++);
newRow.addAll(row);
orderedRows.add(newRow);
}
} else {
orderedRows = rawOrderedRows;
}
renderHeaders = addRowNo
? new TableHeader().addTableHeaderField(HoodieTableHeaderFields.HEADER_ROW_NO)
.addTableHeaderFields(rowHeader)
: rowHeader;
renderRows = orderedRows.stream().limit(limit)
.map(row -> IntStream.range(0, renderHeaders.getNumFields()).mapToObj(idx -> {
String fieldName = renderHeaders.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> iterator() {
if (!finishedAdding) {
throw new IllegalStateException("Container must be flipped before reading the data");
}
return renderRows.iterator();
}
@Override
public void forEach(Consumer super List> action) {
if (!finishedAdding) {
throw new IllegalStateException("Container must be flipped before reading the data");
}
renderRows.forEach(action);
}
public List getFieldNames() {
if (renderHeaders != null) {
return renderHeaders.getFieldNames();
}
return rowHeader.getFieldNames();
}
public List> getRenderRows() {
return renderRows;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy