| /* |
| * 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.freemarker.generator.base.table; |
| |
| import org.apache.freemarker.generator.base.util.ListUtils; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.stream.Collectors; |
| |
| import static java.util.Collections.emptyList; |
| import static java.util.Objects.requireNonNull; |
| |
| /** |
| * Simple table model filled from maps or rows representing tabular data. |
| */ |
| public class Table { |
| |
| /** List of column names */ |
| private final List<String> columnNames; |
| |
| /** Column types derived from tabular data */ |
| private final List<Class<?>> columnTypes; |
| |
| /** Table data as rows */ |
| private final List<List<Object>> values; |
| |
| /** Map column names to column index */ |
| private final Map<String, Integer> columnMap; |
| |
| private Table() { |
| this.columnNames = emptyList(); |
| this.columnTypes = emptyList(); |
| this.values = emptyList(); |
| this.columnMap = new HashMap<>(); |
| } |
| |
| private Table(Collection<String> columnNames, Collection<Class<?>> columnTypes, List<List<Object>> columnValuesList) { |
| this.columnNames = new ArrayList<>(requireNonNull(columnNames)); |
| this.columnTypes = new ArrayList<>(requireNonNull(columnTypes)); |
| this.values = ListUtils.transpose(requireNonNull(columnValuesList)); |
| this.columnMap = columnMap(this.columnNames); |
| } |
| |
| public List<String> getColumnNames() { |
| return columnNames; |
| } |
| |
| public List<Class<?>> getColumnTypes() { |
| return columnTypes; |
| } |
| |
| public List<List<Object>> getValues() { |
| return values; |
| } |
| |
| public int getNrOfColumns() { |
| return columnNames.isEmpty() ? values.isEmpty() ? 0 : values.get(0).size() : columnNames.size(); |
| } |
| |
| public int size() { |
| return values.size(); |
| } |
| |
| public boolean isEmpty() { |
| return values.isEmpty(); |
| } |
| |
| public List<Object> getRow(int row) { |
| return values.get(row); |
| } |
| |
| public Object get(int row, int column) { |
| return values.get(row).get(column); |
| } |
| |
| public Object get(int row, String column) { |
| return values.get(row).get(columnMap.get(column)); |
| } |
| |
| public boolean hasColumnHeaderRow() { |
| return !columnNames.isEmpty(); |
| } |
| |
| /** |
| * Create a table from a list of maps. Non-tabular data is supported, |
| * i.e. not all maps contains all possible keys. |
| * |
| * @param maps list of maps |
| * @return table |
| */ |
| public static Table fromMaps(Collection<Map<String, Object>> maps) { |
| if (maps == null || maps.isEmpty()) { |
| return new Table(); |
| } |
| |
| final List<String> columnNames = columnNames(maps); |
| final List<List<Object>> columnValuesList = columnValuesList(maps, columnNames); |
| final List<Class<?>> columnTypes = columnTypes(columnValuesList); |
| |
| return new Table( |
| columnNames, |
| columnTypes, |
| columnValuesList); |
| } |
| |
| /** |
| * Create a table from a list of rows representing tabular data. |
| * |
| * @param rows row values |
| * @return table |
| */ |
| public static Table fromRows(List<List<Object>> rows) { |
| requireNonNull(rows, "rows is null"); |
| |
| final List<List<Object>> columnValuesList = ListUtils.transpose(rows); |
| final List<Class<?>> columnTypes = columnTypes(columnValuesList); |
| |
| return new Table( |
| new ArrayList<>(), |
| columnTypes, |
| columnValuesList); |
| } |
| |
| /** |
| * Create a table from a list of rows representing tabular data |
| * where the first row may consist of column headers. |
| * |
| * @param rows row values |
| * @param withFirstRowAsColumnNames column names as first row? |
| * @return table |
| */ |
| public static Table fromRows(List<List<Object>> rows, boolean withFirstRowAsColumnNames) { |
| if (ListUtils.isNullOrEmpty(rows) && withFirstRowAsColumnNames) { |
| throw new IllegalArgumentException("Header columns expected but list is empty"); |
| } |
| |
| if (withFirstRowAsColumnNames) { |
| final List<String> columnNames = columnNames(rows.get(0)); |
| final List<List<Object>> table = rows.subList(1, rows.size()); |
| return fromRows(columnNames, table); |
| } else { |
| return fromRows(rows); |
| } |
| } |
| |
| /** |
| * Create a table from column names and row values. |
| * |
| * @param columnNames list of column names |
| * @param rows row values as rows |
| * @return table |
| */ |
| public static Table fromRows(Collection<String> columnNames, List<List<Object>> rows) { |
| requireNonNull(columnNames, "columnNames is null"); |
| requireNonNull(rows, "rows is null"); |
| |
| final List<List<Object>> columnValuesList = ListUtils.transpose(rows); |
| final List<Class<?>> columnTypes = columnTypes(columnValuesList); |
| |
| return new Table( |
| new ArrayList<>(columnNames), |
| columnTypes, |
| columnValuesList); |
| } |
| |
| /** |
| * Determine column names based on all available keys of the maps. |
| * |
| * @param maps list of maps |
| * @return column names |
| */ |
| private static List<String> columnNames(Collection<Map<String, Object>> maps) { |
| return maps.stream() |
| .map(Map::keySet) |
| .flatMap(Collection::stream) |
| .distinct() |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Determine column names based on a single list. |
| * |
| * @param list list of column names |
| * @return column names |
| */ |
| private static List<String> columnNames(List<Object> list) { |
| return list.stream() |
| .map(Object::toString) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Determine all column values. |
| * |
| * @param maps list of maps |
| * @param columnNames column names |
| * @return list of column values |
| */ |
| private static List<List<Object>> columnValuesList(Collection<Map<String, Object>> maps, List<String> columnNames) { |
| return columnNames.stream() |
| .map(columnName -> columnValues(maps, columnName)) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Determine the values of a single column. |
| * |
| * @param maps list of maps |
| * @param columnName column name |
| * @return values of the given column |
| */ |
| private static List<Object> columnValues(Collection<Map<String, Object>> maps, String columnName) { |
| return maps.stream() |
| .map(map -> map.getOrDefault(columnName, null)) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Determine the column types based on the first non-null values. |
| * |
| * @param columnValuesList list of column values |
| * @return classes of the first non-null value |
| */ |
| private static List<Class<?>> columnTypes(List<List<Object>> columnValuesList) { |
| return columnValuesList.stream() |
| .map(Table::columnType) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Determine the column type based on the first non-null value. |
| * |
| * @param columnValues column values |
| * @return class of the first non-null value |
| */ |
| private static Class<?> columnType(List<Object> columnValues) { |
| return ListUtils.coalesce(columnValues).getClass(); |
| } |
| |
| private static Map<String, Integer> columnMap(List<String> columnNames) { |
| final Map<String, Integer> result = new HashMap<>(); |
| for (int i = 0; i < columnNames.size(); i++) { |
| result.put(columnNames.get(i), i); |
| } |
| return result; |
| } |
| } |