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

package org.tiefaces.components.websheet.configuration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
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.Comment;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.tiefaces.common.TieConstants;
import org.tiefaces.components.websheet.TieWebSheetBean;
import org.tiefaces.components.websheet.dataobjects.CellAttributesMap;
import org.tiefaces.components.websheet.dataobjects.CellFormAttributes;
import org.tiefaces.components.websheet.dataobjects.CellRange;
import org.tiefaces.components.websheet.utility.CellUtility;
import org.tiefaces.components.websheet.utility.ConfigurationUtility;
import org.tiefaces.components.websheet.utility.ParserUtility;
import org.tiefaces.components.websheet.utility.SaveAttrsUtility;
import org.tiefaces.components.websheet.utility.WebSheetUtility;

/**
 * Handler class for configuration setting.
 *
 *
 * @author Jason Jiang
 *
 */
public class ConfigurationHandler {

	/** command map. */
	@SuppressWarnings("rawtypes")
	private static Map<String, Class> commandMap = new HashMap<>();

	static {
		commandMap.put(TieConstants.COMMAND_FORM, FormCommand.class);
		commandMap.put(TieConstants.COMMAND_EACH, EachCommand.class);
	}

	/** refer to parent bean class. */
	private TieWebSheetBean parent = null;

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

	/**
	 * constructor.
	 *
	 * @param pparent
	 *            the parent
	 */
	public ConfigurationHandler(final TieWebSheetBean pparent) {
		super();
		this.parent = pparent;
	}

	/**
	 * Builds the configuration.
	 *
	 * @return the map
	 */
	public final Map<String, SheetConfiguration> buildConfiguration() {

		Map<String, SheetConfiguration> sheetConfigMap = new LinkedHashMap<>();
		// in buildsheet, it's possible to add sheets in workbook.
		// so cache the sheetname first here.
		List<String> sheetNames = new ArrayList<>();
		String sname;
		for (int i = 0; i < parent.getWb().getNumberOfSheets(); i++) {
			sname = parent.getWb().getSheetName(i);
			if (!sname.startsWith(org.tiefaces.common.TieConstants.COPY_SHEET_PREFIX)) {
				sheetNames.add(sname);
			}
		}

		for (String sheetName : sheetNames) {
			Sheet sheet = parent.getWb().getSheet(sheetName);
			ConfigurationUtility.buildSheetCommentFromAlias(sheet, parent.getTieCommandAliasList());
			buildSheet(sheet, sheetConfigMap, parent.getCellAttributesMap());
		}
		return sheetConfigMap;

	}

	/**
	 * Gets the sheet configuration.
	 *
	 * @param sheet
	 *            the sheet
	 * @param formName
	 *            the form name
	 * @param sheetRightCol
	 *            the sheet right col
	 * @return the sheet configuration
	 */
	private SheetConfiguration getSheetConfiguration(final Sheet sheet, final String formName,
			final int sheetRightCol) {

		SheetConfiguration sheetConfig = new SheetConfiguration();
		sheetConfig.setFormName(formName);
		sheetConfig.setSheetName(sheet.getSheetName());
		int leftCol = sheet.getLeftCol();
		int lastRow = sheet.getLastRowNum();
		int firstRow = sheet.getFirstRowNum();
		int rightCol = 0;
		int maxRow = 0;
		for (Row row : sheet) {
			if (row.getRowNum() > TieConstants.TIE_WEB_SHEET_MAX_ROWS) {
				break;
			}
			maxRow = row.getRowNum();
			int firstCellNum = row.getFirstCellNum();
			if (firstCellNum >= 0 && firstCellNum < leftCol) {
				leftCol = firstCellNum;
			}
			if ((row.getLastCellNum() - 1) > rightCol) {
				int verifiedcol = verifyLastCell(row, rightCol, sheetRightCol);
				if (verifiedcol > rightCol) {
					rightCol = verifiedcol;
				}
			}
		}
		if (maxRow < lastRow) {
			lastRow = maxRow;
		}
		// header range row set to 0 while column set to first column to
		// max
		// column (FF) e.g. $A$0 : $FF$0
		String tempStr = TieConstants.CELL_ADDR_PRE_FIX + WebSheetUtility.getExcelColumnName(leftCol)
				+ TieConstants.CELL_ADDR_PRE_FIX + "0 : " + TieConstants.CELL_ADDR_PRE_FIX
				+ WebSheetUtility.getExcelColumnName(rightCol) + TieConstants.CELL_ADDR_PRE_FIX + "0";
		sheetConfig.setFormHeaderRange(tempStr);
		sheetConfig.setHeaderCellRange(new CellRange(tempStr));
		// body range row set to first row to last row while column set
		// to
		// first column to max column (FF) e.g. $A$1 : $FF$1000
		tempStr = TieConstants.CELL_ADDR_PRE_FIX + WebSheetUtility.getExcelColumnName(leftCol)
				+ TieConstants.CELL_ADDR_PRE_FIX + (firstRow + 1) + " : " + TieConstants.CELL_ADDR_PRE_FIX
				+ WebSheetUtility.getExcelColumnName(rightCol) + TieConstants.CELL_ADDR_PRE_FIX + (lastRow + 1);
		sheetConfig.setFormBodyRange(tempStr);
		sheetConfig.setBodyCellRange(new CellRange(tempStr));
		sheetConfig.setFormBodyType(org.tiefaces.common.TieConstants.FORM_TYPE_FREE);
		sheetConfig.setCellFormAttributes(new HashMap<String, List<CellFormAttributes>>());

		// check it's a hidden sheet
		int sheetIndex = parent.getWb().getSheetIndex(sheet);
		if (parent.getWb().isSheetHidden(sheetIndex) || parent.getWb().isSheetVeryHidden(sheetIndex)) {
			sheetConfig.setHidden(true);
		}

		return sheetConfig;

	}

	/**
	 * Builds the form command from sheet config.
	 *
	 * @param sheetConfig
	 *            the sheet config
	 * @param sheet
	 *            the sheet
	 * @return the form command
	 */
	private FormCommand buildFormCommandFromSheetConfig(final SheetConfiguration sheetConfig, final Sheet sheet) {

		int firstRow = sheetConfig.getBodyCellRange().getTopRow();
		int leftCol = sheetConfig.getBodyCellRange().getLeftCol();
		int rightCol = sheetConfig.getBodyCellRange().getRightCol();
		int lastRow = sheetConfig.getBodyCellRange().getBottomRow();

		Cell firstCell = sheet.getRow(firstRow).getCell(leftCol, MissingCellPolicy.CREATE_NULL_AS_BLANK);

		FormCommand fcommand = new FormCommand();
		fcommand.setCommandTypeName(TieConstants.COMMAND_FORM);
		if (sheetConfig.isHidden()) {
			fcommand.setHidden(TieConstants.TRUE_STRING);
		} else {
			fcommand.setHidden(TieConstants.FALSE_STRING);
		}
		fcommand.setName(sheetConfig.getFormName());
		fcommand.getConfigRange().setFirstRowRef(firstCell, true);
		fcommand.getConfigRange().setLastRowPlusRef(sheet, rightCol, lastRow, true);
		fcommand.setHeaderLength("0");
		fcommand.setFooterLength("0");
		fcommand.setLength(Integer.toString(lastRow - firstRow + 1));
		return fcommand;
	}

	/**
	 * check last column. if it's blank then treat it as null cell.
	 *
	 * @param row
	 *            row object.
	 * @param stoppoint
	 *            the left cell we want to stop check.
	 * @param sheetRightCol
	 *            the sheet right col
	 * @return integer. the last column without blank cell.
	 */
	private int verifyLastCell(final Row row, final int stoppoint, final int sheetRightCol) {

		int lastCol = sheetRightCol;
		int col;
		for (col = lastCol; col >= stoppoint; col--) {

			Cell cell = row.getCell(col);
			if ((cell != null) && (cell.getCellTypeEnum() != CellType.BLANK)) {
				break;
			}
		}
		return col;
	}

	/**
	 * new implement of configuration with setting in comments.
	 *
	 * @param sheet
	 *            the sheet
	 * @param sheetConfigMap
	 *            the sheet config map
	 * @param cellAttributesMap
	 *            the cell attributes map
	 */

	/**
	 * build a sheet for configuration map.
	 *
	 * @param sheet
	 *            sheet.
	 * @param sheetConfigMap
	 *            sheetConfiguration map.
	 * @param cellAttributesMap
	 *            the cell attributes map
	 */
	public final void buildSheet(final Sheet sheet, final Map<String, SheetConfiguration> sheetConfigMap,
			final CellAttributesMap cellAttributesMap) {

		if ((sheet.getLastRowNum() <= 0) && (sheet.getRow(0) == null)) {
			// this is a empty sheet. skip it.
			return;
		}
		checkAndRepairLastRow(sheet);

		int sheetRightCol = WebSheetUtility.getSheetRightCol(sheet);

		List<ConfigCommand> commandList = buildCommandListFromSheetComment((XSSFSheet) sheet, sheetRightCol,
				cellAttributesMap);

		boolean hasEachCommand = hasEachCommandInTheList(commandList);

		List<String> formList = new ArrayList<>();

		buildSheetConfigMapFromFormCommand(sheet, sheetConfigMap, commandList, formList, sheetRightCol);
		// match parent command
		matchParentCommand(commandList);
		// setup save attrs in hidden column in the sheet.
		// loop command list again to assemble other command list into sheet
		// configuration
		matchSheetConfigForm(sheetConfigMap, commandList, formList);
		initTemplateForCommand(sheet, sheetConfigMap, formList, hasEachCommand);
	}

	/**
	 * check and repair the sheet's lastrow. If the row is blank then remove it.
	 *
	 * @param sheet
	 *            the sheet
	 */
	private final void checkAndRepairLastRow(final Sheet sheet) {
		// repair last row if it's inserted in the configuration generation
		Row lastrow = sheet.getRow(sheet.getLastRowNum());
		// if it's lastrow and all the cells are blank. then remove the lastrow.
		if (lastrow != null) {
			for (Cell cell : lastrow) {
				if ((cell.getCellTypeEnum() != CellType._NONE) && (cell.getCellTypeEnum() != CellType.BLANK)) {
					return;
				}
			}
			sheet.removeRow(lastrow);
		}

	}

	/**
	 * Initialize template for command to use. e.g. set origin row number and copy
	 * template if there's each command. create missing row as row require
	 * sequenced.
	 *
	 * @param sheet
	 *            sheet.
	 * @param sheetConfigMap
	 *            sheet config map.
	 * @param formList
	 *            list of form.
	 * @param hasEachCommand
	 *            whether has each command.
	 */
	private void initTemplateForCommand(final Sheet sheet, final Map<String, SheetConfiguration> sheetConfigMap,
			final List<String> formList, final boolean hasEachCommand) {

		for (String formname : formList) {
			SheetConfiguration sheetConfig = sheetConfigMap.get(formname);
			CellRange range = sheetConfig.getBodyCellRange();
			for (int index = range.getTopRow(); index <= range.getBottomRow(); index++) {
				Row row = sheet.getRow(index);
				if (row == null) {
					row = sheet.createRow(index);
				}
				if (hasEachCommand) {
					ConfigurationUtility.setOriginalRowNumInHiddenColumn(row, index);
				}

			}
		}

		if (hasEachCommand) {
			copyTemplateForTieCommands(sheet);
		}

	}

	/**
	 * build command list from comments. after transfer the comment to command,
	 * remove it from comments.
	 *
	 * @param sheet
	 *            sheet.
	 * @param sheetRightCol
	 *            the sheet right col
	 * @param cellAttributesMap
	 *            the cell attributes map
	 * @return command list.
	 */
	private List<ConfigCommand> buildCommandListFromSheetComment(final XSSFSheet sheet, final int sheetRightCol,
			final CellAttributesMap cellAttributesMap) {
		List<ConfigCommand> commandList = new ArrayList<>();
		// if skip then return empty list.
		if (parent.isSkipConfiguration()) {
			return commandList;
		}

		Map<CellAddress, ? extends Comment> comments = null;

		try {
			// due to a poi bug. null exception throwed if no comments in the
			// sheet.
			comments = sheet.getCellComments();
		} catch (Exception ex) {
			LOG.log(Level.FINE, "Null exception throwed when no comment exists: " + ex.getLocalizedMessage(), ex);
		}
		if (comments == null) {
			return commandList;
		}

		// not sure the map is sorted. So use tree map to sort it.
		SortedSet<CellAddress> keys = new TreeSet<>(comments.keySet());
		// go through each comments
		// if found tie command then transfer it to list also remove from
		// comments.
		for (CellAddress key : keys) {
			Cell cell = sheet.getRow(key.getRow()).getCell(key.getColumn(), MissingCellPolicy.CREATE_NULL_AS_BLANK);
			buildCommandList(sheet, sheetRightCol, cell, commandList, cellAttributesMap);
		}
		return commandList;

	}

