You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CellRangeAddressBase.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ss.util;
  16. import java.util.EnumSet;
  17. import java.util.Iterator;
  18. import java.util.Map;
  19. import java.util.NoSuchElementException;
  20. import java.util.Set;
  21. import java.util.Spliterator;
  22. import java.util.Spliterators;
  23. import java.util.function.Supplier;
  24. import org.apache.poi.common.Duplicatable;
  25. import org.apache.poi.common.usermodel.GenericRecord;
  26. import org.apache.poi.ss.SpreadsheetVersion;
  27. import org.apache.poi.ss.usermodel.Cell;
  28. import org.apache.poi.util.GenericRecordUtil;
  29. /**
  30. * See OOO documentation: excelfileformat.pdf sec 2.5.14 - 'Cell Range Address'<p>
  31. *
  32. * Common superclass of 8-bit and 16-bit versions
  33. */
  34. public abstract class CellRangeAddressBase implements Iterable<CellAddress>, Duplicatable, GenericRecord {
  35. /**
  36. * Indicates a cell or range is in the given relative position in a range.
  37. * More than one of these may apply at once.
  38. */
  39. public enum CellPosition {
  40. /** range starting rows are equal */
  41. TOP,
  42. /** range ending rows are equal */
  43. BOTTOM,
  44. /** range starting columns are equal */
  45. LEFT,
  46. /** range ending columns are equal */
  47. RIGHT,
  48. /** a cell or range is completely inside another range, without touching any edges (a cell in this position can't be in any others) */
  49. INSIDE
  50. }
  51. private int _firstRow;
  52. private int _firstCol;
  53. private int _lastRow;
  54. private int _lastCol;
  55. protected CellRangeAddressBase(int firstRow, int lastRow, int firstCol, int lastCol) {
  56. _firstRow = firstRow;
  57. _lastRow = lastRow;
  58. _firstCol = firstCol;
  59. _lastCol = lastCol;
  60. }
  61. /**
  62. * Validate the range limits against the supplied version of Excel
  63. *
  64. * @param ssVersion the version of Excel to validate against
  65. * @throws IllegalArgumentException if the range limits are outside of the allowed range
  66. */
  67. public void validate(SpreadsheetVersion ssVersion) {
  68. validateRow(_firstRow, ssVersion);
  69. validateRow(_lastRow, ssVersion);
  70. validateColumn(_firstCol, ssVersion);
  71. validateColumn(_lastCol, ssVersion);
  72. }
  73. /**
  74. * Runs a bounds check for row numbers
  75. */
  76. private static void validateRow(int row, SpreadsheetVersion ssVersion) {
  77. int maxrow = ssVersion.getLastRowIndex();
  78. if (row > maxrow) throw new IllegalArgumentException("Maximum row number is " + maxrow);
  79. if (row < 0) throw new IllegalArgumentException("Minumum row number is 0");
  80. }
  81. /**
  82. * Runs a bounds check for column numbers
  83. */
  84. private static void validateColumn(int column, SpreadsheetVersion ssVersion) {
  85. int maxcol = ssVersion.getLastColumnIndex();
  86. if (column > maxcol) throw new IllegalArgumentException("Maximum column number is " + maxcol);
  87. if (column < 0) throw new IllegalArgumentException("Minimum column number is 0");
  88. }
  89. //TODO use the correct SpreadsheetVersion
  90. public final boolean isFullColumnRange() {
  91. return (_firstRow == 0 && _lastRow == SpreadsheetVersion.EXCEL97.getLastRowIndex())
  92. || (_firstRow == -1 && _lastRow == -1);
  93. }
  94. //TODO use the correct SpreadsheetVersion
  95. public final boolean isFullRowRange() {
  96. return (_firstCol == 0 && _lastCol == SpreadsheetVersion.EXCEL97.getLastColumnIndex())
  97. || (_firstCol == -1 && _lastCol == -1);
  98. }
  99. /**
  100. * @return column number for the upper left hand corner
  101. */
  102. public final int getFirstColumn() {
  103. return _firstCol;
  104. }
  105. /**
  106. * @return row number for the upper left hand corner
  107. */
  108. public final int getFirstRow() {
  109. return _firstRow;
  110. }
  111. /**
  112. * @return column number for the lower right hand corner
  113. */
  114. public final int getLastColumn() {
  115. return _lastCol;
  116. }
  117. /**
  118. * @return row number for the lower right hand corner
  119. */
  120. public final int getLastRow() {
  121. return _lastRow;
  122. }
  123. /**
  124. * Determines if the given coordinates lie within the bounds
  125. * of this range.
  126. *
  127. * @param rowInd The row, 0-based.
  128. * @param colInd The column, 0-based.
  129. * @return True if the coordinates lie within the bounds, false otherwise.
  130. * @see #intersects(CellRangeAddressBase) for checking if two ranges overlap
  131. */
  132. public boolean isInRange(int rowInd, int colInd) {
  133. return _firstRow <= rowInd && rowInd <= _lastRow && //containsRow
  134. _firstCol <= colInd && colInd <= _lastCol; //containsColumn
  135. }
  136. /**
  137. * Determines if the given {@link CellReference} lies within the bounds
  138. * of this range.
  139. * <p>NOTE: It is up to the caller to ensure the reference is
  140. * for the correct sheet, since this instance doesn't have a sheet reference.
  141. *
  142. * @param ref the CellReference to check
  143. * @return True if the reference lies within the bounds, false otherwise.
  144. * @see #intersects(CellRangeAddressBase) for checking if two ranges overlap
  145. */
  146. public boolean isInRange(CellReference ref) {
  147. return isInRange(ref.getRow(), ref.getCol());
  148. }
  149. /**
  150. * Determines if the given {@link CellAddress} lies within the bounds
  151. * of this range.
  152. * <p>NOTE: It is up to the caller to ensure the reference is
  153. * for the correct sheet, since this instance doesn't have a sheet reference.
  154. *
  155. * @param ref the CellAddress to check
  156. * @return True if the reference lies within the bounds, false otherwise.
  157. * @see #intersects(CellRangeAddressBase) for checking if two ranges overlap
  158. */
  159. public boolean isInRange(CellAddress ref) {
  160. return isInRange(ref.getRow(), ref.getColumn());
  161. }
  162. /**
  163. * Determines if the given {@link Cell} lies within the bounds
  164. * of this range.
  165. * <p>NOTE: It is up to the caller to ensure the reference is
  166. * for the correct sheet, since this instance doesn't have a sheet reference.
  167. *
  168. * @param cell the Cell to check
  169. * @return True if the cell lies within the bounds, false otherwise.
  170. * @see #intersects(CellRangeAddressBase) for checking if two ranges overlap
  171. */
  172. public boolean isInRange(Cell cell) {
  173. return isInRange(cell.getRowIndex(), cell.getColumnIndex());
  174. }
  175. /**
  176. * Check if the row is in the specified cell range
  177. *
  178. * @param rowInd the row to check
  179. * @return true if the range contains the row [rowInd]
  180. */
  181. public boolean containsRow(int rowInd) {
  182. return _firstRow <= rowInd && rowInd <= _lastRow;
  183. }
  184. /**
  185. * Check if the column is in the specified cell range
  186. *
  187. * @param colInd the column to check
  188. * @return true if the range contains the column [colInd]
  189. */
  190. public boolean containsColumn(int colInd) {
  191. return _firstCol <= colInd && colInd <= _lastCol;
  192. }
  193. /**
  194. * Determines whether or not this CellRangeAddress and the specified CellRangeAddress intersect.
  195. *
  196. * @param other a candidate cell range address to check for intersection with this range
  197. * @return returns true if this range and other range have at least 1 cell in common
  198. * @see #isInRange(int, int) for checking if a single cell intersects
  199. */
  200. public boolean intersects(CellRangeAddressBase other) {
  201. return this._firstRow <= other._lastRow &&
  202. this._firstCol <= other._lastCol &&
  203. other._firstRow <= this._lastRow &&
  204. other._firstCol <= this._lastCol;
  205. }
  206. /**
  207. * Useful for logic like table/range styling, where some elements apply based on relative position in a range.
  208. * @return set of {@link CellPosition}s occupied by the given coordinates. Empty if the coordinates are not in the range, never null.
  209. * @since 3.17 beta 1
  210. */
  211. public Set<CellPosition> getPosition(int rowInd, int colInd) {
  212. Set<CellPosition> positions = EnumSet.noneOf(CellPosition.class);
  213. if (rowInd > getFirstRow() && rowInd < getLastRow() && colInd > getFirstColumn() && colInd < getLastColumn()) {
  214. positions.add(CellPosition.INSIDE);
  215. return positions; // entirely inside, matches no boundaries
  216. }
  217. // check edges
  218. if (rowInd == getFirstRow()) positions.add(CellPosition.TOP);
  219. if (rowInd == getLastRow()) positions.add(CellPosition.BOTTOM);
  220. if (colInd == getFirstColumn()) positions.add(CellPosition.LEFT);
  221. if (colInd == getLastColumn()) positions.add(CellPosition.RIGHT);
  222. return positions;
  223. }
  224. /**
  225. * @param firstCol column number for the upper left hand corner
  226. */
  227. public final void setFirstColumn(int firstCol) {
  228. _firstCol = firstCol;
  229. }
  230. /**
  231. * @param firstRow row number for the upper left hand corner
  232. */
  233. public final void setFirstRow(int firstRow) {
  234. _firstRow = firstRow;
  235. }
  236. /**
  237. * @param lastCol column number for the lower right hand corner
  238. */
  239. public final void setLastColumn(int lastCol) {
  240. _lastCol = lastCol;
  241. }
  242. /**
  243. * @param lastRow row number for the lower right hand corner
  244. */
  245. public final void setLastRow(int lastRow) {
  246. _lastRow = lastRow;
  247. }
  248. /**
  249. * @return the size of the range (number of cells in the area).
  250. */
  251. public int getNumberOfCells() {
  252. return (_lastRow - _firstRow + 1) * (_lastCol - _firstCol + 1);
  253. }
  254. /**
  255. * Returns an iterator over the CellAddresses in this cell range in row-major order.
  256. * @since POI 4.0.0
  257. */
  258. @Override
  259. public Iterator<CellAddress> iterator() {
  260. return new RowMajorCellAddressIterator(this);
  261. }
  262. /**
  263. * Returns a spliterator over the CellAddresses in this cell range in row-major order.
  264. * @since POI 5.2.0
  265. */
  266. @Override
  267. public Spliterator<CellAddress> spliterator() {
  268. return Spliterators.spliterator(iterator(), getNumberOfCells(), 0);
  269. }
  270. /**
  271. * Iterates over the cell addresses in a cell range in row major order
  272. *
  273. * The iterator is unaffected by changes to the CellRangeAddressBase instance
  274. * after the iterator is created.
  275. */
  276. private static class RowMajorCellAddressIterator implements Iterator<CellAddress> {
  277. private final int firstRow, firstCol, lastRow, lastCol;
  278. private int r, c;
  279. public RowMajorCellAddressIterator(CellRangeAddressBase ref) {
  280. r = firstRow = ref.getFirstRow();
  281. c = firstCol = ref.getFirstColumn();
  282. lastRow = ref.getLastRow();
  283. lastCol = ref.getLastColumn();
  284. // whole row and whole column ranges currently not supported
  285. if (firstRow < 0) throw new IllegalStateException("First row cannot be negative.");
  286. if (firstCol < 0) throw new IllegalStateException("First column cannot be negative.");
  287. // avoid infinite iteration
  288. if (firstRow > lastRow) throw new IllegalStateException("First row cannot be greater than last row.");
  289. if (firstCol > lastCol) throw new IllegalStateException("First column cannot be greater than last column.");
  290. }
  291. @Override
  292. public boolean hasNext() {
  293. return r <= lastRow && c <= lastCol;
  294. }
  295. @Override
  296. public CellAddress next() {
  297. if (hasNext()) {
  298. final CellAddress addr = new CellAddress(r, c);
  299. // row major order
  300. if (c < lastCol) {
  301. c++;
  302. }
  303. else { //c >= lastCol, end of row reached
  304. c = firstCol; //CR
  305. r++; //LF
  306. }
  307. return addr;
  308. }
  309. throw new NoSuchElementException();
  310. }
  311. }
  312. @Override
  313. public final String toString() {
  314. CellAddress crA = new CellAddress(_firstRow, _firstCol);
  315. CellAddress crB = new CellAddress(_lastRow, _lastCol);
  316. return getClass().getName() + " [" + crA.formatAsString() + ":" + crB.formatAsString() +"]";
  317. }
  318. // In case _firstRow > _lastRow or _firstCol > _lastCol
  319. protected int getMinRow() {
  320. return Math.min(_firstRow, _lastRow);
  321. }
  322. protected int getMaxRow() {
  323. return Math.max(_firstRow, _lastRow);
  324. }
  325. protected int getMinColumn() {
  326. return Math.min(_firstCol, _lastCol);
  327. }
  328. protected int getMaxColumn() {
  329. return Math.max(_firstCol, _lastCol);
  330. }
  331. @Override
  332. public boolean equals(Object other) {
  333. if (other instanceof CellRangeAddressBase) {
  334. CellRangeAddressBase o = (CellRangeAddressBase) other;
  335. return ((getMinRow() == o.getMinRow()) &&
  336. (getMaxRow() == o.getMaxRow()) &&
  337. (getMinColumn() == o.getMinColumn()) &&
  338. (getMaxColumn() == o.getMaxColumn()));
  339. }
  340. return false;
  341. }
  342. @Override
  343. public int hashCode() {
  344. return (getMinColumn() +
  345. (getMaxColumn() << 8) +
  346. (getMinRow() << 16) +
  347. (getMaxRow() << 24));
  348. }
  349. @Override
  350. public Map<String, Supplier<?>> getGenericProperties() {
  351. return GenericRecordUtil.getGenericProperties(
  352. "firstRow", this::getFirstRow,
  353. "firstCol", this::getFirstColumn,
  354. "lastRow", this::getLastRow,
  355. "lastCol", this::getLastColumn
  356. );
  357. }
  358. }