PageRenderTime 63ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/plugins/adufour/workbooks/IcySpreadSheet.java

https://bitbucket.org/adufour/icy-workbooks
Java | 697 lines | 403 code | 82 blank | 212 comment | 112 complexity | 215eef82694740f002e989e48c51ae19 MD5 | raw file
  1. package plugins.adufour.workbooks;
  2. import java.awt.Color;
  3. import java.lang.reflect.Array;
  4. import java.util.ArrayList;
  5. import java.util.Arrays;
  6. import org.apache.poi.hssf.usermodel.HSSFPalette;
  7. import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  8. import org.apache.poi.hssf.util.HSSFColor;
  9. import org.apache.poi.ss.formula.FormulaParseException;
  10. import org.apache.poi.ss.usermodel.Cell;
  11. import org.apache.poi.ss.usermodel.CellStyle;
  12. import org.apache.poi.ss.usermodel.FormulaEvaluator;
  13. import org.apache.poi.ss.usermodel.Row;
  14. import org.apache.poi.ss.usermodel.Sheet;
  15. import org.apache.poi.ss.usermodel.Workbook;
  16. import org.apache.poi.xssf.usermodel.XSSFCellStyle;
  17. import org.apache.poi.xssf.usermodel.XSSFColor;
  18. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  19. /**
  20. * Results table for Icy. This class relies on the Apache POI library, and acts as a high-level
  21. * wrapper for the {@link Sheet} class
  22. *
  23. * @author Alexandre Dufour
  24. */
  25. public class IcySpreadSheet
  26. {
  27. private final Sheet sheet;
  28. private final FormulaEvaluator evaluator;
  29. public IcySpreadSheet(Sheet sheet)
  30. {
  31. this.sheet = sheet;
  32. this.evaluator = sheet.getWorkbook().getCreationHelper().createFormulaEvaluator();
  33. }
  34. /**
  35. * Copies a cell into another (only works for valid source and target cells)
  36. *
  37. * @param sourceCell
  38. * cannot be null
  39. * @param targetCell
  40. * cannot be null
  41. */
  42. private static void copyCell(Cell sourceCell, Cell targetCell) throws NullPointerException
  43. {
  44. targetCell.setCellComment(sourceCell.getCellComment());
  45. switch (sourceCell.getCellType())
  46. {
  47. case Cell.CELL_TYPE_BOOLEAN:
  48. targetCell.setCellValue(sourceCell.getBooleanCellValue());
  49. break;
  50. case Cell.CELL_TYPE_ERROR:
  51. targetCell.setCellErrorValue(sourceCell.getErrorCellValue());
  52. break;
  53. case Cell.CELL_TYPE_FORMULA:
  54. targetCell.setCellFormula(sourceCell.getCellFormula());
  55. break;
  56. case Cell.CELL_TYPE_NUMERIC:
  57. targetCell.setCellValue(sourceCell.getNumericCellValue());
  58. break;
  59. case Cell.CELL_TYPE_STRING:
  60. targetCell.setCellValue(sourceCell.getStringCellValue());
  61. break;
  62. default:
  63. System.err.println("Unknown cell type: " + sourceCell.getCellType());
  64. }
  65. }
  66. /**
  67. * @param source
  68. * the index of the row to copy (NB: the first row is at index 0)
  69. * @param target
  70. * the index of the row where the source row will be pasted (NB: the first row is at
  71. * index 0)
  72. */
  73. public void copyColumn(int sourceColumn, int targetColumn)
  74. {
  75. Row source = sheet.getRow(sourceColumn);
  76. Row target = sheet.getRow(targetColumn);
  77. if (source == null)
  78. {
  79. if (target != null) sheet.removeRow(target);
  80. return;
  81. }
  82. if (target == null) sheet.createRow(targetColumn);
  83. int nCells = source.getLastCellNum();
  84. for (int i = 0; i < nCells; i++)
  85. {
  86. Cell sourceCell = source.getCell(i, Row.RETURN_BLANK_AS_NULL);
  87. if (sourceCell != null)
  88. {
  89. Cell targetCell = target.getCell(i, Row.CREATE_NULL_AS_BLANK);
  90. copyCell(sourceCell, targetCell);
  91. }
  92. }
  93. }
  94. /**
  95. * @param source
  96. * the index of the row to copy (NB: the first row is at index 0)
  97. * @param target
  98. * the index of the row where the source row will be pasted (NB: the first row is at
  99. * index 0)
  100. */
  101. public void copyRow(int sourceRow, int targetRow)
  102. {
  103. Row source = sheet.getRow(sourceRow);
  104. Row target = getOrCreateRow(targetRow);
  105. if (source == null)
  106. {
  107. if (target != null) sheet.removeRow(target);
  108. return;
  109. }
  110. if (target == null) sheet.createRow(targetRow);
  111. int nCells = source.getLastCellNum();
  112. for (int i = 0; i < nCells; i++)
  113. {
  114. Cell sourceCell = source.getCell(i, Row.RETURN_BLANK_AS_NULL);
  115. if (sourceCell != null)
  116. {
  117. Cell targetCell = target.getCell(i, Row.CREATE_NULL_AS_BLANK);
  118. copyCell(sourceCell, targetCell);
  119. }
  120. }
  121. }
  122. /**
  123. * Deletes the cell at the specified row and column (NB: the first row or column index is 0).
  124. *
  125. * @param rowIndex
  126. * the row index of the cell to delete (starting at 0)
  127. * @param columnIndex
  128. * the column index of the cell to delete (starting at 0)
  129. */
  130. public void deleteCell(int rowIndex, int columnIndex)
  131. {
  132. Row row = sheet.getRow(rowIndex);
  133. if (row == null) return;
  134. Cell cell = row.getCell(columnIndex);
  135. if (cell != null)
  136. {
  137. row.removeCell(cell);
  138. evaluator.notifyDeleteCell(cell);
  139. }
  140. }
  141. /**
  142. * Deletes the column at the specified index (NB: the first column is at index 0).
  143. *
  144. * @param columnIndex
  145. * the index of the column to remove (starting at 0)
  146. * @param shiftData
  147. * set to <code>true</code> to shift all other columns up, or <code>false</code> to
  148. * leave the column empty
  149. */
  150. public void deleteColumn(int columnIndex, boolean shiftData)
  151. {
  152. int lastRow = sheet.getLastRowNum();
  153. for (int r = 0; r <= lastRow; r++)
  154. {
  155. Row row = sheet.getRow(r);
  156. if (row == null) continue;
  157. int lastColumn = row.getLastCellNum() - 1;
  158. if (columnIndex > lastColumn) continue;
  159. Cell cell = row.getCell(columnIndex);
  160. if (cell != null) row.removeCell(cell);
  161. if (shiftData && columnIndex < lastColumn)
  162. {
  163. for (int i = columnIndex; i < lastColumn; i++)
  164. copyCell(row.getCell(i + 1), row.getCell(i, Row.CREATE_NULL_AS_BLANK));
  165. row.removeCell(row.getCell(lastColumn));
  166. }
  167. }
  168. }
  169. /**
  170. * Deletes the row at the specified index (NB: the first row is at index 0).
  171. *
  172. * @param rowIndex
  173. * the index of the row to remove (starting at 0)
  174. * @param shiftData
  175. * set to <code>true</code> to shift all other lines up, or <code>false</code> to
  176. * leave the line empty
  177. */
  178. public void deleteRow(int rowIndex, boolean shiftData)
  179. {
  180. int lastRow = sheet.getLastRowNum();
  181. // don't do anything if the column does not exist
  182. if (rowIndex >= lastRow) return;
  183. Row rowToRemove = sheet.getRow(rowIndex);
  184. if (rowToRemove != null) sheet.removeRow(rowToRemove);
  185. if (shiftData && rowIndex < lastRow)
  186. {
  187. for (int i = rowIndex + 1; i <= lastRow; i++)
  188. copyRow(i + 1, i);
  189. sheet.removeRow(sheet.getRow(lastRow));
  190. }
  191. }
  192. /**
  193. * @param row
  194. * @param column
  195. * @return the background color of the specified cell
  196. */
  197. public Color getFillColor(int row, int column)
  198. {
  199. Cell cell = getOrCreateRow(row).getCell(column, Row.CREATE_NULL_AS_BLANK);
  200. org.apache.poi.ss.usermodel.Color color = cell.getCellStyle().getFillForegroundColorColor();
  201. if (color instanceof HSSFColor)
  202. {
  203. short[] rgb = ((HSSFColor) color).getTriplet();
  204. if (rgb[0] != 0 || rgb[1] != 0 || rgb[2] != 0)
  205. {
  206. return new Color(rgb[0], rgb[1], rgb[2]);
  207. }
  208. }
  209. else if (color instanceof XSSFColor)
  210. {
  211. byte[] rgb = ((XSSFColor) color).getRGB();
  212. if (rgb[0] != 0 || rgb[1] != 0 || rgb[2] != 0)
  213. {
  214. return new Color(rgb[0] & 0xff, rgb[1] & 0xff, rgb[2] & 0xff);
  215. }
  216. }
  217. return null;
  218. }
  219. /**
  220. * @param rowIndex
  221. * the 0-based row index of the cell
  222. * @param columnIndex
  223. * the 0-based column index of the cell
  224. * @return the cell formula at the specified location (or an empty string if the cell does not
  225. * hold a formula)
  226. */
  227. public String getFormula(int rowIndex, int columnIndex)
  228. {
  229. Row row = sheet.getRow(rowIndex);
  230. if (row == null) return "";
  231. Cell cell = row.getCell(columnIndex, Row.RETURN_BLANK_AS_NULL);
  232. if (cell == null) return "";
  233. return cell.getCellType() == Cell.CELL_TYPE_FORMULA ? cell.getCellFormula() : "";
  234. }
  235. /**
  236. * @param rowIndex
  237. * the 0-based row index of the cell
  238. * @param columnIndex
  239. * the 0-based column index of the cell
  240. * @return the cell at the specified location (or an empty string if the cell is empty)
  241. */
  242. public Object getValue(int rowIndex, int columnIndex)
  243. {
  244. Row row = sheet.getRow(rowIndex);
  245. if (row == null) return "";
  246. Cell cell = row.getCell(columnIndex, Row.RETURN_BLANK_AS_NULL);
  247. if (cell == null) return "";
  248. if (cell.getCellType() == Cell.CELL_TYPE_STRING) return cell.getStringCellValue();
  249. if (cell.getCellType() == Cell.CELL_TYPE_BOOLEAN) return cell.getBooleanCellValue();
  250. return evaluator.evaluate(cell).getNumberValue();
  251. }
  252. /**
  253. * @param rowIndex
  254. * the 0-based row index
  255. * @return
  256. */
  257. private Row getOrCreateRow(int rowIndex)
  258. {
  259. Row row = sheet.getRow(rowIndex);
  260. return row != null ? row : sheet.createRow(rowIndex);
  261. }
  262. /**
  263. * @return the index of this sheet within its workbook (NB: the first index is 0)
  264. */
  265. public int getIndex()
  266. {
  267. return sheet.getWorkbook().getSheetIndex(sheet);
  268. }
  269. /**
  270. * @return the name of this sheet
  271. */
  272. public String getName()
  273. {
  274. return sheet.getSheetName();
  275. }
  276. /**
  277. * @return The number of rows in this table. Note that this number indicates the index of the
  278. * last row, but does not necessarily indicate that all other rows contain data
  279. */
  280. public int getNumberOfRows()
  281. {
  282. return sheet.getLastRowNum() + 1;
  283. }
  284. /**
  285. * @return The number of columns in this table. Note that this number indicates the index of the
  286. * last known column, but does not necessarily indicate that all other columns contain
  287. * data, or that all rows use that many columns
  288. */
  289. public int getNumberOfColumns()
  290. {
  291. int nCols = 0;
  292. int lastRow = sheet.getLastRowNum();
  293. for (int i = 0; i <= lastRow; i++)
  294. {
  295. Row row = sheet.getRow(i);
  296. if (row != null) nCols = Math.max(nCols, row.getLastCellNum());
  297. }
  298. return nCols;
  299. }
  300. /**
  301. * @return the underlying {@link Sheet} implementation backing this object.
  302. */
  303. public Sheet getSheet()
  304. {
  305. return sheet;
  306. }
  307. /**
  308. * Sets the fill color of the specified cell
  309. *
  310. * @param row
  311. * @param column
  312. * @param color
  313. * the color to assign
  314. */
  315. public void setFillColor(int row, int column, Color color)
  316. {
  317. Cell cell = getOrCreateRow(row).getCell(column, Row.CREATE_NULL_AS_BLANK);
  318. short colorIndex;
  319. Workbook book = sheet.getWorkbook();
  320. if (book instanceof HSSFWorkbook)
  321. {
  322. HSSFPalette palette = ((HSSFWorkbook) book).getCustomPalette();
  323. ((HSSFWorkbook) book).getNumCellStyles();
  324. colorIndex = palette.findSimilarColor(color.getRed(), color.getGreen(), color.getBlue()).getIndex();
  325. // look for an existing style
  326. boolean styleExists = false;
  327. try
  328. {
  329. int numStyles = book.getNumCellStyles();
  330. for (int i = 0; i < numStyles; i++)
  331. {
  332. CellStyle cellStyle = book.getCellStyleAt(i);
  333. if (cellStyle.getFillForegroundColor() == colorIndex)
  334. {
  335. cell.setCellStyle(cellStyle);
  336. styleExists = true;
  337. break;
  338. }
  339. }
  340. }
  341. catch (ClassCastException ccE)
  342. {
  343. styleExists = false;
  344. }
  345. catch (IllegalStateException isE)
  346. {
  347. styleExists = false;
  348. }
  349. if (!styleExists)
  350. {
  351. CellStyle newStyle = book.createCellStyle();
  352. newStyle.setFillForegroundColor(colorIndex);
  353. cell.setCellStyle(newStyle);
  354. }
  355. }
  356. else if (book instanceof XSSFWorkbook)
  357. {
  358. XSSFColor newColor = new XSSFColor(color);
  359. // look for an existing style
  360. boolean styleExists = false;
  361. try
  362. {
  363. int numStyles = book.getNumCellStyles();
  364. for (int i = 0; i < numStyles; i++)
  365. {
  366. XSSFCellStyle cellStyle = (XSSFCellStyle) book.getCellStyleAt(i);
  367. if (cellStyle.getFillForegroundXSSFColor() == newColor)
  368. {
  369. cell.setCellStyle(cellStyle);
  370. styleExists = true;
  371. break;
  372. }
  373. }
  374. }
  375. catch (IllegalStateException e)
  376. {
  377. styleExists = false;
  378. }
  379. if (!styleExists)
  380. {
  381. XSSFCellStyle newStyle = (XSSFCellStyle) book.createCellStyle();
  382. newStyle.setFillForegroundColor(newColor);
  383. cell.setCellStyle(newStyle);
  384. }
  385. }
  386. cell.getCellStyle().setFillPattern(CellStyle.SOLID_FOREGROUND);
  387. }
  388. /**
  389. * Sets the formula for the specified cell. The formula follows the standard Excel-compatible
  390. * syntax with alphanumeric references to other cells (e.g. "=A1*A2"). <br/>
  391. * NOTE: this method is not responsible for checking the formula for potential syntax errors.
  392. *
  393. * @param row
  394. * @param column
  395. * @param formula
  396. * the formula
  397. * @throws FormulaParseException
  398. * if the formula has errors
  399. */
  400. public void setFormula(int row, int column, String formula) throws FormulaParseException
  401. {
  402. Cell cell = getOrCreateRow(row).getCell(column, Row.CREATE_NULL_AS_BLANK);
  403. if (formula.startsWith("=")) formula = formula.substring(1);
  404. cell.setCellFormula(formula);
  405. evaluator.notifySetFormula(cell);
  406. }
  407. /**
  408. * Sets the cell value at the specified location. Values of the following types are supported:
  409. * <ul>
  410. * <li>{@link String} (plain text)</li>
  411. * <li>{@link Boolean} (<code>true</code> or <code>false</code>)</li>
  412. * <li>Any compatible {@link Number} (will be converted to double precision)</li>
  413. * </ul>
  414. * Other unsupported objects will be replaced by their String representation (via
  415. * {@link Object#toString()})<br/>
  416. * NOTE: to assign a formula to this cell, use {@link #setFormula(int, int, String)}.
  417. *
  418. * @param row
  419. * the index of the row containing the cell (NB: the first row is at index 0)
  420. * @param column
  421. * the index of the column containing the cell (NB: the first column is at index 0)
  422. * @param value
  423. * the value to assign to the cell. NB: the type of cell will be automatically
  424. * guessed from the provided value, and if no standard format is found (number,
  425. * boolean, text), its String representation will be used
  426. */
  427. public void setValue(int row, int column, Object value)
  428. {
  429. if (value == null)
  430. {
  431. deleteCell(row, column);
  432. return;
  433. }
  434. Cell cell = getOrCreateRow(row).getCell(column, Row.CREATE_NULL_AS_BLANK);
  435. // what is the value like?
  436. if (value instanceof String)
  437. {
  438. String text = (String) value;
  439. try
  440. {
  441. value = Double.parseDouble(text);
  442. }
  443. catch (NumberFormatException numberE)
  444. {
  445. // Parse boolean values manually (because Boolean.parseBoolean() never fails)
  446. if (text.equalsIgnoreCase("true"))
  447. {
  448. value = true;
  449. }
  450. else if (text.equalsIgnoreCase("false"))
  451. {
  452. value = false;
  453. }
  454. }
  455. }
  456. // a number?
  457. if (Number.class.isAssignableFrom(value.getClass()))
  458. {
  459. cell.setCellValue(((Number) value).doubleValue());
  460. }
  461. else if (value instanceof Boolean)
  462. {
  463. cell.setCellValue((Boolean) value);
  464. }
  465. // if not, default to a String representation
  466. else
  467. {
  468. cell.setCellValue(value.toString());
  469. }
  470. evaluator.notifyUpdateCell(cell);
  471. }
  472. /**
  473. * Sets an entire column of values from a list of values
  474. *
  475. * @param columnIndex
  476. * the index of the target column (Note: the first column is at index 0)
  477. * @param columnValues
  478. * an array of values to add, line by line. NB: numbers and string will be recognized
  479. * as such, any other item will be converted to string using
  480. * {@link Object#toString()}, while <code>null</code> values will result in an empty
  481. * cell
  482. */
  483. public void setColumn(int columnIndex, Object... columnValues)
  484. {
  485. setColumn(columnIndex, null, columnValues);
  486. }
  487. /**
  488. * Sets an entire column of values from a list of values
  489. *
  490. * @param columnHeader
  491. * a header text placed in an extra row before the data (or <code>null</code> if not
  492. * needed)
  493. * @param columnIndex
  494. * the index of the target column (Note: the first column is at index 0)
  495. * @param columnValues
  496. * an array of values to add, line by line. NB: numbers and string will be recognized
  497. * as such, any other item will be converted to string using
  498. * {@link Object#toString()}, while <code>null</code> values will result in an empty
  499. * cell
  500. */
  501. public void setColumn(int columnIndex, String columnHeader, Object... columnValues)
  502. {
  503. int rowIndex = 0;
  504. if (columnHeader != null)
  505. {
  506. Row currentRow = getOrCreateRow(rowIndex);
  507. currentRow.getCell(columnIndex, Row.CREATE_NULL_AS_BLANK).setCellValue(columnHeader);
  508. rowIndex = 1;
  509. }
  510. for (Object obj : columnValues)
  511. {
  512. if (obj == null)
  513. {
  514. rowIndex++;
  515. continue;
  516. }
  517. setValue(rowIndex++, columnIndex, obj);
  518. }
  519. }
  520. /**
  521. * Convenience method that sets an entire row of values at the specified location in a given
  522. * workbook
  523. *
  524. * @param rowIndex
  525. * the index of the row to fill (Note: the first column is at index 0)
  526. * @param values
  527. * the values to add, separated by commas, or a single array containing the values to
  528. * add.<br/>
  529. * NB: numbers and string will be recognized as such, any other item will be
  530. * converted to string using {@link Object#toString()}, while <code>null</code>
  531. * values will result in an empty cell
  532. */
  533. @SuppressWarnings({ "rawtypes", "unchecked" })
  534. public void setRow(int rowIndex, Object... values)
  535. {
  536. Iterable<?> list = null;
  537. // special case: rowValues could contain a single array
  538. if (values.length == 1 && values[0].getClass().isArray())
  539. {
  540. Object array = values[0];
  541. int n = Array.getLength(array);
  542. ArrayList arrayList = new ArrayList(n);
  543. for (int i = 0; i < n; i++)
  544. arrayList.add(Array.get(array, i));
  545. list = arrayList;
  546. }
  547. else
  548. {
  549. list = Arrays.asList(values);
  550. }
  551. setRow(rowIndex, null, list);
  552. }
  553. /**
  554. * Sets an entire row of values at the specified (0-based) row index
  555. *
  556. * @param header
  557. * a header text placed in the first column, before the data (or <code>null</code> if
  558. * not needed)
  559. * @param rowIndex
  560. * the index of the target row (Note: the first row is at index 0)
  561. * @param rowValues
  562. * an array of values to add, column by column. NB: numbers and string will be
  563. * recognized as such, any other item will be converted to string using
  564. * {@link Object#toString()}, while <code>null</code> values will result in an empty
  565. * cell
  566. */
  567. public void setRow(int rowIndex, String header, Iterable<?> rowValues)
  568. {
  569. int colIndex = 0;
  570. if (header != null) setValue(rowIndex, colIndex++, header);
  571. for (Object obj : rowValues)
  572. if (obj != null) setValue(rowIndex, colIndex++, obj);
  573. }
  574. /**
  575. * Removes all rows in this sheet
  576. */
  577. public void removeRows()
  578. {
  579. removeRows(0);
  580. }
  581. /**
  582. * Removes all rows in this sheet starting at the specified (0-based) row index
  583. *
  584. * @param startRowIndex
  585. * the (0-based) index of the first row to remove
  586. */
  587. public void removeRows(int startRowIndex)
  588. {
  589. for (int i = startRowIndex; i <= sheet.getLastRowNum(); i++)
  590. {
  591. Row row = sheet.getRow(i);
  592. if (row != null) sheet.removeRow(row);
  593. }
  594. }
  595. /**
  596. * @param columnIndex
  597. * the column to search
  598. * @return All values in the column corresponding to a number (empty cells and other cell
  599. * formats are discarded)
  600. */
  601. public double[] getColumnValues(int columnIndex)
  602. {
  603. ArrayList<Double> values = new ArrayList<Double>(sheet.getLastRowNum() + 1);
  604. for (int i = 0; i <= sheet.getLastRowNum(); i++)
  605. {
  606. Object val = getValue(i, columnIndex);
  607. if (val instanceof Number) values.add(((Number) val).doubleValue());
  608. }
  609. if (values.isEmpty()) return null;
  610. double[] result = new double[values.size()];
  611. for (int i = 0; i < result.length; i++)
  612. result[i] = values.get(i);
  613. return result;
  614. }
  615. }