hrytsenko.gscripts.io.XlsFiles.java Source code

Java tutorial

Introduction

Here is the source code for hrytsenko.gscripts.io.XlsFiles.java

Source

/*
 * #%L
 * gscripts
 * %%
 * Copyright (C) 2015 Anton Hrytsenko
 * %%
 * 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.
 * #L%
 */
package hrytsenko.gscripts.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableList;

import hrytsenko.gscripts.AppException;
import hrytsenko.gscripts.util.NamedArgs;
import hrytsenko.gscripts.util.Records;

/**
 * Methods for load/save of XLSX files.
 * 
 * <p>
 * Named arguments:
 * <dl>
 * <dt>{@link NamedArgs#PATH}</dt>
 * <dd>The path to file.</dd>
 * <dt>{@link #SHEET_NAME}</dt>
 * <dd>The name of sheet, default: {@link #SHEET_NAME_DEFAULT}.</dd>
 * </dl>
 * 
 * @author hrytsenko.anton
 */
public final class XlsFiles {

    private static final Logger LOGGER = LoggerFactory.getLogger(XlsFiles.class);

    private static final String SHEET_NAME = "sheetName";
    private static final String SHEET_NAME_DEFAULT = "records";

    private XlsFiles() {
    }

    /**
     * Loads record from file.
     * 
     * @param args
     *            the named arguments.
     * 
     * @return the list of records.
     * 
     * @throws IOException
     *             if file could not be loaded.
     */
    public static List<Map<String, String>> loadXls(Map<String, ?> args) {
        Path path = NamedArgs.findPath(args);
        LOGGER.info("Load {}.", path.getFileName());

        try (InputStream stream = Files.newInputStream(path); Workbook workbook = WorkbookFactory.create(stream);) {
            Sheet sheet = workbook.getSheetAt(0);
            List<Row> rows = ImmutableList.copyOf(sheet.rowIterator());
            if (rows.isEmpty()) {
                return Collections.emptyList();
            }

            List<String> columns = rows.stream().findFirst().map(XlsFiles::cellValues)
                    .orElseThrow(() -> new IllegalStateException("Cannot read columns."));

            return rows.stream().skip(1).map(toRecord(columns)).collect(Collectors.toList());
        } catch (Exception exception) {
            throw new AppException(String.format("Could not load file %s.", path.getFileName()), exception);
        }
    }

    private static Function<Row, Map<String, String>> toRecord(List<String> columns) {
        return row -> IntStream.range(0, columns.size())
                .collect(LinkedHashMap::new,
                        (r, i) -> r.put(columns.get(i),
                                Optional.ofNullable(row.getCell(i)).map(XlsFiles::cellValue).orElse("")),
                        Map::putAll);
    }

    /**
     * Save records to file.
     * 
     * <p>
     * If file already exists, then it will be overridden.
     * 
     * @param input
     *            the list of records to save.
     * @param args
     *            the named arguments {@link XlsFiles}.
     * 
     * @throws IOException
     *             if file could not be saved.
     */
    public static void saveXls(List<Map<String, ?>> input, Map<String, ?> args) {
        if (input.isEmpty()) {
            LOGGER.info("No records to save.");
            return;
        }

        Path path = NamedArgs.findPath(args);
        LOGGER.info("Save {}.", path.getFileName());

        try (OutputStream stream = Files.newOutputStream(path); Workbook workbook = new XSSFWorkbook()) {
            Sheet sheet = workbook.createSheet();
            workbook.setSheetName(0, NamedArgs.tryFind(args, SHEET_NAME).orElse(SHEET_NAME_DEFAULT));

            List<String> columns = Records.columns(input);
            int columnsNum = columns.size();

            Row header = createHeader(sheet);
            createCells(header, columnsNum).forEach(setValueByPos(columns::get));

            int recordsNum = input.size();
            createRows(sheet, recordsNum).forEach(row -> {
                Map<String, String> record = Records.normalize(input.get(row.getRowNum() - 1));
                createCells(row, columnsNum).forEach(setValueByPos(i -> record.get(columns.get(i))));
            });

            workbook.write(stream);
        } catch (IOException exception) {
            throw new AppException(String.format("Could not save file %s.", path.getFileName()), exception);
        }
    }

    private static Row createHeader(Sheet sheet) {
        sheet.createFreezePane(0, 1);
        return sheet.createRow(0);
    }

    private static Stream<Row> createRows(Sheet sheet, int total) {
        return IntStream.range(0, total).map(i -> i + 1).mapToObj(sheet::createRow);
    }

    private static Stream<Cell> createCells(Row row, int total) {
        return IntStream.range(0, total).mapToObj(row::createCell);
    }

    private static Consumer<Cell> setValueByPos(Function<Integer, String> supplier) {
        return cell -> cell.setCellValue(supplier.apply(cell.getColumnIndex()));
    }

    private static List<String> cellValues(Row row) {
        return StreamSupport
                .stream(Spliterators.spliteratorUnknownSize(row.cellIterator(), Spliterator.ORDERED), false)
                .map(XlsFiles::cellValue).collect(Collectors.toList());
    }

    private static String cellValue(Cell cell) {
        switch (cell.getCellType()) {
        case Cell.CELL_TYPE_STRING:
            return cell.getStringCellValue();
        case Cell.CELL_TYPE_NUMERIC:
            DecimalFormat format = new DecimalFormat("0.#");
            return format.format(cell.getNumericCellValue());
        case Cell.CELL_TYPE_BOOLEAN:
            return String.valueOf(cell.getBooleanCellValue());
        default:
            return "";
        }
    }

}