/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.configuration;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.jexl2.Expression;
import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.jexl2.JexlEngine;
import org.apache.commons.jexl2.MapContext;
import org.tiefaces.exception.EvaluationException;

/**
 * expEngine use jexl engine evaluate an expression. see apache jexl project for
 * detail. use local thread for thread safe.
 *
 * @author Jason Jiang
 *
 */
public class ExpressionEngine {
	/** expression. */
	private Expression jExpression;

	/**
	 * context. private JexlContext jContext;
	 */
	/** jexlEngine local map. */
	private static final ThreadLocal<JexlEngine>
	JEXL_LOCAL = new ThreadLocal<JexlEngine>() {
		@Override
		protected JexlEngine initialValue() {
			return new JexlEngine();
		}
	};
	/** jexpmap local map. */
	private static final ThreadLocal<Map<String, Expression>>
	JEXL_MAP_LOCAL = new ThreadLocal<Map<String, Expression>>() {
		@Override
		protected Map<String, Expression> initialValue() {
			return new HashMap<>();
		}
	};

	/**
	 * empty context used for evaluate single script.
	 */
	private static final Map<String, Object>
	EMPTY_CONTEXT = new HashMap<>();

	/**
	 * constructor.
	 *
	 * @param pExpression
	 *            expression.
	 */
	public ExpressionEngine(final String pExpression) {
		JexlEngine jexl = JEXL_LOCAL.get();
		jExpression = jexl.createExpression(pExpression);
	}

	/**
	 * constructor.
	 */
	public ExpressionEngine() {
		super();
	}

	/**
	 * Evaluate the expression.
	 *
	 * @param expression
	 *            string input expression.
	 * @return evaluated object.
	 */
	public final Object evaluate(final String expression) {
		return evaluate(expression, EMPTY_CONTEXT);
	}

	/**
	 * Evaluate the expression.
	 *
	 * @param expression
	 *            string input expression.
	 * @param context
	 *            context map.
	 * @return evaluated object.
	 */
	public final Object evaluate(final String expression,
			final Map<String, Object> context) {
		JexlContext jexlContext = new MapContext(context);
		try {
			JexlEngine jexl = JEXL_LOCAL.get();
			Map<String, Expression> expMap = JEXL_MAP_LOCAL.get();
			Expression jexlExpression = expMap.get(expression);
			if (jexlExpression == null) {
				jexlExpression = jexl.createExpression(expression);
				expMap.put(expression, jexlExpression);
			}
			return jexlExpression.evaluate(jexlContext);
		} catch (Exception e) {
			throw new EvaluationException(e);
		}
	}

	/**
	 * evaluate from giving context.
	 *
	 * @param context
	 *            context.
	 * @return object evaluated.
	 */
	public final Object evaluate(final Map<String, Object> context) {
		JexlContext jexlContext = new MapContext(context);
		try {
			return jExpression.evaluate(jexlContext);
		} catch (Exception e) {
			throw new EvaluationException(
					"An error occurred when evaluating expression "
							+ jExpression.getExpression(), e);
		}
	}

	/**
	 * get jexpression.
	 *
	 * @return jexlexpression.
	 */

	public final Expression getJexlExpression() {
		return jExpression;
	}

