/**
 * Copyright 2014 Hanei Management Co.,Ltd. 
 * 
 * This file is part of Jaxcel
 * 
 *  Jaxcel is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Jaxcel is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.hanei.jaxcel.util;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;


import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.hssf.record.cf.CellRangeUtil;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.FormulaParser;
import org.apache.poi.ss.formula.FormulaParsingWorkbook;
import org.apache.poi.ss.formula.FormulaRenderer;
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.ss.formula.ptg.AreaPtg;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.RefPtg;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook;
import org.apache.poi.xssf.usermodel.XSSFHyperlink;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFTable;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.hanei.jaxcel.exception.JaxcelInputException;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTDrawing;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTAutoFilter;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTablePart;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

/**
 * Excelユーティリティクラス
 * 
 * @version 1.00.00
 * @author Noboru Saito
 */
public class ExcelUtil {

	private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
	
	/** 範囲判定 */
	public static final String INSIDE = "inside";
	public static final String OVERLAP = "overlap";
	public static final String ENCLOSES = "encloses";
	public static final String NO_INTERSECTION = "no_intersection";	

	/**
	 * シートの最大行数を返却
	 * 
	 * @param sheet Worksheetオブジェクト
	 * 
	 * @return シートの最大行数
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static int getMaxRowIndex(Sheet sheet) {
		log.trace("getMaxRowIndex start");
		
		if(sheet == null) {
			log.error("sheet is null");
			throw new JaxcelInputException("sheet is null");
		}
		int max;
		if(sheet instanceof HSSFSheet) {
			max = SpreadsheetVersion.EXCEL97.getLastRowIndex();
		}
		else if(sheet instanceof XSSFSheet || sheet instanceof SXSSFSheet){
			max = SpreadsheetVersion.EXCEL2007.getLastRowIndex();
		}
		else {
			log.error("sheet is unsupported type");
			throw new JaxcelInputException("sheet is unsupported type");
		}
		
		log.trace("getMaxRowIndex end: {}", max);
		return max;
	}

	/**
	 * シートの最大列数を返却
	 * 
	 * @param sheet Worksheetオブジェクト
	 * 
	 * @return シートの最大行数
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static int getMaxColumnIndex(Sheet sheet) {
		log.trace("getMaxColumnIndex start");

		if(sheet == null) {
			log.error("sheet is null");
			throw new JaxcelInputException("sheet is null");
		}
		int max;
		if(sheet instanceof HSSFSheet) {
			max = SpreadsheetVersion.EXCEL97.getLastColumnIndex();
		}
		else if(sheet instanceof XSSFSheet || sheet instanceof SXSSFSheet){
			max = SpreadsheetVersion.EXCEL2007.getLastColumnIndex();
		}
		else {
			log.error("sheet is unsupported type");
			throw new JaxcelInputException("sheet is null");
		}
		
		log.trace("getMaxColumnIndex end: {}", max);
		return max;
	}

	/**
	 * 範囲の妥当性チェック
	 * 
	 * @param sheet Worksheetオブジェクト
	 * @param range 対象範囲
	 * 
	 * @return 判定結果
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static boolean validateRange(Sheet sheet, CellRangeAddress range) {
		log.trace("validateRange start");
		
		boolean ret = validateColumn(sheet, range) && validateRow(sheet, range);
		
		log.trace("validateRange end: {}", ret);
		return ret;
	}

	/**
	 * 範囲の列方向妥当性チェック
	 * 
	 * @param sheet Worksheetオブジェクト
	 * @param range 対象範囲
	 * 
	 * @return 判定結果
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static boolean validateColumn(Sheet sheet, CellRangeAddress range) {
		log.trace("validateColumn start");
		
		if(range == null) {
			log.error("range is null");
			throw new JaxcelInputException("range is null");
		}
		boolean ret = false;
		int max = getMaxColumnIndex(sheet);
		int firstColumn = range.getFirstColumn();
		int lastColumn = range.getLastColumn();

		if((isFullRowRange(sheet, range)) || (firstColumn <= lastColumn && firstColumn >= 0 && firstColumn <= max && lastColumn >= 0 && lastColumn <= max)){
			ret = true;
		}

		log.trace("validateColumn end: {}", ret);
		return ret;
	}

	/**
	 * 範囲の行方向妥当性チェック
	 * 
	 * @param sheet Worksheetオブジェクト
	 * @param range 対象範囲
	 * 
	 * @return 判定結果
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static boolean validateRow(Sheet sheet, CellRangeAddress range) {
		log.trace("validateRow start");
		
		if(range == null) {
			log.error("range is null");
			throw new JaxcelInputException("range is null");
		}
		boolean ret = false;
		int max = getMaxRowIndex(sheet);
		int firstRow = range.getFirstRow();
		int lastRow = range.getLastRow();

		if((isFullColumnRange(sheet, range)) || (firstRow <= lastRow && firstRow >= 0 && firstRow <= max && lastRow >= 0 && lastRow <= max)){
			ret = true;
		}

		log.trace("validateRow end: {}", ret);
		return ret;
	}
	
	/**
	 * 行全体を指定している範囲であるかの判定
	 * 
	 * @param sheet Worksheetオブジェクト
	 * @param range 対象範囲
	 * 
	 * @return 判定結果
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static boolean isFullRowRange(Sheet sheet, CellRangeAddress range) {
		log.trace("isFullRowRange start");

		if(range == null) {
			log.error("range is null");
			throw new JaxcelInputException("range is null");
		}
		boolean ret = false;
		int max = getMaxColumnIndex(sheet);
		int firstColumn = range.getFirstColumn();
		int lastColumn = range.getLastColumn();
		
		if(log.isDebugEnabled()) {
			log.debug("sheet max column: {}", max);
			log.debug("range first column: {}", firstColumn);
			log.debug("range last column: {}", lastColumn);
		}

		if((firstColumn == 0 && lastColumn == max) || (firstColumn == -1 && lastColumn == -1)) {
			ret = true;
		}

		log.trace("isFullRowRange end: {}", ret);
		return ret;
	}
	
	/**
	 * 列全体を指定している範囲であるかの判定
	 * 
	 * @param sheet Worksheetオブジェクト
	 * @param range 対象範囲
	 * 
	 * @return 判定結果
	 */
	public static boolean isFullColumnRange(Sheet sheet, CellRangeAddress range) {
		log.trace("isFullColumnRange start");

		if(range == null) {
			log.error("range is null");
			throw new JaxcelInputException("range is null");
		}
		boolean ret = false;
		int max = getMaxRowIndex(sheet);
		int firstRow = range.getFirstRow();
		int lastRow = range.getLastRow();
		
		if(log.isDebugEnabled()) {
			log.debug("sheet max row: {}", max);
			log.debug("range first row: {}", firstRow);
			log.debug("range last row: {}", lastRow);
		}

		if((firstRow == 0 && lastRow == max) || (firstRow == -1 && lastRow == -1)) {
			ret = true;
		}

		log.trace("isFullColumnRange end: {}", ret);
		return ret;
	}

