patch from Stefan Thurnherr: bug 57450: autosize columns in SXSSF using rows that have been flushed to disk

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1717146 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Javen O'Neal 2015-11-30 00:22:50 +00:00
parent 303721c9a7
commit 226fefe0c9
10 changed files with 1147 additions and 11 deletions

View File

@ -36,6 +36,7 @@ import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.util.Internal;
/** /**
@ -244,7 +245,8 @@ public class SheetUtil {
* @param wb the workbook to get the default character width from * @param wb the workbook to get the default character width from
* @return default character width in pixels * @return default character width in pixels
*/ */
private static int getDefaultCharWidth(final Workbook wb) { @Internal
public static int getDefaultCharWidth(final Workbook wb) {
Font defaultFont = wb.getFontAt((short) 0); Font defaultFont = wb.getFontAt((short) 0);
AttributedString str = new AttributedString(String.valueOf(defaultChar)); AttributedString str = new AttributedString(String.valueOf(defaultChar));

View File

@ -0,0 +1,371 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.xssf.streaming;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.SheetUtil;
import org.apache.poi.util.Internal;
/**
* Tracks best fit column width for rows of an {@link SXSSFSheet},
* to be able to correctly calculate auto-sized column widths even
* if some rows are already flushed to disk.
* This is an auxiliary data structure that uses a TreeMap containing
* one entry per tracked column, where the key is the column index and
* the value is a pair of doubles. This data structure's memory footprint
* is linear with the number of *tracked* columns and invariant with
* the number of rows or columns in the sheet.
* @since 3.14beta1
*/
@Internal
/*package*/ class AutoSizeColumnTracker {
private final int defaultCharWidth;
private final DataFormatter dataFormatter = new DataFormatter();
// map of tracked columns, with values containing the best-fit width for the column
// Using a HashMap instead of a TreeMap because insertion (trackColumn), removal (untrackColumn), and membership (everything)
// will be called more frequently than getTrackedColumns(). The O(1) cost of insertion, removal, and membership operations
// outweigh the infrequent O(n*log n) cost of sorting getTrackedColumns().
// Memory consumption for a HashMap and TreeMap is about the same
private final Map<Integer, ColumnWidthPair> maxColumnWidths = new HashMap<Integer, ColumnWidthPair>();
// untrackedColumns stores columns have been explicitly untracked so they aren't implicitly re-tracked by trackAllColumns
// Using a HashSet instead of a TreeSet because we don't care about order.
private final Set<Integer> untrackedColumns = new HashSet<Integer>();
private boolean trackAllColumns = false;
/**
* Tuple to store the column widths considering and not considering merged cells
* If more permutations are needed, it may be prudent to require the user to specify
* how they intend to auto-size a column when they track the column, so calculations
* are limited to the desired intentions. Unless this proves to be a performance problem,
* it's probably better to let the user defer how they want to auto-size to SXSSFSheet.autoSizeColumn,
* rather than twice (via SXSSFSheet.trackColumn(int column, boolean useMergedCells) and again at
* SXSFSheet.autoSizeColumn(int column, boolean useMergedCells))
* @since 3.14beta1
*/
private static class ColumnWidthPair {
private double withSkipMergedCells;
private double withUseMergedCells;
public ColumnWidthPair() {
this(-1.0, -1.0);
}
public ColumnWidthPair(final double columnWidthSkipMergedCells, final double columnWidthUseMergedCells) {
withSkipMergedCells = columnWidthSkipMergedCells;
withUseMergedCells = columnWidthUseMergedCells;
}
/**
* Gets the current best-fit column width for the provided settings
*
* @param useMergedCells true if merged cells are considered into the best-fit column width calculation
* @return best fit column width, measured in default character widths.
*/
public double getMaxColumnWidth(final boolean useMergedCells) {
return useMergedCells ? withUseMergedCells : withSkipMergedCells;
}
/**
* Sets the best-fit column width to the maximum of the current width and the provided width
*
* @param unmergedWidth the best-fit column width calculated with useMergedCells=False
* @param mergedWidth the best-fit column width calculated with useMergedCells=True
*/
public void setMaxColumnWidths(double unmergedWidth, double mergedWidth) {
withUseMergedCells = Math.max(withUseMergedCells, mergedWidth);
withSkipMergedCells = Math.max(withUseMergedCells, unmergedWidth);
}
}
/**
* AutoSizeColumnTracker constructor. Holds no reference to <code>sheet</code>
*
* @param sheet the sheet associated with this auto-size column tracker
* @since 3.14beta1
*/
public AutoSizeColumnTracker(final Sheet sheet) {
// If sheet needs to be saved, use a java.lang.ref.WeakReference to avoid garbage collector gridlock.
defaultCharWidth = SheetUtil.getDefaultCharWidth(sheet.getWorkbook());
}
/**
* Get the currently tracked columns, naturally ordered.
* Note if all columns are tracked, this will only return the columns that have been explicitly or implicitly tracked,
* which is probably only columns containing 1 or more non-blank values
*
* @return a set of the indices of all tracked columns
* @since 3.14beta1
*/
public SortedSet<Integer> getTrackedColumns() {
SortedSet<Integer> sorted = new TreeSet<Integer>(maxColumnWidths.keySet());
return Collections.unmodifiableSortedSet(sorted);
}
/**
* Returns true if column is currently tracked for auto-sizing.
*
* @param column the index of the column to check
* @return true if column is tracked
* @since 3.14beta1
*/
public boolean isColumnTracked(int column) {
return trackAllColumns || maxColumnWidths.containsKey(column);
}
/**
* Returns true if all columns are implicitly tracked.
*
* @return true if all columns are implicitly tracked
* @since 3.14beta1
*/
public boolean isAllColumnsTracked() {
return trackAllColumns;
}
/**
* Tracks all non-blank columns
* Allows columns that have been explicitly untracked to be tracked
* @since 3.14beta1
*/
public void trackAllColumns() {
trackAllColumns = true;
untrackedColumns.clear();
}
/**
* Untrack all columns that were previously tracked for auto-sizing.
* All best-fit column widths are forgotten.
* @since 3.14beta1
*/
public void untrackAllColumns() {
trackAllColumns = false;
maxColumnWidths.clear();
untrackedColumns.clear();
}
/**
* Marks multiple columns for inclusion in auto-size column tracking.
* Note this has undefined behavior if columns are tracked after one or more rows are written to the sheet.
* Any column in <code>columns</code> that are already tracked are ignored by this call.
*
* @param columns the indices of the columns to track
* @since 3.14beta1
*/
public void trackColumns(Collection<Integer> columns)
{
for (final int column : columns) {
trackColumn(column);
}
}
/**
* Marks a column for inclusion in auto-size column tracking.
* Note this has undefined behavior if a column is tracked after one or more rows are written to the sheet.
* If <code>column</code> is already tracked, this call does nothing.
*
* @param column the index of the column to track for auto-sizing
* @return if column is already tracked, the call does nothing and returns false
* @since 3.14beta1
*/
public boolean trackColumn(int column) {
untrackedColumns.remove(column);
if (!maxColumnWidths.containsKey(column)) {
maxColumnWidths.put(column, new ColumnWidthPair());
return true;
}
return false;
}
/**
* Implicitly track a column if it has not been explicitly untracked
* If it has been explicitly untracked, this call does nothing and returns false.
* Otherwise return true
*
* @param column the column to implicitly track
* @return false if column has been explicitly untracked, otherwise return true
*/
private boolean implicitlyTrackColumn(int column) {
if (!untrackedColumns.contains(column)) {
trackColumn(column);
return true;
}
return false;
}
/**
* Removes columns that were previously marked for inclusion in auto-size column tracking.
* When a column is untracked, the best-fit width is forgotten.
* Any column in <code>columns</code> that is not tracked will be ignored by this call.
*
* @param columns the indices of the columns to track for auto-sizing
* @return true if one or more columns were untracked as a result of this call
* @since 3.14beta1
*/
public boolean untrackColumns(Collection<Integer> columns)
{
untrackedColumns.addAll(columns);
return maxColumnWidths.keySet().removeAll(columns);
}
/**
* Removes a column that was previously marked for inclusion in auto-size column tracking.
* When a column is untracked, the best-fit width is forgotten.
* If <code>column</code> is not tracked, it will be ignored by this call.
*
* @param column the index of the column to track for auto-sizing
* @return true if column was tracked prior this call, false if no action was taken
* @since 3.14beta1
*/
public boolean untrackColumn(int column) {
untrackedColumns.add(column);
return maxColumnWidths.keySet().remove(column);
}
/**
* Get the best-fit width of a tracked column
*
* @param column the index of the column to get the current best-fit width of
* @param useMergedCells true if merged cells should be considered when computing the best-fit width
* @return best-fit column width, measured in units of 1/256th of a character width
* @throws IllegalStateException if column is not tracked and trackAllColumns is false
* @since 3.14beta1
*/
public int getBestFitColumnWidth(int column, boolean useMergedCells) {
if (!maxColumnWidths.containsKey(column)) {
// if column is not tracked, implicitly track the column if trackAllColumns is True and column has not been explicitly untracked
if (trackAllColumns) {
if (!implicitlyTrackColumn(column)) {
final Throwable reason = new IllegalStateException(
"Column was explicitly untracked after trackAllColumns() was called.");
throw new IllegalStateException(
"Cannot get best fit column width on explicitly untracked column " + column + ". " +
"Either explicitly track the column or track all columns.", reason);
}
}
else {
final Throwable reason = new IllegalStateException(
"Column was never explicitly tracked and isAllColumnsTracked() is false " +
"(trackAllColumns() was never called or untrackAllColumns() was called after trackAllColumns() was called).");
throw new IllegalStateException(
"Cannot get best fit column width on untracked column " + column + ". " +
"Either explicitly track the column or track all columns.", reason);
}
}
final double width = maxColumnWidths.get(column).getMaxColumnWidth(useMergedCells);
return (int) (256*width);
}
/**
* Calculate the best fit width for each tracked column in row
*
* @param row the row to get the cells
* @since 3.14beta1
*/
public void updateColumnWidths(Row row) {
// track new columns
implicitlyTrackColumnsInRow(row);
// update the widths
// for-loop over the shorter of the number of cells in the row and the number of tracked columns
// these two for-loops should do the same thing
if (maxColumnWidths.size() < row.getPhysicalNumberOfCells()) {
// loop over the tracked columns, because there are fewer tracked columns than cells in this row
for (final Entry<Integer, ColumnWidthPair> e : maxColumnWidths.entrySet()) {
final int column = e.getKey();
final Cell cell = row.getCell(column); //is MissingCellPolicy=Row.RETURN_NULL_AND_BLANK needed?
// FIXME: if cell belongs to a merged region, some of the merged region may have fallen outside of the random access window
// In this case, getting the column width may result in an error. Need to gracefully handle this.
// FIXME: Most cells are not merged, so calling getCellWidth twice re-computes the same value twice.
// Need to rewrite this to avoid unnecessary computation if this proves to be a performance bottleneck.
if (cell != null) {
final ColumnWidthPair pair = e.getValue();
updateColumnWidth(cell, pair);
}
}
}
else {
// loop over the cells in this row, because there are fewer cells in this row than tracked columns
for (final Cell cell : row) {
final int column = cell.getColumnIndex();
// FIXME: if cell belongs to a merged region, some of the merged region may have fallen outside of the random access window
// In this case, getting the column width may result in an error. Need to gracefully handle this.
// FIXME: Most cells are not merged, so calling getCellWidth twice re-computes the same value twice.
// Need to rewrite this to avoid unnecessary computation if this proves to be a performance bottleneck.
if (maxColumnWidths.containsKey(column)) {
final ColumnWidthPair pair = maxColumnWidths.get(column);
updateColumnWidth(cell, pair);
}
}
}
}
/**
* Helper for {@link #updateColumnWidths(Row)}.
* Implicitly track the columns corresponding to the cells in row.
* If all columns in the row are already tracked, this call does nothing.
* Explicitly untracked columns will not be tracked.
*
* @param row the row containing cells to implicitly track the columns
* @since 3.14beta1
*/
private void implicitlyTrackColumnsInRow(Row row) {
// track new columns
if (trackAllColumns) {
// if column is not tracked, implicitly track the column if trackAllColumns is True and column has not been explicitly untracked
for (final Cell cell : row) {
final int column = cell.getColumnIndex();
implicitlyTrackColumn(column);
}
}
}
/**
* Helper for {@link #updateColumnWidths(Row)}.
*
* @param cell the cell to compute the best fit width on
* @param pair the column width pair to update
* @since 3.14beta1
*/
private void updateColumnWidth(final Cell cell, final ColumnWidthPair pair) {
final double unmergedWidth = SheetUtil.getCellWidth(cell, defaultCharWidth, dataFormatter, false);
final double mergedWidth = SheetUtil.getCellWidth(cell, defaultCharWidth, dataFormatter, true);
pair.setMaxColumnWidths(unmergedWidth, mergedWidth);
}
}

View File

@ -19,9 +19,11 @@ package org.apache.poi.xssf.streaming;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import org.apache.poi.hssf.util.PaneInformation; import org.apache.poi.hssf.util.PaneInformation;
@ -62,6 +64,7 @@ public class SXSSFSheet implements Sheet, Cloneable
private final TreeMap<Integer,SXSSFRow> _rows=new TreeMap<Integer,SXSSFRow>(); private final TreeMap<Integer,SXSSFRow> _rows=new TreeMap<Integer,SXSSFRow>();
private final SheetDataWriter _writer; private final SheetDataWriter _writer;
private int _randomAccessWindowSize = SXSSFWorkbook.DEFAULT_WINDOW_SIZE; private int _randomAccessWindowSize = SXSSFWorkbook.DEFAULT_WINDOW_SIZE;
private final AutoSizeColumnTracker _autoSizeColumnTracker;
private int outlineLevelRow = 0; private int outlineLevelRow = 0;
private int lastFlushedRowNumber = -1; private int lastFlushedRowNumber = -1;
private boolean allFlushed = false; private boolean allFlushed = false;
@ -71,6 +74,7 @@ public class SXSSFSheet implements Sheet, Cloneable
_sh = xSheet; _sh = xSheet;
_writer = workbook.createSheetDataWriter(); _writer = workbook.createSheetDataWriter();
setRandomAccessWindowSize(_workbook.getRandomAccessWindowSize()); setRandomAccessWindowSize(_workbook.getRandomAccessWindowSize());
_autoSizeColumnTracker = new AutoSizeColumnTracker(this);
} }
/** /**
@ -1378,6 +1382,118 @@ public class SXSSFSheet implements Sheet, Cloneable
_sh.setDefaultColumnStyle(column, style); _sh.setDefaultColumnStyle(column, style);
} }
/**
* Track a column in the sheet for auto-sizing.
* Note this has undefined behavior if a column is tracked after one or more rows are written to the sheet.
* If <code>column</code> is already tracked, this call does nothing.
*
* @param column the column to track for autosizing
* @since 3.14beta1
* @see #trackColumnsForAutoSizing(Collection)
* @see #trackAllColumnsForAutoSizing()
*/
public void trackColumnForAutoSizing(int column)
{
_autoSizeColumnTracker.trackColumn(column);
}
/**
* Track several columns in the sheet for auto-sizing.
* Note this has undefined behavior if columns are tracked after one or more rows are written to the sheet.
* Any column in <code>columns</code> that are already tracked are ignored by this call.
*
* @param columns the columns to track for autosizing
* @since 3.14beta1
*/
public void trackColumnsForAutoSizing(Collection<Integer> columns)
{
_autoSizeColumnTracker.trackColumns(columns);
}
/**
* Tracks all columns in the sheet for auto-sizing. If this is called, individual columns do not need to be tracked.
* Because determining the best-fit width for a cell is expensive, this may affect the performance.
* @since 3.14beta1
*/
public void trackAllColumnsForAutoSizing()
{
_autoSizeColumnTracker.trackAllColumns();
}
/**
* Removes a column that was previously marked for inclusion in auto-size column tracking.
* When a column is untracked, the best-fit width is forgotten.
* If <code>column</code> is not tracked, it will be ignored by this call.
*
* @param column the index of the column to track for auto-sizing
* @return true if column was tracked prior to being untracked, false if no action was taken
*/
/**
*
*
* @param column the index of the column to track for auto-sizing
* @return true if column was tracked prior to this call, false if no action was taken
* @since 3.14beta1
* @see #untrackColumnsForAutoSizing(Collection)
* @see #untrackAllColumnsForAutoSizing(int)
*/
public boolean untrackColumnForAutoSizing(int column)
{
return _autoSizeColumnTracker.untrackColumn(column);
}
/**
* Untracks several columns in the sheet for auto-sizing.
* When a column is untracked, the best-fit width is forgotten.
* Any column in <code>columns</code> that is not tracked will be ignored by this call.
*
* @param columns the indices of the columns to track for auto-sizing
* @return true if one or more columns were untracked as a result of this call
*
* @param columns the columns to track for autosizing
* @since 3.14beta1
*/
public boolean untrackColumnsForAutoSizing(Collection<Integer> columns)
{
return _autoSizeColumnTracker.untrackColumns(columns);
}
/**
* Untracks all columns in the sheet for auto-sizing. Best-fit column widths are forgotten.
* If this is called, individual columns do not need to be untracked.
* @since 3.14beta1
*/
public void untrackAllColumnsForAutoSizing()
{
_autoSizeColumnTracker.untrackAllColumns();
}
/**
* Returns true if column is currently tracked for auto-sizing.
*
* @param column the index of the column to check
* @return true if column is tracked
* @since 3.14beta1
*/
public boolean isColumnTrackedForAutoSizing(int column)
{
return _autoSizeColumnTracker.isColumnTracked(column);
}
/**
* Get the currently tracked columns for auto-sizing.
* Note if all columns are tracked, this will only return the columns that have been explicitly or implicitly tracked,
* which is probably only columns containing 1 or more non-blank values
*
* @return a set of the indices of all tracked columns
* @since 3.14beta1
*/
public Set<Integer> getTrackedColumnsForAutoSizing()
{
return _autoSizeColumnTracker.getTrackedColumns();
}
/** /**
* Adjusts the column width to fit the contents. * Adjusts the column width to fit the contents.
* *
@ -1389,7 +1505,16 @@ public class SXSSFSheet implements Sheet, Cloneable
* You can specify whether the content of merged cells should be considered or ignored. * You can specify whether the content of merged cells should be considered or ignored.
* Default is to ignore merged cells. * Default is to ignore merged cells.
* *
* @param column the column index * <p>
* Special note about SXSSF implementation: You must register the columns you wish to track with
* the SXSSFSheet using {@link #trackColumnForAutoSizing(int)} or {@link #trackAllColumnsForAutoSizing()}.
* This is needed because the rows needed to compute the column width may have fallen outside the
* random access window and been flushed to disk.
* Tracking columns is required even if all rows are in the random access window.
* </p>
* <p><i>New in POI 3.14 beta 1: auto-sizes columns using cells from current and flushed rows.</i></p>
*
* @param column the column index to auto-size
*/ */
@Override @Override
public void autoSizeColumn(int column) public void autoSizeColumn(int column)
@ -1407,21 +1532,53 @@ public class SXSSFSheet implements Sheet, Cloneable
* You can specify whether the content of merged cells should be considered or ignored. * You can specify whether the content of merged cells should be considered or ignored.
* Default is to ignore merged cells. * Default is to ignore merged cells.
* *
* @param column the column index * <p>
* Special note about SXSSF implementation: You must register the columns you wish to track with
* the SXSSFSheet using {@link #trackColumnForAutoSizing(int)} or {@link #trackAllColumnsForAutoSizing()}.
* This is needed because the rows needed to compute the column width may have fallen outside the
* random access window and been flushed to disk.
* Tracking columns is required even if all rows are in the random access window.
* </p>
* <p><i>New in POI 3.14 beta 1: auto-sizes columns using cells from current and flushed rows.</i></p>
*
* @param column the column index to auto-size
* @param useMergedCells whether to use the contents of merged cells when calculating the width of the column * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column
*/ */
@Override @Override
public void autoSizeColumn(int column, boolean useMergedCells) public void autoSizeColumn(int column, boolean useMergedCells)
{ {
double width = SheetUtil.getColumnWidth(this, column, useMergedCells); // Multiple calls to autoSizeColumn need to look up the best-fit width
// of rows already flushed to disk plus re-calculate the best-fit width
// of rows in the current window. It isn't safe to update the column
// widths before flushing to disk because columns in the random access
// window rows may change in best-fit width. The best-fit width of a cell
// is only fixed when it becomes inaccessible for modification.
// Changes to the shared strings table, styles table, or formulas might
// be able to invalidate the auto-size width without the opportunity
// to recalculate the best-fit width for the flushed rows. This is an
// inherent limitation of SXSSF. If having correct auto-sizing is
// critical, the flushed rows would need to be re-read by the read-only
// XSSF eventmodel (SAX) or the memory-heavy XSSF usermodel (DOM).
final int flushedWidth;
try {
// get the best fit width of rows already flushed to disk
flushedWidth = _autoSizeColumnTracker.getBestFitColumnWidth(column, useMergedCells);
}
catch (final IllegalStateException e) {
throw new IllegalStateException("Could not auto-size column. Make sure the column was tracked prior to auto-sizing the column.", e);
}
if (width != -1) { // get the best-fit width of rows currently in the random access window
width *= 256; final int activeWidth = (int) (256 * SheetUtil.getColumnWidth(this, column, useMergedCells));
int maxColumnWidth = 255*256; // The maximum column width for an individual cell is 255 characters
if (width > maxColumnWidth) { // the best-fit width for both flushed rows and random access window rows
width = maxColumnWidth; // flushedWidth or activeWidth may be negative if column contains only blank cells
} final int bestFitWidth = Math.max(flushedWidth, activeWidth);
setColumnWidth(column, (int)(width));
if (bestFitWidth > 0) {
final int maxColumnWidth = 255*256; // The maximum column width for an individual cell is 255 characters
final int width = Math.min(bestFitWidth, maxColumnWidth);
setColumnWidth(column, width);
} }
} }
@ -1681,6 +1838,8 @@ public class SXSSFSheet implements Sheet, Cloneable
if (firstRowNum!=null) { if (firstRowNum!=null) {
int rowIndex = firstRowNum.intValue(); int rowIndex = firstRowNum.intValue();
SXSSFRow row = _rows.get(firstRowNum); SXSSFRow row = _rows.get(firstRowNum);
// Update the best fit column widths for auto-sizing just before the rows are flushed
_autoSizeColumnTracker.updateColumnWidths(row);
_writer.writeRow(rowIndex, row); _writer.writeRow(rowIndex, row);
_rows.remove(firstRowNum); _rows.remove(firstRowNum);
lastFlushedRowNumber = rowIndex; lastFlushedRowNumber = rowIndex;

View File

@ -0,0 +1,203 @@
package org.apache.poi.xssf.streaming;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.SheetUtil;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
/**
* Tests the auto-sizing behaviour of {@link SXSSFSheet} when not all
* rows fit into the memory window size etc.
*
* @see Bug #57450 which reported the original mis-behaviour
*/
public class TestAutoSizeColumnTracker {
private SXSSFSheet sheet;
private SXSSFWorkbook workbook;
private AutoSizeColumnTracker tracker;
private static final SortedSet<Integer> columns;
static {
SortedSet<Integer>_columns = new TreeSet<Integer>();
_columns.add(0);
_columns.add(1);
_columns.add(3);
columns = Collections.unmodifiableSortedSet(_columns);
}
private final static String SHORT_MESSAGE = "short";
private final static String LONG_MESSAGE = "This is a test of a long message! This is a test of a long message!";
@Before
public void setUpSheetAndWorkbook() {
workbook = new SXSSFWorkbook();
sheet = workbook.createSheet();
tracker = new AutoSizeColumnTracker(sheet);
}
@After
public void tearDownSheetAndWorkbook() throws IOException {
if (sheet != null) {
sheet.dispose();
}
if (workbook != null) {
workbook.close();
}
}
@Test
public void trackAndUntrackColumn() {
assumeTrue(tracker.getTrackedColumns().isEmpty());
tracker.trackColumn(0);
Set<Integer> expected = new HashSet<Integer>();
expected.add(0);
assertEquals(expected, tracker.getTrackedColumns());
tracker.untrackColumn(0);
assertTrue(tracker.getTrackedColumns().isEmpty());
}
@Test
public void trackAndUntrackColumns() {
assumeTrue(tracker.getTrackedColumns().isEmpty());
tracker.trackColumns(columns);
assertEquals(columns, tracker.getTrackedColumns());
tracker.untrackColumn(3);
tracker.untrackColumn(0);
tracker.untrackColumn(1);
assertTrue(tracker.getTrackedColumns().isEmpty());
tracker.trackColumn(0);
tracker.trackColumns(columns);
tracker.untrackColumn(4);
assertEquals(columns, tracker.getTrackedColumns());
tracker.untrackColumns(columns);
assertTrue(tracker.getTrackedColumns().isEmpty());
}
@Test
public void trackAndUntrackAllColumns() {
assumeTrue(tracker.getTrackedColumns().isEmpty());
tracker.trackAllColumns();
assertTrue(tracker.getTrackedColumns().isEmpty());
Row row = sheet.createRow(0);
for (int column : columns) {
row.createCell(column);
}
// implicitly track the columns
tracker.updateColumnWidths(row);
assertEquals(columns, tracker.getTrackedColumns());
tracker.untrackAllColumns();
assertTrue(tracker.getTrackedColumns().isEmpty());
}
@Test
public void isColumnTracked() {
assumeFalse(tracker.isColumnTracked(0));
tracker.trackColumn(0);
assertTrue(tracker.isColumnTracked(0));
tracker.untrackColumn(0);
assertFalse(tracker.isColumnTracked(0));
}
@Test
public void getTrackedColumns() {
assumeTrue(tracker.getTrackedColumns().isEmpty());
for (int column : columns) {
tracker.trackColumn(column);
}
assertEquals(3, tracker.getTrackedColumns().size());
assertEquals(columns, tracker.getTrackedColumns());
}
@Test
public void isAllColumnsTracked() {
assertFalse(tracker.isAllColumnsTracked());
tracker.trackAllColumns();
assertTrue(tracker.isAllColumnsTracked());
tracker.untrackAllColumns();
assertFalse(tracker.isAllColumnsTracked());
}
@Test
public void updateColumnWidths_and_getBestFitColumnWidth() {
tracker.trackAllColumns();
Row row1 = sheet.createRow(0);
Row row2 = sheet.createRow(1);
// A1, B1, D1
for (int column : columns) {
row1.createCell(column).setCellValue(LONG_MESSAGE);
row2.createCell(column+1).setCellValue(SHORT_MESSAGE);
}
tracker.updateColumnWidths(row1);
tracker.updateColumnWidths(row2);
sheet.addMergedRegion(CellRangeAddress.valueOf("D1:E1"));
assumeRequiredFontsAreInstalled(workbook, row1.getCell(columns.iterator().next()));
// Excel 2013 and LibreOffice 4.2.8.2 both treat columns with merged regions as blank
/** A B C D E
* 1 LONG LONG LONGMERGE
* 2 SHORT SHORT SHORT
*/
// measured in Excel 2013. Sizes may vary.
final int longMsgWidth = (int) (57.43*256);
final int shortMsgWidth = (int) (4.86*256);
checkColumnWidth(longMsgWidth, 0, true);
checkColumnWidth(longMsgWidth, 0, false);
checkColumnWidth(longMsgWidth, 1, true);
checkColumnWidth(longMsgWidth, 1, false);
checkColumnWidth(shortMsgWidth, 2, true);
checkColumnWidth(shortMsgWidth, 2, false);
checkColumnWidth(-1, 3, true);
checkColumnWidth(longMsgWidth, 3, false);
checkColumnWidth(shortMsgWidth, 4, true); //but is it really? shouldn't autosizing column E use "" from E1 and SHORT from E2?
checkColumnWidth(shortMsgWidth, 4, false);
}
private void checkColumnWidth(int expectedWidth, int column, boolean useMergedCells) {
final int bestFitWidth = tracker.getBestFitColumnWidth(column, useMergedCells);
if (bestFitWidth < 0 && expectedWidth < 0) return;
final double abs_error = Math.abs(bestFitWidth-expectedWidth);
final double rel_error = abs_error / expectedWidth;
if (rel_error > 0.25) {
fail("check column width: " +
rel_error + ", " + abs_error + ", " +
expectedWidth + ", " + bestFitWidth);
}
}
private static void assumeRequiredFontsAreInstalled(final Workbook workbook, final Cell cell) {
// autoSize will fail if required fonts are not installed, skip this test then
Font font = workbook.getFontAt(cell.getCellStyle().getFontIndex());
System.out.println(font.getFontHeightInPoints());
System.out.println(font.getFontName());
Assume.assumeTrue("Cannot verify autoSizeColumn() because the necessary Fonts are not installed on this machine: " + font,
SheetUtil.canComputeColumnWidth(font));
}
}

View File

@ -23,6 +23,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.IOException; import java.io.IOException;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.BaseTestSheet; import org.apache.poi.ss.usermodel.BaseTestSheet;
@ -46,6 +48,12 @@ public class TestSXSSFSheet extends BaseTestSheet {
SXSSFITestDataProvider.instance.cleanup(); SXSSFITestDataProvider.instance.cleanup();
} }
@Override
protected void trackColumnsForAutoSizingIfSXSSF(Sheet sheet) {
SXSSFSheet sxSheet = (SXSSFSheet) sheet;
sxSheet.trackAllColumnsForAutoSizing();
}
/** /**
* cloning of sheets is not supported in SXSSF * cloning of sheets is not supported in SXSSF

View File

@ -0,0 +1,363 @@
package org.apache.poi.xssf.streaming;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Font;
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.ss.util.SheetUtil;
import org.junit.After;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
/**
* Tests the auto-sizing behaviour of {@link SXSSFSheet} when not all
* rows fit into the memory window size etc.
*
* @see Bug #57450 which reported the original mis-behaviour
*/
@RunWith(Parameterized.class)
public class TestSXSSFSheetAutoSizeColumn {
private static final String SHORT_CELL_VALUE = "Ben";
private static final String LONG_CELL_VALUE = "B Be Ben Beni Benif Benify Benif Beni Ben Be B";
// Approximative threshold to decide whether test is PASS or FAIL:
// shortCellValue ends up with approx column width 1_000 (on my machine),
// longCellValue ends up with approx. column width 10_000 (on my machine)
// so shortCellValue can be expected to be < 5000 for all fonts
// and longCellValue can be expected to be > 5000 for all fonts
private static final int COLUMN_WIDTH_THRESHOLD_BETWEEN_SHORT_AND_LONG = 5000;
private static final int MAX_COLUMN_WIDTH = 255*256;
private static final SortedSet<Integer> columns;
static {
SortedSet<Integer>_columns = new TreeSet<Integer>();
_columns.add(0);
_columns.add(1);
_columns.add(3);
columns = Collections.unmodifiableSortedSet(_columns);
}
private SXSSFSheet sheet;
private SXSSFWorkbook workbook;
@Parameter(0)
public boolean useMergedCells;
@Parameters(name="{index}: useMergedCells={0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{false},
{true},
});
}
@After
public void tearDownSheetAndWorkbook() throws IOException {
if (sheet != null) {
sheet.dispose();
}
if (workbook != null) {
workbook.close();
}
}
@Test
public void test_EmptySheet_NoException() {
workbook = new SXSSFWorkbook();
sheet = workbook.createSheet();
sheet.trackAllColumnsForAutoSizing();
for (int i = 0; i < 10; i++) {
sheet.autoSizeColumn(i, useMergedCells);
}
}
@Test
public void test_WindowSizeDefault_AllRowsFitIntoWindowSize() {
workbook = new SXSSFWorkbook();
sheet = workbook.createSheet();
sheet.trackAllColumnsForAutoSizing();
final Cell cellRow0 = createRowWithCellValues(sheet, 0, LONG_CELL_VALUE);
assumeRequiredFontsAreInstalled(workbook, cellRow0);
createRowWithCellValues(sheet, 1, SHORT_CELL_VALUE);
sheet.autoSizeColumn(0, useMergedCells);
assertColumnWidthStrictlyWithinRange(sheet.getColumnWidth(0), COLUMN_WIDTH_THRESHOLD_BETWEEN_SHORT_AND_LONG, MAX_COLUMN_WIDTH);
}
@Test
public void test_WindowSizeEqualsOne_ConsiderFlushedRows() {
workbook = new SXSSFWorkbook(null, 1); // Window size 1 so only last row will be in memory
sheet = workbook.createSheet();
sheet.trackAllColumnsForAutoSizing();
final Cell cellRow0 = createRowWithCellValues(sheet, 0, LONG_CELL_VALUE);
assumeRequiredFontsAreInstalled(workbook, cellRow0);
createRowWithCellValues(sheet, 1, SHORT_CELL_VALUE);
sheet.autoSizeColumn(0, useMergedCells);
assertColumnWidthStrictlyWithinRange(sheet.getColumnWidth(0), COLUMN_WIDTH_THRESHOLD_BETWEEN_SHORT_AND_LONG, MAX_COLUMN_WIDTH);
}
@Test
public void test_WindowSizeEqualsOne_lastRowIsNotWidest() {
workbook = new SXSSFWorkbook(null, 1); // Window size 1 so only last row will be in memory
sheet = workbook.createSheet();
sheet.trackAllColumnsForAutoSizing();
final Cell cellRow0 = createRowWithCellValues(sheet, 0, LONG_CELL_VALUE);
assumeRequiredFontsAreInstalled(workbook, cellRow0);
createRowWithCellValues(sheet, 1, SHORT_CELL_VALUE);
sheet.autoSizeColumn(0, useMergedCells);
assertColumnWidthStrictlyWithinRange(sheet.getColumnWidth(0), COLUMN_WIDTH_THRESHOLD_BETWEEN_SHORT_AND_LONG, MAX_COLUMN_WIDTH);
}
@Test
public void test_WindowSizeEqualsOne_lastRowIsWidest() {
workbook = new SXSSFWorkbook(null, 1); // Window size 1 so only last row will be in memory
sheet = workbook.createSheet();
sheet.trackAllColumnsForAutoSizing();
final Cell cellRow0 = createRowWithCellValues(sheet, 0, SHORT_CELL_VALUE);
assumeRequiredFontsAreInstalled(workbook, cellRow0);
createRowWithCellValues(sheet, 1, LONG_CELL_VALUE);
sheet.autoSizeColumn(0, useMergedCells);
assertColumnWidthStrictlyWithinRange(sheet.getColumnWidth(0), COLUMN_WIDTH_THRESHOLD_BETWEEN_SHORT_AND_LONG, MAX_COLUMN_WIDTH);
}
// fails only for useMergedCell=true
@Test
public void test_WindowSizeEqualsOne_flushedRowHasMergedCell() {
workbook = new SXSSFWorkbook(null, 1); // Window size 1 so only last row will be in memory
sheet = workbook.createSheet();
sheet.trackAllColumnsForAutoSizing();
Cell a1 = createRowWithCellValues(sheet, 0, LONG_CELL_VALUE);
assumeRequiredFontsAreInstalled(workbook, a1);
sheet.addMergedRegion(CellRangeAddress.valueOf("A1:B1"));
createRowWithCellValues(sheet, 1, SHORT_CELL_VALUE, SHORT_CELL_VALUE);
/**
* A B
* 1 LONGMERGED
* 2 SHORT SHORT
*/
sheet.autoSizeColumn(0, useMergedCells);
sheet.autoSizeColumn(1, useMergedCells);
if (useMergedCells) {
// Excel and LibreOffice behavior: ignore merged cells for auto-sizing.
// POI behavior: evenly distribute the column width among the merged columns.
// each column must be auto-sized in order for the column widths
// to add up to the best fit width.
final int colspan = 2;
final int expectedWidth = (10000 + 1000)/colspan; //average of 1_000 and 10_000
final int minExpectedWidth = expectedWidth / 2;
final int maxExpectedWidth = expectedWidth * 3 / 2;
assertColumnWidthStrictlyWithinRange(sheet.getColumnWidth(0), minExpectedWidth, maxExpectedWidth); //short
} else {
assertColumnWidthStrictlyWithinRange(sheet.getColumnWidth(0), COLUMN_WIDTH_THRESHOLD_BETWEEN_SHORT_AND_LONG, MAX_COLUMN_WIDTH); //long
}
assertColumnWidthStrictlyWithinRange(sheet.getColumnWidth(1), 0, COLUMN_WIDTH_THRESHOLD_BETWEEN_SHORT_AND_LONG); //short
}
@Test
public void autoSizeColumn_trackColumnForAutoSizing() {
workbook = new SXSSFWorkbook();
sheet = workbook.createSheet();
sheet.trackColumnForAutoSizing(0);
SortedSet<Integer> expected = new TreeSet<Integer>();
expected.add(0);
assertEquals(expected, sheet.getTrackedColumnsForAutoSizing());
sheet.autoSizeColumn(0, useMergedCells);
try {
sheet.autoSizeColumn(1, useMergedCells);
fail("Should not be able to auto-size an untracked column");
}
catch (final IllegalStateException e) {
// expected
}
}
@Test
public void autoSizeColumn_trackColumnsForAutoSizing() {
workbook = new SXSSFWorkbook();
sheet = workbook.createSheet();
sheet.trackColumnsForAutoSizing(columns);
SortedSet<Integer> sorted = new TreeSet<Integer>(columns);
assertEquals(sorted, sheet.getTrackedColumnsForAutoSizing());
sheet.autoSizeColumn(sorted.first(), useMergedCells);
try {
assumeFalse(columns.contains(5));
sheet.autoSizeColumn(5, useMergedCells);
fail("Should not be able to auto-size an untracked column");
}
catch (final IllegalStateException e) {
// expected
}
}
@Test
public void autoSizeColumn_untrackColumnForAutoSizing() {
workbook = new SXSSFWorkbook();
sheet = workbook.createSheet();
sheet.trackColumnsForAutoSizing(columns);
sheet.untrackColumnForAutoSizing(columns.first());
assumeTrue(sheet.getTrackedColumnsForAutoSizing().contains(columns.last()));
sheet.autoSizeColumn(columns.last(), useMergedCells);
try {
assumeFalse(sheet.getTrackedColumnsForAutoSizing().contains(columns.first()));
sheet.autoSizeColumn(columns.first(), useMergedCells);
fail("Should not be able to auto-size an untracked column");
}
catch (final IllegalStateException e) {
// expected
}
}
@Test
public void autoSizeColumn_untrackColumnsForAutoSizing() {
workbook = new SXSSFWorkbook();
sheet = workbook.createSheet();
sheet.trackColumnForAutoSizing(15);
sheet.trackColumnsForAutoSizing(columns);
sheet.untrackColumnsForAutoSizing(columns);
assumeTrue(sheet.getTrackedColumnsForAutoSizing().contains(15));
sheet.autoSizeColumn(15, useMergedCells);
try {
assumeFalse(sheet.getTrackedColumnsForAutoSizing().contains(columns.first()));
sheet.autoSizeColumn(columns.first(), useMergedCells);
fail("Should not be able to auto-size an untracked column");
}
catch (final IllegalStateException e) {
// expected
}
}
@Test
public void autoSizeColumn_isColumnTrackedForAutoSizing() {
workbook = new SXSSFWorkbook();
sheet = workbook.createSheet();
sheet.trackColumnsForAutoSizing(columns);
for (int column : columns) {
assertTrue(sheet.isColumnTrackedForAutoSizing(column));
assumeFalse(columns.contains(column+10));
assertFalse(sheet.isColumnTrackedForAutoSizing(column+10));
}
}
@Test
public void autoSizeColumn_trackAllColumns() {
workbook = new SXSSFWorkbook();
sheet = workbook.createSheet();
sheet.trackAllColumnsForAutoSizing();
sheet.autoSizeColumn(0, useMergedCells);
sheet.untrackAllColumnsForAutoSizing();
try {
sheet.autoSizeColumn(0, useMergedCells);
fail("Should not be able to auto-size an implicitly untracked column");
} catch (final IllegalStateException e) {
// expected
}
}
@Test
public void autoSizeColumn_trackAllColumns_explicitUntrackColumn() {
workbook = new SXSSFWorkbook();
sheet = workbook.createSheet();
sheet.trackColumnsForAutoSizing(columns);
sheet.trackAllColumnsForAutoSizing();
sheet.untrackColumnForAutoSizing(0);
try {
sheet.autoSizeColumn(0, useMergedCells);
fail("Should not be able to auto-size an explicitly untracked column");
} catch (final IllegalStateException e) {
// expected
}
}
private static void assumeRequiredFontsAreInstalled(final Workbook workbook, final Cell cell) {
// autoSize will fail if required fonts are not installed, skip this test then
Font font = workbook.getFontAt(cell.getCellStyle().getFontIndex());
Assume.assumeTrue("Cannot verify autoSizeColumn() because the necessary Fonts are not installed on this machine: " + font,
SheetUtil.canComputeColumnWidth(font));
}
private static Cell createRowWithCellValues(final Sheet sheet, final int rowNumber, final String... cellValues) {
Row row = sheet.createRow(rowNumber);
int cellIndex = 0;
Cell firstCell = null;
for (final String cellValue : cellValues) {
Cell cell = row.createCell(cellIndex++);
if (firstCell == null) {
firstCell = cell;
}
cell.setCellValue(cellValue);
}
return firstCell;
}
private static void assertColumnWidthStrictlyWithinRange(final int actualColumnWidth, final int lowerBoundExclusive, final int upperBoundExclusive) {
assertTrue("Expected a column width greater than " + lowerBoundExclusive + " but found " + actualColumnWidth,
actualColumnWidth > lowerBoundExclusive);
assertTrue("Expected column width less than " + upperBoundExclusive + " but found " + actualColumnWidth, actualColumnWidth < upperBoundExclusive);
}
}