	/**
	 * build top level configuration map from command list. User can either put
	 * tie:form command in the comments (which will transfer to sheetConfig), Or
	 * just ignore it, then use whole sheet as one form.
	 *
	 * @param sheet
	 *            sheet.
	 * @param sheetConfigMap
	 *            sheetConfigMap.
	 * @param commandList
	 *            command list.
	 * @param formList
	 *            form list.
	 * @param sheetRightCol
	 *            the sheet right col
	 */
	private void buildSheetConfigMapFromFormCommand(final Sheet sheet,
			final Map<String, SheetConfiguration> sheetConfigMap, final List<ConfigCommand> commandList,
			final List<String> formList, final int sheetRightCol) {
		boolean foundForm = false;
		int minRowNum = sheet.getLastRowNum();
		int maxRowNum = sheet.getFirstRowNum();
		for (Command command : commandList) {
			// check whether is form command
			if (command.getCommandTypeName().equalsIgnoreCase(TieConstants.COMMAND_FORM)) {
				foundForm = true;
				FormCommand fcommand = (FormCommand) command;
				sheetConfigMap.put(fcommand.getName(),
						getSheetConfigurationFromConfigCommand(sheet, fcommand, sheetRightCol));
				formList.add(fcommand.getName());
				if (fcommand.getTopRow() < minRowNum) {
					minRowNum = fcommand.getTopRow();
				}
				if (fcommand.getLastRow() > maxRowNum) {
					maxRowNum = fcommand.getLastRow();
				}
			}
		}
		// if no form found, then use the whole sheet as form
		if (!foundForm) {
			WebSheetUtility.clearHiddenColumns(sheet);
			String formName = sheet.getSheetName();
			SheetConfiguration sheetConfig = getSheetConfiguration(sheet, formName, sheetRightCol);
			FormCommand fcommand = buildFormCommandFromSheetConfig(sheetConfig, sheet);
			commandList.add(fcommand);
			sheetConfig.setFormCommand(fcommand);
			sheetConfigMap.put(formName, sheetConfig);
			formList.add(formName);
			minRowNum = sheet.getFirstRowNum();
			maxRowNum = sheet.getLastRowNum();
		}

		// if skip config then return.
		if (parent.isSkipConfiguration()) {
			return;
		}
		SaveAttrsUtility.setSaveAttrsForSheet(sheet, minRowNum, maxRowNum,
				parent.getCellAttributesMap().getTemplateCommentMap().get(TieConstants.SAVE_COMMENT_KEY_IN_MAP));
	}

	/**
	 * Set up parent attribute for each command (exclude form command). The top
	 * level commands have no parent.
	 *
	 * @param commandList
	 *            the command list
	 */
	private void matchParentCommand(final List<ConfigCommand> commandList) {

		if (commandList == null) {
			return;
		}
		for (int i = 0; i < commandList.size(); i++) {
			ConfigCommand child = commandList.get(i);
			if (!child.getCommandTypeName().equalsIgnoreCase(TieConstants.COMMAND_FORM)) {
				setParentForChildCommand(commandList, i, child);
			}
		}
	}

	/**
	 * Sets the parent for child command.
	 *
	 * @param commandList
	 *            the command list
	 * @param i
	 *            the i
	 * @param child
	 *            the child
	 */
	private void setParentForChildCommand(final List<ConfigCommand> commandList, final int i,
			final ConfigCommand child) {
		int matchIndex = -1;
		ConfigRange matchRange = null;
		for (int j = 0; j < commandList.size(); j++) {
			if (j != i) {
				Command commandParent = commandList.get(j);
				if (!commandParent.getCommandTypeName().equalsIgnoreCase(TieConstants.COMMAND_FORM)
						&& WebSheetUtility.insideRange(child.getConfigRange(), commandParent.getConfigRange())
						&& ((matchRange == null)
								|| (WebSheetUtility.insideRange(commandParent.getConfigRange(), matchRange)))) {
					matchRange = commandParent.getConfigRange();
					matchIndex = j;
				}
			}

		}
		if (matchIndex >= 0) {
			commandList.get(matchIndex).getConfigRange().addCommand(child);
			child.setParentFound(true);
		}
	}

