com.blackducksoftware.tools.commonframework.standard.protex.report.template.TemplateWriter.java Source code

Java tutorial

Introduction

Here is the source code for com.blackducksoftware.tools.commonframework.standard.protex.report.template.TemplateWriter.java

Source

/*******************************************************************************
 * Copyright (C) 2016 Black Duck Software, Inc.
 * http://www.blackducksoftware.com/
 *
 * 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 com.blackducksoftware.tools.commonframework.standard.protex.report.template;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.poi.ss.formula.FormulaParser;
import org.apache.poi.ss.formula.FormulaRenderer;
import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.ss.formula.ptg.AreaPtg;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.RefPtgBase;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
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.xssf.usermodel.XSSFEvaluationWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.blackducksoftware.tools.commonframework.standard.protex.report.model.TemplateColumn;
import com.blackducksoftware.tools.commonframework.standard.protex.report.model.TemplatePojo;
import com.blackducksoftware.tools.commonframework.standard.protex.report.model.TemplateSheet;

/**
 * Writes out rows to an existing Template created by the {@link TemplateReader } .
 * 
 * @author Ari Kamen
 * @param <T>
 *            This is the extension of the TemplatePojo
 */
public class TemplateWriter<T extends TemplatePojo> {

    /** The log. */
    private final Logger log = LoggerFactory.getLogger(this.getClass().getName());

    /**
     * Flags to instruct the TemplateWriter to either ignore or proceed on mapping errors
     * Mapping errors are where the Pojo methods do not match to the columns
     */
    public static final Boolean IGNORE_MAPPING_ERRORS = true;

    // Indicator for Excel Workbook style setup - will be cloned and applied

    public static final Boolean WORKBOOK_STYLE_CLONE_TRUE = true;

    // Indicator for Excel Workbook style setup - will not be cloned and applied
    public static final Boolean WORKBOOK_STYLE_CLONE_FALSE = false;

    public static final Boolean EXIT_ON_MAPPING_ERRORS = false;

    /** The template reader. */
    private final TemplateReader templateReader;

    /** The pojo class. */
    private Class<T> pojoClass;

    /** The book. */
    private Workbook book;

    /**
     * Instantiates a new template writer.
     * 
     * @param templateReader
     *            the template reader
     */
    public TemplateWriter(TemplateReader templateReader) {
        this.templateReader = templateReader;
    }

    /**
     * Return the work book
     * 
     * @return the work book
     */
    public Workbook getWorkbook() {
        return book;
    }

    /**
     * Writes out pojos to specific sheet by using internal mappings specified
     * by the user and generates the workbook.
     * 
     * @param pojoList
     *            the pojo list
     * @param templateSheet
     *            the template sheet
     * @param pojoClass
     *            the pojo class
     * @param ignoreMissingColumns
     *            - if true, proceeds despite missing mappings (will write out what it can)
     * @throws Exception
     *             the exception
     */
    public void writeOutPojo(List<T> pojoList, TemplateSheet templateSheet, Class<T> pojoClass,
            boolean ignoreMissingColumns) throws Exception {

        writeOutPojo(pojoList, templateSheet, pojoClass, ignoreMissingColumns, IGNORE_MAPPING_ERRORS,
                WORKBOOK_STYLE_CLONE_TRUE);
    }