	/**
	 * シートの最大列数を返却
	 * 
	 * @param sheet Worksheetオブジェクト
	 * 
	 * @return シートの最大列数。<br>
	 * sheetに行データが存在しない場合、または、すべての行にセルが存在しない場合は -1
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static int getLastColNum(Sheet sheet) {
		log.trace("getLastColNum start");

		if(sheet == null) {
			log.error("sheet is null");
			throw new JaxcelInputException("sheet is null");
		}
		int maxColNum = getLastColNum(sheet, sheet.getFirstRowNum(), sheet.getLastRowNum());

		log.trace("getLastColNum end: {}", maxColNum);
		return maxColNum;
	}
	
	/**
	 * シート指定行範囲の最大列数を返却
	 * 
	 * @param sheet Worksheetオブジェクト
	 * @param firstRow 範囲始点行番号（０起点）
	 * @param lastRow 範囲終点行番号（０起点）
	 * 
	 * @return シート指定列範囲の最大列数。<br>
	 * sheetの指定範囲に行データが存在しない場合は -1
	 * sheetの指定範囲に行データが存在しない場合、または、指定範囲のすべての行にセルが存在しない場合は -1
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static int getLastColNum(Sheet sheet, int firstRow, int lastRow) {
		log.trace("getLastColNum start");

		if(sheet == null) {
			log.error("sheet is null");
			throw new JaxcelInputException("sheet is null");
		}
		
		int fRow = firstRow;
		int lRow = lastRow;
		
		if(fRow == -1 && lRow == -1) {
			log.info("firstRow and lastRow is -1. set full row range");
			fRow = 0;
			lRow = sheet.getLastRowNum();
		}
		else if(fRow < 0) {
			log.error("firstRow is lt 0: {}", fRow);
			throw new JaxcelInputException("firstRow is lt 0");
		}
		else if(lRow < 0) {
			log.error("lastRow is lt 0: {}", lRow);
			throw new JaxcelInputException("lastRow is lt 0");
		}
		if(fRow > lRow) {
			log.warn("firstRow gt lastRow: {}, {}. swap", fRow, lRow);
			int tmp = fRow;
			fRow = lRow;
			lRow = tmp;
		}
		
		Row row;
		int maxColNum = -1;
		for(int i = fRow; i <= lRow; i++) {
			row = sheet.getRow(i);
			if(row == null) continue;
			maxColNum = (row.getLastCellNum() > maxColNum ? row.getLastCellNum() : maxColNum);
		}

		log.trace("getLastColNum end: {}", maxColNum);
		return maxColNum;
	}
	
	/**
	 * 対象範囲をブロック指定でシフトする
	 * 
	 * @param sheet		Worksheetオブジェクト
	 * @param range		シフト対象範囲
	 * @param direction	シフト方向。row：行方向(デフォルト)　col：列方向
	 * @param distance	シフト距離。正数：右・下　負数：左・上
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static void shift(Sheet sheet, CellRangeAddress range, String direction, int distance) {
		shift(sheet, range, direction, distance, true);
	}

	/**
	 * 対象範囲をシフトする
	 * 
	 * @param sheet		Worksheetオブジェクト
	 * @param range		シフト対象範囲
	 * @param direction	シフト方向。row：行方向(デフォルト)　col：列方向
	 * @param distance	シフト距離。正数：右・下　負数：左・上
	 * @param block		ブロック指定。true：ブロック指定　false：列・行単位指定
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static void shift(Sheet sheet, CellRangeAddress range, String direction, int distance, boolean block) {
		log.trace("shift start");

		final String ROW = "row";
		final String COL = "col";

		String _direction;	// シフト方向
		CellRangeAddress _range;
		
		// チェック
		if(sheet == null) {
			log.error("sheet is null");
			throw new JaxcelInputException("sheet is null");
		}
		if(range == null) {
			log.error("range is null");
			throw new JaxcelInputException("range is null");
		}
		_range = range;
		if(!validateRange(sheet, range)) {
			_range = getIntersectRange(sheet, range);
			if(_range == null) {
				log.warn("range is illegal: [{}]", range.formatAsString());
				log.trace("shift end");
				return;
			}
			else {
				log.info("resize range: [{}]", _range.formatAsString());
			}
		}
		if(distance == 0) {
			log.debug("distance is 0");
			log.trace("shift end");
			return;
		}
		if(direction == null) {
			log.debug("direction is null. set default: {}", ROW);
			_direction = ROW;
		}
		else if(!COL.equalsIgnoreCase(direction) && !ROW.equalsIgnoreCase(direction)) {
			log.debug("direction is illegal argument. set default: {}", ROW);
			_direction = ROW;
		}
		else {
			_direction = direction.toLowerCase();
		}
		
		if(log.isDebugEnabled()) {
			log.debug("sheet: {}", sheet.getSheetName());
			log.debug("range: [{}]", _range.formatAsString());
			log.debug("distance: {}", distance);
			log.debug("direction: {}", _direction);
			log.debug("block: {}", block);
		}
		
		// シフト範囲の特定　引数の範囲以降を移動対象に含める
		CellRangeAddress fromRange;
		int rowDistance, colDistance;
		int firstIdx, lastIdx;
		
		// 列方向の場合
		if(COL.equals(_direction)) {
			// 先頭列
			firstIdx = _range.getFirstColumn();
			// 最終列
			// block範囲でなければ範囲拡大
			lastIdx = block ? getLastColNum(sheet, _range.getFirstRow(), _range.getLastRow()) : getLastColNum(sheet);
			lastIdx = lastIdx > firstIdx ? lastIdx : firstIdx;

			// 移動元範囲決定
			// block範囲指定でなければ行範囲拡大
			if(block) {
				fromRange = new CellRangeAddress(
						_range.getFirstRow(), _range.getLastRow(), 
						firstIdx, lastIdx);
			} 
			else {
				fromRange = new CellRangeAddress(
						sheet.getFirstRowNum(), sheet.getLastRowNum(), 
						firstIdx, lastIdx);
			}
			
			// 移動量
			rowDistance = 0;
			colDistance = distance;
		}
		// 行方向の場合
		else {
			// 先頭行
			firstIdx = _range.getFirstRow();
			// 最終行
			lastIdx = sheet.getLastRowNum() > firstIdx ? sheet.getLastRowNum() : firstIdx;
			
			// 移動元範囲決定
			// block範囲でなければ列範囲拡大
			if(block) {
				fromRange = new CellRangeAddress(
						firstIdx, lastIdx, 
						_range.getFirstColumn(), _range.getLastColumn());
			}
			else {
				fromRange = new CellRangeAddress(
						firstIdx, lastIdx, 
						0, getLastColNum(sheet));
			}
			
			// 移動量
			rowDistance = distance;
			colDistance = 0;
		}
		
		// 範囲移動
		moveRange(sheet, fromRange, rowDistance, colDistance, block);

		log.trace("shift end");
	}
	
	/**
	 * 数式に使用されている相対参照を指定距離で移動した結果を返却<br>
	 * 配列数式はPOIの制約上使用できません
	 * 
	 * @param sheet		Worksheetオブジェクト
	 * @param formula	数式
	 * @param rowDistance	行方向移動距離。正数：下　負数：上
	 * @param colDistance	列方向移動距離。正数：右　負数：左
	 * 
	 * @return	相対参照を指定距離で移動した結果の数式<br>
	 * 移動した結果、数式内の参照がシート範囲外を指定したとしてもワークシートとしては異常とならない為、移動距離の妥当性チェックは行わない<br>
	 * 配列数式等、POIの制約上解析不可能な数式の場合は引数のまま返却する
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static String getMoveFormula(Sheet sheet, String formula, int rowDistance, int colDistance) {
		log.trace("getMoveFormula start");

		// チェック
		if(sheet == null) {
			log.warn("sheet is null");
			return formula;
		}
		if(formula == null) {
			log.warn("formula is null");
			return formula;
		}
		if(rowDistance == 0 && colDistance == 0) {
			log.debug("rowDistance and colDistance is 0");
			log.trace("getMoveFormula end");
			return formula;
		}

		if(log.isDebugEnabled()) {
			log.debug("sheet: {}", sheet.getSheetName());
			log.debug("formula: {}", formula);
			log.debug("rowDistance: {}", rowDistance);
			log.debug("colDistance: {}", colDistance);
		}
		
		// 数式パースの準備
		FormulaParsingWorkbook fpBook;
		// xls形式
		if(sheet instanceof HSSFSheet) {
			fpBook = HSSFEvaluationWorkbook.create((HSSFWorkbook) sheet.getWorkbook());
		}
		// 2007以降 ooxml形式
		else if(sheet instanceof XSSFSheet) {
			fpBook = XSSFEvaluationWorkbook.create((XSSFWorkbook) sheet.getWorkbook());
		}
		else {
			log.warn("sheet is unsupported type");
			log.trace("getMoveFormula end");
			return formula;
		}

		// 数式のパース
		String returnFormula;
		boolean parseFlg = false;
		
		try {
			Ptg[]   ptg;
			AreaPtg aPtg;
			RefPtg  rPtg;

			// 数式のパートに分解
			ptg = FormulaParser.parse(formula, fpBook, FormulaType.CELL, sheet.getWorkbook().getSheetIndex(sheet));		// TODO: FormulaType.CELL とは？
			for (int i = 0; i < ptg.length; i++){
				log.trace("from ptg: {}", ptg[i]);
				// パートがエリア（range）の場合
				if(ptg[i] instanceof AreaPtg) {
					aPtg = (AreaPtg) ptg[i];
					// 各行・列が相対参照であれば移動量を加算し書き換え
					if(aPtg.isFirstRowRelative()) {
						aPtg.setFirstRow(aPtg.getFirstRow() + rowDistance);
						parseFlg = true;
					}
					if(aPtg.isFirstColRelative()) {
						aPtg.setFirstColumn(aPtg.getFirstColumn() + colDistance);
						parseFlg = true;
					}
					if(aPtg.isLastRowRelative()) {
						aPtg.setLastRow(aPtg.getLastRow() + rowDistance);
						parseFlg = true;
					}
					if(aPtg.isLastColRelative()) {
						aPtg.setLastColumn(aPtg.getLastColumn() + colDistance);
						parseFlg = true;
					}
				}
				// パートがセルの場合
				else if(ptg[i] instanceof RefPtg) {
					rPtg = (RefPtg) ptg[i];
					// 行・列が相対参照であれば移動量を加算し書き換え
					if(rPtg.isRowRelative()) {
						rPtg.setRow(rPtg.getRow() + rowDistance);
						parseFlg = true;
					}
					if(rPtg.isColRelative()) {
						rPtg.setColumn(rPtg.getColumn() + colDistance);
						parseFlg = true;
					}
				}
				log.trace("to ptg: {}", ptg[i]);
			}
			// 相対参照が存在し移動させた場合は数式の再構築
			returnFormula = parseFlg ? FormulaRenderer.toFormulaString((FormulaRenderingWorkbook) fpBook, ptg) : formula;
		}
		catch(Exception e) {
			log.error("formula parse error: {}", e.getMessage(), e);
			returnFormula = formula;
		}

		log.trace("getMoveFormula end: {}", returnFormula);
		return returnFormula;
	}
	
	/**
	 * 対象範囲をクリアする
	 * 
	 * @param sheet		Worksheetオブジェクト
	 * @param range		移動対象範囲
	 * @param clearStyle	セルスタイルのクリア指定。trueでスタイルをクリアする
	 * @param clearMerge	セル結合のクリア指定。trueで結合をクリアする
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static void clearRange(Sheet sheet, CellRangeAddress range, boolean clearStyle, boolean clearMerge) {
		log.trace("clearRange start");
		
		CellRangeAddress tmpRange;
		CellRangeAddress _range;
		
		// チェック
		if(sheet == null) {
			log.error("sheet is null");
			throw new JaxcelInputException("sheet is null");
		}
		if(range == null) {
			log.error("range is null");
			throw new JaxcelInputException("range is null");
		}
		_range = range;
		if(!validateRange(sheet, range)) {
			_range = getIntersectRange(sheet, range);
			if(_range == null) {
				log.error("range is illegal: [{}]", range.formatAsString());
				throw new JaxcelInputException("range is illegal");
			}
			else {
				log.info("resize range: [{}]", _range.formatAsString());
			}
		}
		if(log.isDebugEnabled()) {
			log.debug("sheet: {}", sheet.getSheetName());
			log.debug("range: [{}]", _range.formatAsString());
			log.debug("clearStyle: {}", clearStyle);
			log.debug("clearMerge: {}", clearMerge);
		}

		// 結合の解除
		if(clearMerge && sheet.getNumMergedRegions() > 0) {
			for(int i = 0; i < sheet.getNumMergedRegions(); i++) {
				// 結合範囲取得
				tmpRange = sheet.getMergedRegion(i);
				log.trace("mergedRegion renge: [{}]", tmpRange.formatAsString());

				// 範囲内・範囲に掛かっている結合の解除
				if(CellRangeUtil.NO_INTERSECTION != CellRangeUtil.intersect(_range, tmpRange)) {
					log.debug("mergedRegion {}. clear: [{}]", getIntersectString(_range, tmpRange), tmpRange.formatAsString());
					sheet.removeMergedRegion(i);
					i = -1;
				}
			}
		}
			
		// オブジェクトの削除・クリア Excel2007以降 ooxml形式のみ対応
		if(sheet instanceof XSSFSheet) {
			XSSFSheet xSheet = (XSSFSheet) sheet;
			String ref;

			// オートフィルタ
			CTAutoFilter af = xSheet.getCTWorksheet().getAutoFilter();
			if(af != null) {
				ref = af.getRef();
				log.trace("auto filter renge: [{}]", ref);
				
				// CellRangeAddressに変換し位置情報設定
				tmpRange = CellRangeAddress.valueOf(ref);
				// 範囲内に含まれている or 掛かっている オートフィルタクリア 
				switch(CellRangeUtil.intersect(_range, tmpRange)) {
				case CellRangeUtil.INSIDE:
				case CellRangeUtil.OVERLAP:
					xSheet.getCTWorksheet().unsetAutoFilter();
					log.debug("auto filter {}. clear: [{}]", getIntersectString(_range, tmpRange), ref);
				}
			}
			
			// DocumentPartでループ
			CTDrawing drawing;
			CTTable table;
			CTTwoCellAnchor anchor;
			List<CTTablePart> tableList;

			for(POIXMLDocumentPart part : xSheet.getRelations()) {
				if(part == null) continue;
				log.debug("documentPart class: {}", part.getClass().getName());

				// DocumentPartがテーブルの場合
				if(part instanceof XSSFTable) {
					// テーブルオブジェクト取得
					table = ((XSSFTable) part).getCTTable();
					// テーブル範囲取得
					ref = table.getRef();
					log.trace("table range: [{}]", ref);
					tmpRange = CellRangeAddress.valueOf(ref);

					// 範囲内に含まれている or 掛かっている テーブルクリア
					switch(CellRangeUtil.intersect(_range, tmpRange)) {
					case CellRangeUtil.INSIDE:
					case CellRangeUtil.OVERLAP:
						// シート上に存在するテーブルと対象テーブルのIDが一致するものを検索しクリアする
						tableList = xSheet.getCTWorksheet().getTableParts().getTablePartList();
						for(int i = 0; i < tableList.size(); i++) {
							if(tableList.get(i).getId() != null && tableList.get(i).getId().equalsIgnoreCase(part.getPackageRelationship().getId())) {
								xSheet.getCTWorksheet().getTableParts().removeTablePart(i);
								log.debug("table {}. clear: [{}]", getIntersectString(_range, tmpRange), ref);
								break;
							}
						}
					}
				}
				// DocumentPartがDrawingオブジェクトの場合
				else if(part instanceof XSSFDrawing) {
					// Drawingオブジェクト取得
					drawing = ((XSSFDrawing) part).getCTDrawing();
					// 画像やシェイプ等のアンカーでループ
					for(int i = 0; i < drawing.getTwoCellAnchorList().size(); i++) {
						// 位置情報取得
						anchor = drawing.getTwoCellAnchorList().get(i);
						tmpRange = new CellRangeAddress(anchor.getFrom().getRow(), anchor.getTo().getRow(), anchor.getFrom().getCol(), anchor.getTo().getCol());
						log.debug("object range: [{}]", tmpRange.formatAsString());

						// 範囲内に掛かっていない以外 オブジェクトの削除
						if(CellRangeUtil.NO_INTERSECTION != CellRangeUtil.intersect(_range, tmpRange)) {
							drawing.removeTwoCellAnchor(i);
							log.debug("object {}. delete: [{}]", getIntersectString(_range, tmpRange), tmpRange.formatAsString());
							i = -1;
						}
					}
				}
			}
		}
		// セルのクリア
		Row row;
		Cell cell;
		// 行でループ
		for(int i = range.getFirstRow() == -1 ? 0 : range.getFirstRow(), ii = range.getLastRow() == - 1 ? getMaxRowIndex(sheet) : range.getLastRow(); i <= ii; i++) {
			row = sheet.getRow(i);
			if(row == null) continue;
			// 列でループ
			for(int c = range.getFirstColumn() == -1 ? 0 : range.getFirstColumn(), cc = range.getLastColumn() == -1 ? getMaxColumnIndex(sheet) : range.getLastColumn(); c <= cc; c++) {
				cell = row.getCell(c);
				if(cell == null) continue;
				// ハイパーリンクのクリア
				clearHyperlink(sheet, cell);
				// コメントクリア
				cell.removeCellComment();
				// スタイルクリアならセルの削除
				if(clearStyle) {
					row.removeCell(cell);
				}
				// スタイルクリアでないならブランクセル
				else {
					cell.setCellType(Cell.CELL_TYPE_BLANK);
				}
				log.debug("cell clear: [{}]", (new CellReference(cell).formatAsString()));
			}
		}
		log.trace("clearRange end");
	}
	
	/**
	 * 指定された範囲の交差状態を定数文字列で返却する<br>
	 * ブック、シートは意識しません。
	 * 
	 * @param rangeA	対象範囲Ａ
	 * @param rangeB	対象範囲Ｂ
	 * 
	 * @return	ExcelUtil.INSIDE：対象範囲Ａに対象範囲Ｂが含まれている<br>
	 * ExcelUtil.OVERLAP：対象範囲Ａに対象範囲Ｂの一部が交差している<br>
	 * ExcelUtil.ENCLOSES：対象範囲Ｂに対象範囲Ａが含まれている<br>
	 * ExcelUtil.NO_INTERSECTION：対象範囲Ａに対象範囲Ｂは一部の含まれていない
	 */
	public static String getIntersectString(CellRangeAddress rangeA, CellRangeAddress rangeB) {
		// 範囲内に含まれている or 掛かっている テーブルクリア 
		switch(CellRangeUtil.intersect(rangeA, rangeB)) {
		case CellRangeUtil.NO_INTERSECTION:
			return NO_INTERSECTION;
		case CellRangeUtil.INSIDE:
			return INSIDE;
		case CellRangeUtil.ENCLOSES:
			return ENCLOSES;
		default:
			return OVERLAP;
		}
	}
	