	/**
	 * get local exl engine.
	 *
	 * @return jexlengine.
	 */
	public final JexlEngine getJexlEngine() {
		return JEXL_LOCAL.get();
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.serializable;

import java.io.IOException;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;

/**
 * serialize workbook.
 *
 * @author Jason Jiang
 *
 */
public class SerialRow implements Serializable {

	/**
	 * serial key.
	 */
	private static final long serialVersionUID = 1L;

	/** logger. */
	private static final Logger LOG = Logger
			.getLogger(SerialRow.class.getName());

	/**
	 * cell is transient.
	 */
	private transient Row row;

	/**
	 * use for saving cell address when serialize.
	 */
	private int rowIndex = -1;

	/**
	 * Instantiates a new serial row.
	 */
	public SerialRow() {
		this(null);
		LOG.fine("serial row constructor");
	}

	/**
	 * constructor.
	 *
	 * @param prow
	 *            row.
	 */

	public SerialRow(final Row prow) {
		this(prow, -1);
	}

	/**
	 * constructor.
	 *
	 * @param prow
	 *            row.
	 * @param prowIndex
	 *            row index.
	 */

	public SerialRow(final Row prow, final int prowIndex) {
		super();
		this.row = prow;
		this.rowIndex = prowIndex;
	}

	/**
	 * save the row before serialize.
	 *
	 * @param out
	 *            outputstream.
	 * @throws IOException
	 *             io exception.
	 */
	private void writeObject(final java.io.ObjectOutputStream out)
			throws IOException {
		this.rowIndex = this.getRow().getRowNum();
		out.defaultWriteObject();
	}

	/**
	 * load the workbook from saving.
	 *
	 * @param in
	 *            inputstream.
	 * @throws IOException
	 *             io exception.
	 */
	private void readObject(final java.io.ObjectInputStream in)
			throws IOException {
		try {
			in.defaultReadObject();
		} catch (EncryptedDocumentException | ClassNotFoundException e) {
			LOG.log(Level.SEVERE, " error in readObject of serial row : "
					+ e.getLocalizedMessage(), e);
		}
	}

	/**
	 * Gets the row.
	 *
	 * @return the row
	 */
	public final Row getRow() {
		return row;
	}

	/**
	 * Sets the row.
	 *
	 * @param prow
	 *            the row to set
	 */
	public final void setRow(final Row prow) {
		this.row = prow;
	}

	/**
	 * Gets the row index.
	 *
	 * @return the rowIndex
	 */
	public final int getRowIndex() {
		return rowIndex;
	}

	/**
	 * Sets the row index.
	 *
	 * @param prowIndex
	 *            the rowIndex to set
	 */
	public final void setRowIndex(final int prowIndex) {
		this.rowIndex = prowIndex;
	}

	/**
	 * recover row by using it's address.
	 *
	 * @param sheet
	 *            sheet.
	 */
	public final void recover(final Sheet sheet) {

		if (rowIndex >= 0) {
			this.setRow(sheet.getRow(rowIndex));
			this.setRowIndex(-1);
		}

	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#toString()
	 */
	@Override
	public final String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("{");
		if (this.getRow() != null) {
			sb.append("row = " + this.getRow().getRowNum());
		}
		sb.append("}");
		return sb.toString();
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.configuration;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.tiefaces.components.websheet.serializable.SerialRow;

/**
 * The Class RowsMapping.
 */
public class RowsMapping implements Serializable {

	/**
	 * serialVersionUID.
	 */
	private static final long serialVersionUID = 1L;
	/** The rows map. */
	private Map<Integer, List<SerialRow>> rowsMap = new HashMap<>();

	/**
	 * Instantiates a new rows mapping.
	 */
	public RowsMapping() {
		super();
	}

	/**
	 * Copy constructor.
	 *
	 * @param source
	 *            the source
	 */
	public RowsMapping(final RowsMapping source) {
		this.rowsMap.putAll(source.getRowsMap());
	}

	/**
	 * Gets the rows map.
	 *
	 * @return the rows map
	 */
	public final Map<Integer, List<SerialRow>> getRowsMap() {
		return rowsMap;
	}

	/**
	 * Adds the row.
	 *
	 * @param sourceRowNum
	 *            the source row num
	 * @param targetRow
	 *            the target row
	 */
	public final void addRow(final Integer sourceRowNum,
			final Row targetRow) {
		List<SerialRow> mapRowList = rowsMap.get(sourceRowNum);
		if (mapRowList == null) {
			mapRowList = new ArrayList<>();
		}
		SerialRow serialTarget = new SerialRow(targetRow, -1);
		if (!mapRowList.contains(serialTarget)) {
			mapRowList.add(serialTarget);
			rowsMap.put(sourceRowNum, mapRowList);
		}
	}

	/**
	 * Removes the row.
	 *
	 * @param sourceRowNum
	 *            the source row num
	 * @param targetRow
	 *            the target row
	 */
	public final void removeRow(final Integer sourceRowNum,
			final Row targetRow) {
		List<SerialRow> mapRowList = rowsMap.get(sourceRowNum);
		if (mapRowList != null) {
			mapRowList.remove(new SerialRow(targetRow, -1));
			rowsMap.put(sourceRowNum, mapRowList);
		}
	}

	/**
	 * Gets the.
	 *
	 * @param sourceRowNum
	 *            the source row num
	 * @return the list
	 */
	public final List<SerialRow> get(final Integer sourceRowNum) {
		return rowsMap.get(sourceRowNum);
	}

	/**
	 * Merge map.
	 *
	 * @param addMap
	 *            the add map
	 */
	public final void mergeMap(final RowsMapping addMap) {
		Map<Integer, List<SerialRow>> map = addMap.getRowsMap();
		for (Map.Entry<Integer, List<SerialRow>> entry : map.entrySet()) {
			List<SerialRow> entryRowList = entry.getValue();
			if ((entryRowList != null) && (!entryRowList.isEmpty())) {
				for (SerialRow row : entryRowList) {
					this.addRow(entry.getKey(), row.getRow());
				}
			}
		}
	}

	/**
	 * recover rows mapping by using it's address.
	 *
	 * @param sheet
	 *            sheet.
	 */
	public final void recover(final Sheet sheet) {

		for (Map.Entry<Integer, List<SerialRow>> entry : this.getRowsMap()
				.entrySet()) {
			List<SerialRow> listRow = entry.getValue();
			for (SerialRow serialRow : listRow) {
				serialRow.recover(sheet);
			}
		}

	}

	/**
	 * Obtain a human readable representation.
	 *
	 * @return String Human readable label
	 */
	@Override
	public final String toString() {

		StringBuilder sb = new StringBuilder();
		sb.append("{");
		for (Map.Entry<Integer, List<SerialRow>> entry : this.getRowsMap()
				.entrySet()) {
			sb.append(entry.getKey() + "=[");
			for (SerialRow row : entry.getValue()) {
				sb.append(row.getRow().getRowNum() + ",");
			}
			sb.append("], ");
		}
		sb.append("}");
		return sb.toString();
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.utility;

import java.awt.BasicStroke;
import java.util.List;
import java.util.Map;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.xssf.model.ThemesTable;
import org.apache.poi.xssf.usermodel.XSSFChart;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTCatAx;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTPlotArea;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTDrawing;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor;
import org.tiefaces.common.AppUtils;
import org.tiefaces.components.websheet.chart.ChartAxis;
import org.tiefaces.components.websheet.chart.ChartData;
import org.tiefaces.components.websheet.chart.ChartType;
import org.tiefaces.components.websheet.chart.ChartsData;
import org.tiefaces.components.websheet.chart.objects.ChartObject;
import org.tiefaces.exception.IllegalChartException;
import org.w3c.dom.Attr;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Utility Class for Chart.
 *
 * @author Jason Jiang
 */
public final class ChartUtility {

    /** Line style: line. */
    public static final String STYLE_LINE = "line";
    /** Line style: dashed. */
    public static final String STYLE_DASH = "dash";
    /** Line style: dotted. */
    public static final String STYLE_DOT = "dot";
    /** STROKE_DEFAULT_LINE_WIDTH. */
    public static final float STROKE_DEFAULT_LINE_WIDTH = 0.2f;
    /** STROKE_DEFAULT_DASH_WIDTH. */
    public static final float STROKE_DEFAULT_DASH_WIDTH = 5.0f;
    /** STROKE_MITER_LIMIT_STYLE_DOT. */
    public static final float STROKE_MITER_LIMIT_STYLE_DOT = 2.0f;
    /** STROKE_MITER_LIMIT_STYLE_DASH. */
    public static final float STROKE_MITER_LIMIT_STYLE_DASH = 10.0f;
    /** STROKE_DEFAULT_DASHPHASE. */
    public static final float STROKE_DEFAULT_DASHPHASE = 0.0f;

    /**
     * hide constructor.
     */
    private ChartUtility() {
        super();
    }

    /**
     * return chart type from CTChart object.
     *
     * @param ctChart
     *            object.
     * @return ChartType.
     */
    public static ChartType getChartType(final CTChart ctChart) {
        CTPlotArea plotArea = ctChart.getPlotArea();

        for (ChartType chartType : ChartType.values()) {
            if (chartType.isThisType(plotArea)) {
                return chartType;
            }
        }
        return null;
    }

    /**
     * Convert style string to stroke object.
     *
     * @param style
     *            One of STYLE_xxx.
     * @return Stroke for <i>style</i> or null if style not supported.
     */
    public static BasicStroke toStroke(final String style) {
        BasicStroke result = null;

        if (style != null) {
            float lineWidth = STROKE_DEFAULT_LINE_WIDTH;
            float[] dash = { STROKE_DEFAULT_DASH_WIDTH };
            float[] dot = { lineWidth };

            if (style.equalsIgnoreCase(STYLE_LINE)) {
                result = new BasicStroke(lineWidth);
            } else if (style.equalsIgnoreCase(STYLE_DASH)) {
                result = new BasicStroke(lineWidth, BasicStroke.CAP_BUTT,
                        BasicStroke.JOIN_MITER,
                        STROKE_MITER_LIMIT_STYLE_DASH, dash,
                        STROKE_DEFAULT_DASHPHASE);
            } else if (style.equalsIgnoreCase(STYLE_DOT)) {
                result = new BasicStroke(lineWidth, BasicStroke.CAP_BUTT,
                        BasicStroke.JOIN_MITER,
                        STROKE_MITER_LIMIT_STYLE_DOT, dot,
                        STROKE_DEFAULT_DASHPHASE);
            }
        }

        return result;
    }

    /**
     * init chart data.
     *
     * @param chartId
     *            chartId.
     * @param chart
     *            chart.
     * @param wb
     *            wb.
     * @return chartdata.
     */
    public static ChartData initChartDataFromXSSFChart(final String chartId,
            final XSSFChart chart, final XSSFWorkbook wb) {

        ThemesTable themeTable = wb.getStylesSource().getTheme();

        ChartData chartData = new ChartData();
        XSSFRichTextString chartTitle = chart.getTitle();
        if (chartTitle != null) {
            chartData.setTitle(chartTitle.toString());
        }
        CTChart ctChart = chart.getCTChart();
        ChartType chartType = ChartUtility.getChartType(ctChart);
        if (chartType == null) {
            throw new IllegalChartException("Unknown chart type");
        }

        chartData.setBgColor(
                ColorUtility.getBgColor(ctChart.getPlotArea(), themeTable));

        chartData.setId(chartId);
        chartData.setType(chartType);

        List<CTCatAx> ctCatAxList = ctChart.getPlotArea().getCatAxList();
        if ((ctCatAxList != null) && (!ctCatAxList.isEmpty())) {
            chartData.setCatAx(new ChartAxis(ctCatAxList.get(0)));
        }
        List<CTValAx> ctValAxList = ctChart.getPlotArea().getValAxList();
        if ((ctValAxList != null) && (!ctValAxList.isEmpty())) {
            chartData.setValAx(new ChartAxis(ctValAxList.get(0)));
        }

        ChartObject ctObj = chartType.createChartObject();

        if (ctObj == null) {
            throw new IllegalChartException("Cannot create chart object.");
        }

        setUpChartData(chartData, ctChart, themeTable, ctObj);

        return chartData;
    }

    /**
     * build chartData for line chart. chartData include categoryList and
     * seriesList which used for generate jfreechart.
     *
     * @param chartData
     *            chart data.
     * @param ctChart
     *            ct chart.
     * @param themeTable
     *            themeTable used for get color with theme name.
     * @param ctObj
     *            ct object.
     */

    public static void setUpChartData(final ChartData chartData,
            final CTChart ctChart, final ThemesTable themeTable,
            final ChartObject ctObj) {

        Object chartObj = null;
        @SuppressWarnings("rawtypes")
        List plotCharts = ctObj.getChartListFromCtChart(ctChart);

        // chart object
        if (plotCharts != null && (!plotCharts.isEmpty())) {
            chartObj = plotCharts.get(0);
        }
        if (chartObj != null) {
            @SuppressWarnings("rawtypes")
            List bsers = ctObj.getSerListFromCtObjChart(chartObj);
            if (!AppUtils.emptyList(bsers)) {
                chartData.buildCategoryList(
                        ctObj.getCtAxDataSourceFromSerList(bsers));
                chartData.buildSeriesList(bsers, themeTable, ctObj);
            }
        }
    }

    /**
     * retrieve anchor information from draw.xml for all the charts in the
     * workbook. then save them to anchors map.
     *
     * @param wb
     *            workbook.
     * @param charsData
     *            the chars data
     */
    public static void initXSSFAnchorsMap(final XSSFWorkbook wb,
            final ChartsData charsData) {

        Map<String, ClientAnchor> anchortMap = charsData
                .getChartAnchorsMap();
        Map<String, String> positionMap = charsData.getChartPositionMap();
        anchortMap.clear();
        positionMap.clear();
        for (int i = 0; i < wb.getNumberOfSheets(); i++) {
            initXSSFAnchorsMapForSheet(anchortMap, positionMap,
                    wb.getSheetAt(i));
        }
    }

    /**
     * Inits the XSSF anchors map for sheet.
     *
     * @param anchortMap
     *            the anchort map
     * @param positionMap
     *            the position map
     * @param sheet
     *            the sheet
     */
    private static void initXSSFAnchorsMapForSheet(
            final Map<String, ClientAnchor> anchortMap,
            final Map<String, String> positionMap, final XSSFSheet sheet) {
        XSSFDrawing drawing = sheet.createDrawingPatriarch();
        CTDrawing ctDrawing = drawing.getCTDrawing();
        if (ctDrawing.sizeOfTwoCellAnchorArray() <= 0) {
            return;
        }
        List<CTTwoCellAnchor> alist = ctDrawing.getTwoCellAnchorList();
        for (int j = 0; j < alist.size(); j++) {
            CTTwoCellAnchor ctanchor = alist.get(j);
            String singleChartId = getAnchorAssociateChartId(ctanchor);
            if (singleChartId != null) {
                String chartId = sheet.getSheetName() + "!" + singleChartId;
                int dx1 = (int) ctanchor.getFrom().getColOff();
                int dy1 = (int) ctanchor.getFrom().getRowOff();
                int dx2 = (int) ctanchor.getTo().getColOff();
                int dy2 = (int) ctanchor.getTo().getRowOff();
                int col1 = ctanchor.getFrom().getCol();
                int row1 = ctanchor.getFrom().getRow();
                int col2 = ctanchor.getTo().getCol();
                int row2 = ctanchor.getTo().getRow();
                anchortMap.put(chartId, new XSSFClientAnchor(dx1, dy1, dx2,
                        dy2, col1, row1, col2, row2));
                positionMap.put(WebSheetUtility.getFullCellRefName(
                        sheet.getSheetName(), row1, col1), chartId);
            }
        }
    }

    /**
	 * Gets the anchor associate chart id.
	 *
	 * @param ctanchor
	 *            the ctanchor
	 * @return the anchor associate chart id
	 */
    private static String getAnchorAssociateChartId(
            final CTTwoCellAnchor ctanchor) {

        if (ctanchor.getGraphicFrame() == null) {
            return null;
        }

        Node parentNode = ctanchor.getGraphicFrame().getGraphic()
                .getGraphicData().getDomNode();

        NodeList childNodes = parentNode.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node childNode = childNodes.item(i);
            if ((childNode != null)
                    && ("c:chart".equalsIgnoreCase(childNode.getNodeName()))
                    && (childNode.hasAttributes())) {
                String rId = getChartIdFromChildNodeAttributes(
                        childNode.getAttributes());
                if (rId != null) {
                    return rId;
                }
            }
        }
        return null;
    }

    /**
     * Gets the chart id from child node attributes.
     *
     * @param attrs
     *            the attrs
     * @return the chart id from child node attributes
     */
    private static String getChartIdFromChildNodeAttributes(
            final NamedNodeMap attrs) {
        for (int j = 0; j < attrs.getLength(); j++) {
            Attr attribute = (Attr) attrs.item(j);
            if ("r:id".equalsIgnoreCase(attribute.getName())) {
                return attribute.getValue();
            }
        }
        return null;
    }

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.serializable;

import java.io.IOException;
import java.io.Serializable;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.tiefaces.components.websheet.configuration.SheetConfiguration;

/**
 * serialize workbook.
 *
 * @author Jason Jiang
 *
 */
public class SerialWorkbook implements Serializable {

	/** logger. */
	private static final Logger LOG = Logger
			.getLogger(SerialWorkbook.class.getName());

	/**
	 * serial id.
	 */
	private static final long serialVersionUID = 4711304873642901753L;

	/**
	 * workbook is transient.
	 */
	private transient Workbook wb;

	/** hold configuration for each sheet. */
	private Map<String, SheetConfiguration> sheetConfigMap;

	/**
	 * save the workbook before serialize.
	 *
	 * @param out
	 *            outputstream.
	 * @throws IOException
	 *             io exception.
	 */
	private void writeObject(final java.io.ObjectOutputStream out)
			throws IOException {
		out.defaultWriteObject();
		if (wb != null) {
			wb.write(out);
		}
	}

	/**
	 * load the workbook from saving.
	 *
	 * @param in
	 *            inputstream.
	 * @throws IOException
	 *             io exception.
	 */
	private void readObject(final java.io.ObjectInputStream in)
			throws IOException {
		try {
			in.defaultReadObject();
			wb = WorkbookFactory.create(in);
			recover();
		} catch (EncryptedDocumentException | InvalidFormatException
				| ClassNotFoundException e) {
			LOG.log(Level.SEVERE,
					" error in readObject of serialWorkbook : "
							+ e.getLocalizedMessage(),
					e);
		}
	}

	/**
	 * Gets the wb.
	 *
	 * @return the wb
	 */
	public final Workbook getWb() {
		return wb;
	}

	/**
	 * set wb.
	 *
	 * @param pwb
	 *            the wb to set.
	 */
	public final void setWb(final Workbook pwb) {
		this.wb = pwb;
	}

	/**
	 * Gets the sheet config map.
	 *
	 * @return the sheetConfigMap
	 */
	public final Map<String, SheetConfiguration> getSheetConfigMap() {
		return sheetConfigMap;
	}

	/**
	 * Sets the sheet config map.
	 *
	 * @param psheetConfigMap
	 *            the sheetConfigMap to set
	 */
	public void setSheetConfigMap(
			final Map<String, SheetConfiguration> psheetConfigMap) {
		this.sheetConfigMap = psheetConfigMap;
	}

	/**
	 * recover the cell reference to the sheet.
	 */
	public void recover() {
		Map<String, SheetConfiguration> map = this.getSheetConfigMap();
		for (Entry<String, SheetConfiguration> entry : map.entrySet()) {
			entry.getValue().recover(this.getWb());
		}
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.configuration;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.tiefaces.components.websheet.utility.CommandUtility;
import org.tiefaces.components.websheet.utility.ConfigurationUtility;

/**
 * Each command represent the repeat area which typically using a group of data.
 * i.e. tie:each(items="department.staff" var="employee" length="1"
 * allowAdd="True") department.staff is a object collection. In the cell area
 * can use ${employee.name} to define the "employee" attributes.
 *
 * @author Jason Jiang
 *
 */
public class EachCommand extends ConfigCommand implements Serializable {

	/** logger. */
	private static final Logger LOG = Logger
			.getLogger(EachCommand.class.getName());

	/**
	 * serialVersionUID.
	 */
	private static final long serialVersionUID = -281289717576958023L;
	/** items holder. */
	private String items;
	/** var holder. */
	private String var;
	/** allowAdd holder. */
	private String allowAdd;
	/** select holder. */
	private String select;
	/** class name holder. */
	private String className;

	/**
	 * Instantiates a new each command.
	 */
	public EachCommand() {
		super();
	}

	/**
	 * Instantiates a new each command.
	 *
	 * @param sourceCommand
	 *            the source command
	 */
	public EachCommand(final EachCommand sourceCommand) {
		super((ConfigCommand) sourceCommand);
		this.items = sourceCommand.items;
		this.var = sourceCommand.var;
		this.allowAdd = sourceCommand.allowAdd;
		this.select = sourceCommand.select;
	}

	/**
	 * Gets the items.
	 *
	 * @return the items
	 */
	public final String getItems() {
		return items;
	}

	/**
	 * Sets the items.
	 *
	 * @param pItems
	 *            the new items
	 */
	public final void setItems(final String pItems) {
		this.items = pItems;
	}

	/**
	 * Gets the var.
	 *
	 * @return the var
	 */
	public final String getVar() {
		return var;
	}

	/**
	 * Sets the var.
	 *
	 * @param pVar
	 *            the new var
	 */
	public final void setVar(final String pVar) {
		this.var = pVar;
	}

	/**
	 * Gets the allow add.
	 *
	 * @return the allow add
	 */
	public final String getAllowAdd() {
		return allowAdd;
	}

	/**
	 * Sets the allow add.
	 *
	 * @param pAllowAdd
	 *            the new allow add
	 */
	public final void setAllowAdd(final String pAllowAdd) {
		this.allowAdd = pAllowAdd;
	}

	/**
	 * Gets the select.
	 *
	 * @return the select
	 */
	public final String getSelect() {
		return select;
	}

	/**
	 * Sets the select.
	 *
	 * @param pselect
	 *            the new select
	 */
	public final void setSelect(final String pselect) {
		this.select = pselect;
	}

	/**
	 * Gets the class name.
	 *
	 * @return the class name
	 */
	public final String getClassName() {
		return className;
	}

	/**
	 * Sets the class name.
	 *
	 * @param pclassName
	 *            the new class name
	 */
	public final void setClassName(final String pclassName) {
		this.className = pclassName;
	}

	/**
	 * Obtain a human readable representation.
	 *
	 * @return String Human readable label
	 */
	@Override
	public final String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("{");
		sb.append("commandName = " + this.getCommandTypeName());
		sb.append(",");
		sb.append("length = " + this.getLength());
		sb.append(",");
		sb.append("items = " + this.getItems());
		sb.append(",");
		sb.append("var = " + this.getVar());
		sb.append(",");
		sb.append("className = " + this.getClassName());
		sb.append(",");
		sb.append("select = " + this.getSelect());
		sb.append(",");
		sb.append("Allow Add = " + this.getAllowAdd());
		sb.append("}");
		return sb.toString();

	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.tiefaces.components.websheet.configuration.Command#buildAt(java.lang.
	 * String, org.tiefaces.components.websheet.configuration.ConfigBuildRef,
	 * int, java.util.Map, java.util.List)
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public final int buildAt(String fullName,
			final ConfigBuildRef configBuildRef, final int atRow,
			final Map<String, Object> context,
			final List<RowsMapping> currentRowsMappingList) {

		fullName = fullName + ":" + this.getCommandName();
		Collection itemsCollection = ConfigurationUtility
				.transformToCollectionObject(configBuildRef.getEngine(),
						this.getItems(), context);

		String objClassName = this.getClassName();

		if (objClassName == null) {
			objClassName = configBuildRef.getCollectionObjNameMap()
					.get(this.getVar());
		}
		if (configBuildRef.isAddMode() && itemsCollection.isEmpty()) {
			// do something here to insert one empty object
			try {
				itemsCollection
						.add(Class.forName(objClassName).newInstance());
			} catch (Exception ex) {
				LOG.log(Level.SEVERE,
						"canot insert empty object in itemCollections error = "
								+ ex.getLocalizedMessage(),
						ex);
				return 0;
			}
		}

		int insertPosition = buildEachObjects(fullName, configBuildRef,
				atRow, context, currentRowsMappingList, itemsCollection,
				objClassName);

		return insertPosition - atRow;
	}

	/**
	 * Builds the each objects.
	 *
	 * @param fullName
	 *            the full name
	 * @param configBuildRef
	 *            the config build ref
	 * @param atRow
	 *            the at row
	 * @param context
	 *            the context
	 * @param currentRowsMappingList
	 *            the current rows mapping list
	 * @param itemsCollection
	 *            the items collection
	 * @param objClassName
	 *            the obj class name
	 * @return the int
	 */
	@SuppressWarnings("rawtypes")
	private int buildEachObjects(String fullName,
			final ConfigBuildRef configBuildRef, final int atRow,
			final Map<String, Object> context,
			final List<RowsMapping> currentRowsMappingList,
			final Collection itemsCollection, final String objClassName) {

		int index = 0;
		int insertPosition = atRow;
		String thisObjClassName = objClassName;

		// loop through each object in the collection
		for (Object obj : itemsCollection) {
			// gather and cache object class name which used for add row
			if (thisObjClassName == null) {
				thisObjClassName = obj.getClass().getName();
				configBuildRef.getCollectionObjNameMap().put(this.var,
						thisObjClassName);
			}
			RowsMapping unitRowsMapping = new RowsMapping();
			context.put(var, obj);
			CommandUtility.insertEachTemplate(this.getConfigRange(),
					configBuildRef, index, insertPosition, unitRowsMapping);
			ConfigRange currentRange = ConfigurationUtility
					.buildCurrentRange(this.getConfigRange(),
							configBuildRef.getSheet(), insertPosition);
			currentRowsMappingList.add(unitRowsMapping);

			String unitFullName = fullName + "." + index;
			currentRange.getAttrs().setAllowAdd(false);
			if ((this.allowAdd != null)
					&& ("true".equalsIgnoreCase(this.allowAdd.trim()))) {
				currentRange.getAttrs().setAllowAdd(true);
				configBuildRef.setBodyAllowAdd(true);
			}
			configBuildRef.putShiftAttrs(unitFullName,
					currentRange.getAttrs(),
					new RowsMapping(unitRowsMapping));

			int length = currentRange.buildAt(unitFullName, configBuildRef,
					insertPosition, context, currentRowsMappingList);
			currentRange.getAttrs().setFinalLength(length);
			insertPosition += length;
			currentRowsMappingList.remove(unitRowsMapping);
			index++;
			context.remove(var);
		}
		return insertPosition;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.tiefaces.components.websheet.configuration.Command#getCommandName()
	 */
	/*
	 *
	 * @see
	 * org.tiefaces.components.websheet.configuration.Command#getCommandName()
	 */
	@Override
	public final String getCommandName() {
		return this.getCommandTypeName().substring(0, 1).toUpperCase() + "."
				+ this.getVar().trim();
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */

package org.tiefaces.components.websheet.service;

import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.poi.ss.usermodel.Cell;
import org.primefaces.context.RequestContext;
import org.tiefaces.common.TieConstants;
import org.tiefaces.components.websheet.TieWebSheetBean;
import org.tiefaces.components.websheet.dataobjects.CollectionObject;
import org.tiefaces.components.websheet.dataobjects.FacesCell;
import org.tiefaces.components.websheet.utility.CellUtility;
import org.tiefaces.components.websheet.utility.CommandUtility;
import org.tiefaces.components.websheet.utility.ConfigurationUtility;
import org.tiefaces.components.websheet.utility.SaveAttrsUtility;

/**
 * Helper class for web sheet cells.
 *
 * @author Jason Jiang
 *
 */
public class CellHelper {

	/** instance to parent websheet bean. */
	private TieWebSheetBean parent = null;

	/** logger. */
	private static final Logger LOG = Logger
			.getLogger(CellHelper.class.getName());

	/**
	 * Instantiates a new cell helper.
	 */
	public CellHelper() {
		super();
	}

	/**
	 * Instantiates a new cell helper.
	 *
	 * @param pparent
	 *            parent bean.
	 */
	public CellHelper(final TieWebSheetBean pparent) {
		super();
		this.parent = pparent;
	}

	/**
	 * Save data in context.
	 *
	 * @param poiCell
	 *            the poi cell
	 * @param strValue
	 *            the str value
	 */
	public final void saveDataInContext(final Cell poiCell,
			final String strValue) {

		String saveAttr = SaveAttrsUtility.prepareContextAndAttrsForCell(poiCell, ConfigurationUtility.getFullNameFromRow(poiCell.getRow()), this);
		if (saveAttr!= null) {
			SaveAttrsUtility.saveDataToObjectInContext(
							parent.getSerialDataContext().getDataContext(),
							saveAttr, strValue, parent.getExpEngine());
			parent.getHelper().getWebSheetLoader().setUnsavedStatus(
							RequestContext.getCurrentInstance(), true);
		}
	}

	/**
	 * recalc whole workbook.
	 */
	public final void reCalc() {

		parent.getFormulaEvaluator().clearAllCachedResultValues();
		try {
			parent.getFormulaEvaluator().evaluateAll();
		} catch (Exception ex) {
			// skip the formula exception when recalc but log it
			LOG.log(Level.SEVERE,
					" recalc formula error : " + ex.getLocalizedMessage(),
					ex);
		}

	}

	/**
	 * evaluate boolean express.
	 *
	 * @param pscript
	 *            express.
	 * @return true (express is true) false ( express is false or invalid).
	 */
	public final boolean evalBoolExpression(final String pscript) {
		return CommandUtility.evalBoolExpression(parent.getExpEngine(),
				pscript);
	}

	/**
	 * Gets the poi cell with row col from current page.
	 *
	 * @param rowIndex
	 *            the row index
	 * @param colIndex
	 *            the col index
	 * @return the poi cell with row col from current page
	 */
	public final Cell getPoiCellWithRowColFromCurrentPage(
			final int rowIndex, final int colIndex) {
		return CellUtility.getPoiCellWithRowColFromCurrentPage(rowIndex,
				colIndex, parent.getWb());
	}

	/**
	 * Gets the poi cell with row col from tab.
	 *
	 * @param rowIndex
	 *            the row index
	 * @param colIndex
	 *            the col index
	 * @param tabName
	 *            the tab name
	 * @return the poi cell with row col from tab
	 */
	public final Cell getPoiCellWithRowColFromTab(final int rowIndex,
			final int colIndex, final String tabName) {
		if (parent.getWb() != null) {

			return CellUtility.getPoiCellFromSheet(rowIndex, colIndex,
					parent.getWb().getSheet(parent.getSheetConfigMap()
							.get(tabName).getSheetName()));
		}
		return null;
	}

	/**
	 * Gets the faces cell with row col from current page.
	 *
	 * @param rowIndex
	 *            the row index
	 * @param colIndex
	 *            the col index
	 * @return the faces cell with row col from current page
	 */
	public final FacesCell getFacesCellWithRowColFromCurrentPage(
			final int rowIndex, final int colIndex) {
		if (parent.getBodyRows() != null) {
			int top = parent.getCurrent().getCurrentTopRow();
			int left = parent.getCurrent().getCurrentLeftColumn();
			return parent.getBodyRows().get(rowIndex - top).getCells()
					.get(colIndex - left);
		}
		return null;
	}

	/**
	 * Restore data context.
	 *
	 * @param fullName
	 *            the full name
	 */
	public final void restoreDataContext(final String fullName) {

		String[] parts = fullName.split(":");

		if (!isNeedRestore(fullName, parts)) {
			return;
		}

		boolean stopSkip = false;
		List<String> list = parent.getCurrent()
				.getCurrentDataContextNameList();
		int listSize = list.size();

		// prepare collection data in context.
		// must loop through the full name which may have multiple
		// layer.
		// i.e. E.department.1:E.employee.0
		// need prepare department.1 and employee.0

		for (int i = 0; i < parts.length; i++) {
			String part = parts[i];
			boolean skip = false;
			if ((!stopSkip) && (i < listSize)) {
				String listPart = list.get(i);
				if (part.equalsIgnoreCase(listPart)) {
					skip = true;
				}
			}
			if (!skip) {
				stopSkip = true;
				startRestoreDataContext(part);
			}
		}
		if (stopSkip) {
			parent.getCurrent().setCurrentDataContextName(fullName);
		}

		return;
	}

	/**
	 * Get last collect object from full name.
	 *
	 * Last collect contain each command, collection and index.
	 *
	 * @param fullName
	 *            the full name
	 * @return the collection object
	 */
	public final CollectionObject getLastCollect(final String fullName) {
		String[] parts = fullName.split(":");
		String part = parts[parts.length - 1];
		return startRestoreDataContext(part);
	}

	/**
	 * Checks if is need restore.
	 *
	 * @param fullName
	 *            the full name
	 * @param parts
	 *            the parts
	 * @return true, if is need restore
	 */
	private boolean isNeedRestore(final String fullName,
			final String[] parts) {
		if (fullName == null) {
			return false;
		}
		if ((parent.getCurrent().getCurrentDataContextName() != null)
				&& (parent.getCurrent().getCurrentDataContextName()
						.toLowerCase()
						.startsWith(fullName.toLowerCase()))) {
			return false;
		}

		return ((parts != null) && (parts.length > 1));
	}

	/**
	 * Start restore data context.
	 *
	 * @param part
	 *            the part
	 * @return the collection object
	 */
	private CollectionObject startRestoreDataContext(final String part) {
		if (part.startsWith(TieConstants.EACH_COMMAND_FULL_NAME_PREFIX)) {
			String[] varparts = part.split("\\.");
			CollectionObject collect = new CollectionObject();

			collect.setEachCommand(
					CommandUtility
							.getEachCommandFromPartsName(
									parent.getCurrentSheetConfig()
											.getCommandIndexMap(),
									varparts));
			collect.setLastCollection(ConfigurationUtility
					.transformToCollectionObject(parent.getExpEngine(),
							collect.getEachCommand().getItems(),
							parent.getSerialDataContext()
									.getDataContext()));
			collect.setLastCollectionIndex(
					CommandUtility.prepareCollectionDataInContext(varparts,
							collect.getLastCollection(),
							parent.getSerialDataContext()
									.getDataContext()));
			return collect;
		}
		return null;
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.configuration;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.tiefaces.components.websheet.serializable.SerialCellAddress;
import org.tiefaces.components.websheet.utility.CommandUtility;
import org.tiefaces.components.websheet.utility.ConfigurationUtility;

/**
 * Simple class for area range. Also include nested command list. Note: command
 * also can include configRange.
 *
 * firstCell, lastCell point to the start and end point of the area. The
 * lastCell will be expanded if area contain dynamic area.
 *
 * firstCellAddr, lastCellAddr is static address for initial area and will not
 * be changed. These mainly used for calculate related address from parent
 * command to child command area.
 *
 * @author Jason Jiang
 *
 */
public class ConfigRange implements Serializable {

	/**
	 * serialid.
	 */
	private static final long serialVersionUID = 1L;

	/** logger. */
	private static final Logger LOG = Logger
			.getLogger(ConfigRange.class.getName());

	/** The attrs. */
	private ConfigRangeAttrs attrs = new ConfigRangeAttrs(false);

	/** command list. */
	private List<ConfigCommand> commandList;

	/**
	 * Instantiates a new config range.
	 */
	public ConfigRange() {
		super();
	}

	/**
	 * Copy constructor.
	 *
	 * @param source
	 *            the source
	 */
	public ConfigRange(final ConfigRange source) {
		this.attrs.setFirstRowRef(source.attrs.getFirstRowRef());
		this.attrs.setFirstRowAddr(source.attrs.getFirstRowAddr());
		this.attrs.setLastRowPlusRef(source.attrs.getLastRowPlusRef());
		this.attrs.setLastRowPlusAddr(source.attrs.getLastRowPlusAddr());
		this.attrs.setLastCellCreated(source.attrs.isLastCellCreated());

		if (source.commandList != null) {
			this.commandList = this.getCommandList();
			for (ConfigCommand sourceCommand : source.commandList) {
				Object newCommand = null;
				if (sourceCommand instanceof FormCommand) {
					newCommand = new FormCommand(
							(FormCommand) sourceCommand);
				} else {
					newCommand = new EachCommand(
							(EachCommand) sourceCommand);
				}
				this.commandList.add((ConfigCommand) newCommand);
			}
		}

	}

	/**
	 * Gets the attrs.
	 *
	 * @return the attrs
	 */
	public final ConfigRangeAttrs getAttrs() {
		return attrs;
	}

	/**
	 * Shift row ref.
	 *
	 * @param sheet
	 *            the sheet
	 * @param shiftnum
	 *            the shiftnum
	 */
	public final void shiftRowRef(final Sheet sheet, final int shiftnum) {
		try {
			this.setFirstRowRef(sheet
					.getRow(attrs.getFirstRowAddr().getRow() + shiftnum)
					.getCell(attrs.getFirstRowAddr().getColumn(),
							MissingCellPolicy.CREATE_NULL_AS_BLANK),
					false);
			this.setLastRowPlusRef(sheet,
					attrs.getLastRowPlusAddr().getColumn(),
					attrs.getLastRowPlusAddr().getRow() + shiftnum - 1,
					false);

			if (commandList != null) {
				for (ConfigCommand command : commandList) {
					command.shiftRowRef(sheet, shiftnum);
				}
			}

		} catch (Exception ex) {
			LOG.log(Level.SEVERE,
					"shiftRowRef error =" + ex.getLocalizedMessage(), ex);
		}
	}

	/**
	 * Gets the first row ref.
	 *
	 * @return the first row ref
	 */
	public final Cell getFirstRowRef() {
		return attrs.getFirstRowRef();
	}

	/**
	 * set first cell also set static relative address firstCellAddress.
	 *
	 * @param pFirstRowRef
	 *            first cell.
	 * @param alsoCreateAddr
	 *            whether need create cell address.
	 */
	public final void setFirstRowRef(final Cell pFirstRowRef,
			final boolean alsoCreateAddr) {
		this.attrs.setFirstRowRef(pFirstRowRef);
		if (alsoCreateAddr) {
			this.setFirstRowAddr(new SerialCellAddress(pFirstRowRef));
		}
	}

	/**
	 * Gets the last row plus ref.
	 *
	 * @return the last row plus ref
	 */
	public final Cell getLastRowPlusRef() {
		return attrs.getLastRowPlusRef();
	}

	/**
	 * set last cell also set static relative address lastCellAddress.
	 *
	 * @param sheet
	 *            sheet.
	 * @param rightCol
	 *            rightColumn.
	 * @param lastRow
	 *            lastRow.
	 * @param alsoSetAddr
	 *            whether need create cell address.
	 */
	public final void setLastRowPlusRef(final Sheet sheet,
			final int rightCol, final int lastRow,
			final boolean alsoSetAddr) {

		if ((lastRow >= 0) && (sheet != null) && (rightCol >= 0)) {
			Row row = sheet.getRow(lastRow + 1);
			if (row == null) {
				row = sheet.createRow(lastRow + 1);
			}
			Cell cell = row.getCell(rightCol);
			if (cell == null) {
				cell = row.getCell(rightCol,
						MissingCellPolicy.CREATE_NULL_AS_BLANK);
				this.attrs.setLastCellCreated(true);
			} else {
				this.attrs.setLastCellCreated(false);
			}
			this.attrs.setLastRowPlusRef(cell);
			if (alsoSetAddr) {
				this.setLastRowPlusAddr(new SerialCellAddress(cell));
			}
		} else {
			this.attrs.setLastRowPlusRef(null);
			if (alsoSetAddr) {
				this.attrs.setLastRowPlusAddr(null);
			}
		}
	}

	/**
	 * Gets the first row addr.
	 *
	 * @return the first row addr
	 */
	public final SerialCellAddress getFirstRowAddr() {
		return attrs.getFirstRowAddr();
	}

	/**
	 * Sets the first row addr.
	 *
	 * @param pFirstRowAddr
	 *            the new first row addr
	 */
	public final void setFirstRowAddr(
			final SerialCellAddress pFirstRowAddr) {
		this.attrs.setFirstRowAddr(pFirstRowAddr);
	}

	/**
	 * Gets the last row plus addr.
	 *
	 * @return the last row plus addr
	 */
	public final SerialCellAddress getLastRowPlusAddr() {
		return attrs.getLastRowPlusAddr();
	}

	/**
	 * Sets the last row plus addr.
	 *
	 * @param pLastRowPlusAddr
	 *            the new last row plus addr
	 */
	public final void setLastRowPlusAddr(
			final SerialCellAddress pLastRowPlusAddr) {
		this.attrs.setLastRowPlusAddr(pLastRowPlusAddr);
	}

	/**
	 * command list.
	 *
	 * @return command list.
	 */
	public final List<ConfigCommand> getCommandList() {
		if (this.commandList == null) {
			this.commandList = new ArrayList<>();
		}
		return this.commandList;
	}

	/**
	 * Sets the command list.
	 *
	 * @param pCommandList
	 *            the new command list
	 */
	public final void setCommandList(
			final List<ConfigCommand> pCommandList) {
		this.commandList = pCommandList;
	}

	/**
	 * Checks if is last cell created.
	 *
	 * @return true, if is last cell created
	 */
	public final boolean isLastCellCreated() {
		return attrs.isLastCellCreated();
	}

	/**
	 * add command to the list.
	 *
	 * @param configCommand
	 *            .
	 * @return command list.
	 */
	public final List<ConfigCommand> addCommand(
			final ConfigCommand configCommand) {
		this.getCommandList().add(configCommand);
		return this.getCommandList();
	}

	/**
	 * build the config range at specified point (start row). context include
	 * the data objects for evaluation. build sequence is inside-out. e.g. first
	 * build all the command area the range included. each command will hold the
	 * final length after data populated. then buildCells build all cells in the
	 * range except those commands. and update the formulas.
	 *
	 * @param fullName
	 *            the full name
	 * @param configBuildRef
	 *            the config build ref
	 * @param atRow
	 *            the at row
	 * @param context
	 *            context map.
	 * @param currentRowsMappingList
	 *            the current rows mapping list
	 * @return final length.
	 */
	public final int buildAt(final String fullName,
			final ConfigBuildRef configBuildRef, final int atRow,
			final Map<String, Object> context,
			final List<RowsMapping> currentRowsMappingList) {

		if (commandList != null) {
			for (int i = 0; i < commandList.size(); i++) {
				Command command = commandList.get(i);
				command.setFinalLength(0);
				int populatedLength = command.buildAt(fullName,
						configBuildRef,
						command.getConfigRange().getFirstRowRef()
								.getRowIndex(),
						context, currentRowsMappingList);
				command.setFinalLength(populatedLength);
			}
		}

		buildCells(fullName, configBuildRef, atRow, context,
				currentRowsMappingList);

		return this.getLastRowPlusRef().getRowIndex()
				- this.getFirstRowRef().getRowIndex();

	}

	/**
	 * Index command range.
	 *
	 * @param indexMap
	 *            the index map
	 */
	public final void indexCommandRange(
			final Map<String, Command> indexMap) {
		CommandUtility.indexCommandRange(this, indexMap);
	}

	/**
	 * Build all the static cells in the range (exclude command areas).
	 *
	 * @param fullName
	 *            the full name
	 * @param configBuildRef
	 *            the config build ref
	 * @param atRow
	 *            start row.
	 * @param context
	 *            context.
	 * @param rowsMappingList
	 *            the rows mapping list
	 */
	private void buildCells(final String fullName,
			final ConfigBuildRef configBuildRef, final int atRow,
			final Map<String, Object> context,
			final List<RowsMapping> rowsMappingList) {

		if ((context == null) || context.isEmpty()) {
			// no need to evaluate as there's no data object.
			return;
		}

		// keep rowsMappingList as current as no change
		// allRowsMappingList = child + current

		List<RowsMapping> allRowsMappingList = ConfigurationUtility
				.findChildRowsMappingFromShiftMap(fullName,
						configBuildRef.getShiftMap());
		allRowsMappingList.addAll(rowsMappingList);

		int lastRowPlus = this.getLastRowPlusRef().getRowIndex();
		ShiftFormulaRef shiftFormulaRef = new ShiftFormulaRef(
				configBuildRef.getWatchList(), allRowsMappingList);
		for (int i = atRow; i < lastRowPlus; i++) {
			buildCellsForRow(configBuildRef.getSheet().getRow(i), fullName,
					context, configBuildRef, shiftFormulaRef);
		}
	}

	/**
	 * Builds the cells for row.
	 *
	 * @param row
	 *            the row
	 * @param fullName
	 *            the full name
	 * @param context
	 *            the context
	 * @param configBuildRef
	 *            the config build ref
	 * @param shiftFormulaRef
	 *            the shift formula ref
	 */
	private void buildCellsForRow(final Row row, final String fullName,
			final Map<String, Object> context,
			final ConfigBuildRef configBuildRef,
			ShiftFormulaRef shiftFormulaRef) {
		if ((row == null)
				|| !ConfigurationUtility.isStaticRowRef(this, row)) {
			return;
		}
		for (Cell cell : row) {
			buildSingleCell(cell, context, configBuildRef, shiftFormulaRef);
		}
		ConfigurationUtility.setFullNameInHiddenColumn(row, fullName);
	}

	/**
	 * Builds the single cell.
	 *
	 * @param cell
	 *            the cell
	 * @param context
	 *            the context
	 * @param configBuildRef
	 *            the config build ref
	 * @param shiftFormulaRef
	 *            the shift formula ref
	 */
	private void buildSingleCell(final Cell cell,
			final Map<String, Object> context,
			final ConfigBuildRef configBuildRef,
			final ShiftFormulaRef shiftFormulaRef) {
		try {
			CommandUtility.evaluate(context, cell,
					configBuildRef.getEngine());
			if (cell.getCellTypeEnum() == CellType.FORMULA) {
				// rebuild formula if necessary for dynamic row
				String originFormula = cell.getCellFormula();
				shiftFormulaRef.setFormulaChanged(0);
				ConfigurationUtility.buildCellFormulaForShiftedRows(
						configBuildRef.getSheet(),
						configBuildRef.getWbWrapper(), shiftFormulaRef,
						cell, cell.getCellFormula());
				if (shiftFormulaRef.getFormulaChanged() > 0) {
					configBuildRef.getCachedCells().put(cell,
							originFormula);
				}
			}

		} catch (Exception ex) {
			LOG.log(Level.SEVERE,
					"build cell ( row = " + cell.getRowIndex()
							+ " column = " + cell.getColumnIndex()
							+ " error = " + ex.getLocalizedMessage(),
					ex);
		}
	}

	/**
	 * recover by using it's address.
	 *
	 * @param sheet
	 *            sheet.
	 */
	public final void recover(final Sheet sheet) {

		this.getAttrs().recover(sheet);
		if (this.commandList != null) {
			for (ConfigCommand command : this.commandList) {
				command.recover(sheet);
			}
		}

	}

	/**
	 * Obtain a human readable representation.
	 *
	 * @return String Human readable label
	 */
	@Override
	public final String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("{");
		sb.append("FirstRowRef = " + this.getFirstRowRef());
		sb.append(",");
		sb.append("FirstRowAddr = " + this.getFirstRowAddr());
		sb.append(",");
		sb.append("LastRowPlusRef = " + this.getLastRowPlusRef());
		sb.append(",");
		sb.append("LastRowPlusAddr = " + this.getLastRowPlusAddr());
		sb.append(",");
		sb.append("LastCellCreated = " + this.isLastCellCreated());
		if (this.commandList != null) {
			sb.append("[");
			for (ConfigCommand command : this.commandList) {
				sb.append("command = " + command);
				sb.append(",");
			}
			sb.append("]");
		}
		sb.append("}");
		return sb.toString();
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.serializable;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.poi.EncryptedDocumentException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

/**
 * serialize workbook.
 *
 * @author Jason Jiang
 *
 */
public class SerialDataContext implements Serializable {

	/** logger. */
	private static final Logger LOG = Logger
			.getLogger(SerialDataContext.class.getName());

	/**
	 * serial id.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * data context map is transient.
	 */
	private transient Map<String, Object> dataContext;

	/** convert map to json string. */
	private String mapToJson;

	/**
	 * save the workbook before serialize.
	 *
	 * @param out
	 *            outputstream.
	 * @throws IOException
	 *             io exception.
	 */
	private void writeObject(final java.io.ObjectOutputStream out)
			throws IOException {
		Gson objGson = new GsonBuilder().setPrettyPrinting().create();
		this.mapToJson = objGson.toJson(this.dataContext);
		out.defaultWriteObject();
	}

	/**
	 * load the workbook from saving.
	 *
	 * @param in
	 *            inputstream.
	 * @throws IOException
	 *             io exception.
	 */
	private void readObject(final java.io.ObjectInputStream in)
			throws IOException {
		try {
			in.defaultReadObject();
			Gson objGson = new GsonBuilder().setPrettyPrinting().create();
			Type listType = new TypeToken<Map<String, Object>>() {
			}.getType();
			this.dataContext = objGson.fromJson(mapToJson, listType);
		} catch (EncryptedDocumentException | ClassNotFoundException e) {
			LOG.log(Level.SEVERE,
					" error in readObject of serialWorkbook : "
							+ e.getLocalizedMessage(),
					e);
		}
	}

	/**
	 * Gets the data context.
	 *
	 * @return the dataContext
	 */
	public final Map<String, Object> getDataContext() {
		return dataContext;
	}

	/**
	 * Sets the data context.
	 *
	 * @param pdataContext
	 *            the dataContext to set
	 */
	public final void setDataContext(
			final Map<String, Object> pdataContext) {
		this.dataContext = pdataContext;
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.chart;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.model.ThemesTable;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTAxDataSource;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTDPt;
import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties;
import org.tiefaces.components.websheet.chart.objects.ChartObject;
import org.tiefaces.components.websheet.dataobjects.ParsedCell;
import org.tiefaces.components.websheet.dataobjects.XColor;
import org.tiefaces.components.websheet.utility.ColorUtility;
import org.tiefaces.components.websheet.utility.WebSheetUtility;

/**
 * Chart Data class.
 *
 * @author Jason Jiang.
 *
 */
public class ChartData {

	/** id. */
	private String id;

	/** title. */
	private String title;

	/** bgcolor. */
	private XColor bgColor;

	/** type. */
	private ChartType type;

	/** catAx. */
	private ChartAxis catAx;

	/** valAx. */
	private ChartAxis valAx;

	/** categoryList. */
	private List<ParsedCell> categoryList;

	/** seriesList. */
	private List<ChartSeries> seriesList;

	/** logger. */
	private static final Logger LOG = Logger
			.getLogger(ChartData.class.getName());

	/**
	 * get category list.
	 *
	 * @return category list.
	 */
	public final List<ParsedCell> getCategoryList() {
		return categoryList;
	}

	/**
	 * set categorylist.
	 *
	 * @param pcategoryList
	 *            category list.
	 */
	public final void setCategoryList(
			final List<ParsedCell> pcategoryList) {
		this.categoryList = pcategoryList;
	}

	/**
	 * get serieslist.
	 *
	 * @return serieslist.
	 */
	public final List<ChartSeries> getSeriesList() {
		return seriesList;
	}

	/**
	 * set series list.
	 *
	 * @param pseriesList
	 *            series list.
	 */
	public final void setSeriesList(final List<ChartSeries> pseriesList) {
		this.seriesList = pseriesList;
	}

	/**
	 * get bg color.
	 *
	 * @return bg color.
	 */
	public final XColor getBgColor() {
		return bgColor;
	}

	/**
	 * set bg color.
	 *
	 * @param pbgColor
	 *            bgcolor.
	 */
	public final void setBgColor(final XColor pbgColor) {
		this.bgColor = pbgColor;
	}

	/**
	 * build categotry list.
	 *
	 * @param ctAxDs
	 *            ctaxdatasource.
	 */
	public final void buildCategoryList(final CTAxDataSource ctAxDs) {

		List<ParsedCell> cells = new ArrayList<>();
		try {
			String fullRangeName = ctAxDs.getStrRef().getF();
			String sheetName = WebSheetUtility
					.getSheetNameFromFullCellRefName(fullRangeName);
			CellRangeAddress region = CellRangeAddress.valueOf(
					WebSheetUtility.removeSheetNameFromFullCellRefName(
							fullRangeName));
			for (int row = region.getFirstRow(); row <= region
					.getLastRow(); row++) {
				for (int col = region.getFirstColumn(); col <= region
						.getLastColumn(); col++) {
					cells.add(new ParsedCell(sheetName, row, col));
				}
			}

		} catch (Exception ex) {
			LOG.log(Level.FINE, "failed in buildCategoryList", ex);
		}
		this.setCategoryList(cells);
	}

	/**
	 * build series list.
	 *
	 * @param bsers
	 *            bsers.
	 * @param themeTable
	 *            themetable.
	 * @param ctObj
	 *            ctobj.
	 */
	@SuppressWarnings("rawtypes")
	public final void buildSeriesList(final List bsers,
			final ThemesTable themeTable, final ChartObject ctObj) {

		List<ChartSeries> lseriesList = new ArrayList<>();
		try {
			for (int index = 0; index < bsers.size(); index++) {
				Object ctObjSer = bsers.get(index);
				ChartSeries ctSer = buildChartSeriesInList(themeTable,
						ctObj, ctObjSer, index);
				lseriesList.add(ctSer);
			}
		} catch (Exception ex) {
			LOG.log(Level.FINE, "failed in buildSerialList", ex);
		}
		this.setSeriesList(lseriesList);
	}

	/**
	 * Builds the chart series in list.
	 *
	 * @param themeTable
	 *            the theme table
	 * @param ctObj
	 *            the ct obj
	 * @param ctObjSer
	 *            the ct obj ser
	 * @param index
	 *            the index
	 * @return the chart series
	 */
	private ChartSeries buildChartSeriesInList(final ThemesTable themeTable,
			final ChartObject ctObj, final Object ctObjSer, final int index) {
		ChartSeries ctSer = new ChartSeries();
		ctSer.setSeriesLabel(new ParsedCell(
				ctObj.getSeriesLabelFromCTSer(ctObjSer)));
		ctSer.setSeriesColor(ColorUtility.geColorFromSpPr(index,
				ctObj.getShapePropertiesFromCTSer(ctObjSer),
				themeTable, ctObj.isLineColor()));
		List<ParsedCell> cells = new ArrayList<>();
		String fullRangeName = (ctObj
				.getCTNumDataSourceFromCTSer(ctObjSer)).getNumRef()
						.getF();
		String sheetName = WebSheetUtility
				.getSheetNameFromFullCellRefName(fullRangeName);
		CellRangeAddress region = CellRangeAddress
				.valueOf(WebSheetUtility
						.removeSheetNameFromFullCellRefName(
								fullRangeName));
		for (int row = region.getFirstRow(); row <= region
				.getLastRow(); row++) {
			for (int col = region.getFirstColumn(); col <= region
					.getLastColumn(); col++) {
				cells.add(new ParsedCell(sheetName, row, col));
			}
		}
		ctSer.setValueList(cells);
		ctSer.setValueColorList(getColorListFromDPTWithValueList(
				ctObj.getDPtListFromCTSer(ctObjSer), cells,
				themeTable, ctObj));
		return ctSer;
	}

	/**
	 * get color list from dpt.
	 *
	 * @param dptList
	 *            dpt.
	 * @param cells
	 *            cells.
	 * @param themeTable
	 *            themetable.
	 * @param ctObj
	 *            ctobj.
	 * @return list of xcolor.
	 */
	private List<XColor> getColorListFromDPTWithValueList(
			final List<CTDPt> dptList, final List<ParsedCell> cells,
			final ThemesTable themeTable, final ChartObject ctObj) {
		List<XColor> colors = new ArrayList<>();
		if ((dptList != null) && (cells != null)) {

			for (int index = 0; index < cells.size(); index++) {
				CTDPt dpt = getDPtFromListWithIndex(dptList, index);
				CTShapeProperties ctSpPr = null;
				if (dpt != null) {
					ctSpPr = dpt.getSpPr();
				}
				colors.add(ColorUtility.geColorFromSpPr(index, ctSpPr,
						themeTable, ctObj.isLineColor()));
			}
		}
		return colors;

	}

	/**
	 * get dpt from list.
	 *
	 * @param dptList
	 *            dptlist.
	 * @param index
	 *            index.
	 * @return ctdpt.
	 */
	private CTDPt getDPtFromListWithIndex(final List<CTDPt> dptList,
			final int index) {

		if (dptList != null) {
			for (CTDPt dpt : dptList) {
				if (dpt.getIdx().getVal() == index) {
					return dpt;
				}
			}
		}
		return null;
	}

	/**
	 * get id.
	 *
	 * @return id.
	 */
	public final String getId() {
		return id;
	}

	/**
	 * set id.
	 *
	 * @param pid
	 *            id.
	 */
	public final void setId(final String pid) {
		id = pid;
	}

	/**
	 * get title.
	 *
	 * @return title.
	 */
	public final String getTitle() {
		return title;
	}

	/**
	 * set title.
	 *
	 * @param ptitle
	 *            title.
	 */
	public final void setTitle(final String ptitle) {
		title = ptitle;
	}

	/**
	 * get type.
	 *
	 * @return type.
	 */
	public final ChartType getType() {
		return type;
	}

	/**
	 * set chart type.
	 *
	 * @param ptype
	 *            type.
	 */
	public final void setType(final ChartType ptype) {
		type = ptype;
	}

	/**
	 * get cat ax.
	 *
	 * @return chartaxis.
	 */
	public final ChartAxis getCatAx() {
		return catAx;
	}

	/**
	 * set catax.
	 *
	 * @param pcatAx
	 *            catax.
	 */
	public final void setCatAx(final ChartAxis pcatAx) {
		this.catAx = pcatAx;
	}

	/**
	 * get valax.
	 *
	 * @return chartaxis.
	 */
	public final ChartAxis getValAx() {
		return valAx;
	}

	/**
	 * set valax.
	 *
	 * @param pvalAx
	 *            valax.
	 */
	public final void setValAx(final ChartAxis pvalAx) {
		this.valAx = pvalAx;
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */

package org.tiefaces.components.websheet.configuration;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.logging.Logger;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.tiefaces.components.websheet.dataobjects.CellFormAttributes;
import org.tiefaces.components.websheet.dataobjects.CellRange;
import org.tiefaces.components.websheet.serializable.SerialCellMap;
import org.tiefaces.components.websheet.serializable.SerialWorkbook;

/**
 * Configuration object hold all the attributes defined in Configuration tab
 * Also some attributes hold runtime value for each web sheet.
 *
 * @author Jason Jiang
 */
public class SheetConfiguration implements Serializable {

	/**
	 * serialVersionUID.
	 */
	private static final long serialVersionUID = -108522960628560962L;

	/** The form name. */
	private String formName; // corresponds to tab name of configuration tab

	/** The sheet name. */
	private String sheetName; // corresponds to sheet name of configuration tab

	/** The form header range. */
	private String formHeaderRange; // corresponds to formHeaderRange of

	/** The form body range. */
	// configuration tab
	private String formBodyRange; // corresponds to formBodyRange of

	/** The form footer range. */
	// configuration tab
	private String formFooterRange; // corresponds to formFooterRange of

	/** The header cell range. */
	private CellRange headerCellRange; // transfer formHeaderRange to CellRange

	/** The body cell range. */
	// object
	private CellRange bodyCellRange; // transfer formBodyRange to CellRange

	/** The footer cell range. */
	// object
	private CellRange footerCellRange; // transfer formFooterRange to CellRange

	/** The form body type. */
	// object
	private String formBodyType; // formBodyType: Repeat or Free

	/** The body allow add rows. */
	private boolean bodyAllowAddRows; // whether allow dynamic insert row in

	/** The body initial rows. */
	// form body
	private int bodyInitialRows; // initial rows number for form body. support

	/** The body populated. */
	// EL.
	private boolean bodyPopulated; // runtime holder

	/** The max row per page. */
	private int maxRowPerPage; // max rows per page

	/** The cell form attributes. */
	private Map<String, List<CellFormAttributes>> cellFormAttributes;
	/**
	 * The form command.
	 */
	private FormCommand formCommand;

	/** The shift map. */
	private NavigableMap<String, ConfigRangeAttrs> shiftMap;

	/** The command index map. */
	private HashMap<String, Command> commandIndexMap;

	/** The collection obj name map. */
	private Map<String, String> collectionObjNameMap;

	/** The watch list. */
	private List<Integer> watchList;

	/** The hidden. */
	private boolean hidden = false; // in some case e.g. prepop, we choose to
									// hide the sheet.
	/** fixedWidthStyle. */
	private boolean fixedWidthStyle = false;

	/** The cached origin formulas. */
	private SerialCellMap serialCachedCells = new SerialCellMap();

	/**
	 * due to poi bug. cannot set comment during evaluate cell time. have to
	 * output comments following sequence. i.e. row by row. hold comments for
	 * the final sheet config form.
	 *
	 * key is cell. value is the comments.
	 */
	private SerialCellMap serialFinalCommentMap = new SerialCellMap();

	/** logger. */
	private static final Logger LOG = Logger
			.getLogger(SerialWorkbook.class.getName());

	/**
	 * Instantiates a new sheet configuration.
	 */
	public SheetConfiguration() {
		super();
		LOG.fine("Sheet Configuration constructor");
	}

	/**
	 * Gets the sheet name.
	 *
	 * @return the sheet name
	 */
	public final String getSheetName() {
		return sheetName;
	}

	/**
	 * Sets the sheet name.
	 *
	 * @param psheetName
	 *            the new sheet name
	 */
	public final void setSheetName(final String psheetName) {
		this.sheetName = psheetName;
	}

	/**
	 * Gets the form name.
	 *
	 * @return the form name
	 */
	public final String getFormName() {
		return formName;
	}

	/**
	 * Sets the form name.
	 *
	 * @param pformName
	 *            the new form name
	 */
	public final void setFormName(final String pformName) {
		this.formName = pformName;
	}

	/**
	 * Gets the form header range.
	 *
	 * @return the form header range
	 */
	public final String getFormHeaderRange() {
		return formHeaderRange;
	}

	/**
	 * Sets the form header range.
	 *
	 * @param pformHeaderRange
	 *            the new form header range
	 */
	public final void setFormHeaderRange(final String pformHeaderRange) {
		this.formHeaderRange = pformHeaderRange;
	}

	/**
	 * Gets the form body range.
	 *
	 * @return the form body range
	 */
	public final String getFormBodyRange() {
		return formBodyRange;
	}

	/**
	 * Sets the form body range.
	 *
	 * @param pformBodyRange
	 *            the new form body range
	 */
	public final void setFormBodyRange(final String pformBodyRange) {
		this.formBodyRange = pformBodyRange;
	}

	/**
	 * Gets the form footer range.
	 *
	 * @return the form footer range
	 */
	public final String getFormFooterRange() {
		return formFooterRange;
	}

	/**
	 * Sets the form footer range.
	 *
	 * @param pformFooterRange
	 *            the new form footer range
	 */
	public final void setFormFooterRange(final String pformFooterRange) {
		this.formFooterRange = pformFooterRange;
	}

	/**
	 * Gets the cell form attributes.
	 *
	 * @return the cell form attributes
	 */
	public final Map<String, List<CellFormAttributes>> getCellFormAttributes() {
		return cellFormAttributes;
	}

	/**
	 * Sets the cell form attributes.
	 *
	 * @param pcellFormAttributes
	 *            the cell form attributes
	 */
	public final void setCellFormAttributes(
			final Map<String, List<CellFormAttributes>> pcellFormAttributes) {
		this.cellFormAttributes = pcellFormAttributes;
	}

	/**
	 * Gets the header cell range.
	 *
	 * @return the header cell range
	 */
	public final CellRange getHeaderCellRange() {
		return headerCellRange;
	}

	/**
	 * Sets the header cell range.
	 *
	 * @param pheaderCellRange
	 *            the new header cell range
	 */
	public final void setHeaderCellRange(final CellRange pheaderCellRange) {
		this.headerCellRange = pheaderCellRange;
	}

	/**
	 * Gets the body cell range.
	 *
	 * @return the body cell range
	 */
	public final CellRange getBodyCellRange() {
		return bodyCellRange;
	}

	/**
	 * Sets the body cell range.
	 *
	 * @param pbodyCellRange
	 *            the new body cell range
	 */
	public final void setBodyCellRange(final CellRange pbodyCellRange) {
		this.bodyCellRange = pbodyCellRange;
	}

	/**
	 * Gets the footer cell range.
	 *
	 * @return the footer cell range
	 */
	public final CellRange getFooterCellRange() {
		return footerCellRange;
	}

	/**
	 * Sets the footer cell range.
	 *
	 * @param pfooterCellRange
	 *            the new footer cell range
	 */
	public final void setFooterCellRange(final CellRange pfooterCellRange) {
		this.footerCellRange = pfooterCellRange;
	}

	/**
	 * Gets the form body type.
	 *
	 * @return the form body type
	 */
	public final String getFormBodyType() {
		return formBodyType;
	}

	/**
	 * Sets the form body type.
	 *
	 * @param pformBodyType
	 *            the new form body type
	 */
	public final void setFormBodyType(final String pformBodyType) {
		this.formBodyType = pformBodyType;
	}

	/**
	 * Checks if is body allow add rows.
	 *
	 * @return true, if is body allow add rows
	 */
	public final boolean isBodyAllowAddRows() {
		return bodyAllowAddRows;
	}

	/**
	 * Sets the body allow add rows.
	 *
	 * @param pbodyAllowAddRows
	 *            the new body allow add rows
	 */
	public final void setBodyAllowAddRows(final boolean pbodyAllowAddRows) {
		this.bodyAllowAddRows = pbodyAllowAddRows;
	}

	/**
	 * Gets the body initial rows.
	 *
	 * @return the body initial rows
	 */
	public final int getBodyInitialRows() {
		return bodyInitialRows;
	}

	/**
	 * Sets the body initial rows.
	 *
	 * @param pbodyInitialRows
	 *            the new body initial rows
	 */
	public final void setBodyInitialRows(final int pbodyInitialRows) {
		this.bodyInitialRows = pbodyInitialRows;
	}

	/**
	 * Checks if is body populated.
	 *
	 * @return true, if is body populated
	 */
	public final boolean isBodyPopulated() {
		return bodyPopulated;
	}

	/**
	 * Sets the body populated.
	 *
	 * @param pbodyPopulated
	 *            the new body populated
	 */
	public final void setBodyPopulated(final boolean pbodyPopulated) {
		this.bodyPopulated = pbodyPopulated;
	}

	/**
	 * Gets the max row per page.
	 *
	 * @return the max row per page
	 */
	public final int getMaxRowPerPage() {
		return maxRowPerPage;
	}

	/**
	 * Sets the max row per page.
	 *
	 * @param pmaxRowPerPage
	 *            the new max row per page
	 */
	public final void setMaxRowPerPage(final int pmaxRowPerPage) {
		this.maxRowPerPage = pmaxRowPerPage;
	}

	/**
	 * Gets the watch list.
	 *
	 * @return the watch list
	 */
	public final List<Integer> getWatchList() {
		return watchList;
	}

	/**
	 * Sets the watch list.
	 *
	 * @param pwatchList
	 *            the new watch list
	 */
	public final void setWatchList(final List<Integer> pwatchList) {
		this.watchList = pwatchList;
	}

	/**
	 * Gets the shift map.
	 *
	 * @return the shift map
	 */
	public final NavigableMap<String, ConfigRangeAttrs> getShiftMap() {
		return shiftMap;
	}

	/**
	 * Sets the shift map.
	 *
	 * @param pshiftMap
	 *            the shift map
	 */
	public final void setShiftMap(
			final NavigableMap<String, ConfigRangeAttrs> pshiftMap) {
		this.shiftMap = pshiftMap;
	}

	/**
	 * Gets the command index map.
	 *
	 * @return the command index map
	 */
	public final Map<String, Command> getCommandIndexMap() {
		return commandIndexMap;
	}

	/**
	 * Sets the command index map.
	 *
	 * @param pcommandIndexMap
	 *            the command index map
	 */
	public final void setCommandIndexMap(
			final Map<String, Command> pcommandIndexMap) {
		if (pcommandIndexMap instanceof HashMap) {
			this.commandIndexMap = (HashMap<String, Command>) pcommandIndexMap;
		} else {
			this.commandIndexMap = new HashMap<>();
			this.commandIndexMap.putAll(pcommandIndexMap);
		}
	}

	/**
	 * Gets the collection obj name map.
	 *
	 * @return the collection obj name map
	 */
	public final Map<String, String> getCollectionObjNameMap() {
		return collectionObjNameMap;
	}

	/**
	 * Sets the collection obj name map.
	 *
	 * @param pcollectionObjNameMap
	 *            the collection obj name map
	 */
	public final void setCollectionObjNameMap(
			final Map<String, String> pcollectionObjNameMap) {
		this.collectionObjNameMap = pcollectionObjNameMap;
	}

	/**
	 * Gets the form command.
	 *
	 * @return the form command
	 */
	public final FormCommand getFormCommand() {
		if (this.formCommand == null) {
			this.formCommand = new FormCommand();
		}
		return formCommand;
	}

	/**
	 * Sets the form command.
	 *
	 * @param pformCommand
	 *            the new form command
	 */
	public final void setFormCommand(final FormCommand pformCommand) {
		this.formCommand = pformCommand;
	}

	/**
	 * Checks if is hidden.
	 *
	 * @return true, if is hidden
	 */
	public final boolean isHidden() {
		return hidden;
	}

	/**
	 * Sets the hidden.
	 *
	 * @param phidden
	 *            the new hidden
	 */
	public final void setHidden(final boolean phidden) {
		this.hidden = phidden;
	}

	/**
	 * Gets the cached origin formulas.
	 *
	 * @return the cached origin formulas
	 */
	public final Map<Cell, String> getCachedCells() {
		return serialCachedCells.getMap();
	}

	/**
	 * Gets the serial cached cells.
	 *
	 * @return the serialCachedCells
	 */
	public final SerialCellMap getSerialCachedCells() {
		return serialCachedCells;
	}

	/**
	 * Gets the final comment map.
	 *
	 * @return the final comment map
	 */
	public final Map<Cell, String> getFinalCommentMap() {
		return serialFinalCommentMap.getMap();
	}

	/**
	 * Gets the serial final comment map.
	 *
	 * @return the serialFinalCommentMap
	 */
	public final SerialCellMap getSerialFinalCommentMap() {
		return serialFinalCommentMap;
	}

	/**
	 * recover the cell reference to the sheet.
	 *
	 * @param wb
	 *            workbook.
	 */
	public void recover(final Workbook wb) {
		Sheet sheet = wb.getSheet(this.sheetName);
		this.getSerialCachedCells().recover(sheet);
		this.getSerialFinalCommentMap().recover(sheet);
		this.getFormCommand().recover(sheet);
		if (this.getShiftMap() != null) {
			for (Map.Entry<String, ConfigRangeAttrs> entry : this
					.getShiftMap().entrySet()) {
				entry.getValue().recover(sheet);
			}
		}
		if (this.getCommandIndexMap() != null) {
			for (Map.Entry<String, Command> entry : this
					.getCommandIndexMap().entrySet()) {
				entry.getValue().recover(sheet);
			}
		}

	}

	/**
     * @return the fixedWidthStyle
     */
    public final boolean isFixedWidthStyle() {
        return fixedWidthStyle;
    }

    /**
     * @param pfixedWidthStyle the fixedWidthStyle to set
     */
    public final void setFixedWidthStyle(final boolean pfixedWidthStyle) {
        this.fixedWidthStyle = pfixedWidthStyle;
    }

    /**
	 * Obtain a human readable representation.
	 *
	 * @return String Human readable label
	 */
	@Override
	public final String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("{");
		sb.append("formName = " + formName);
		sb.append(",");
		sb.append("sheetName = " + sheetName);
		sb.append(",");
		sb.append("formHeaderRange = " + formHeaderRange);
		sb.append(",");
		sb.append("formBodyRange = " + formBodyRange);
		sb.append(",");
		sb.append("formFooterRange = " + formFooterRange);
		sb.append(",");
		sb.append("formBodyType = " + formBodyType);
		sb.append(",");
		sb.append("bodyAllowAddRows = " + bodyAllowAddRows);
		sb.append(",");
		sb.append("bodyInitialRows = " + bodyInitialRows);
		sb.append(",");
		sb.append("HeaderCellRange = " + headerCellRange);
		sb.append(",");
		sb.append("BodyCellRange = " + bodyCellRange);
		sb.append(",");
		sb.append("BodyPopulated = " + bodyPopulated);
		sb.append(",");
		sb.append("FooterCellRange = " + footerCellRange);
		sb.append(",");
		sb.append("cellFormAttributes = " + cellFormAttributes);
		sb.append(",");
		sb.append("cachedOriginFormulas = " + serialCachedCells);
		sb.append(",");
		sb.append("finalCommentMap = " + serialFinalCommentMap);
		sb.append(",");
		sb.append("formCommand = " + formCommand);
		sb.append(",");
		sb.append("shiftMap = " + shiftMap);
		sb.append(",");
		sb.append("commandIndexMap = " + commandIndexMap);
		sb.append(",");
		sb.append("collectionObjNameMap = " + collectionObjNameMap);
		sb.append(",");
		sb.append("watchList = " + watchList);
		sb.append("}");
		return sb.toString();
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */

package org.tiefaces.components.websheet.utility;

import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Picture;
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.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFPicture;
import org.apache.poi.xssf.usermodel.XSSFPictureData;
import org.apache.poi.xssf.usermodel.XSSFShape;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
import org.tiefaces.components.websheet.dataobjects.AnchorSize;
import org.tiefaces.components.websheet.dataobjects.FacesCell;

/**
 * The Class PicturesHelper.
 */
public final class PicturesUtility {

	/** logger. */
	private static final Logger LOG = Logger
			.getLogger(PicturesUtility.class.getName());

	/**
	 * hide constructor.
	 */
	private PicturesUtility() {
		// not called
	}

	/**
	 * Gets the pictrues map.
	 *
	 * @param wb
	 *            the wb
	 * @param picMap
	 *            the pic map
	 *
	 */
	public static void getPictruesMap(final Workbook wb,
			final Map<String, Picture> picMap) {
		if (wb instanceof XSSFWorkbook) {
			getXSSFPictruesMap((XSSFWorkbook) wb, picMap);
		}
		return;
	}

	/**
	 * Gets the XSSF pictrues map.
	 *
	 * @param wb
	 *            the wb
	 * @param picMap
	 *            the pic map
	 */
	private static void getXSSFPictruesMap(final XSSFWorkbook wb,
			final Map<String, Picture> picMap) {

		picMap.clear();
		List<XSSFPictureData> pictures = wb.getAllPictures();
		if (pictures.isEmpty()) {
			return;
		}
		for (int i = 0; i < wb.getNumberOfSheets(); i++) {
			XSSFSheet sheet = wb.getSheetAt(i);
			for (POIXMLDocumentPart dr : sheet.getRelations()) {
				try {
					indexPictureInMap(picMap, sheet, dr);
				} catch (Exception ex) {
					LOG.log(Level.SEVERE, "Load Picture error = "
							+ ex.getLocalizedMessage(), ex);
				}
			}
		}

		return;

	}

	/**
	 * save pciture in map with index.
	 *
	 * @param picMap
	 *            pciture map.
	 * @param sheet
	 *            sheet.
	 * @param dr
	 *            documentme part.
	 */
	private static void indexPictureInMap(final Map<String, Picture> picMap,
			final XSSFSheet sheet, final POIXMLDocumentPart dr) {
		if (dr instanceof XSSFDrawing) {
			XSSFDrawing drawing = (XSSFDrawing) dr;
			List<XSSFShape> shapes = drawing.getShapes();
			for (XSSFShape shape : shapes) {
				if (shape instanceof XSSFPicture) {
					XSSFPicture pic = (XSSFPicture) shape;
					XSSFClientAnchor anchor = pic.getPreferredSize();
					CTMarker ctMarker = anchor.getFrom();
					String picIndex = WebSheetUtility.getFullCellRefName(
							sheet.getSheetName(), ctMarker.getRow(),
							ctMarker.getCol());
					picMap.put(picIndex, pic);
				}
			}
		}
	}

	/**
	 * Generate picture style.
	 *
	 * @param sheet1
	 *            the sheet 1
	 * @param fcell
	 *            the fcell
	 * @param cell
	 *            the cell
	 * @param pic
	 *            the pic
	 * @return the string
	 */
	public static String generatePictureStyle(final Sheet sheet1,
			final FacesCell fcell, final Cell cell, final Picture pic) {

		ClientAnchor anchor = pic.getClientAnchor();
		if (anchor != null) {
			AnchorSize anchorSize = getAnchorSize(sheet1, fcell, cell,
					anchor);
			if (anchorSize != null) {
				return "MARGIN-LEFT:"
						+ String.format("%.2f", anchorSize.getPercentLeft())
						+ "%;MARGIN-TOP:"
						+ String.format("%.2f", anchorSize.getPercentTop())
						+ "%;width:" + String.format("%.2f",
								anchorSize.getPercentWidth())
						+ "%;";
			}
		}
		return "";

	}

	/**
	 * Generate chart style.
	 *
	 * @param sheet1
	 *            the sheet 1
	 * @param fcell
	 *            the fcell
	 * @param cell
	 *            the cell
	 * @param chartId
	 *            the chart id
	 * @param anchorsMap
	 *            the anchors map
	 * @return the string
	 */
	public static String generateChartStyle(final Sheet sheet1,
			final FacesCell fcell, final Cell cell, final String chartId,
			final Map<String, ClientAnchor> anchorsMap) {

		ClientAnchor anchor = anchorsMap.get(chartId);
		if (anchor != null) {
			AnchorSize anchorSize = getAnchorSize(sheet1, fcell, cell,
					anchor);
			if (anchorSize != null) {
				return "MARGIN-LEFT:"
						+ String.format("%.2f", anchorSize.getPercentLeft())
						+ "%;MARGIN-TOP:"
						+ String.format("%.2f", anchorSize.getPercentTop())
						+ "%;width:"
						+ String.format("%.2f",
								anchorSize.getPercentWidth())
						+ "%;height:135%;";
			}
		}
		return "";
	}

	/**
	 * Gets the anchor size.
	 *
	 * @param sheet1
	 *            the sheet 1
	 * @param fcell
	 *            the fcell
	 * @param cell
	 *            the cell
	 * @param anchor
	 *            the anchor
	 * @return the anchor size
	 */
	public static AnchorSize getAnchorSize(final Sheet sheet1,
			final FacesCell fcell, final Cell cell,
			final ClientAnchor anchor) {

		if (!(sheet1 instanceof XSSFSheet)) {
			return null;
		}
		double picWidth = 0.0;
		double picHeight = 0.0;
		int left = anchor.getDx1()
				/ org.apache.poi.util.Units.EMU_PER_PIXEL;
		int top = (int) ((double) anchor.getDy1()
				/ org.apache.poi.util.Units.EMU_PER_PIXEL
				/ WebSheetUtility.PICTURE_HEIGHT_ADJUST);
		int right = anchor.getDx2()
				/ org.apache.poi.util.Units.EMU_PER_PIXEL;
		int bottom = (int) ((double) anchor.getDy2()
				/ org.apache.poi.util.Units.EMU_PER_PIXEL
				/ WebSheetUtility.PICTURE_HEIGHT_ADJUST);

		double cellWidth = 0.0;
		double cellHeight = 0.0;

		if ((cell != null) && (fcell != null)) {
			for (int col = cell.getColumnIndex(); col < cell
					.getColumnIndex() + fcell.getColspan(); col++) {
				cellWidth += sheet1.getColumnWidthInPixels(col);
			}
			double lastCellWidth = sheet1.getColumnWidthInPixels(
					cell.getColumnIndex() + fcell.getColspan() - 1);

			for (int rowIndex = cell.getRowIndex(); rowIndex < cell
					.getRowIndex() + fcell.getRowspan(); rowIndex++) {
				cellHeight += WebSheetUtility.pointsToPixels(
						sheet1.getRow(rowIndex).getHeightInPoints());
			}
			double lastCellHeight = WebSheetUtility.pointsToPixels(sheet1
					.getRow(cell.getRowIndex() + fcell.getRowspan() - 1)
					.getHeightInPoints());

			picWidth = cellWidth - lastCellWidth + right - left;
			picHeight = cellHeight - lastCellHeight + bottom - top;
		} else {
			for (short col = anchor.getCol1(); col < anchor
					.getCol2(); col++) {
				picWidth += sheet1.getColumnWidthInPixels(col);
			}
			for (int rowindex = anchor.getRow1(); rowindex < anchor
					.getRow2(); rowindex++) {
				Row row = sheet1.getRow(rowindex);
				if (row != null) {
					picHeight += WebSheetUtility
							.pointsToPixels(row.getHeightInPoints());
				}
			}
		}

		return new AnchorSize(left, top, (int) picWidth, (int) picHeight,
				cellWidth, cellHeight);

	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.dataobjects;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.tiefaces.components.websheet.TieWebSheetBean;
import org.tiefaces.components.websheet.utility.CellUtility;

/**
 * Use to cache formula cells. The purpose is to compare current cell with
 * cached one. If they are different, then mean need to refresh them. All cached
 * cells are in current display sheet.
 *
 * @author Jason Jiang
 *
 */
public class CachedCells implements Serializable {

	/**
	 * serialVersionUID.
	 */
	private static final long serialVersionUID = 916959757743324812L;

	/** The cached map. */
	private HashMap<Cell, FormulaMapping> cachedMap = new HashMap<>();

	/** The parent. */
	private TieWebSheetBean parent = null;

	/**
	 * Instantiates a new cached cells.
	 *
	 * @param pparent
	 *            the parent
	 */
	public CachedCells(final TieWebSheetBean pparent) {
		this.parent = pparent;
	}

	/**
	 * Put.
	 *
	 * @param cell
	 *            the cell
	 * @param formula
	 *            the cell type
	 */
	public final void put(final Cell cell, final CellType formula) {
		Map<Cell, FormulaMapping> map = cachedMap;
		// if cellType not null then only specified Type will be put into Cache
		// e.g. only formula cell will be cached then pass in
		// Cell.CELL_TYPE_FORMULA
		if ((cell != null) && ((formula == null)
				|| (cell.getCellTypeEnum() == formula))) {
			String value = CellUtility.getCellValueWithFormat(cell,
					parent.getFormulaEvaluator(),
					parent.getDataFormatter());
			FormulaMapping f = map.get(cell);
			if (f == null) {
				f = new FormulaMapping();
			}
			f.setValue(value);
			map.put(cell, f);
		}
	}

	/**
	 * Put.
	 *
	 * @param cell
	 *            the cell
	 * @param originFormula
	 *            the origin formula
	 */
	public final void put(final Cell cell, final String originFormula) {
		Map<Cell, FormulaMapping> map = cachedMap;
		// if cellType not null then only specified Type will be put into Cache
		// e.g. only formula cell will be cached then pass in
		// Cell.CELL_TYPE_FORMULA
		if ((cell != null) && (originFormula != null)) {
			FormulaMapping f = map.get(cell);
			if (f == null) {
				f = new FormulaMapping();
			}
			f.setOriginFormula(originFormula);
			String value = CellUtility.getCellValueWithFormat(cell,
					parent.getFormulaEvaluator(),
					parent.getDataFormatter());
			f.setValue(value);
			map.put(cell, f);
		}
	}

	/**
	 * Gets the value.
	 *
	 * @param cell
	 *            the cell
	 * @return the value
	 */
	public final String getValue(final Cell cell) {
		return cachedMap.get(cell).getValue();
	}

	/**
	 * Clear.
	 */
	public final void clear() {
		cachedMap.clear();
	}

	/**
	 * Checks if is value changed.
	 *
	 * @param cell
	 *            the cell
	 *
	 * @return true, if is value changed
	 */
	public final boolean isValueChanged(final Cell cell) {
		String newValue = CellUtility.getCellValueWithFormat(cell,
				parent.getFormulaEvaluator(), parent.getDataFormatter());
		return isValueChanged(cell, newValue);
	}

	/**
	 * Checks if is value changed.
	 *
	 * @param cell
	 *            the cell
	 * @param pnewValue
	 *            the new value
	 *
	 * @return true, if is value changed
	 */
	public final boolean isValueChanged(final Cell cell,
			final String pnewValue) {
		Map<Cell, FormulaMapping> map = cachedMap;
		String oldValue = map.get(cell).getValue();
		String newValue = pnewValue;
		if (oldValue == null) {
			oldValue = "";
		}
		if (newValue == null) {
			newValue = "";
		}
		return (!oldValue.equals(newValue));
	}

	/**
	 * Gets the cached map.
	 *
	 * @return the cached map
	 */
	public final Map<Cell, FormulaMapping> getCachedMap() {
		return cachedMap;
	}

}


package org.tiefaces.components.websheet.dataobjects;

import java.util.regex.Pattern;

import org.tiefaces.components.websheet.utility.ParserUtility;

/**
 * This class is to help end user to easily define the control behavior of the cell.
 * In the real world, two group of people will involve in the design of template:
 * (1) developer (2) business users
 * While business user are difficult to understand the $widget command, but they
 * are the main designer for the template. So they need a shortcut for the controls.
 *
 * First we can setup a list alias for commands like:
 * example 1: matches part of data field
 * alias = ${*date*} , command = $widget.calendar... $validate....
 * end-user only need to define ${date} in the template while
 * widget and validate command could be added in fly
 *
 * example 2: matches special string which could be removed
 * alias = ${dropdown1} command = $widget.dropdown.... remove = true
 * when set the remove to true, the alias ${dropdown1} will be removed
 * after set the command. In this case, user can define a cell like:
 * ${data_field1} ${dropdown1}
 * And the ${dropdown1} will be removed later.
 *
 * The alias will support wildcard, e.g. ${*date*} match ${object.field_date_1}
 */
public class TieCommandAlias {

/** The alias. */
private String alias = null;

/** The command. */
private String command = null;

/** The remove. */
private boolean remove = false;

/** The pattern. */
private Pattern pattern = null;

/**
 * Instantiates a new tie command alias.
 *
 * @param alias the alias
 * @param command the command
 */
public TieCommandAlias(String alias, String command) {
	this.alias = alias;
	this.command = command;
}

/**
 * Instantiates a new tie command alias.
 *
 * @param alias the alias
 * @param command the command
 * @param remove the remove
 */
public TieCommandAlias(String alias, String command, boolean remove) {
	this.alias = alias;
	this.command = command;
	this.remove = remove;
}

/**
 * Gets the alias.
 *
 * @return the alias
 */
public String getAlias() {
	return alias;
}

/**
 * Sets the alias.
 *
 * @param alias the new alias
 */
public void setAlias(String alias) {
	this.alias = alias;
}

/**
 * Gets the command.
 *
 * @return the command
 */
public String getCommand() {
	return command;
}

/**
 * Sets the command.
 *
 * @param command the new command
 */
public void setCommand(String command) {
	this.command = command;
}

/**
 * Checks if is removes the.
 *
 * @return true, if is removes the
 */
public boolean isRemove() {
	return remove;
}

/**
 * Sets the removes the.
 *
 * @param remove the new removes the
 */
public void setRemove(boolean remove) {
	this.remove = remove;
}

/**
 * Gets the pattern.
 *
 * @return the pattern
 */
public Pattern getPattern() {
	if ((this.pattern == null) && (alias != null)) {
		this.pattern = Pattern.compile("\\s*" + ParserUtility.wildcardToRegex(alias));
	}
	return pattern;
}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */

package org.tiefaces.components.websheet;

import java.io.IOException;
import java.util.logging.Logger;

import javax.faces.component.FacesComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;

import org.tiefaces.common.TieConstants;

/**
 * component class.
 *
 * @author Jason Jiang
 *
 */
@FacesComponent("tieWebSheetComponent")
public class TieWebSheetComponent extends UINamingContainer {

    /** parent. */
    private TieWebSheetBean webSheetBean = null;

    /** logger. */
    private static final Logger LOG = Logger
            .getLogger(TieWebSheetComponent.class.getName());

    /** constructor. */
    public TieWebSheetComponent() {
        LOG.fine("TieWebSheetComponent Constructor");
    }

    /**
     * Return the bean.
     *
     * @return bean.
     */
    public final TieWebSheetBean getWebSheetBean() {
        return webSheetBean;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context
     * .FacesContext)
     */
    @Override
    public final void encodeBegin(final FacesContext context)
            throws IOException {
        webSheetBean = (TieWebSheetBean) this.getAttributes()
                .get(TieConstants.ATTRS_WEBSHEETBEAN);
        if ((webSheetBean != null)
                && (webSheetBean.getWebFormClientId() == null)) {
            LOG.fine("WebSheet component parameter setup");
            webSheetBean.setClientId(this.getClientId());
            webSheetBean.setWebFormClientId(
                    this.getClientId() + ":" + TieConstants.COMPONENT_ID);

            String maxrows = (String) this.getAttributes()
                    .get("maxRowsPerPage");
            if ((maxrows != null) && (!maxrows.isEmpty())) {
                webSheetBean.setMaxRowsPerPage(Integer.valueOf(maxrows));
            }

            Boolean hideSingleSheetTabTitle = (Boolean) this.getAttributes()
                    .get("hideSingleSheetTabTitle");
            if (hideSingleSheetTabTitle != null) {
                webSheetBean.setHideSingleSheetTabTitle(
                        hideSingleSheetTabTitle);
            }
        }
        super.encodeBegin(context);
    }

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */

package org.tiefaces.components.websheet.service;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.logging.Logger;

import javax.enterprise.context.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseId;
import javax.imageio.ImageIO;
import javax.inject.Named;

import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent;

/**
 * This serve as a managed bean for retrieve chart as picture from session.
 *
 * @author Jason Jiang
 *
 */

@Named
@SessionScoped
public class TieWebSheetChartsService implements Serializable {

	/**
	 * serialVersionUID.
	 */
	private static final long serialVersionUID = -7490246985617724098L;
	/** log instance. */
	private static final Logger LOG = Logger
			.getLogger(TieWebSheetChartsService.class.getName());

	/**
	 * Constructor.
	 */
	public TieWebSheetChartsService() {
		LOG.fine("ChartsService Constructor");
	}

	/**
	 * Return real chart picture when browser requesting the image.
	 *
	 * @return empty chart ( phase == render) or real chart ( browser request).
	 * @throws IOException
	 *             exception.
	 */
	public StreamedContent getChart() throws IOException {
		FacesContext context = FacesContext.getCurrentInstance();

		if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
			// So, we're rendering the HTML. Return a stub StreamedContent so
			// that it will generate right URL.
			LOG.fine(" return empty chart picture");
			return new DefaultStreamedContent();
		} else {
			// So, browser is requesting the image. Return a real
			// StreamedContent with the image bytes.
			String chartId = context.getExternalContext()
					.getRequestParameterMap().get("chartViewId");

			BufferedImage bufferedImg = (BufferedImage) FacesContext
					.getCurrentInstance().getExternalContext()
					.getSessionMap().get(chartId);
			ByteArrayOutputStream os = new ByteArrayOutputStream();
			ImageIO.write(bufferedImg, "png", os);
			FacesContext.getCurrentInstance().getExternalContext()
					.getSessionMap().remove(chartId);
			return new DefaultStreamedContent(new ByteArrayInputStream(
					os.toByteArray()), "image/png");
		}
	}

}


/*
 * Copyright 2017 TieFaces.
 * Licensed under MIT
 */
package org.tiefaces.components.websheet.serializable;

import java.io.IOException;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;

/**
 * serialize workbook.
 *
 * @author Jason Jiang
 *
 */
public class SerialCell implements Serializable {

	/**
	 * serial key.
	 */
	private static final long serialVersionUID = 1L;

	/** logger. */
	private static final Logger LOG = Logger
			.getLogger(SerialCell.class.getName());

	/**
	 * cell is transient.
	 */
	private transient Cell cell;

	/**
	 * use for saving cell address when serialize.
	 */
	private SerialCellAddress cellAddr;

	/**
	 * Constructor.
	 */
	public SerialCell() {
		super();
	}

	/**
	 * Constructor.
	 *
	 * @param pcell
	 *            cell.
	 * @param pcellAddr
	 *            cell address.
	 */
	public SerialCell(final Cell pcell, final SerialCellAddress pcellAddr) {
		super();
		this.cell = pcell;
		this.cellAddr = pcellAddr;
	}

	/**
	 * save the cell before serialize.
	 *
	 * @param out
	 *            outputstream.
	 * @throws IOException
	 *             io exception.
	 */
	private void writeObject(final java.io.ObjectOutputStream out)
			throws IOException {
		this.cellAddr = new SerialCellAddress(this.cell);
		out.defaultWriteObject();
	}

	/**
	 * load the workbook from saving.
	 *
	 * @param in
	 *            inputstream.
	 * @throws IOException
	 *             io exception.
	 */
	private void readObject(final java.io.ObjectInputStream in)
			throws IOException {
		try {
			in.defaultReadObject();
		} catch (EncryptedDocumentException | ClassNotFoundException e) {
			LOG.log(Level.SEVERE,
					" error in readObject of serialWorkbook : "
							+ e.getLocalizedMessage(),
					e);
		}
	}

	/**
	 * Gets the cell.
	 *
	 * @return the cell
	 */
	public final Cell getCell() {
		return cell;
	}

	/**
	 * Sets the cell.
	 *
	 * @param pcell
	 *            the cell to set
	 */
	public final void setCell(final Cell pcell) {
		this.cell = pcell;
	}

	/**
	 * Gets the cell addr.
	 *
	 * @return the cellAddr
	 */
	public final SerialCellAddress getCellAddr() {
		return cellAddr;
	}

	/**
	 * Sets the cell addr.
	 *
	 * @param pcellAddr
	 *            the cellAddr to set
	 */
	public final void setCellAddr(final SerialCellAddress pcellAddr) {
		this.cellAddr = pcellAddr;
	}

	/**
	 * recover cell by using it's address.
	 *
	 * @param sheet
	 *            sheet.
	 */
	public final void recover(final Sheet sheet) {

		if (this.cellAddr != null) {
			this.setCell(sheet.getRow(this.cellAddr.getRow())
					.getCell(this.cellAddr.getColumn()));
		}

	}

}