    /**
     * Writes out pojos to specific sheet by using internal mappings specified
     * by the user.
     * 
     * @param pojoList
     *            the pojo list
     * @param templateSheet
     *            the template sheet
     * @param pojoClass
     *            the pojo class
     * @param ignoreMissingColumns
     *            - if true, proceeds despite missing mappings (will write out what it can, if the generateWorkBook flag
     *            is set to true)
     * @param generateWorkBook
     *            - if true, proceeds with generating the workbook should be written out after the sheet processing is
     *            complete
     * @param cloneStyle
     *            - if true, apply the styles to the new cells
     * @throws Exception
     *             the exception
     */
    public void writeOutPojo(List<T> pojoList, TemplateSheet templateSheet, Class<T> pojoClass,
            boolean ignoreMissingColumns, boolean generateWorkBook, boolean cloneStyle) throws Exception {

        this.pojoClass = pojoClass;
        testReflectionMappings(templateSheet, pojoList.get(0), ignoreMissingColumns);

        book = templateReader.getInternalWorkBook();
        Map<String, TemplateColumn> columnMap = templateSheet.getColumnMap();

        Sheet activeSheet = book.getSheet(templateSheet.getSheetName());
        int i = 1;
        for (TemplatePojo pojo : pojoList) {
            Row activeRow = activeSheet.createRow(i);
            writePojoValuesToRow(activeSheet, activeRow, pojo, columnMap, cloneStyle);
            i++;
        }

        // Write the workbook if the flag is set to true
        if (generateWorkBook) {
            writeWorkBook(generateWorkBook);
        }
    }

    /**
     * Handles writing the pojo values to row for the provided sheet
     * 
     * @param pojoList
     *            the list of pojo values
     * @param templateSheet
     *            the template sheet
     * @param pojoClass
     *            the pojo class
     * @param ignoreMissingColumns
     *            - if true, proceeds despite missing mappings (will write out what it can)
     * @return the updated workbook
     * @throws Exception
     */
    public Workbook writeOutPojoValuesToSheet(List<T> pojoList, TemplateSheet templateSheet, Class<T> pojoClass,
            boolean ignoreMissingColumns) throws Exception {

        this.pojoClass = pojoClass;
        testReflectionMappings(templateSheet, pojoList.get(0), ignoreMissingColumns);

        book = templateReader.getInternalWorkBook();

        Map<String, TemplateColumn> columnMap = templateSheet.getColumnMap();

        Sheet activeSheet = book.getSheet(templateSheet.getSheetName());

        int i = 1;
        for (TemplatePojo pojo : pojoList) {
            Row activeRow = activeSheet.createRow(i);
            writePojoValuesToRow(activeSheet, activeRow, pojo, columnMap, WORKBOOK_STYLE_CLONE_TRUE);
            i++;
        }
        return book;
    }

    /**
     * Write pojo values to row for the provided sheet.
     * 
     * @param activeSheet
     *            the provided sheet for pojo values write out to the rows
     * @param activeRow
     *            the active row
     * @param pojo
     *            the pojo
     * @param columnMap
     *            the column map
     * @param cloneStyle
     *            - if true, apply the styles to the new cells
     */
    private void writePojoValuesToRow(Sheet activeSheet, Row activeRow, TemplatePojo pojo,
            Map<String, TemplateColumn> columnMap, boolean cloneStyle) {

        Iterator<String> it = columnMap.keySet().iterator();
        while (it.hasNext()) {
            String key = it.next();
            TemplateColumn column = columnMap.get(key);
            Integer position = column.getColumnPos();
            CellStyle styleFromTemplate = column.getCellStyle();

            Cell activeCell;
            int cellType = column.getCellType();
            if (cellType == Cell.CELL_TYPE_FORMULA) {
                activeCell = activeRow.createCell(position, Cell.CELL_TYPE_FORMULA);
                log.debug("Active Cell is PartOfArrayFormulaGroup: " + activeCell.isPartOfArrayFormulaGroup());
            } else if (cellType == Cell.CELL_TYPE_NUMERIC) {
                activeCell = activeRow.createCell(position, Cell.CELL_TYPE_NUMERIC);
            } else {
                activeCell = activeRow.createCell(position, Cell.CELL_TYPE_STRING);
            }

            // Set the value
            String pojoValue = getValueFromPojo(pojo, column.getLookupMappingName());
            activeCell.setCellValue(pojoValue);

            // Set the cell style
            // TODO: This catches the XML Disconnected exception, but the styles come out all wrong on subsequent
            // sheets.
            // Appears to only happen in the unit tests.
            if (cloneStyle) {
                try {
                    CellStyle newcs = book.createCellStyle();
                    newcs.cloneStyleFrom(styleFromTemplate);
                    activeCell.setCellStyle(newcs);
                } catch (Exception e) {
                    log.warn("Unable to copy cell styles!" + e.getMessage());
                }
            }

            if (cellType == Cell.CELL_TYPE_FORMULA) {
                copyFormula(activeSheet, activeCell, activeRow, column);
            }
        }
    }