	/**
	 * 指定された範囲がシートに収まらない場合、収まるサイズにリサイズした範囲を返却する
	 * 
	 * @param sheet		Worksheetオブジェクト
	 * @param range		対象範囲
	 * 
	 * @return	シートに収まる範囲<br>
	 * シートに収まる範囲が存在しない場合はnull
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static CellRangeAddress getIntersectRange(Sheet sheet, CellRangeAddress range) {
		log.trace("getIntersectRange start");

		// チェック
		if(sheet == null) {
			log.error("sheet is null");
			throw new JaxcelInputException("sheet is null");
		}
		CellRangeAddress tmpRange = getIntersectRange(sheet, (new CellRangeAddress(0, getMaxRowIndex(sheet), 0, getMaxColumnIndex(sheet))), range);

		log.trace("getIntersectRange end : [{}]", (tmpRange == null ? null : tmpRange.formatAsString()));
		return tmpRange;
	}
	
	/**
	 * 指定された範囲の交差する範囲を返却する<br>
	 * 
	 * @param sheet		Worksheetオブジェクト
	 * @param rangeA	対象範囲Ａ
	 * @param rangeB	対象範囲Ｂ
	 * 
	 * @return	交差する範囲<br>
	 * 交差する範囲が存在しない場合はnull
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static CellRangeAddress getIntersectRange(Sheet sheet, CellRangeAddress rangeA, CellRangeAddress rangeB) {
		log.trace("getIntersectRange start");
		
		// チェック
		if(sheet == null) {
			log.error("sheet is null");
			throw new JaxcelInputException("sheet is null");
		}
		if(rangeA == null) {
			log.error("rangeA is null");
			throw new JaxcelInputException("rangeA is null");
		}
		if(rangeB == null) {
			log.error("rangeB is null");
			throw new JaxcelInputException("rangeA is null");
		}
//		if(!validateRange(sheet, rangeA)) {
//			log.error("rangeA is illegal: [{}]", rangeA.formatAsString());
//			throw new JaxcelInputException("rangeA is illegal");
//		}
//		if(!validateRange(sheet, rangeB)) {
//			log.error("rangeB is illegal: [{}]", rangeB.formatAsString());
//			throw new JaxcelInputException("rangeB is illegal");
//		}
		if(log.isDebugEnabled()) {
			log.debug("sheet: {}", sheet.getSheetName());
			log.debug("rangeA: [{}]", rangeA.formatAsString());
			log.debug("rangeB: [{}]", rangeB.formatAsString());
		}

		// 交差範囲生成
		CellRangeAddress tmpRange = new CellRangeAddress(
				(rangeA.getFirstRow() >= rangeB.getFirstRow() ? rangeA.getFirstRow() : rangeB.getFirstRow()),
				(rangeA.getLastRow() <= rangeB.getLastRow() ? rangeA.getLastRow() : rangeB.getLastRow()),
				(rangeA.getFirstColumn() >= rangeB.getFirstColumn() ? rangeA.getFirstColumn() : rangeB.getFirstColumn()),
				(rangeA.getLastColumn() <= rangeB.getLastColumn() ? rangeA.getLastColumn() : rangeB.getLastColumn()));
		
		
		// 交差範囲チェック
		if(!validateRange(sheet, tmpRange)) {
			log.debug("rangeA rangeB is not intersect");
			log.trace("getIntersectRange end");
			return null;
		}
		
		log.trace("getIntersectRange end : {}", tmpRange.formatAsString());
		return tmpRange;
	}
	

	/**
	 * 対象範囲を移動する
	 * 
	 * @param sheet		Worksheetオブジェクト
	 * @param range		移動対象範囲
	 * @param rowDistance	行方向移動距離。正数：下　負数：上
	 * @param colDistance	列方向移動距離。正数：右　負数：左
	 * @param block		ブロック指定。true：ブロック指定　false：列・行単位指定
	 * 
	 * @throws JaxcelInputException 入力例外発生時
	 */
	public static void moveRange(Sheet sheet, CellRangeAddress range, int rowDistance, int colDistance, boolean block) {
		log.trace("moveRange start");
		
		// チェック
		if(sheet == null) {
			log.error("sheet is null");
			throw new JaxcelInputException("sheet is null");
		}
		if(range == null) {
			log.error("range is null");
			throw new JaxcelInputException("range is null");
		}
		if(!validateRange(sheet, range)) {
			log.error("range is illegal: [{}]", range.formatAsString());
			throw new JaxcelInputException("range is illegal");
		}
		if(rowDistance == 0 && colDistance == 0) {
			log.debug("distance is 0");
			log.trace("moveRange end");
			return;
		}
		// 斜め移動（rowDistance、colDistanceのいずれも０でない）の場合はblock指定はtrueとする
		else if(!block && rowDistance != 0 && colDistance != 0) {
			log.info("change block mode");
			block = true;
		}

		if(log.isDebugEnabled()) {
			log.debug("sheet: {}", sheet.getSheetName());
			log.debug("range: {}", range.formatAsString());
			log.debug("rowDistance: {}", rowDistance);
			log.debug("colDistance: {}", colDistance);
			log.debug("block: {}", block);
		}
		
		// 移動先範囲シートに収まる範囲を取得
		CellRangeAddress toRange = getIntersectRange(sheet, new CellRangeAddress(
				(range.getFirstRow() == -1 ? 0 : range.getFirstRow()) + rowDistance, 
				(range.getLastRow() == -1 ? sheet.getLastRowNum() : range.getLastRow()) + rowDistance,
				(range.getFirstColumn() == - 1 ? 0 : range.getFirstColumn()) + colDistance,
				(range.getLastColumn() == -1 ? getLastColNum(sheet) : range.getLastColumn()) + colDistance));
		// シートに収まる範囲がなければ
		if(toRange == null) {
			// 移動元範囲のクリア
			log.debug("toRange outside sheet. fromRange clear");
			clearRange(sheet, range, true, true);
			log.trace("moveRange end");
			return;
		}
		log.debug("toRange: {}", toRange.formatAsString());

		// 移動元範囲内の接合の保持・削除
		ArrayList<CellRangeAddress> mergedRegionList = new ArrayList<>();
		CellRangeAddress tmpRange;
		if(sheet.getNumMergedRegions() > 0) {
			log.trace("fromRange mergedRegion check");
			
			for(int i = 0; i < sheet.getNumMergedRegions(); i++) {
				tmpRange = sheet.getMergedRegion(i);
				log.trace("mergedRegion renge: [{}]", tmpRange.formatAsString());

				// 移動元範囲内に含まれている 接合の保持
				// 移動元範囲に掛かっている or 移動元範囲を囲っている 接合の削除
				switch(CellRangeUtil.intersect(range, tmpRange)) {
				case CellRangeUtil.INSIDE:
					log.debug("mergedRegion {}. save: [{}]", getIntersectString(range, tmpRange), tmpRange.formatAsString());
					mergedRegionList.add(tmpRange);
					sheet.removeMergedRegion(i);
					i = -1;
					break;
				case CellRangeUtil.ENCLOSES:
				case CellRangeUtil.OVERLAP:
					log.debug("mergedRegion {}. remove: [{}]", getIntersectString(range, tmpRange), tmpRange.formatAsString());
					sheet.removeMergedRegion(i);
					i = -1;
					break;
				}
			}
		}
		
		// 移動先範囲内の接合の削除
		if(sheet.getNumMergedRegions() > 0) {
			log.trace("toRange mergedRegion check");
	
			for(int i = 0; i < sheet.getNumMergedRegions(); i++) {
				tmpRange = sheet.getMergedRegion(i);
				log.trace("mergedRegion renge: [{}]", tmpRange.formatAsString());

				// 移動先範囲内に掛かっていない以外 接合の削除
				if(CellRangeUtil.NO_INTERSECTION != CellRangeUtil.intersect(toRange, tmpRange)) {
					log.debug("mergedRegion {}. remove: [{}]", getIntersectString(toRange, tmpRange), tmpRange.formatAsString());
					sheet.removeMergedRegion(i);
					i = -1;
				}
			}
		}
			
		// 保持した結合があれば移動先で追加
		if(!mergedRegionList.isEmpty()) {
			for(CellRangeAddress mergedRegion : mergedRegionList) {
				// シートに収まる範囲を取得
				tmpRange = getIntersectRange(sheet, new CellRangeAddress(
						mergedRegion.getFirstRow() + rowDistance, 
						mergedRegion.getLastRow() + rowDistance, 
						mergedRegion.getFirstColumn() + colDistance, 
						mergedRegion.getLastColumn() + colDistance));
				// シートに収まれば
				if(tmpRange != null) {
					// 移動先で追加
					sheet.addMergedRegion(tmpRange);
					log.debug("mergedRegion move. from: [{}] to: [{}]", mergedRegion.formatAsString(), tmpRange.formatAsString());
				}
				else {
					log.debug("mergedRegion move to outside sheet. clear: [{}]", mergedRegion.formatAsString());
				}
			}
		}
		
/*		
		// シェイプ等のオブジェクトの削除・移動
		// xls形式
		if(sheet instanceof HSSFSheet) {
			try {
				HSSFClientAnchor anchor;
				HSSFPatriarch patriarch = ((HSSFSheet)sheet).getDrawingPatriarch();
				if(patriarch != null) {
					List<HSSFShape> shapes = patriarch.getChildren();
					if(shapes != null) {
						int r1, c1, r2, c2;
						for (int i = 0; i < shapes.size(); i++) {
							log.debug("shape class: {}", shapes.get(i).getClass().getName());
							// オブジェクトの位置情報取得
							anchor = (HSSFClientAnchor)shapes.get(i).getAnchor();
							if(anchor == null) continue;
							r1 = anchor.getRow1();
							c1 = anchor.getCol1();
							r2 = anchor.getRow2();
							c2 = anchor.getCol2();
							tmpRange = new CellRangeAddress(r1, r2, c1, c2);
							
							// 移動先範囲に含まれている、掛かっているなら移動
							
							// 移動元レンジに含まれている、掛かっているなら移動
							switch(CellRangeUtil.intersect(range, tmpRange)) {
							case CellRangeUtil.INSIDE:
							case CellRangeUtil.OVERLAP:
								anchor.setRow1(r1 + rowDistance);
								anchor.setRow2(r2 + rowDistance);
								anchor.setCol1(c1 + colDistance);
								anchor.setCol2(c2 + colDistance);
								log.debug("shape move from: [{}] to: [{}]", tmpRange.formatAsString(), (new CellRangeAddress(anchor.getRow1(), anchor.getRow2(), anchor.getCol1(), anchor.getCol2())).formatAsString());
							}
						}
					}
				}
			}
			catch(Exception e) {
				log.error("shape move error: {}", e.getMessage(), e);
			}
		}
*/
		// シェイプ等のオブジェクトの削除・移動 Excel2007以降 ooxml形式のみ対応
		if(sheet instanceof XSSFSheet) {
			XSSFSheet xSheet = (XSSFSheet) sheet;
			int r1, c1, r2, c2;
			String ref;
			CTDrawing ctDrawing;
			CTTable ctTable;
			CTTwoCellAnchor fAnchor;
			CTMarker from, to;
			// DocumentPartでループ
			try {
				// オートフィルタ
				CTAutoFilter af = xSheet.getCTWorksheet().getAutoFilter();
				if(af != null) {
					// 位置情報取得
					ref = af.getRef();
					log.trace("auto filter range: [{}]", ref);
					tmpRange = CellRangeAddress.valueOf(ref);
					
					// オートフィルタの移動・クリア 
					switch(CellRangeUtil.intersect(range, tmpRange)) {
					case CellRangeUtil.OVERLAP:
						// 移動元レンジに掛かっている オートフィルタクリア 
						xSheet.getCTWorksheet().unsetAutoFilter();
						log.debug("auto filter clear: [{}]", ref);
						break;

					case CellRangeUtil.INSIDE:
						// 移動元レンジに含まれている
						tmpRange.setFirstRow(tmpRange.getFirstRow() + rowDistance);
						tmpRange.setLastRow(tmpRange.getLastRow() + rowDistance);
						tmpRange.setFirstColumn(tmpRange.getFirstColumn() + colDistance);
						tmpRange.setLastColumn(tmpRange.getLastColumn() + colDistance);
						// 移動先範囲妥当性チェック（シートに収まっているか）
						if(validateRange(xSheet, tmpRange)) {
							// オートフィルタ移動
							xSheet.setAutoFilter(tmpRange);
							log.debug("auto filter move from: [{}] to: [{}]", ref, tmpRange.formatAsString());
						}
						else {
							// オートフィルタクリア
							xSheet.getCTWorksheet().unsetAutoFilter();
							log.debug("auto filter move to outside sheet. clear: [{}]", ref);
						}
						break;
					
					case CellRangeUtil.NO_INTERSECTION:
						// 移動元レンジに含まれていない and (移動先レンジに含まれている or 掛かっている) オートフィルタクリア 
						switch(CellRangeUtil.intersect(toRange, tmpRange)) {
						case CellRangeUtil.INSIDE:
						case CellRangeUtil.OVERLAP:
							xSheet.getCTWorksheet().unsetAutoFilter();
							log.debug("auto filter clear: [{}]", ref);
						}
					}
				}

				// DocumentPartでループ
				for(POIXMLDocumentPart part : xSheet.getRelations()) {
					if(part == null) continue;
					log.debug("DocumentPart class: {}", part.getClass().getName());

					// DocumentPartがテーブルの場合
					if(part instanceof XSSFTable) {
						// テーブルオブジェクト取得
						ctTable = ((XSSFTable) part).getCTTable();

						// 位置情報取得
						ref = ctTable.getRef();
						log.debug("table ref: [{}]", ref);
						tmpRange = CellRangeAddress.valueOf(ref);
						
						switch(CellRangeUtil.intersect(range, tmpRange)) {
						case CellRangeUtil.OVERLAP:
							// 移動元レンジに掛かっている テーブルクリア
							clearTable(xSheet, part.getPackageRelationship().getId());
							log.debug("table clear: [{}]", ref);
							break;

						case CellRangeUtil.INSIDE:
							// 移動元レンジに含まれている 
							tmpRange.setFirstRow(tmpRange.getFirstRow() + rowDistance);
							tmpRange.setLastRow(tmpRange.getLastRow() + rowDistance);
							tmpRange.setFirstColumn(tmpRange.getFirstColumn() + colDistance);
							tmpRange.setLastColumn(tmpRange.getLastColumn() + colDistance);
							// 移動先範囲妥当性チェック（シートに収まっているか）
							if(validateRange(xSheet, tmpRange)) {
								// テーブル移動
								ctTable.setRef(tmpRange.formatAsString());
								log.debug("table move from: [{}] to: [{}]", ref, tmpRange.formatAsString());
							}
							else {
								// テーブルクリア 
								clearTable(xSheet, part.getPackageRelationship().getId());
								log.debug("table clear: [{}]", ref);
							}
							break;

						case CellRangeUtil.NO_INTERSECTION:
							// 移動元レンジに含まれていない and
							// (移動先レンジに含まれている or 掛かっている) テーブルクリア 
							switch(CellRangeUtil.intersect(toRange, tmpRange)) {
							case CellRangeUtil.INSIDE:
							case CellRangeUtil.OVERLAP:
								clearTable(xSheet, part.getPackageRelationship().getId());
								log.debug("table clear: [{}]", ref);
							}
						}
					}
					// DocumentPartがDrawingオブジェクトの場合
					else if(part instanceof XSSFDrawing) {
						ctDrawing = ((XSSFDrawing) part).getCTDrawing();
						if(ctDrawing != null) {
							// 画像やシェイプ等のアンカーでループ
							for (int i = 0; i < ctDrawing.getTwoCellAnchorList().size(); i++) {
								// 位置情報取得
								fAnchor = ctDrawing.getTwoCellAnchorList().get(i);
								from = fAnchor.getFrom();
								r1 = from.getRow();
								c1 = from.getCol();
								to = fAnchor.getTo();
								r2 = to.getRow();
								c2 = to.getCol();
								tmpRange = new CellRangeAddress(r1, r2, c1, c2);

								// (移動先レンジに含まれている or 掛かっている) and 
								// (移動元レンジに含まれていない and 掛かっていない) 削除
								switch(CellRangeUtil.intersect(toRange, tmpRange)) {
								case CellRangeUtil.INSIDE:
								case CellRangeUtil.OVERLAP:
									if(CellRangeUtil.intersect(range, tmpRange) == CellRangeUtil.NO_INTERSECTION) {
										ctDrawing.removeTwoCellAnchor(i);
										log.debug("object delete : [{}]", tmpRange.formatAsString());
										i--;
										continue;
									}
								}
								
								// 移動元レンジに含まれている、掛かっているなら移動
								switch(CellRangeUtil.intersect(range, tmpRange)) {
								case CellRangeUtil.INSIDE:
								case CellRangeUtil.OVERLAP:
									from.setRow(r1 + rowDistance);
									to.setRow(r2 + rowDistance);
									from.setCol(c1 + colDistance);
									to.setCol(c2 + colDistance);

									// 移動先範囲妥当性チェック
									if(!validateRange(xSheet, (new CellRangeAddress(from.getRow(), to.getRow(), from.getCol(), to.getCol())))) {
										// シートに収まっていない 削除
										ctDrawing.removeTwoCellAnchor(i);
										log.debug("object delete : [{}]", tmpRange.formatAsString());
										i--;
										continue;
									}

									log.debug("object move from: [{}] to: [{}]", tmpRange.formatAsString(), (new CellRangeAddress(from.getRow(), to.getRow(), from.getCol(), to.getCol())).formatAsString());
									break;
								default:
									log.debug("object not move: [{}]", tmpRange.formatAsString());
								}
							}
						}
					}
				}
			}
			catch(Exception e) {
				log.error("object move error: {}", e.getMessage(), e);
			}
		}
		
		// セル移動
		Row fRow, tRow;
		Cell fCell, tCell;
		// 移動元範囲行でループ　上へ移動なら正順、下へ移動なら逆順
		for(
			int r = (rowDistance < 0 ? range.getFirstRow() : range.getLastRow());
			(rowDistance < 0 && r <= range.getLastRow()) ||
			(rowDistance >= 0 && r >= range.getFirstRow());
			r = (rowDistance < 0 ? r + 1 : r - 1)) 
		{
			// 移動元行
			fRow = sheet.getRow(r) == null ? sheet.createRow(r) : sheet.getRow(r);

			// 移動先行
			if(r + rowDistance < 0 || r + rowDistance > getMaxRowIndex(sheet)) {
				// シート範囲外ならnull
				tRow = null;
			}
			else {
				// シート範囲内
				tRow = sheet.getRow(r + rowDistance) == null ? sheet.createRow(r + rowDistance) : sheet.getRow(r + rowDistance);
				// 縦（行）移動でブロック範囲でないなら行情報（高さ）もコピー
				if(!block && rowDistance != 0) {
					tRow.setHeight(fRow.getHeight());
				}
			}

			// 移動元範囲列でループ　左へ移動なら正順、右へ移動なら逆順
			for(
				int c = (colDistance < 0 ? range.getFirstColumn() : range.getLastColumn());
				(colDistance < 0 && c <= range.getLastColumn()) ||
				(colDistance >= 0 && c >= range.getFirstColumn());
				c = (colDistance < 0 ? c + 1 : c - 1))
			{
				// 移動元セル
				fCell = fRow.getCell(c);
				
				// 移動先セル
				if(tRow == null || c + colDistance < 0 || c + colDistance > getMaxColumnIndex(sheet)) {
					// シート範囲外ならnull
					tCell = null;
				}
				else {
					tCell = tRow.getCell(c + colDistance) == null ? tRow.createCell(c + colDistance) : tRow.getCell(c + colDistance);
					// 横（列）移動でブロック範囲でないなら列情報（幅）もコピー(１行目でのみ行う)
					if(!block && colDistance != 0 && c == (colDistance < 0 ? range.getFirstColumn() : range.getLastColumn())) {
						sheet.setColumnWidth(tCell.getColumnIndex(), sheet.getColumnWidth(c));
					}
				}

				// 移動
				// 移動元セルがnull、移動先がシート範囲外ならcontinue
				if(fCell == null && tCell == null) {
					continue;
				}
				// 移動元セルがnull、移動先がシート範囲内 
				if(fCell == null && tCell != null) {
					log.debug("clear cell: [{}]", (new CellReference(tCell)).formatAsString());
					// 移動先セルのハイパーリンク クリア
					clearHyperlink(sheet, tCell);
					// 移動先セル クリア
					tRow.removeCell(tCell);
				}
				// 移動元セルがnullでない、移動先がシート範囲外 
				else if(fCell != null && tCell == null) {
					log.debug("cell move to outside sheet. clear cell: [{}]", (new CellReference(fCell)).formatAsString());
					// コメントクリア
					fCell.removeCellComment();
					// 移動元セルのハイパーリンク クリア
					clearHyperlink(sheet, fCell);
					// 移動元セル クリア
					fRow.removeCell(fCell);
				}
				// 移動元セルがnullでない、移動先がシート範囲内 セル移動
				else {
					log.debug("move cell. from: [{}] to: [{}]", (new CellReference(fCell)).formatAsString(), (new CellReference(tCell)).formatAsString());

					// 移動先セルクリア
					tCell.setCellType(Cell.CELL_TYPE_BLANK);
					// スタイルのコピー
					tCell.setCellStyle(fCell.getCellStyle());
					// コメントのコピー
					tCell.removeCellComment();
					if(fCell.getCellComment() != null) {
						tCell.setCellComment(fCell.getCellComment());
					}
					// 値
					switch(fCell.getCellType()) {
					case Cell.CELL_TYPE_BOOLEAN:
						tCell.setCellType(Cell.CELL_TYPE_BOOLEAN);
						tCell.setCellValue(fCell.getBooleanCellValue());
						break;
					case Cell.CELL_TYPE_ERROR:
						tCell.setCellType(Cell.CELL_TYPE_ERROR);
						tCell.setCellErrorValue(fCell.getErrorCellValue());
						break;
					case Cell.CELL_TYPE_FORMULA:
						tCell.setCellType(Cell.CELL_TYPE_FORMULA);
						// 数式のパース・移動先に合わせる
						tCell.setCellFormula(getMoveFormula(sheet, fCell.getCellFormula(), rowDistance, colDistance));
						break;
					case Cell.CELL_TYPE_NUMERIC:
						tCell.setCellType(Cell.CELL_TYPE_NUMERIC);
						tCell.setCellValue(fCell.getNumericCellValue());
						break;
					case Cell.CELL_TYPE_STRING:
						tCell.setCellType(Cell.CELL_TYPE_STRING);
						tCell.setCellValue(fCell.getRichStringCellValue());
						break;
					}
					// ハイパーリンク 移動
					moveHyperlink(sheet, fCell, tCell);
					// 移動元セル コメントクリア
					fCell.removeCellComment();
					// 移動元セル クリア
					fRow.removeCell(fCell);
				}
			}
		}
		log.trace("moveRange end");
	}
	