	/**
	 * check whether contain each command in the list.
	 *
	 * @param commandList
	 *            command list.
	 * @return true if contain each command.
	 */
	private boolean hasEachCommandInTheList(final List<ConfigCommand> commandList) {

		if (commandList != null) {
			for (ConfigCommand command : commandList) {
				if (command.getCommandTypeName().equalsIgnoreCase(TieConstants.COMMAND_EACH)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Assemble top level command to sheetConfiguration (form). top level commands
	 * are those haven't matched from matchParentCommand function.
	 *
	 * @param sheetConfigMap
	 *            sheet configuration map.
	 * @param commandList
	 *            command list.
	 * @param formList
	 *            form list.
	 */
	private void matchSheetConfigForm(final Map<String, SheetConfiguration> sheetConfigMap,
			final List<ConfigCommand> commandList, final List<String> formList) {
		for (ConfigCommand command : commandList) {
			// check weather it's form command
			if (!command.getCommandTypeName().equalsIgnoreCase(TieConstants.COMMAND_FORM)
					&& (!command.isParentFound())) {
				matchCommandToSheetConfigForm(sheetConfigMap, formList, command);
			}
		}
	}

	/**
	 * Match command to sheet config form.
	 *
	 * @param sheetConfigMap
	 *            the sheet config map
	 * @param formList
	 *            the form list
	 * @param command
	 *            the command
	 */
	private void matchCommandToSheetConfigForm(final Map<String, SheetConfiguration> sheetConfigMap,
			final List<String> formList, final ConfigCommand command) {
		for (String formname : formList) {
			SheetConfiguration sheetConfig = sheetConfigMap.get(formname);
			if (WebSheetUtility.insideRange(command.getConfigRange(), sheetConfig.getFormCommand().getConfigRange())) {
				sheetConfig.getFormCommand().getConfigRange().addCommand(command);
				break;
			}
		}
	}

	/**
	 * Copy the each command area to seperated sheet. As it will be used for
	 * iteration.
	 *
	 * @param sheet
	 *            sheet.
	 */
	private void copyTemplateForTieCommands(final Sheet sheet) {
		// if skip configuration. then return.
		if (parent.isSkipConfiguration()) {
			return;
		}
		Workbook wb = sheet.getWorkbook();
		String copyName = TieConstants.COPY_SHEET_PREFIX + sheet.getSheetName();
		if (wb.getSheet(copyName) == null) {
			Sheet newSheet = wb.cloneSheet(wb.getSheetIndex(sheet));
			int sheetIndex = wb.getSheetIndex(newSheet);
			wb.setSheetName(sheetIndex, copyName);
			wb.setSheetHidden(sheetIndex, Workbook.SHEET_STATE_VERY_HIDDEN);
		}
	}

	/**
	 * build command list from comment.
	 *
	 * @param sheet
	 *            sheet.
	 * @param sheetRightCol
	 *            sheet right column.
	 * @param cell
	 *            the cell
	 * @param cList
	 *            command list.
	 * @param cellAttributesMap
	 *            the cell attributes map
	 * @return command list.
	 */
	private List<ConfigCommand> buildCommandList(final Sheet sheet, final int sheetRightCol, final Cell cell,
			final List<ConfigCommand> cList, final CellAttributesMap cellAttributesMap) {

		Comment comment = cell.getCellComment();
		String text = comment.getString().getString();
		String[] commentLines = text.split("\\n");
		StringBuilder newComment = new StringBuilder();
		boolean changed = false;
		for (String commentLine : commentLines) {
			String line = commentLine.trim();
			if (ParserUtility.isCommandString(line)) {
				processCommandLine(sheet, cell, line, cList, sheetRightCol);
				changed = true;
			} else if (ParserUtility.isEmptyMethodString(line) || ParserUtility.isMethodString(line)) {
				processMethodLine(cell, line, cellAttributesMap);
				changed = true;
			} else {
				if (newComment.length() > 0) {
					newComment.append("\\n" + commentLine);
				} else {
					newComment.append(commentLine);
				}
			}
		}
		if (!changed) {
			moveCommentToMap(cell, text, cellAttributesMap.getTemplateCommentMap(), true);
		} else {
			// reset comment string if changed
			if (newComment.length() > 0) {
				moveCommentToMap(cell, newComment.toString(), cellAttributesMap.getTemplateCommentMap(), true);
				CreationHelper factory = sheet.getWorkbook().getCreationHelper();
				RichTextString str = factory.createRichTextString(newComment.toString());
				comment.setString(str);
			} else {
				// remove cell comment if new comment become empty.
				cell.removeCellComment();
			}
		}

		return cList;
	}

	/**
	 * Process method line.
	 *
	 * @param cell
	 *            the cell
	 * @param line
	 *            the line
	 * @param cellAttributesMap
	 *            the cell attributes map
	 */
	private void processMethodLine(final Cell cell, final String line, final CellAttributesMap cellAttributesMap) {
		if (ParserUtility.isWidgetMethodString(line)) {
			ParserUtility.parseWidgetAttributes(cell, line, cellAttributesMap);
		} else if (ParserUtility.isValidateMethodString(line)) {
			ParserUtility.parseValidateAttributes(cell, line, cellAttributesMap);
		} else {
			moveCommentToMap(cell, line, cellAttributesMap.getTemplateCommentMap(), false);
		}
	}

	/**
	 * Process command line.
	 *
	 * @param sheet
	 *            the sheet
	 * @param cell
	 *            the cell
	 * @param line
	 *            the line
	 * @param cList
	 *            the c list
	 * @param sheetRightCol
	 *            the sheet right col
	 */
	private void processCommandLine(final Sheet sheet, final Cell cell, final String line,
			final List<ConfigCommand> cList, final int sheetRightCol) {
		int nameEndIndex = line.indexOf(TieConstants.ATTR_PREFIX, TieConstants.COMMAND_PREFIX.length());
		if (nameEndIndex < 0) {
			String errMsg = "Failed to parse command line [" + line + "]. Expected '" + TieConstants.ATTR_PREFIX
					+ "' symbol.";
			LOG.severe(errMsg);
			throw new IllegalStateException(errMsg);
		}
		String commandName = line.substring(TieConstants.COMMAND_PREFIX.length(), nameEndIndex).trim();
		Map<String, String> attrMap = buildAttrMap(line, nameEndIndex);
		ConfigCommand configCommand = createConfigCommand(sheet, cell, sheetRightCol, commandName, attrMap);
		if (configCommand != null) {
			cList.add(configCommand);
		}
	}

	/**
	 * change the comment.
	 *
	 * @param cell
	 *            the cell
	 * @param newComment
	 *            updated comment.
	 * @param sheetCommentMap
	 *            the sheet comment map
	 * @param normalComment
	 *            the normal comment
	 */
	private void moveCommentToMap(final Cell cell, final String newComment,
			final Map<String, Map<String, String>> sheetCommentMap, final boolean normalComment) {

		String cellKey = cell.getSheet().getSheetName() + "!$" + cell.getColumnIndex() + "$" + cell.getRowIndex();

		ParserUtility.parseCommentToMap(cellKey, newComment, sheetCommentMap, normalComment);

	}

	/**
	 * create configuration command.
	 *
	 * @param sheet
	 *            sheet.
	 * @param firstCell
	 *            first cell.
	 * @param sheetRightCol
	 *            sheet right col.
	 * @param commandName
	 *            command name.
	 * @param attrMap
	 *            attibutes map.
	 * @return configuration command.
	 */

	private ConfigCommand createConfigCommand(final Sheet sheet, final Cell firstCell, final int sheetRightCol,
			final String commandName, final Map<String, String> attrMap) {
		@SuppressWarnings("rawtypes")
		Class clas = commandMap.get(commandName);
		if (clas == null) {
			LOG.log(Level.WARNING, "Cannot find command class for {} ", commandName);
			return null;
		}
		try {
			ConfigCommand command = (ConfigCommand) clas.newInstance();
			command.setCommandTypeName(commandName);
			for (Map.Entry<String, String> attr : attrMap.entrySet()) {
				WebSheetUtility.setObjectProperty(command, attr.getKey(), attr.getValue(), true);
			}
			command.getConfigRange().setFirstRowRef(firstCell, true);
			command.getConfigRange().setLastRowPlusRef(sheet, sheetRightCol, command.getLastRow(), true);
			return command;
		} catch (Exception e) {
			LOG.log(Level.WARNING,
					"Failed to initialize command class " + clas.getName() + " for command" + commandName, e);
			return null;
		}
	}

	/**
	 * Build the attributes map.
	 *
	 * @param commandLine
	 *            command line from comment.
	 * @param nameEndIndex
	 *            index of command name's end.
	 * @return attributes map.
	 */
	private Map<String, String> buildAttrMap(final String commandLine, final int nameEndIndex) {
		int paramsEndIndex = commandLine.lastIndexOf(TieConstants.ATTR_SUFFIX);
		if (paramsEndIndex < 0) {
			String errMsg = "Failed to parse command line [" + commandLine + "]. Expected '" + TieConstants.ATTR_SUFFIX
					+ "' symbol.";
			throw new IllegalArgumentException(errMsg);
		}
		String attrString = commandLine.substring(nameEndIndex + 1, paramsEndIndex).trim();
		return ParserUtility.parseCommandAttributes(attrString);
	}

	/**
	 * Create sheet configuration from form command.
	 *
	 * @param sheet
	 *            sheet.
	 * @param fcommand
	 *            form command.
	 * @param sheetRightCol
	 *            the sheet right col
	 * @return sheet configuration.
	 */
	private SheetConfiguration getSheetConfigurationFromConfigCommand(final Sheet sheet, final FormCommand fcommand,
			final int sheetRightCol) {

		SheetConfiguration sheetConfig = new SheetConfiguration();
		sheetConfig.setFormName(fcommand.getName());
		sheetConfig.setSheetName(sheet.getSheetName());
		int leftCol = fcommand.getLeftCol();
		int lastRow = fcommand.getLastRow();
		int rightCol = 0;
		int maxRow = 0;
		for (Row row : sheet) {
			if (row.getRowNum() > TieConstants.TIE_WEB_SHEET_MAX_ROWS) {
				break;
			}
			maxRow = row.getRowNum();
			if ((row.getLastCellNum() - 1) > rightCol) {
				int verifiedcol = verifyLastCell(row, rightCol, sheetRightCol);
				if (verifiedcol > rightCol) {
					rightCol = verifiedcol;
				}
			}
		}
		if (maxRow < lastRow) {
			lastRow = maxRow;
		}
		// header range row set to 0 while column set to first column to
		// max
		// column (FF) e.g. $A$0 : $FF$0
		setHeaderOfSheetConfiguration(fcommand, sheetConfig, leftCol, rightCol);
		// body range row set to first row to last row while column set
		// to
		// first column to max column (FF) e.g. $A$1 : $FF$1000
		setBodyOfSheetConfiguration(fcommand, sheetConfig, leftCol, lastRow, rightCol);

		// footer range row set to 0 while column set to first column to
		// max
		// column (FF) e.g. $A$0 : $FF$0
		setFooterOfSheetConfiguration(fcommand, sheetConfig, leftCol, rightCol);

		String hidden = fcommand.getHidden();
		if ((hidden != null) && (Boolean.parseBoolean(hidden))) {
			sheetConfig.setHidden(true);
		}
		String fixedWidthStyle = fcommand.getFixedWidthStyle();
		if ((fixedWidthStyle != null) && (Boolean.parseBoolean(fixedWidthStyle))) {
			sheetConfig.setFixedWidthStyle(true);
		}
		sheetConfig.setFormCommand(fcommand);
		return sheetConfig;

	}

	/**
	 * Sets the footer of sheet configuration.
	 *
	 * @param fcommand
	 *            the fcommand
	 * @param sheetConfig
	 *            the sheet config
	 * @param leftCol
	 *            the left col
	 * @param rightCol
	 *            the right col
	 */
	private void setFooterOfSheetConfiguration(final FormCommand fcommand, final SheetConfiguration sheetConfig,
			final int leftCol, final int rightCol) {
		String tempStr;
		if (fcommand.calcFooterLength() == 0) {
			tempStr = CellUtility.getCellIndexLetterKey(leftCol, 0) + " : "
					+ CellUtility.getCellIndexLetterKey(rightCol, 0);
		} else {
			tempStr = CellUtility.getCellIndexLetterKey(leftCol,
					fcommand.getTopRow() + fcommand.calcHeaderLength() + fcommand.calcBodyLength()) + " : "
					+ CellUtility.getCellIndexLetterKey(rightCol, fcommand.getTopRow() + fcommand.calcHeaderLength());
		}
		sheetConfig.setFormFooterRange(tempStr);
		sheetConfig.setFooterCellRange(new CellRange(tempStr));
	}

	/**
	 * Sets the body of sheet configuration.
	 *
	 * @param fcommand
	 *            the fcommand
	 * @param sheetConfig
	 *            the sheet config
	 * @param leftCol
	 *            the left col
	 * @param lastRow
	 *            the last row
	 * @param rightCol
	 *            the right col
	 */
	private void setBodyOfSheetConfiguration(final FormCommand fcommand, final SheetConfiguration sheetConfig,
			final int leftCol, final int lastRow, final int rightCol) {
		String tempStr;
		tempStr = CellUtility.getCellIndexLetterKey(leftCol, fcommand.getTopRow() + fcommand.calcHeaderLength() + 1)
				+ " : " + CellUtility.getCellIndexLetterKey(rightCol, lastRow + 1);

		sheetConfig.setFormBodyRange(tempStr);
		sheetConfig.setBodyCellRange(new CellRange(tempStr));
		sheetConfig.setFormBodyType(TieConstants.FORM_TYPE_FREE);
		sheetConfig.setCellFormAttributes(new HashMap<String, List<CellFormAttributes>>());
	}

	/**
	 * Sets the header of sheet configuration.
	 *
	 * @param fcommand
	 *            the fcommand
	 * @param sheetConfig
	 *            the sheet config
	 * @param leftCol
	 *            the left col
	 * @param rightCol
	 *            the right col
	 */
	private void setHeaderOfSheetConfiguration(final FormCommand fcommand, final SheetConfiguration sheetConfig,
			final int leftCol, final int rightCol) {
		String tempStr;
		if (fcommand.calcHeaderLength() == 0) {
			tempStr = CellUtility.getCellIndexLetterKey(leftCol, 0) + " : "
					+ CellUtility.getCellIndexLetterKey(rightCol, 0);
		} else {
			tempStr = CellUtility.getCellIndexLetterKey(leftCol, fcommand.getTopRow() + 1) + " : "
					+ CellUtility.getCellIndexLetterKey(rightCol, fcommand.getTopRow() + fcommand.calcHeaderLength());
		}
		sheetConfig.setFormHeaderRange(tempStr);
		sheetConfig.setHeaderCellRange(new CellRange(tempStr));
	}

}


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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;

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.Workbook;
import org.tiefaces.common.TieConstants;
import org.tiefaces.components.websheet.configuration.Command;
import org.tiefaces.components.websheet.configuration.ConfigBuildRef;
import org.tiefaces.components.websheet.configuration.ConfigRange;
import org.tiefaces.components.websheet.configuration.ConfigRangeAttrs;
import org.tiefaces.components.websheet.configuration.EachCommand;
import org.tiefaces.components.websheet.configuration.ExpressionEngine;
import org.tiefaces.components.websheet.configuration.RowsMapping;
import org.tiefaces.components.websheet.configuration.SheetConfiguration;
import org.tiefaces.components.websheet.dataobjects.CollectionObject;
import org.tiefaces.components.websheet.dataobjects.FacesRow;
import org.tiefaces.components.websheet.dataobjects.TieCell;
import org.tiefaces.exception.AddRowException;
import org.tiefaces.exception.DeleteRowException;
import org.tiefaces.exception.EvaluationException;

/**
 * Helper class for command.
 */
public final class CommandUtility {

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

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

	/**
	 * Adds the row.
	 *
	 * @param configBuildRef
	 *            the config build ref
	 * @param rowIndex
	 *            the row index
	 * @param dataContext
	 *            the data context
	 * @return the int
	 */
	@SuppressWarnings({ "rawtypes" })
	public static int addRow(final ConfigBuildRef configBuildRef,
			final int rowIndex, final Map<String, Object> dataContext) {

		// replace the lastCollection.
		// since here's add one row.
		// Then we should insert one empty object in the list.
		// The collection must be a list to support add/delete function.
		// and the object must support empty constructor.

		String fullName = ConfigurationUtility.getFullNameFromRow(
				configBuildRef.getSheet().getRow(rowIndex));
		String[] parts = fullName.split(":");
		configBuildRef.getCellHelper().restoreDataContext(fullName);
		CollectionObject collect = configBuildRef.getCellHelper()
				.getLastCollect(fullName);

		Collection lastCollection = collect.getLastCollection();
		int lastCollectionIndex = collect.getLastCollectionIndex();
		EachCommand eachCommand = collect.getEachCommand();
		if (lastCollectionIndex < 0) {
			// no each command in the loop.
			throw new AddRowException("No each command found.");
		}
		String unitFullName = CommandUtility.insertEmptyObjectInContext(
				fullName, lastCollection, eachCommand, lastCollectionIndex,
				dataContext);
		RowsMapping unitRowsMapping = new RowsMapping();
		ConfigRangeAttrs savedRangeAttrs = configBuildRef.getShiftMap()
				.get(fullName);
		int insertPosition = savedRangeAttrs.getFirstRowRef().getRowIndex()
				+ savedRangeAttrs.getFinalLength();
		configBuildRef.setInsertPosition(insertPosition);
		CommandUtility.insertEachTemplate(eachCommand.getConfigRange(),
				configBuildRef, lastCollectionIndex + 1, insertPosition,
				unitRowsMapping);
		ConfigRange currentRange = ConfigurationUtility.buildCurrentRange(
				eachCommand.getConfigRange(), configBuildRef.getSheet(),
				insertPosition);
		List<RowsMapping> currentRowsMappingList = ConfigurationUtility
				.findParentRowsMappingFromShiftMap(parts,
						configBuildRef.getShiftMap());
		currentRowsMappingList.add(unitRowsMapping);
		currentRange.getAttrs().setAllowAdd(true);
		configBuildRef.setBodyAllowAdd(true);
		// reverse order of changeMap.
		Map<String, String> changeMap = new TreeMap<>(
				Collections.reverseOrder());
		ConfigurationUtility.changeIndexNumberInHiddenColumn(configBuildRef,
				currentRange.getAttrs().getLastRowPlusRef().getRowIndex(),
				fullName, changeMap, 1);
		ConfigurationUtility.changeIndexNumberInShiftMap(
				configBuildRef.getShiftMap(), changeMap);
		configBuildRef.putShiftAttrs(unitFullName, currentRange.getAttrs(),
				unitRowsMapping);
		int length = currentRange.buildAt(unitFullName, configBuildRef,
				insertPosition, dataContext, currentRowsMappingList);
		currentRange.getAttrs().setFinalLength(length);

		ConfigurationUtility.reBuildUpperLevelFormula(configBuildRef,
				fullName);
		ConfigurationUtility.changeUpperLevelFinalLength(
				configBuildRef.getShiftMap(), fullName, length);
		currentRowsMappingList.remove(unitRowsMapping);
		dataContext.remove(eachCommand.getVar());

		return length;

	}

	/**
	 * Delete row.
	 *
	 * @param configBuildRef
	 *            the config build ref
	 * @param rowIndex
	 *            the row index
	 * @param dataContext
	 *            the data context
	 * @param sheetConfig
	 *            the sheet config
	 * @param bodyRows
	 *            the body rows
	 * @return the int
	 * @throws DeleteRowException
	 *             the delete row exception
	 */
	@SuppressWarnings({ "rawtypes" })
	public static int deleteRow(final ConfigBuildRef configBuildRef,
			final int rowIndex, final Map<String, Object> dataContext,
			final SheetConfiguration sheetConfig,
			final List<FacesRow> bodyRows) {

		String fullName = ConfigurationUtility.getFullNameFromRow(
				configBuildRef.getSheet().getRow(rowIndex));

		configBuildRef.getCellHelper().restoreDataContext(fullName);
		CollectionObject collect = configBuildRef.getCellHelper()
				.getLastCollect(fullName);

		Collection lastCollection = collect.getLastCollection();
		int lastCollectionIndex = collect.getLastCollectionIndex();
		EachCommand eachCommand = collect.getEachCommand();
		if (lastCollectionIndex < 0) {
			// no each command in the loop.
			throw new DeleteRowException("No each command found.");
		}
		if (lastCollection.size() <= 1) {
			// this is the last record and no parent left.
			throw new DeleteRowException(
					"Cannot delete the last record in the group.");
		}

		CommandUtility.deleteObjectInContext(lastCollection, eachCommand,
				lastCollectionIndex, dataContext);

		// find range from shiftmap.
		ConfigRangeAttrs currentRangeAttrs = configBuildRef.getShiftMap()
				.get(fullName);
		if (currentRangeAttrs == null) {
			throw new DeleteRowException("Cannot find delete range.");

		}

		// The lastRowRef is wrong in rangeAttrs. So use length to recalc it.
		int startRow = currentRangeAttrs.getFirstRowIndex();
		int length = currentRangeAttrs.getFinalLength();
		int endRow = startRow + length - 1;

		List<String> removeFullNameList = findRemoveFullNameList(
				configBuildRef.getSheet(), startRow, endRow);
		// remove range from shiftmap.
		removeRangesFromShiftMap(configBuildRef.getShiftMap(),
				removeFullNameList);
		// 1. remove ranged rows from sheet
		String var = eachCommand.getVar();
		CommandUtility.removeRowsInSheet(configBuildRef.getSheet(),
				startRow, endRow, configBuildRef.getCachedCells());
		// 2. reset FacesRow row index.
		CommandUtility.removeRowsInBody(sheetConfig, bodyRows, startRow,
				endRow);
		// 3. decrease index number in hidden column
		Map<String, String> changeMap = new TreeMap<>();
		ConfigurationUtility.changeIndexNumberInHiddenColumn(configBuildRef,
				startRow, fullName, changeMap, -1);
		// 4. decrease index number in shift map
		ConfigurationUtility.changeIndexNumberInShiftMap(
				configBuildRef.getShiftMap(), changeMap);
		// 5. rebuild upper level formula
		ConfigurationUtility.reBuildUpperLevelFormula(configBuildRef,
				fullName);
		// 6. decrease upper level final length
		ConfigurationUtility.changeUpperLevelFinalLength(
				configBuildRef.getShiftMap(), fullName, -length);

		dataContext.remove(var);

		return length;

	}

	/**
	 * Removes the ranges from shift map.
	 *
	 * @param shiftMap
	 *            the shift map
	 * @param removeFullNameList
	 *            the remove full name list
	 */
	private static void removeRangesFromShiftMap(
			final NavigableMap<String, ConfigRangeAttrs> shiftMap,
			final List<String> removeFullNameList) {
		for (String fname : removeFullNameList) {
			shiftMap.remove(fname);
		}

	}

	/**
	 * Find remove full name list.
	 *
	 * @param sheet
	 *            the sheet
	 * @param startRow
	 *            the start row
	 * @param endRow
	 *            the end row
	 * @return the list
	 */
	private static List<String> findRemoveFullNameList(final Sheet sheet,
			final int startRow, final int endRow) {

		List<String> list = new ArrayList<>();

		for (int rowIndex = startRow; rowIndex <= endRow; rowIndex++) {
			String fullName = ConfigurationUtility
					.getFullNameFromRow(sheet.getRow(rowIndex));
			if (!list.contains(fullName)) {
				list.add(fullName);
			}
		}

		return list;
	}

	/**
	 * Gets the each command from parts name.
	 *
	 * @param commandIndexMap
	 *            the command index map
	 * @param varparts
	 *            the varparts
	 * @return the each command from parts name
	 */
	public static EachCommand getEachCommandFromPartsName(
			final Map<String, Command> commandIndexMap,
			final String[] varparts) {
		if (varparts.length == TieConstants.DEFAULT_COMMAND_PART_LENGTH) {
			return (EachCommand) commandIndexMap
					.get(TieConstants.EACH_COMMAND_FULL_NAME_PREFIX
							+ varparts[1]);
		}
		return null;

	}

	/**
	 * Insert empty object in context.
	 *
	 * @param fullName
	 *            the full name
	 * @param lastCollection
	 *            the last collection
	 * @param eachCommand
	 *            the each command
	 * @param lastCollectionIndex
	 *            the last collection index
	 * @param dataContext
	 *            the data context
	 * @return the string
	 * @throws EvaluationException
	 *             the evaluation exception
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private static String insertEmptyObjectInContext(final String fullName,
			final Collection lastCollection, final EachCommand eachCommand,
			final int lastCollectionIndex,
			final Map<String, Object> dataContext) {
		if (!(lastCollection instanceof List)) {
			throw new EvaluationException(
					"Collection must be list in order to insert/delete.");
		}
		List collectionList = (List) lastCollection;
		// the object must support empty constructor.
		Object currentObj = collectionList.get(lastCollectionIndex);
		Object insertObj;
		try {
			insertObj = currentObj.getClass().newInstance();
			collectionList.add(lastCollectionIndex + 1, insertObj);
			dataContext.put(eachCommand.getVar(), insertObj);
			return fullName.substring(0, fullName.lastIndexOf('.') + 1)
					+ (lastCollectionIndex + 1);
		} catch (InstantiationException | IllegalAccessException e) {
			throw new EvaluationException(e);

		}

	}

	/**
	 * Delete object in context.
	 *
	 * @param lastCollection
	 *            the last collection
	 * @param eachCommand
	 *            the each command
	 * @param lastCollectionIndex
	 *            the last collection index
	 * @param dataContext
	 *            the data context
	 */
	@SuppressWarnings({ "rawtypes" })
	private static void deleteObjectInContext(
			final Collection lastCollection, final EachCommand eachCommand,
			final int lastCollectionIndex,
			final Map<String, Object> dataContext) {
		if (!(lastCollection instanceof List)) {
			throw new EvaluationException(eachCommand.getVar()
					+ TieConstants.EACH_COMMAND_INVALID_MSG);
		}
		List collectionList = (List) lastCollection;
		// the object must support empty constructor.

		collectionList.remove(lastCollectionIndex);
		dataContext.remove(eachCommand.getVar());

	}

	/**
	 * Prepare collection data in context.
	 *
	 * @param varparts
	 *            the varparts
	 * @param collection
	 *            the collection
	 * @param dataContext
	 *            the data context
	 * @return the int
	 */
	@SuppressWarnings("rawtypes")
	public static int prepareCollectionDataInContext(
			final String[] varparts, final Collection collection,
			final Map<String, Object> dataContext) {
		if (varparts.length == TieConstants.DEFAULT_COMMAND_PART_LENGTH) {
			int collectionIndex = Integer.parseInt(varparts[2]);
			Object obj = ConfigurationUtility
					.findItemInCollection(collection, collectionIndex);
			if (obj != null) {
				dataContext.put(varparts[1], obj);
				return collectionIndex;
			}
		}
		return -1;
	}

	/**
	 * Index command range.
	 *
	 * @param sourceConfigRange
	 *            the source config range
	 * @param indexMap
	 *            the index map
	 */
	public static void indexCommandRange(
			final ConfigRange sourceConfigRange,
			final Map<String, Command> indexMap) {
		if (sourceConfigRange.getCommandList() != null) {
			for (int i = 0; i < sourceConfigRange.getCommandList()
					.size(); i++) {
				Command command = sourceConfigRange.getCommandList().get(i);
				indexMap.put(command.getCommandName(), command);
				command.getConfigRange().indexCommandRange(indexMap);
			}
		}

	}

	/**
	 * Checks if is row allow add.
	 *
	 * @param row
	 *            the row
	 * @param sheetConfig
	 *            the sheet config
	 * @return true, if is row allow add
	 */
	public static boolean isRowAllowAdd(final Row row,
			final SheetConfiguration sheetConfig) {
		String fullName = ConfigurationUtility.getFullNameFromRow(row);
		if (fullName != null) {
			ConfigRangeAttrs attrs = sheetConfig.getShiftMap()
					.get(fullName);
			if ((attrs != null) && (attrs.isAllowAdd()) && (row
					.getRowNum() == attrs.getFirstRowRef().getRowIndex())) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Insert each template.
	 *
	 * @param sourceConfigRange
	 *            the source config range
	 * @param configBuildRef
	 *            the config build ref
	 * @param index
	 *            the index
	 * @param insertPosition
	 *            the insert position
	 * @param unitRowsMapping
	 *            the unit rows mapping
	 */
	public static void insertEachTemplate(
			final ConfigRange sourceConfigRange,
			final ConfigBuildRef configBuildRef, final int index,
			final int insertPosition, final RowsMapping unitRowsMapping) {
		int srcStartRow = sourceConfigRange.getFirstRowAddr().getRow();
		int srcEndRow = sourceConfigRange.getLastRowPlusAddr().getRow() - 1;

		Sheet sheet = configBuildRef.getSheet();
		Workbook wb = sheet.getWorkbook();
		// excel sheet name has limit 31 chars
		String copyName = TieConstants.COPY_SHEET_PREFIX
				+ sheet.getSheetName();
		if (copyName.length() > TieConstants.EXCEL_SHEET_NAME_LIMIT) {
			copyName = copyName.substring(0,
					TieConstants.EXCEL_SHEET_NAME_LIMIT);
		}
		Sheet srcSheet = wb.getSheet(copyName);
		if (index > 0) {
			CellUtility.copyRows(srcSheet, sheet, srcStartRow, srcEndRow,
					insertPosition, false, true);
		}

		for (int rowIndex = srcStartRow; rowIndex <= srcEndRow; rowIndex++) {
			if (configBuildRef.getWatchList().contains(rowIndex)
					&& (ConfigurationUtility.isStaticRow(sourceConfigRange,
							rowIndex))) {
				unitRowsMapping.addRow(rowIndex, sheet
						.getRow(insertPosition + rowIndex - srcStartRow));
			}
		}
	}

	/**
	 * Evaluate.
	 *
	 * @param context
	 *            the context
	 * @param cell
	 *            the cell
	 * @param engine
	 *            the engine
	 */
	@SuppressWarnings("deprecation")
	public static void evaluate(final Map<String, Object> context,
			final Cell cell, final ExpressionEngine engine) {
		if ((cell != null) && (cell.getCellTypeEnum() == CellType.STRING)) {
			String strValue = cell.getStringCellValue();
			if (isUserFormula(strValue)) {
				evaluateUserFormula(cell, strValue);
			} else {
				evaluateNormalCells(cell, strValue, context, engine);
			}
		}
	}

	/**
	 * Evaluate normal cells.
	 *
	 * @param cell
	 *            cell.
	 * @param strValue
	 *            string value.
	 * @param context
	 *            context.
	 * @param engine
	 *            engine.
	 */
	public static void evaluateNormalCells(final Cell cell,
			final String strValue, final Map<String, Object> context,
			final ExpressionEngine engine) {
		if (strValue.contains(TieConstants.METHOD_PREFIX)) {

			Object evaluationResult = evaluate(strValue, context, engine);
			if (evaluationResult == null) {
				evaluationResult = "";
			}
			CellUtility.setCellValue(cell, evaluationResult.toString());

			createTieCell(cell, context, engine);

		}
	}

	private static void createTieCell(final Cell cell, final Map<String, Object> context,
	    final ExpressionEngine engine) {

        	@SuppressWarnings("unchecked")
        	HashMap<String, TieCell> tieCells = (HashMap<String, TieCell>) context.get("tiecells");

        	// if tiecells exists is because tieWebSheetBean.isAdvancedContext() is
        	// true
        	if (tieCells != null) {

        	    if (SaveAttrsUtility.isHasSaveAttr(cell)) {

        		String saveAttrList = SaveAttrsUtility.getSaveAttrListFromRow(cell.getRow());

        		if (saveAttrList != null) {
        		    String saveAttr = SaveAttrsUtility.getSaveAttrFromList(cell.getColumnIndex(), saveAttrList);
        		    if (saveAttr != null) {

        			int index = saveAttr.lastIndexOf('.');
        			if (index > 0) {
        			    String strObject = saveAttr.substring(0, index);
        			    String strMethod = saveAttr.substring(index + 1);
        			    strObject = "${" + strObject + "}";

        			    Object object = CommandUtility.evaluate(strObject, context, engine);

        			    if (object != null) {
        				TieCell tieCell = CellUtility.getOrAddTieCellInMap(cell, tieCells);
        				tieCell.setContextObject(object);
        				tieCell.setObjectStr(strObject);
        				tieCell.setMethodStr(strMethod);
        			    }

        			}

        		    }
        		}

        	    }

        	}
	}

	/**
	 * Evaluate user formula.
	 *
	 * @param cell
	 *            the cell
	 * @param strValue
	 *            the str value
	 */
	private static void evaluateUserFormula(final Cell cell,
			final String strValue) {
		String formulaStr = strValue.substring(2, strValue.length() - 1);
		if ((formulaStr != null) && (!formulaStr.isEmpty())) {
			cell.setCellFormula(formulaStr);
		}
	}

	/**
	 * Checks if is user formula.
	 *
	 * @param str
	 *            the str
	 * @return true, if is user formula
	 */
	private static boolean isUserFormula(final String str) {
		return str.startsWith(TieConstants.USER_FORMULA_PREFIX)
				&& str.endsWith(TieConstants.USER_FORMULA_SUFFIX);
	}

	/**
	 * Evaluate.
	 *
	 * @param strValue
	 *            the str value
	 * @param context
	 *            the context
	 * @param engine
	 *            the engine
	 * @return the object
	 */
	public static Object evaluate(final String strValue,
			final Map<String, Object> context,
			final ExpressionEngine engine) {
		StringBuffer sb = new StringBuffer();
		int beginExpressionLength = TieConstants.METHOD_PREFIX.length();
		int endExpressionLength = TieConstants.METHOD_END.length();
		Matcher exprMatcher = TieConstants.EXPRESSION_NOTATION_PATTERN
				.matcher(strValue);
		String matchedString;
		String expression;
		Object lastMatchEvalResult = null;
		int matchCount = 0;
		int endOffset = 0;
		while (exprMatcher.find()) {
			endOffset = exprMatcher.end();
			matchCount++;
			matchedString = exprMatcher.group();
			expression = matchedString.substring(beginExpressionLength,
					matchedString.length() - endExpressionLength);
			lastMatchEvalResult = engine.evaluate(expression, context);
			exprMatcher.appendReplacement(sb,
					Matcher.quoteReplacement(lastMatchEvalResult != null
							? lastMatchEvalResult.toString() : ""));
		}
		String lastStringResult = lastMatchEvalResult != null
				? lastMatchEvalResult.toString() : "";
		boolean isAppendTail = matchCount == 1
				&& endOffset < strValue.length();
		Object evaluationResult = null;
		if (matchCount > 1 || isAppendTail) {
			exprMatcher.appendTail(sb);
			evaluationResult = sb.toString();
		} else if (matchCount == 1) {
			if (sb.length() > lastStringResult.length()) {
				evaluationResult = sb.toString();
			} else {
				evaluationResult = lastMatchEvalResult;
			}
		} else if (matchCount == 0) {
			evaluationResult = strValue;
		}
		return evaluationResult;
	}

	/**
	 * Creates the cell comment.
	 *
	 * @param cell
	 *            the cell
	 * @param newComment
	 *            the new comment
	 * @param finalCommentMap
	 *            the final comment map
	 */
	public static void createCellComment(final Cell cell,
			final String newComment,
			final Map<Cell, String> finalCommentMap) {
		// due to poi's bug. the comment must be set in sorted order ( row first
		// then column),
		// otherwise poi will mess up.
		// workaround solution is to save all comments into a map,
		// and output them together when download workbook.

		if (newComment != null) {
			finalCommentMap.put(cell, newComment);

		}
	}

	/**
	 * evaluate boolean express.
	 *
	 * @param expEngine
	 *            expression engine.
	 * @param pscript
	 *            script.
	 * @return true (express is true) false ( express is false or invalid).
	 */
	public static boolean evalBoolExpression(
			final ExpressionEngine expEngine, final String pscript) {
		Object result = null;
		String script = "( " + pscript + " )";
		script = script.toUpperCase().replace("AND", "&&");
		script = script.toUpperCase().replace("OR", "||");
		try {
			result = expEngine.evaluate(script);
		} catch (Exception e) {
			LOG.log(Level.SEVERE,
					"WebForm WebFormHelper evalBoolExpression script = "
							+ script + "; error = "
							+ e.getLocalizedMessage(),
					e);
		}
		if (result != null) {
			return ((Boolean) result).booleanValue();
		} else {
			return false;
		}
	}

	/**
	 * Remove the rows.
	 *
	 * @param sheet
	 *            the sheet
	 * @param rowIndexStart
	 *            start row index.
	 * @param rowIndexEnd
	 *            end row index.
	 * @param cachedMap
	 *            the cached map
	 */
	public static void removeRowsInSheet(final Sheet sheet,
			final int rowIndexStart, final int rowIndexEnd,
			final Map<Cell, String> cachedMap) {

		for (int irow = rowIndexStart; irow <= rowIndexEnd; irow++) {
			removeCachedCellForRow(sheet, irow, cachedMap);
		}
		int irows = rowIndexEnd - rowIndexStart + 1;
		if ((irows < 1) || (rowIndexStart < 0)) {
			return;
		}
		int lastRowNum = sheet.getLastRowNum();
		if (rowIndexEnd < lastRowNum) {
			sheet.shiftRows(rowIndexEnd + 1, lastRowNum, -irows);
		}
		if (rowIndexEnd == lastRowNum) {
			// reverse order to delete rows.
			for (int i = rowIndexEnd; i >= rowIndexStart; i--) {
				removeSingleRowInSheet(sheet, rowIndexStart);
			}
		}
	}

	/**
	 * Removes the single row in sheet.
	 *
	 * @param sheet
	 *            the sheet
	 * @param rowIndexStart
	 *            the row index start
	 */
	private static void removeSingleRowInSheet(final Sheet sheet,
			final int rowIndexStart) {
		Row removingRow = sheet.getRow(rowIndexStart);
		if (removingRow != null) {
			sheet.removeRow(removingRow);
		}
	}

	/**
	 * Removes the cached cell for row.
	 *
	 * @param sheet
	 *            the sheet
	 * @param rowIndexStart
	 *            the row index start
	 * @param cachedMap
	 *            the cached map
	 */
	private static void removeCachedCellForRow(final Sheet sheet,
			final int rowIndexStart, final Map<Cell, String> cachedMap) {
		Row removingRow = sheet.getRow(rowIndexStart);
		if (removingRow != null) {
			// remove cached cell.
			for (Cell cell : removingRow) {
				cachedMap.remove(cell);
			}
		}
	}

	/**
	 * Removes the rows in body.
	 *
	 * @param sheetConfig
	 *            the sheet config
	 * @param bodyRows
	 *            the body rows
	 * @param rowIndexStart
	 *            the row index start
	 * @param rowIndexEnd
	 *            the row index end
	 */
	public static void removeRowsInBody(
			final SheetConfiguration sheetConfig,
			final List<FacesRow> bodyRows, final int rowIndexStart,
			final int rowIndexEnd) {
		int top = sheetConfig.getBodyCellRange().getTopRow();
		if ((rowIndexEnd < rowIndexStart) || (rowIndexStart < top)) {
			return;
		}

		int irows = rowIndexEnd - rowIndexStart + 1;
		for (int rowIndex = rowIndexEnd; rowIndex >= rowIndexStart; rowIndex--) {
			bodyRows.remove(rowIndex - top);
		}
		for (int irow = rowIndexStart - top; irow < bodyRows
				.size(); irow++) {
			FacesRow facesrow = bodyRows.get(irow);
			facesrow.setRowIndex(facesrow.getRowIndex() - irows);
		}
	}
}


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

package org.tiefaces.components.websheet.dataobjects;

import java.io.Serializable;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.faces.context.FacesContext;

import org.apache.poi.ss.usermodel.Cell;
import org.tiefaces.components.websheet.TieWebSheetBean;
import org.tiefaces.components.websheet.utility.CellUtility;

/**
 * Cell Map is actually a virtual map which don't hold any data. Instead it
 * refer to the data in the workbook through getter/setters. The purpose is to
 * create a bridge between JSF Web and workbooks data.
 *
 * @author Jason Jiang
 *
 */
@SuppressWarnings("rawtypes")
public class CellMap implements Serializable, java.util.Map {

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

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

	/** The Constant emptySet. */
	private static final Set emptySet = new HashSet();

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

	/**
	 * Construtor. Pass in websheet bean, So this helper can access related
	 * instance class.
	 *
	 * @param pParent
	 *            parent websheet bean
	 */

	public CellMap(final TieWebSheetBean pParent) {
		super();
		this.parent = pParent;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#size()
	 */
	@Override
	public final int size() {
		return 0;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#isEmpty()
	 */
	@Override
	public final boolean isEmpty() {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#containsKey(java.lang.Object)
	 */
	@Override
	public final boolean containsKey(final Object key) {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#containsValue(java.lang.Object)
	 */
	@Override
	public final boolean containsValue(final Object value) {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#remove(java.lang.Object)
	 */
	@Override
	public final Object remove(final Object key) {
		return null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#putAll(java.util.Map)
	 */
	@Override
	public void putAll(final Map m) {
		emptySet.clear();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#clear()
	 */
	@Override
	public void clear() {
		emptySet.clear();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#keySet()
	 */
	@Override
	public final Set keySet() {
		return emptySet;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#values()
	 */
	@Override
	public final Collection values() {
		return emptySet;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#entrySet()
	 */
	@Override
	public final Set entrySet() {
		return emptySet;
	}

	/**
	 * Put picture image to session map and return the key to web.
	 *
	 * @param rowIndex
	 *            the row index of the cell which contains picture.
	 * @param colIndex
	 *            the column index of the cell which contains picture.
	 * @return the key of the picture image in the session map.
	 */
	private String loadPicture(final int rowIndex, final int colIndex) {

		FacesCell facesCell = parent.getCellHelper()
				.getFacesCellWithRowColFromCurrentPage(rowIndex, colIndex);
		if (facesCell != null && facesCell.isContainPic()) {
			FacesContext context = FacesContext.getCurrentInstance();
			String pictureId = facesCell.getPictureId();
			String pictureViewId = Integer.toHexString(
					System.identityHashCode(parent.getWb())) + pictureId;
			Map<String, Object> sessionMap = context.getExternalContext()
					.getSessionMap();
			if (sessionMap.get(pictureViewId) == null) {
				sessionMap.put(pictureViewId, parent.getPicturesMap()
						.get(pictureId).getPictureData());
			}
			return pictureViewId;
		} else {
			return null;
		}
	}

	/**
	 * Put chart image to session map and return the key to web.
	 *
	 * @param rowIndex
	 *            the row index of the cell which contains picture.
	 * @param colIndex
	 *            the column index of the cell which contains picture.
	 * @return the key of the picture image in the session map.
	 */

	private String loadChart(final int rowIndex, final int colIndex) {

		FacesCell facesCell = parent.getCellHelper()
				.getFacesCellWithRowColFromCurrentPage(rowIndex, colIndex);
		if (facesCell != null && facesCell.isContainChart()) {
			FacesContext context = FacesContext.getCurrentInstance();
			String chartId = facesCell.getChartId();
			String chartViewId = Integer.toHexString(
					System.identityHashCode(parent.getWb())) + chartId;
			if (context != null) {
				Map<String, Object> sessionMap = context
						.getExternalContext().getSessionMap();
				if (sessionMap.get(chartViewId) == null) {
					sessionMap.put(chartViewId, parent.getCharsData()
							.getChartsMap().get(chartId));
				}
			}
			return chartViewId;
		} else {
			return null;
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#get(java.lang.Object)
	 */
	@Override
	public final Object get(final Object key) {
		Object result = "";
		try {
			CellMapKey mkey = new CellMapKey((String) key);
			if (!mkey.isParseSuccess()) {
				return result;
			}
			Cell poiCell = parent.getCellHelper()
					.getPoiCellWithRowColFromCurrentPage(mkey.getRowIndex(),
							mkey.getColIndex());
			if (poiCell == null) {
				return result;
			}
			if (mkey.isCharted()) {
				result = loadChart(mkey.getRowIndex(), mkey.getColIndex());
			} else if (mkey.isPictured()) {
				result = loadPicture(mkey.getRowIndex(),
						mkey.getColIndex());
			} else if (mkey.isFormatted()) {
				result = CellUtility.getCellValueWithFormat(poiCell,
						parent.getFormulaEvaluator(),
						parent.getDataFormatter());
			} else {
				result = CellUtility.getCellValueWithoutFormat(poiCell);
			}

		} catch (Exception ex) {
			LOG.log(Level.SEVERE, "Web Form CellMap get value error="
					+ ex.getLocalizedMessage(), ex);
		}
		// return blank if null
		return result;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Map#put(java.lang.Object, java.lang.Object)
	 */
	@Override
	public final Object put(final Object key, final Object value) {
		try {
			CellMapKey mkey = new CellMapKey((String) key);
			if (!mkey.isParseSuccess()) {
				return null;
			}
			Cell poiCell = parent.getCellHelper()
					.getPoiCellWithRowColFromCurrentPage(mkey.getRowIndex(),
							mkey.getColIndex());
			if (poiCell == null) {
				return null;
			}
			String oldValue = CellUtility
					.getCellValueWithoutFormat(poiCell);
			FacesCell facesCell = parent.getCellHelper()
					.getFacesCellWithRowColFromCurrentPage(
							mkey.getRowIndex(), mkey.getColIndex());
			String newValue = assembleNewValue(value, facesCell);
			if (newValue != null && !newValue.equals(oldValue)) {
				CellUtility.setCellValue(poiCell, newValue);
				if (facesCell.isHasSaveAttr()) {
					parent.getCellHelper().saveDataInContext(poiCell,
							newValue);
				}
				// patch to avoid not updated downloaded file
				CellUtility.copyCell(poiCell.getSheet(), poiCell.getRow(), poiCell.getRow(), poiCell.getColumnIndex(),false);
				parent.getCellHelper().reCalc();
			}

			return value;
		} catch (Exception ex) {
			LOG.log(Level.SEVERE,
					"Save cell data error : " + ex.getLocalizedMessage(),
					ex);
		}
		return null;
	}

	/**
	 * Assemble new value.
	 *
	 * @param value
	 *            the value
	 * @param facesCell
	 *            the faces cell
	 * @return the string
	 */
	private String assembleNewValue(final Object value,
			final FacesCell facesCell) {
		String newValue;
		if (value instanceof java.util.Date) {
			String datePattern = facesCell.getDatePattern();
			if (datePattern == null || datePattern.isEmpty()) {
			    datePattern = parent.getDefaultDatePattern();
			}
			Format formatter = new SimpleDateFormat(datePattern);
			newValue = formatter.format(value);
		} else {
			newValue = (String) value;
		}
		if ("textarea".equalsIgnoreCase(facesCell.getInputType())
				&& (newValue != null)) {
			// remove "\r" because excel issue
			newValue = newValue.replace("\r\n", "\n");
		}
		return newValue;
	}
}


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

package org.tiefaces.components.websheet.utility;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.faces.component.UIComponent;

import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFComment;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFVMLDrawing;
import org.tiefaces.common.FacesUtility;
import org.tiefaces.common.TieConstants;
import org.tiefaces.components.websheet.configuration.SheetConfiguration;
import org.tiefaces.components.websheet.dataobjects.CellAttributesMap;
import org.tiefaces.components.websheet.dataobjects.FacesCell;
import org.tiefaces.components.websheet.dataobjects.FacesRow;
import org.tiefaces.components.websheet.dataobjects.TieCell;

import com.microsoft.schemas.office.excel.CTClientData;
import com.microsoft.schemas.vml.CTShape;
/**
 * Helper class for web sheet cells.
 *
 * @author Jason Jiang
 *
 */
public final class CellUtility {

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

	/**
	 * Instantiates a new cell helper.
	 */
	private CellUtility() {
		super();
	}

	/**
	 * return cell value with format.
	 *
	 * @param poiCell
	 *            cell.
	 * @param formulaEvaluator
	 *            formula evaluator.
	 * @param dataFormatter
	 *            data formatter.
	 * @return cell string value with format.
	 */
	@SuppressWarnings("deprecation")
	public static String getCellValueWithFormat(final Cell poiCell, final FormulaEvaluator formulaEvaluator,
			final DataFormatter dataFormatter) {

		if (poiCell == null) {
			return null;
		}

		String result;
		try {
			CellType cellType = poiCell.getCellTypeEnum();
			if (cellType == CellType.FORMULA) {
				cellType = formulaEvaluator.evaluate(poiCell).getCellTypeEnum();
			}
			if (cellType == CellType.ERROR) {
				result = "";
			} else {
				result = dataFormatter.formatCellValue(poiCell, formulaEvaluator);
			}
		} catch (Exception e) {
			LOG.log(Level.SEVERE,
					"Web Form WebFormHelper getCellValue Error row = " + poiCell.getRowIndex() + " column = "
							+ poiCell.getColumnIndex() + " error = " + e.getLocalizedMessage()
							+ "; Change return result to blank",
					e);
			result = "";
		}

		return result;
	}

	/**
	 * get input cell value. none input return blank
	 *
	 * @param poiCell
	 *            cell.
	 * @return String cell value.
	 */
	@SuppressWarnings("deprecation")
	public static String getCellValueWithoutFormat(final Cell poiCell) {

		if (poiCell == null) {
			return null;
		}

		if (poiCell.getCellTypeEnum() == CellType.FORMULA) {
			return getCellStringValueWithType(poiCell, poiCell.getCachedFormulaResultTypeEnum());
		} else {
			return getCellStringValueWithType(poiCell, poiCell.getCellTypeEnum());
		}
	}

	/**
	 * Get cell value as string but with giving type.
	 *
	 * @param poiCell
	 *            cell.
	 * @param cellType
	 *            cell type.
	 * @return Sting cell value.
	 */
	private static String getCellStringValueWithType(final Cell poiCell, final CellType cellType) {

		switch (cellType) {
		case BOOLEAN:
			return getCellStringValueWithBooleanType(poiCell);
		case NUMERIC:
			return getCellStringValueWithNumberType(poiCell);
		case STRING:
			return poiCell.getStringCellValue();
		default:
			return "";
		} // switch

	}

	/**
	 * Gets the cell string value with boolean type.
	 *
	 * @param poiCell
	 *            the poi cell
	 * @return the cell string value with boolean type
	 */
	private static String getCellStringValueWithBooleanType(final Cell poiCell) {
		if (poiCell.getBooleanCellValue()) {
			return "Y";
		} else {
			return "N";
		}
	}

	/**
	 * Gets the cell string value with number type.
	 *
	 * @param poiCell
	 *            the poi cell
	 * @return the cell string value with number type
	 */
	private static String getCellStringValueWithNumberType(final Cell poiCell) {
		String result;
		if (DateUtil.isCellDateFormatted(poiCell)) {
			result = poiCell.getDateCellValue().toString();
		} else {
			result = BigDecimal.valueOf(poiCell.getNumericCellValue()).toPlainString();
			// remove .0 from end for int
			if (result.endsWith(".0")) {
				result = result.substring(0, result.length() - 2);
			}
		}
		return result;
	}

	/**
	 * Set cell value with giving String value.
	 *
	 * @param c
	 *            cell.
	 * @param value
	 *            giving value.
	 * @return cell.
	 */
	@SuppressWarnings("deprecation")
	public static Cell setCellValue(final Cell c, final String value) {

		try {
			if (value.length() == 0) {
				c.setCellType(CellType.BLANK);
			} else if (WebSheetUtility.isNumeric(value)) {
				setCellValueNumber(c, value);
			} else if (WebSheetUtility.isDate(value)) {
				setCellValueDate(c, value);
			} else if (c.getCellTypeEnum() == CellType.BOOLEAN) {
				setCellValueBoolean(c, value);
			} else {
				setCellValueString(c, value);
			}
		} catch (Exception e) {
			LOG.log(Level.SEVERE, " error in setCellValue of CellUtility = " + e.getLocalizedMessage(), e);
			setCellValueString(c, value);
		}
		return c;
	}

	/**
	 * Sets the cell value string.
	 *
	 * @param c
	 *            the c
	 * @param value
	 *            the value
	 */
	private static void setCellValueString(final Cell c, final String value) {
		c.setCellType(CellType.STRING);
		c.setCellValue(value);
	}

	/**
	 * Sets the cell value boolean.
	 *
	 * @param c
	 *            the c
	 * @param value
	 *            the value
	 */
	private static void setCellValueBoolean(final Cell c, final String value) {
		if ("Y".equalsIgnoreCase(value) || "Yes".equalsIgnoreCase(value) || "True".equalsIgnoreCase(value)) {
			c.setCellValue(true);
		} else {
			c.setCellValue(false);
		}
	}

	/**
	 * Sets the cell value date.
	 *
	 * @param c
	 *            the c
	 * @param value
	 *            the value
	 */
	private static void setCellValueDate(final Cell c, final String value) {
		String date = WebSheetUtility.parseDate(value);
		setCellValueString(c, date);
	}

	/**
	 * Sets the cell value number.
	 *
	 * @param c
	 *            the c
	 * @param value
	 *            the value
	 */
	private static void setCellValueNumber(final Cell c, final String value) {
		double val = Double.parseDouble(value.replace(Character.toString(','), ""));
		c.setCellType(CellType.NUMERIC);
		c.setCellValue(val);
	}

	/**
	 * Copy rows.
	 *
	 * @param srcSheet
	 *            the src sheet
	 * @param destSheet
	 *            the dest sheet
	 * @param srcRowStart
	 *            the src row start
	 * @param srcRowEnd
	 *            the src row end
	 * @param destRow
	 *            the dest row
	 * @param checkLock
	 *            the check lock
	 * @param setHiddenColumn
	 *            the set hidden column
	 */
	public static void copyRows(final Sheet srcSheet, final Sheet destSheet, final int srcRowStart, final int srcRowEnd,
			final int destRow, final boolean checkLock, final boolean setHiddenColumn) {

		int length = srcRowEnd - srcRowStart + 1;
		if (length <= 0) {
			return;
		}
		destSheet.shiftRows(destRow, destSheet.getLastRowNum(), length, true, false);
		for (int i = 0; i < length; i++) {
			copySingleRow(srcSheet, destSheet, srcRowStart + i, destRow + i, checkLock, setHiddenColumn);
		}
		// If there are are any merged regions in the source row, copy to new
		// row
		for (int i = 0; i < srcSheet.getNumMergedRegions(); i++) {
			CellRangeAddress cellRangeAddress = srcSheet.getMergedRegion(i);
			if ((cellRangeAddress.getFirstRow() >= srcRowStart) && (cellRangeAddress.getLastRow() <= srcRowEnd)) {
				int targetRowFrom = cellRangeAddress.getFirstRow() - srcRowStart + destRow;
				int targetRowTo = cellRangeAddress.getLastRow() - srcRowStart + destRow;

				CellRangeAddress newCellRangeAddress = new CellRangeAddress(targetRowFrom, targetRowTo,
						cellRangeAddress.getFirstColumn(), cellRangeAddress.getLastColumn());
				destSheet.addMergedRegion(newCellRangeAddress);
			}
		}
	}

	/**
	 * Copy single row.
	 *
	 * @param srcSheet
	 *            the src sheet
	 * @param destSheet
	 *            the dest sheet
	 * @param sourceRowNum
	 *            the source row num
	 * @param destinationRowNum
	 *            the destination row num
	 * @param checkLock
	 *            the check lock
	 * @param setHiddenColumn
	 *            the set hidden column
	 */
	private static void copySingleRow(final Sheet srcSheet, final Sheet destSheet, final int sourceRowNum,
			final int destinationRowNum, final boolean checkLock, final boolean setHiddenColumn) {
		// Get the source / new row
		Row newRow = destSheet.getRow(destinationRowNum);
		Row sourceRow = srcSheet.getRow(sourceRowNum);

		if (newRow == null) {
			newRow = destSheet.createRow(destinationRowNum);
		}
		newRow.setHeight(sourceRow.getHeight());
		// Loop through source columns to add to new row
		for (int i = 0; i < sourceRow.getLastCellNum(); i++) {
			// Grab a copy of the old/new cell
			copyCell(destSheet, sourceRow, newRow, i, checkLock);
		}
		if (setHiddenColumn) {
			ConfigurationUtility.setOriginalRowNumInHiddenColumn(newRow, sourceRow.getRowNum());
		}
		return;

	}

	/**
	 * Copy cell.
	 *
	 * @param destSheet
	 *            the dest sheet
	 * @param sourceRow
	 *            the source row
	 * @param newRow
	 *            the new row
	 * @param cellIndex
	 *            the cell index
	 * @param checkLock
	 *            the check lock
	 * @return the int
	 */
	public static Cell copyCell(final Sheet destSheet, final Row sourceRow, final Row newRow, final int cellIndex,
			final boolean checkLock) {
		// If the old cell is null jump to next cell
		Cell sourceCell = sourceRow.getCell(cellIndex);
		if (sourceCell == null) {
			return null;
		}
		// If source cell is dest cell refresh it
		boolean refreshCell = false;
		if (sourceRow.equals(newRow)&&(sourceCell.getColumnIndex()==cellIndex)) {
			sourceRow.removeCell(sourceCell);
			refreshCell = true;
		}
		Cell newCell = newRow.createCell(cellIndex);
		try {
			if (!refreshCell && (sourceCell.getCellComment() != null)) {
				// If there is a cell comment, copy
				cloneComment(sourceCell, newCell);
			}
			copyCellSetStyle(destSheet, sourceCell, newCell);
			copyCellSetValue(sourceCell, newCell, checkLock);
		} catch (Exception ex) {
			LOG.log(Level.SEVERE, "copy cell set error = " + ex.getLocalizedMessage(), ex);
		}

		return newCell;
	}

	/**
	 * set cell value.
	 *
	 * @param sourceCell
	 *            source cell.
	 * @param newCell
	 *            new cell.
	 * @param checkLock
	 *            check lock flag.
	 */
	@SuppressWarnings("deprecation")
	private static void copyCellSetValue(final Cell sourceCell, final Cell newCell, final boolean checkLock) {

		CellStyle newCellStyle = newCell.getCellStyle();
		String name = sourceCell.getCellTypeEnum().toString();
		CellValueType e = Enum.valueOf(CellValueType.class, name);
		e.setCellValue(newCell, sourceCell, checkLock, newCellStyle);
	}

	/**
	 * The Enum CellValueType.
	 */
	public enum CellValueType {

		/** The string. */
		STRING {
			@Override
			public void setCellValue(final Cell newCell, final Cell sourceCell, final boolean checkLock,
					final CellStyle newCellStyle) {
				if ((!checkLock) || newCellStyle.getLocked()) {
					newCell.setCellValue(sourceCell.getRichStringCellValue());
				}
			}
		},

		/** The boolean. */
		BOOLEAN {
			@Override
			public void setCellValue(final Cell newCell, final Cell sourceCell, final boolean checkLock,
					final CellStyle newCellStyle) {
				if ((!checkLock) || newCellStyle.getLocked()) {
					newCell.setCellValue(sourceCell.getBooleanCellValue());
				}
			}
		},

		/** The numeric. */
		NUMERIC {
			@Override
			public void setCellValue(final Cell newCell, final Cell sourceCell, final boolean checkLock,
					final CellStyle newCellStyle) {
				if ((!checkLock) || newCellStyle.getLocked()) {
					newCell.setCellValue(sourceCell.getNumericCellValue());
				}
			}
		},

		/** The formula. */
		FORMULA {
			@Override
			public void setCellValue(final Cell newCell, final Cell sourceCell, final boolean checkLock,
					final CellStyle newCellStyle) {
				newCell.setCellFormula(sourceCell.getCellFormula());
			}
		},

		/** The error. */
		ERROR {
			@Override
			public void setCellValue(final Cell newCell, final Cell sourceCell, final boolean checkLock,
					final CellStyle newCellStyle) {
				if ((!checkLock) || newCellStyle.getLocked()) {
					newCell.setCellErrorValue(sourceCell.getErrorCellValue());
				}
			}
		},

		/** The blank. */
		BLANK {
			@Override
			public void setCellValue(final Cell newCell, final Cell sourceCell, final boolean checkLock,
					final CellStyle newCellStyle) {
				newCell.setCellType(CellType.BLANK);
			}
		};
		/**
		 * Sets the cell value.
		 *
		 * @param newCell
		 *            the new cell
		 * @param sourceCell
		 *            the source cell
		 * @param checkLock
		 *            the check lock
		 * @param newCellStyle
		 *            the new cell style
		 */
		public abstract void setCellValue(Cell newCell, Cell sourceCell, boolean checkLock, CellStyle newCellStyle);

	}

	/**
	 * set up cell style.
	 *
	 * @param destSheet
	 *            dest sheet.
	 * @param sourceCell
	 *            source cell.
	 * @param newCell
	 *            new cell.
	 */
	@SuppressWarnings("deprecation")
	private static void copyCellSetStyle(final Sheet destSheet, final Cell sourceCell, final Cell newCell) {
		CellStyle newCellStyle = getCellStyleFromSourceCell(destSheet, sourceCell);
		newCell.setCellStyle(newCellStyle);

		// If there is a cell hyperlink, copy
		if (sourceCell.getHyperlink() != null) {
			newCell.setHyperlink(sourceCell.getHyperlink());
		}

		// Set the cell data type
		newCell.setCellType(sourceCell.getCellTypeEnum());
	}

	/**
	 * clone existing comments into new cell comment.
	 *
	 * @param sourceCell
	 *            source cell.
	 * @param newCell
	 *            target cell.
	 */

	public static void cloneComment(final Cell sourceCell, final Cell newCell) {

		XSSFSheet sheet = (XSSFSheet) newCell.getSheet();
		CreationHelper factory = sheet.getWorkbook().getCreationHelper();
		Drawing drawing = sheet.createDrawingPatriarch();
		XSSFComment sourceComment = (XSSFComment) sourceCell.getCellComment();
		// Below code are from POI busy manual.
		// When the comment box is visible, have it show in a 1x3 space
		ClientAnchor anchor = createCommentAnchor(newCell, factory);
		// Create the comment and set the text+author
		Comment comment = drawing.createCellComment(anchor);
		RichTextString str = factory.createRichTextString(sourceComment.getString().toString());
		comment.setString(str);
		comment.setAuthor(sourceComment.getAuthor());
		// Assign the comment to the cell
		newCell.setCellComment(comment);
		comment.setColumn(newCell.getColumnIndex());
		comment.setRow(newCell.getRowIndex());
		// As POI doesn't has well support for comments,
		// So we have to use low level api to match the comments.
		matchCommentSettings(newCell, sourceCell);
	}

	/**
	 * Creates the comment anchor.
	 *
	 * @param newCell the new cell
	 * @param factory the factory
	 * @return the client anchor
	 */
	private static ClientAnchor createCommentAnchor(final Cell newCell, CreationHelper factory) {
		ClientAnchor anchor = factory.createClientAnchor();
		anchor.setCol1(newCell.getColumnIndex());
		anchor.setCol2(newCell.getColumnIndex() + 1);
		anchor.setRow1(newCell.getRowIndex());
		anchor.setRow2(newCell.getRowIndex() + 3);
		return anchor;
	}

	/**
	 * Creates the or insert comment.
	 *
	 * @param cell the cell
	 * @param commentStr the comment str
	 */
	public static void createOrInsertComment(final Cell cell, final String commentStr) {

		XSSFSheet sheet = (XSSFSheet) cell.getSheet();
		CreationHelper factory = sheet.getWorkbook().getCreationHelper();
		Drawing drawing = sheet.createDrawingPatriarch();
		Comment comment = cell.getCellComment();
		String originStr = "";
		if (comment == null) {
			// Below code are from POI busy manual.
			// When the comment box is visible, have it show in a 1x3 space
			ClientAnchor anchor = createCommentAnchor(cell, factory);
			// Create the comment and set the text+author
			comment = drawing.createCellComment(anchor);
		} else {
			originStr = comment.getString().getString()+"\n";
		}
		originStr +=  commentStr;
		RichTextString str = factory.createRichTextString(originStr);
		comment.setString(str);
		comment.setAuthor("");
		// Assign the comment to the cell
		cell.setCellComment(comment);
		comment.setColumn(cell.getColumnIndex());
		comment.setRow(cell.getRowIndex());
	}

	/**
	 * Use low level API to match the comments setting.
	 *
	 * @param newCell
	 *            target cell.
	 * @param sourceCell
	 *            source cell.
	 */
	private static void matchCommentSettings(final Cell newCell, final Cell sourceCell) {
		try {
			XSSFVMLDrawing sourceVml = getVmlDrawingFromCell(sourceCell);
			XSSFVMLDrawing targetVml = getVmlDrawingFromCell(newCell);
			CTShape sourceCtShape = getCtShapeFromVml(sourceCell, sourceVml);
			CTShape targetCtShape = getCtShapeFromVml(newCell, targetVml);
			targetCtShape.setType(sourceCtShape.getType());
			CTClientData sourceClientData = sourceCtShape.getClientDataArray(0);
			CTClientData targetClientData = targetCtShape.getClientDataArray(0);
			String[] anchorArray = sourceClientData.getAnchorList().get(0).split(",");
			int shiftRows = newCell.getRowIndex() - sourceCell.getRowIndex();
			/*
			 * AchorArray mappings: 0->col1 1->dx1 2->row1 3->dy1 4->col2 5->dx2 6-> row2
			 * 7->dy2
			 */
			anchorArray[2] = Integer.toString(Integer.parseInt(anchorArray[2].trim()) + shiftRows);
			anchorArray[6] = Integer.toString(Integer.parseInt(anchorArray[6].trim()) + shiftRows);
			targetClientData.getAnchorList().set(0, FacesUtility.strJoin(anchorArray, ","));
		} catch (Exception e) {
			LOG.log(Level.SEVERE, "matchCommentSettings error = " + e.getLocalizedMessage(), e);
		}
	}

	/**
	 * Find vmldrawing part according to cell.
	 *
	 * @param cell
	 *            cell.
	 * @return vmldrawing.
	 */
	private static XSSFVMLDrawing getVmlDrawingFromCell(final Cell cell) {
		XSSFSheet sourceSheet = (XSSFSheet) cell.getSheet();
		for (POIXMLDocumentPart sourcePart : sourceSheet.getRelations()) {
			if ((sourcePart != null) && (sourcePart instanceof XSSFVMLDrawing)) {
				return (XSSFVMLDrawing) sourcePart;
			}
		}
		return null;
	}

	/**
	 * Find CtShape from vml object. This class use reflection to invoke the
	 * protected method in POI.
	 *
	 * @param sourceCell            cell.
	 * @param sourceVml            vml.
	 * @return ctShape.
	 * @throws ReflectiveOperationException the reflective operation exception
	 * @throws SecurityException the security exception
	 */
	@SuppressWarnings("rawtypes")
	private static CTShape getCtShapeFromVml(final Cell sourceCell, XSSFVMLDrawing sourceVml)
			throws ReflectiveOperationException {
		Method findshape;
		// int parameter
		Class[] paramInt = new Class[2];
		paramInt[0] = Integer.TYPE;
		paramInt[1] = Integer.TYPE;
		findshape = sourceVml.getClass().getDeclaredMethod("findCommentShape", paramInt);
		findshape.setAccessible(true);
		return (CTShape) findshape.invoke(sourceVml, sourceCell.getRowIndex(), sourceCell.getColumnIndex());
	}

	/**
	 * create cell style from source cell.
	 *
	 * @param destSheet
	 *            dest sheet.
	 * @param sourceCell
	 *            source cell.
	 * @return cell style.
	 */
	private static CellStyle getCellStyleFromSourceCell(final Sheet destSheet, final Cell sourceCell) {
		Workbook wb = destSheet.getWorkbook();
		// Copy style from old cell and apply to new cell
		CellStyle newCellStyle = wb.createCellStyle();
		newCellStyle.cloneStyleFrom(sourceCell.getCellStyle());
		return newCellStyle;
	}

	/**
	 * return cell index number key. e.g. $0$0 for A1 cell.
	 *
	 * @param cell
	 *            input cell.
	 * @return string.
	 */
	public static String getCellIndexNumberKey(final Cell cell) {
		if (cell != null) {
			return TieConstants.CELL_ADDR_PRE_FIX + cell.getColumnIndex() + TieConstants.CELL_ADDR_PRE_FIX
					+ cell.getRowIndex();
		}
		return null;
	}

	/**
	 * return cell index number key. e.g. $0$0 for A1 cell.
	 *
	 * @param columnIndex
	 *            column index.
	 * @param rowIndex
	 *            row index.
	 * @return string.
	 */
	public static String getCellIndexNumberKey(final int columnIndex, final int rowIndex) {
		return TieConstants.CELL_ADDR_PRE_FIX + columnIndex + TieConstants.CELL_ADDR_PRE_FIX + rowIndex;
	}

	/**
	 * return cell index key with column letter and row index. e.g. $A$0 for A1
	 * cell.
	 *
	 * @param columnLetter
	 *            column letter.
	 * @param rowIndex
	 *            row index.
	 * @return String.
	 */
	public static String getCellIndexLetterKey(final String columnLetter, final int rowIndex) {
		return TieConstants.CELL_ADDR_PRE_FIX + columnLetter + TieConstants.CELL_ADDR_PRE_FIX + rowIndex;
	}

	/**
	 * return cell index key with column and row index. e.g. $A$0 for A1 cell.
	 *
	 * @param columnIndex
	 *            column index.
	 * @param rowIndex
	 *            row index.
	 * @return key.
	 */

	public static String getCellIndexLetterKey(final int columnIndex, final int rowIndex) {
		return TieConstants.CELL_ADDR_PRE_FIX + WebSheetUtility.getExcelColumnName(columnIndex)
				+ TieConstants.CELL_ADDR_PRE_FIX + rowIndex;
	}

	/**
	 * Convert cell.
	 *
	 * @param sheetConfig
	 *            the sheet config
	 * @param fcell
	 *            the fcell
	 * @param poiCell
	 *            the poi cell
	 * @param cellRangeMap
	 *            the cell range map
	 * @param originRowIndex
	 *            the origin row index
	 * @param cellAttributesMap
	 *            the cell attributes map
	 * @param saveAttrs
	 *            the save attrs
	 */
	// set up facesCell's attribute from poiCell and others.
	public static void convertCell(final SheetConfiguration sheetConfig, final FacesCell fcell, final Cell poiCell,
			final Map<String, CellRangeAddress> cellRangeMap, final int originRowIndex,
			final CellAttributesMap cellAttributesMap, final String saveAttrs) {
		CellRangeAddress caddress;
		String key = getCellIndexNumberKey(poiCell);
		caddress = cellRangeMap.get(key);
		if (caddress != null) {
			// has col or row span
			fcell.setColspan(caddress.getLastColumn() - caddress.getFirstColumn() + 1);
			fcell.setRowspan(caddress.getLastRow() - caddress.getFirstRow() + 1);
		}

		CellControlsUtility.setupControlAttributes(originRowIndex, fcell, poiCell, sheetConfig, cellAttributesMap);
		fcell.setHasSaveAttr(SaveAttrsUtility.isHasSaveAttr(poiCell, saveAttrs));

	}

	/**
	 * Gets the row col from component attributes.
	 *
	 * @param target
	 *            the target
	 * @return the row col from component attributes
	 */
	public static int[] getRowColFromComponentAttributes(final UIComponent target) {

		int rowIndex = (Integer) target.getAttributes().get("data-row");
		int colIndex = (Integer) target.getAttributes().get("data-column");
		int[] list = new int[2];
		list[0] = rowIndex;
		list[1] = colIndex;
		return list;
	}

	/**
	 * Gets the inits the rows from config.
	 *
	 * @param sheetConfig
	 *            the sheet config
	 * @return the inits the rows from config
	 */
	public static int getInitRowsFromConfig(final SheetConfiguration sheetConfig) {
		int initRows = 1;
		if ("Repeat".equalsIgnoreCase(sheetConfig.getFormBodyType())) {
			initRows = sheetConfig.getBodyInitialRows();
			if (initRows < 1) {
				initRows = 1;
			}
		}
		return initRows;
	}

	/**
	 * Gets the body bottom from config.
	 *
	 * @param sheetConfig
	 *            the sheet config
	 * @return the body bottom from config
	 */
	public static int getBodyBottomFromConfig(final SheetConfiguration sheetConfig) {

		return sheetConfig.getBodyCellRange().getBottomRow();

	}

	/**
	 * Gets the faces row from body row.
	 *
	 * @param row
	 *            the row
	 * @param bodyRows
	 *            the body rows
	 * @param topRow
	 *            the top row
	 * @return faces row
	 */
	public static FacesRow getFacesRowFromBodyRow(final int row, final List<FacesRow> bodyRows, final int topRow) {
		FacesRow frow = null;

		try {
			frow = bodyRows.get(row - topRow);

		} catch (Exception e) {
			LOG.log(Level.SEVERE, "getFacesRowFromBodyRow Error row = " + row + "top row = " + topRow + " ; error = "
					+ e.getLocalizedMessage(), e);
		}
		return frow;
	}

	/**
	 * Gets the faces cell from body row.
	 *
	 * @param row
	 *            the row
	 * @param col
	 *            the col
	 * @param bodyRows
	 *            the body rows
	 * @param topRow
	 *            the top row
	 * @param leftCol
	 *            the left col
	 * @return the faces cell from body row
	 */
	public static FacesCell getFacesCellFromBodyRow(final int row, final int col, final List<FacesRow> bodyRows,
			final int topRow, final int leftCol) {
		FacesCell cell = null;

		try {
			cell = bodyRows.get(row - topRow).getCells().get(col - leftCol);

		} catch (Exception e) {
			LOG.log(Level.SEVERE, "getFacesCellFromBodyRow Error row = " + row + " col = " + col + "top row = " + topRow
					+ " leftCol = " + leftCol + " ; error = " + e.getLocalizedMessage(), e);
		}
		return cell;
	}

	/**
	 * Gets the poi cell with row col from current page.
	 *
	 * @param rowIndex
	 *            the row index
	 * @param colIndex
	 *            the col index
	 * @param wb
	 *            workbook.
	 * @return the poi cell with row col from current page
	 */
	public static Cell getPoiCellWithRowColFromCurrentPage(final int rowIndex, final int colIndex, final Workbook wb) {
		if (wb != null) {
			return getPoiCellFromSheet(rowIndex, colIndex, wb.getSheetAt(wb.getActiveSheetIndex()));
		}
		return null;
	}

	/**
	 * Gets the poi cell from sheet.
	 *
	 * @param rowIndex
	 *            the row index
	 * @param colIndex
	 *            the col index
	 * @param sheet1
	 *            the sheet 1
	 * @return the poi cell from sheet
	 */
	public static Cell getPoiCellFromSheet(final int rowIndex, final int colIndex, final Sheet sheet1) {
		if ((sheet1 != null) && (sheet1.getRow(rowIndex) != null)) {
			return sheet1.getRow(rowIndex).getCell(colIndex);
		}
		return null;
	}

	/**
	 * Gets the skey from poi cell.
	 *
	 * @param poiCell the poi cell
	 * @return the skey from poi cell
	 */
	public static String getSkeyFromPoiCell(final Cell poiCell) {
		return poiCell.getSheet().getSheetName() + "!"
				+ CellUtility.getCellIndexNumberKey(poiCell.getColumnIndex(), poiCell.getRowIndex());
	}

	/**
	 * Gets the or add tie cell in map.
	 *
	 * @param poiCell the poi cell
	 * @param tieCells the tie cells
	 * @return the or add tie cell in map
	 */
	public static TieCell getOrAddTieCellInMap(final Cell poiCell, HashMap<String, TieCell> tieCells) {
		String skey = CellUtility.getSkeyFromPoiCell(poiCell);
		TieCell tieCell = tieCells.get(skey);
		if (tieCell == null) {
			tieCell = new TieCell();
			tieCell.setSkey(skey);
			tieCells.put(skey, tieCell);
		}
		return tieCell;
	}

}


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

import java.util.ArrayList;
import java.util.List;

import org.openxmlformats.schemas.drawingml.x2006.chart.CTAxDataSource;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTBarChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTBarSer;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTDPt;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTNumDataSource;
import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties;

/**
 * Bar Chart.
 *
 * @author JASON JIANG
 *
 */
public class BarChart implements ChartObject {

	/** empty list. */
	private List<CTBarSer> emptySerlist = new ArrayList<>();

	/** empty list. */
	private List<CTDPt> emptyDPlist = new ArrayList<>();
	/*
	 * (non-Javadoc)
	 *
	 * @see org.tiefaces.components.websheet.chart.objects.ChartObject#
	 * getChartListFromCtChart
	 * (org.openxmlformats.schemas.drawingml.x2006.chart.CTChart)
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public List getChartListFromCtChart(final CTChart ctChart) {
		return ctChart.getPlotArea().getBarChartList();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.tiefaces.components.websheet.chart.objects.ChartObject#
	 * getSerListFromCtObjChart(java.lang.Object)
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public List getSerListFromCtObjChart(final Object ctObjChart) {

		if (ctObjChart instanceof CTBarChart) {
			return ((CTBarChart) ctObjChart).getSerList();
		}
		return emptySerlist;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.tiefaces.components.websheet.chart.objects.ChartObject#
	 * getCtAxDataSourceFromSerList(java.util.List)
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public final CTAxDataSource getCtAxDataSourceFromSerList(
			final List serList) {

		if ((serList != null) && (!serList.isEmpty())
				&& (serList.get(0) instanceof CTBarSer)) {
			return ((CTBarSer) serList.get(0)).getCat();
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.tiefaces.components.websheet.chart.objects.ChartObject#
	 * getSeriesLabelFromCTSer(java.lang.Object)
	 */
	@Override
	public final String getSeriesLabelFromCTSer(final Object ctObjSer) {
		if (ctObjSer instanceof CTBarSer) {
			return ((CTBarSer) ctObjSer).getTx().getStrRef().getF();
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.tiefaces.components.websheet.chart.objects.ChartObject#
	 * getShapePropertiesFromCTSer(java.lang.Object)
	 */
	@Override
	public final CTShapeProperties getShapePropertiesFromCTSer(
			final Object ctObjSer) {
		if (ctObjSer instanceof CTBarSer) {
			return ((CTBarSer) ctObjSer).getSpPr();
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.tiefaces.components.websheet.chart.objects.ChartObject#
	 * getCTNumDataSourceFromCTSer(java.lang.Object)
	 */
	@Override
	public final CTNumDataSource getCTNumDataSourceFromCTSer(
			final Object ctObjSer) {
		if (ctObjSer instanceof CTBarSer) {
			return ((CTBarSer) ctObjSer).getVal();
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.tiefaces.components.websheet.chart.objects.ChartObject#
	 * getDPtListFromCTSer(java.lang.Object)
	 */
	@Override
	public final List<CTDPt> getDPtListFromCTSer(final Object ctObjSer) {
		return emptyDPlist;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.tiefaces.components.websheet.chart.objects.ChartObject#isLineColor()
	 */
	@Override
	public final boolean isLineColor() {
		return false;
	}

	/**
	 * Gets the empty serlist.
	 *
	 * @return the emptySerlist
	 */
	public final List<CTBarSer> getEmptySerlist() {
		return emptySerlist;
	}

}


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

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

/**
 * Current collections for websheet bean.
 *
 * @author Jason Jiang
 *
 */
public class TieWebSheetBeanCurrent implements Serializable {
	/**
	 * serialVersionUID.
	 */
	private static final long serialVersionUID = -4330466518786654088L;
	/** current data context name. */
	private transient String currentDataContextName;
	/** current data context name list. */
	private transient List<String> currentDataContextNameList;
	/** current tab name of display sheet. */
	private String currentTabName;
	/** current top row of display sheet. */
	private int currentTopRow;
	/** current left column of display sheet. */
	private int currentLeftColumn;

	/**
	 * Gets the current data context name list.
	 *
	 * @return the currentDataContextNameList
	 */
	public final List<String> getCurrentDataContextNameList() {
		if (currentDataContextNameList == null) {
			currentDataContextNameList = new ArrayList<>();
		}
		return currentDataContextNameList;
	}

	/**
	 * Sets the current data context name list.
	 *
	 * @param pcurrentDataContextNameList
	 *            the currentDataContextNameList to set
	 */
	public final void setCurrentDataContextNameList(
			final List<String> pcurrentDataContextNameList) {
		this.currentDataContextNameList = pcurrentDataContextNameList;
	}

	/**
	 * Gets the current data context name.
	 *
	 * @return the currentDataContextName
	 */
	public final String getCurrentDataContextName() {
		if (currentDataContextName == null) {
			StringBuilder sb = new StringBuilder();
			List<String> list = this.getCurrentDataContextNameList();
			for (int i = 0; i < list.size(); i++) {
				if (i > 0) {
					sb.append(":" + list.get(i));
				} else {
					sb.append(list.get(i));
				}
			}
			this.setCurrentDataContextName(sb.toString());
		}
		return currentDataContextName;
	}

	/**
	 * Sets the current data context name.
	 *
	 * @param pcurrentDataContextName
	 *            the currentDataContextName to set
	 */
	public final void setCurrentDataContextName(
			final String pcurrentDataContextName) {
		this.currentDataContextName = pcurrentDataContextName;
	}

	/**
	 * Gets the current tab name.
	 *
	 * @return the currentTabName
	 */
	public final String getCurrentTabName() {
		return currentTabName;
	}

	/**
	 * Sets the current tab name.
	 *
	 * @param pcurrentTabName
	 *            the currentTabName to set
	 */
	public final void setCurrentTabName(final String pcurrentTabName) {
		this.currentTabName = pcurrentTabName;
	}

	/**
	 * Gets the current top row.
	 *
	 * @return the currentTopRow
	 */
	public final int getCurrentTopRow() {
		return currentTopRow;
	}

	/**
	 * Sets the current top row.
	 *
	 * @param pcurrentTopRow
	 *            the currentTopRow to set
	 */
	public final void setCurrentTopRow(final int pcurrentTopRow) {
		this.currentTopRow = pcurrentTopRow;
	}

	/**
	 * Gets the current left column.
	 *
	 * @return the currentLeftColumn
	 */
	public final int getCurrentLeftColumn() {
		return currentLeftColumn;
	}

	/**
	 * Sets the current left column.
	 *
	 * @param pcurrentLeftColumn
	 *            the currentLeftColumn to set
	 */
	public final void setCurrentLeftColumn(final int pcurrentLeftColumn) {
		this.currentLeftColumn = pcurrentLeftColumn;
	}

}