    /**
     * Handles copying the formula for the provided sheet and active row
     * 
     * @param sheet
     *            the provided sheet
     * @param targetCell
     *            the target cell to copy the formula
     * @param activeRow
     *            the active row
     * @param column
     *            the TemplateColumn to be used for
     */
    private void copyFormula(Sheet sheet, Cell targetCell, Row activeRow, TemplateColumn column) {
        if (targetCell == null || sheet == null || targetCell.getCellType() != Cell.CELL_TYPE_FORMULA) {
            return;
        }

        String formula = column.getCellFormula();

        int shiftRows = activeRow.getRowNum() - 1;
        int shiftCols = 0;

        XSSFEvaluationWorkbook workbookWrapper = XSSFEvaluationWorkbook.create((XSSFWorkbook) sheet.getWorkbook());

        Ptg[] ptgs = FormulaParser.parse(formula, workbookWrapper, FormulaType.CELL,
                sheet.getWorkbook().getSheetIndex(sheet));

        for (Ptg ptg : ptgs) {
            if (ptg instanceof RefPtgBase) {
                RefPtgBase ref = (RefPtgBase) ptg;
                if (ref.isColRelative()) {
                    ref.setColumn(ref.getColumn() + shiftCols);
                }
                if (ref.isRowRelative()) {
                    ref.setRow(ref.getRow() + shiftRows);
                }
            } else if (ptg instanceof AreaPtg) {
                AreaPtg ref = (AreaPtg) ptg;
                if (ref.isFirstColRelative()) {
                    ref.setFirstColumn(ref.getFirstColumn() + shiftCols);
                }
                if (ref.isLastColRelative()) {
                    ref.setLastColumn(ref.getLastColumn() + shiftCols);
                }
                if (ref.isFirstRowRelative()) {
                    ref.setFirstRow(ref.getFirstRow() + shiftRows);
                }
                if (ref.isLastRowRelative()) {
                    ref.setLastRow(ref.getLastRow() + shiftRows);
                }
            }
        }

        formula = FormulaRenderer.toFormulaString(workbookWrapper, ptgs);
        targetCell.setCellFormula(formula);
        log.debug("Set Formula for row " + activeRow.getRowNum() + " : " + formula);
        targetCell.setAsActiveCell();
    }

    /**
     * Write work book.
     * 
     * @throws Exception
     *             the exception
     */
    public void writeWorkBook() throws Exception {
        writeWorkBook(true);
    }

    /**
     * Write work book.
     * 
     * @param workbookUpdatePeriodic
     *            indicator for workbook periodic update after each sheet processing
     * @throws Exception
     *             the exception
     */
    public void writeWorkBook(boolean workbookUpdatePeriodic) throws Exception {

        FileOutputStream stream = null;
        File outputLocation = templateReader.getOutputLocation();

        try {
            if (outputLocation != null) {
                stream = new FileOutputStream(outputLocation);
            } else {
                throw new Exception("Unable to write, destination unknown!");
            }

            book.write(stream);
            log.info("Finished writing workbook to: " + outputLocation);

            // Relead the book every time, in case multiple sheets need to be
            // written to.
            // POI [Bug 49940]
            // https://issues.apache.org/bugzilla/show_bug.cgi?id=49940
            if (workbookUpdatePeriodic) {
                TemplateReader.generateWorkBookFromFile(outputLocation);
            }

        } catch (Exception e) {
            log.error("Fatal: " + e.getMessage());
            throw new Exception("Fatal error, unable to write out workbook:", e);
        } finally {
            try {
                stream.close();
            } catch (IOException ioe) {
            }
        }
    }