	/**
	 * テーブルのクリア<br>
	 * Excel2007以降 ooxml形式のみ対応
	 * 
	 * @param sheet Worksheetオブジェクト
	 * @param tableId テーブルID
	 */
	private static void clearTable(Sheet sheet, String tableId) {
		if(sheet instanceof XSSFSheet) {
			XSSFSheet xSheet = (XSSFSheet) sheet;
			List<CTTablePart> tableList = xSheet.getCTWorksheet().getTableParts().getTablePartList();
			for(int i = 0; i < tableList.size(); i++) {
				if(tableList.get(i).getId() != null && tableList.get(i).getId().equalsIgnoreCase(tableId)) {
					xSheet.getCTWorksheet().getTableParts().removeTablePart(i);
					break;
				}
			}
		}
	}

	/**
	 * ハイパーリンクの移動<br>
	 * Excel2007以降 ooxml形式のみ対応
	 * 
	 * @param sheet Worksheetオブジェクト
	 * @param fromCell 移動元セル
	 * @param toCell 移動先セル
	 */
	private static void moveHyperlink(Sheet sheet, Cell fromCell, Cell toCell) {
		if(fromCell.getHyperlink() != null && sheet instanceof XSSFSheet) {
			// ハイパーリンクの移動
			toCell.setHyperlink(fromCell.getHyperlink());
			log.debug("hyperlink move. from: [{}] to: [{}]", (new CellReference(fromCell)).formatAsString(), (new CellReference(toCell)).formatAsString());

			// 移動元セルのハイパーリンクのクリア
			clearHyperlink(sheet, fromCell);
		}
	}

	/**
	 * ハイパーリンクのクリア<br>
	 * Excel2007以降 ooxml形式のみ対応
	 * 
	 * @param sheet Worksheetオブジェクト
	 * @param cell クリア対象セル
	 */
	private static void clearHyperlink(Sheet sheet, Cell cell) {
		// ハイパーリンクのクリア Excel2007以降 ooxml形式のみ対応
		if(sheet instanceof XSSFSheet && cell.getHyperlink() != null) {
			try {
				Field field = XSSFSheet.class.getDeclaredField("hyperlinks");
				field.setAccessible(true);
				@SuppressWarnings("unchecked")
				List<XSSFHyperlink> hyperlinks = (List<XSSFHyperlink>)field.get(sheet);
				hyperlinks.remove(cell.getHyperlink());
				log.debug("hyperlink clear: [{}]", (new CellReference(cell)).formatAsString());
			} catch (Exception e) {
				log.error("hyperlink clear error: {}", e.getMessage(), e);
			}
		}
	}
}