View File

@ -24,6 +24,7 @@ import org.apache.poi.ss.usermodel.PrintSetup;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.SXSSFITestDataProvider; import org.apache.poi.xssf.SXSSFITestDataProvider;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@ -33,6 +34,12 @@ public final class TestSXSSFBugs extends BaseTestBugzillaIssues {
super(SXSSFITestDataProvider.instance); super(SXSSFITestDataProvider.instance);
} }
@Override
protected void trackColumnsForAutoSizingIfSXSSF(Sheet sheet) {
SXSSFSheet sxSheet = (SXSSFSheet) sheet;
sxSheet.trackAllColumnsForAutoSizing();
}
// override some tests which do not work for SXSSF // override some tests which do not work for SXSSF
@Override @Ignore("cloneSheet() not implemented") @Test public void bug18800() { /* cloneSheet() not implemented */ } @Override @Ignore("cloneSheet() not implemented") @Test public void bug18800() { /* cloneSheet() not implemented */ }
@Override @Ignore("cloneSheet() not implemented") @Test public void bug22720() { /* cloneSheet() not implemented */ } @Override @Ignore("cloneSheet() not implemented") @Test public void bug22720() { /* cloneSheet() not implemented */ }

View File

@ -56,6 +56,10 @@ public abstract class BaseTestBugzillaIssues {
_testDataProvider = testDataProvider; _testDataProvider = testDataProvider;
} }
protected void trackColumnsForAutoSizingIfSXSSF(Sheet sheet) {
// do nothing for Sheet base class. This will be overridden for SXSSFSheets.
}
/** /**
* Unlike org.junit.Assert.assertEquals(double expected, double actual, double delta), * Unlike org.junit.Assert.assertEquals(double expected, double actual, double delta),
* where delta is an absolute error value, this function's factor is a relative error, * where delta is an absolute error value, this function's factor is a relative error,
@ -373,6 +377,7 @@ public abstract class BaseTestBugzillaIssues {
Workbook wb = _testDataProvider.createWorkbook(); Workbook wb = _testDataProvider.createWorkbook();
BaseTestSheetAutosizeColumn.fixFonts(wb); BaseTestSheetAutosizeColumn.fixFonts(wb);
Sheet sheet = wb.createSheet("Sheet1"); Sheet sheet = wb.createSheet("Sheet1");
trackColumnsForAutoSizingIfSXSSF(sheet);
Row row = sheet.createRow(0); Row row = sheet.createRow(0);
Cell cell0 = row.createCell(0); Cell cell0 = row.createCell(0);
@ -429,6 +434,7 @@ public abstract class BaseTestBugzillaIssues {
Workbook wb = _testDataProvider.createWorkbook(); Workbook wb = _testDataProvider.createWorkbook();
BaseTestSheetAutosizeColumn.fixFonts(wb); BaseTestSheetAutosizeColumn.fixFonts(wb);
Sheet sheet = wb.createSheet(); Sheet sheet = wb.createSheet();
trackColumnsForAutoSizingIfSXSSF(sheet);
Row row = sheet.createRow(0); Row row = sheet.createRow(0);
Cell cell0 = row.createCell(0); Cell cell0 = row.createCell(0);
Cell cell1 = row.createCell(1); Cell cell1 = row.createCell(1);
@ -664,6 +670,7 @@ public abstract class BaseTestBugzillaIssues {
d2Percent.setDataFormat(format.getFormat("0.00%")); d2Percent.setDataFormat(format.getFormat("0.00%"));
Sheet s = wb.createSheet(); Sheet s = wb.createSheet();
trackColumnsForAutoSizingIfSXSSF(s);
Row r1 = s.createRow(0); Row r1 = s.createRow(0);
for (int i=0; i<3; i++) { for (int i=0; i<3; i++) {

View File

@ -51,6 +51,10 @@ public abstract class BaseTestSheet {
_testDataProvider = testDataProvider; _testDataProvider = testDataProvider;
} }
protected void trackColumnsForAutoSizingIfSXSSF(Sheet sheet) {
// do nothing for Sheet base class. This will be overridden for SXSSFSheets.
}
@Test @Test
public void createRow() throws IOException { public void createRow() throws IOException {
Workbook workbook = _testDataProvider.createWorkbook(); Workbook workbook = _testDataProvider.createWorkbook();
@ -994,6 +998,7 @@ public abstract class BaseTestSheet {
public void bug48325() throws IOException { public void bug48325() throws IOException {
Workbook wb = _testDataProvider.createWorkbook(); Workbook wb = _testDataProvider.createWorkbook();
Sheet sheet = wb.createSheet("Test"); Sheet sheet = wb.createSheet("Test");
trackColumnsForAutoSizingIfSXSSF(sheet);
CreationHelper factory = wb.getCreationHelper(); CreationHelper factory = wb.getCreationHelper();
Row row = sheet.createRow(0); Row row = sheet.createRow(0);

View File

@ -57,12 +57,17 @@ public abstract class BaseTestSheetAutosizeColumn {
_testDataProvider = testDataProvider; _testDataProvider = testDataProvider;
} }
protected void trackColumnsForAutoSizingIfSXSSF(Sheet sheet) {
// do nothing for Sheet base class. This will be overridden for SXSSFSheets.
}
@Test @Test
public void numericCells() throws Exception { public void numericCells() throws Exception {
Workbook workbook = _testDataProvider.createWorkbook(); Workbook workbook = _testDataProvider.createWorkbook();
fixFonts(workbook); fixFonts(workbook);
DataFormat df = workbook.getCreationHelper().createDataFormat(); DataFormat df = workbook.getCreationHelper().createDataFormat();
Sheet sheet = workbook.createSheet(); Sheet sheet = workbook.createSheet();
trackColumnsForAutoSizingIfSXSSF(sheet);
Row row = sheet.createRow(0); Row row = sheet.createRow(0);
row.createCell(0).setCellValue(0); // getCachedFormulaResult() returns 0 for not evaluated formula cells row.createCell(0).setCellValue(0); // getCachedFormulaResult() returns 0 for not evaluated formula cells
@ -104,6 +109,7 @@ public abstract class BaseTestSheetAutosizeColumn {
Workbook workbook = _testDataProvider.createWorkbook(); Workbook workbook = _testDataProvider.createWorkbook();
fixFonts(workbook); fixFonts(workbook);
Sheet sheet = workbook.createSheet(); Sheet sheet = workbook.createSheet();
trackColumnsForAutoSizingIfSXSSF(sheet);
Row row = sheet.createRow(0); Row row = sheet.createRow(0);
row.createCell(0).setCellValue(0); // getCachedFormulaResult() returns 0 for not evaluated formula cells row.createCell(0).setCellValue(0); // getCachedFormulaResult() returns 0 for not evaluated formula cells
@ -135,6 +141,7 @@ public abstract class BaseTestSheetAutosizeColumn {
Workbook workbook = _testDataProvider.createWorkbook(); Workbook workbook = _testDataProvider.createWorkbook();
fixFonts(workbook); fixFonts(workbook);
Sheet sheet = workbook.createSheet(); Sheet sheet = workbook.createSheet();
trackColumnsForAutoSizingIfSXSSF(sheet);
DataFormat df = workbook.getCreationHelper().createDataFormat(); DataFormat df = workbook.getCreationHelper().createDataFormat();
CellStyle style1 = workbook.createCellStyle(); CellStyle style1 = workbook.createCellStyle();
@ -202,6 +209,7 @@ public abstract class BaseTestSheetAutosizeColumn {
Workbook workbook = _testDataProvider.createWorkbook(); Workbook workbook = _testDataProvider.createWorkbook();
fixFonts(workbook); fixFonts(workbook);
Sheet sheet = workbook.createSheet(); Sheet sheet = workbook.createSheet();
trackColumnsForAutoSizingIfSXSSF(sheet);
Row row = sheet.createRow(0); Row row = sheet.createRow(0);
Font defaultFont = workbook.getFontAt((short)0); Font defaultFont = workbook.getFontAt((short)0);
@ -237,6 +245,7 @@ public abstract class BaseTestSheetAutosizeColumn {
Workbook workbook = _testDataProvider.createWorkbook(); Workbook workbook = _testDataProvider.createWorkbook();
fixFonts(workbook); fixFonts(workbook);
Sheet sheet = workbook.createSheet(); Sheet sheet = workbook.createSheet();
trackColumnsForAutoSizingIfSXSSF(sheet);
Row row = sheet.createRow(0); Row row = sheet.createRow(0);
CellStyle style1 = workbook.createCellStyle(); CellStyle style1 = workbook.createCellStyle();
@ -264,6 +273,7 @@ public abstract class BaseTestSheetAutosizeColumn {
Workbook workbook = _testDataProvider.createWorkbook(); Workbook workbook = _testDataProvider.createWorkbook();
fixFonts(workbook); fixFonts(workbook);
Sheet sheet = workbook.createSheet(); Sheet sheet = workbook.createSheet();
trackColumnsForAutoSizingIfSXSSF(sheet);
Row row = sheet.createRow(0); Row row = sheet.createRow(0);
sheet.addMergedRegion(CellRangeAddress.valueOf("A1:B1")); sheet.addMergedRegion(CellRangeAddress.valueOf("A1:B1"));
@ -292,6 +302,7 @@ public abstract class BaseTestSheetAutosizeColumn {
Workbook workbook = _testDataProvider.createWorkbook(); Workbook workbook = _testDataProvider.createWorkbook();
fixFonts(workbook); fixFonts(workbook);
Sheet sheet = workbook.createSheet(); Sheet sheet = workbook.createSheet();
trackColumnsForAutoSizingIfSXSSF(sheet);
Row r0 = sheet.createRow(0); Row r0 = sheet.createRow(0);
r0.createCell(0).setCellValue("I am ROW 0"); r0.createCell(0).setCellValue("I am ROW 0");