    /**
     * Tests to make sure that the mappings properly map to the POJO.
     * 
     * @param templateSheet
     *            the template sheet
     * @param pojo
     *            the pojo
     * @param ignoreMissingColumns
     * @throws Exception
     *             If Mappings are wrong
     */
    private void testReflectionMappings(TemplateSheet templateSheet, T pojo, boolean ignoreMissingColumns)
            throws Exception {
        boolean missingMappings = false;
        boolean missingPojoMethods = false;

        List<String> missingNames = new ArrayList<String>();
        List<String> missingMethods = new ArrayList<String>();

        Collection<TemplateColumn> columns = templateSheet.getColumnMap().values();
        for (TemplateColumn column : columns) {
            String lookupName = column.getLookupMappingName();
            if (lookupName == null) {
                missingMappings = true;
                missingNames.add(column.getColumnName());

            }
            String value = getValueFromPojo(pojo, lookupName);
            if (value == null) {
                missingPojoMethods = true;
                missingMethods.add(lookupName);
                log.error("Missing method: lookupName = " + lookupName + " for column " + column.getColumnName());
            }

        }

        if (missingMappings) {
            Collections.sort(missingNames);
            String prettyListStr = getPrettyList(missingNames);
            if (ignoreMissingColumns) {
                log.debug("Proceeding with writing, despite missing mappings for: " + prettyListStr);
            } else {
                throw new Exception("Columns missing mapping: " + prettyListStr);
            }
        }
        if (missingPojoMethods) {
            Collections.sort(missingMethods);
            String prettyListStr = getPrettyList(missingMethods);
            if (ignoreMissingColumns) {
                log.debug("Proceeding with writing, despite missing methods: " + prettyListStr);
            } else {
                throw new Exception("Methods missing from POJO: " + prettyListStr);
            }
        }

    }

    /**
     * Gets the pretty list.
     * 
     * @param list
     *            the list
     * @return the pretty list
     */
    private String getPrettyList(List<String> list) {
        StringBuilder sb = new StringBuilder();
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String s = it.next();
            sb.append(s);

            if (it.hasNext()) {
                sb.append(",");
            }
        }

        return sb.toString();
    }

    /**
     * Use reflection to derive the value from the POJO.
     * 
     * @param pojo
     *            the pojo
     * @param lookupMappingName
     *            the lookup mapping name
     * @return the value from pojo
     */
    private String getValueFromPojo(TemplatePojo pojo, String lookupMappingName) {
        String value = null;
        String lookUpMethodName = "get" + lookupMappingName;

        try {
            // Use method instead of declared to handle any class extension that may take place.
            Method method = pojo.getClass().getMethod(lookUpMethodName);
            pojo.getClass().getDeclaredMethods();
            if (method == null) {
                log.warn("Method for the pojo class does not exist with name: " + lookUpMethodName);
                return value;
            }

            Object ret = method.invoke(pojo);
            value = (ret == null) ? "" : ret.toString();

        } catch (Exception e) {
            log.warn("Unable to reflectively get value for method: " + lookupMappingName + " - POJO: "
                    + pojo.getClass());
        }

        return value;
    }

    /**
     * Print out the memory usage information
     */
    public void getMemoryInformation() {
        ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
        log.debug("Heap:" + ManagementFactory.getMemoryMXBean().getHeapMemoryUsage());
        log.debug("NonHeap:" + ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage());
        List<MemoryPoolMXBean> beans = ManagementFactory.getMemoryPoolMXBeans();
        for (MemoryPoolMXBean bean : beans) {
            log.debug(bean.getName() + ":" + bean.getUsage());
        }

        for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
            log.debug(bean.getName() + ":" + bean.getCollectionCount() + ":" + bean.getCollectionTime());
        }
    }
}