[github-184] New EmittingSXSSFWorkbook. Thanks to mobreza. This closes #184

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1879302 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
PJ Fanning 2020-06-28 11:57:37 +00:00
parent 09d81ea7da
commit f06c45421b
10 changed files with 1138 additions and 213 deletions

View File

@ -0,0 +1,69 @@
/* ====================================================================
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.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.poi.util.Beta;
import org.apache.poi.xssf.usermodel.XSSFSheet;
/**
* A variant of SXSSFSheet that uses IRowGenerator to create rows.
*
* This variant is experimental and APIs may change at short notice.
*
* @see EmittingSXSSFWorkbook
* @since 5.0.0
*/
@Beta
public class EmittingSXSSFSheet extends SXSSFSheet {
private IRowGenerator rowGenerator;
public EmittingSXSSFSheet(EmittingSXSSFWorkbook workbook, XSSFSheet xSheet) throws IOException {
super(workbook, xSheet, workbook.getRandomAccessWindowSize());
}
@Override
public InputStream getWorksheetXMLInputStream() throws IOException {
throw new RuntimeException("Not supported by EmittingSXSSFSheet");
}
public void setRowGenerator(IRowGenerator rowGenerator) {
this.rowGenerator = rowGenerator;
}
public void writeRows(OutputStream out) throws IOException {
// delayed creation of SheetDataWriter
_writer = ((EmittingSXSSFWorkbook) _workbook).createSheetDataWriter(out);
try {
if (this.rowGenerator != null) {
this.rowGenerator.generateRows(this);
}
} catch (Exception e) {
throw new IOException("Error generating Excel rows", e);
} finally {
// flush buffered rows
flushRows(0);
// flush writer buffer
_writer.close();
out.flush();
}
}
}

View File

@ -0,0 +1,219 @@
/* ====================================================================
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.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.util.Beta;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
/**
* An variant of SXSSFWorkbook that avoids generating a temporary file and writes data directly to
* the provided OutputStream.
*
* This variant is experimental and APIs may change at short notice.
*
* @since 5.0.0
*/
@Beta
public class EmittingSXSSFWorkbook extends SXSSFWorkbook {
private static final POILogger logger = POILogFactory.getLogger(EmittingSXSSFWorkbook.class);
public EmittingSXSSFWorkbook() {
this(null);
}
public EmittingSXSSFWorkbook(XSSFWorkbook workbook) {
this(workbook, SXSSFWorkbook.DEFAULT_WINDOW_SIZE);
}
public EmittingSXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize) {
super(workbook, rowAccessWindowSize, false, false);
}
@Override
protected SheetDataWriter createSheetDataWriter() throws IOException {
throw new RuntimeException("Not supported by EmittingSXSSFWorkbook");
}
protected StreamingSheetWriter createSheetDataWriter(OutputStream out) throws IOException {
return new StreamingSheetWriter(out);
}
@Override
protected ISheetInjector createSheetInjector(SXSSFSheet sxSheet) throws IOException {
EmittingSXSSFSheet ssxSheet = (EmittingSXSSFSheet) sxSheet;
return (output) -> {
ssxSheet.writeRows(output);
};
}
@Override
SXSSFSheet createAndRegisterSXSSFSheet(XSSFSheet xSheet) {
final EmittingSXSSFSheet sxSheet;
try {
sxSheet = new EmittingSXSSFSheet(this, xSheet);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
registerSheetMapping(sxSheet, xSheet);
return sxSheet;
}
public EmittingSXSSFSheet createSheet() {
return (EmittingSXSSFSheet) super.createSheet();
}
public EmittingSXSSFSheet createSheet(String sheetname) {
return (EmittingSXSSFSheet) super.createSheet(sheetname);
}
/**
* Returns an iterator of the sheets in the workbook in sheet order. Includes hidden and very hidden sheets.
*
* @return an iterator of the sheets.
*/
@Override
public Iterator<Sheet> sheetIterator() {
return new SheetIterator<Sheet>();
}
private final class SheetIterator<T extends Sheet> implements Iterator<T> {
final private Iterator<XSSFSheet> it;
@SuppressWarnings("unchecked")
public SheetIterator() {
it = (Iterator<XSSFSheet>) (Iterator<? extends Sheet>) _wb.iterator();
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
@SuppressWarnings("unchecked")
public T next() throws NoSuchElementException {
final XSSFSheet xssfSheet = it.next();
EmittingSXSSFSheet sxSheet = (EmittingSXSSFSheet) getSXSSFSheet(xssfSheet);
return (T) (sxSheet == null ? xssfSheet : sxSheet);
}
/**
* Unexpected behavior may occur if sheets are reordered after iterator has been created. Support for the remove
* method may be added in the future if someone can figure out a reliable implementation.
*/
@Override
public void remove() throws IllegalStateException {
throw new UnsupportedOperationException("remove method not supported on XSSFWorkbook.iterator(). "
+ "Use Sheet.removeSheetAt(int) instead.");
}
}
/**
* Alias for {@link #sheetIterator()} to allow foreach loops
*/
@Override
public Iterator<Sheet> iterator() {
return sheetIterator();
}
@Override
public SXSSFSheet getSheetAt(int index) {
throw new RuntimeException("Not supported by EmittingSXSSFWorkbook");
}
public XSSFSheet getXSSFSheetAt(int index) {
return _wb.getSheetAt(index);
}
/**
* Gets the sheet at the given index for streaming.
*
* @param index the index
* @return the streaming sheet at
*/
public EmittingSXSSFSheet getStreamingSheetAt(int index) {
XSSFSheet xSheet = _wb.getSheetAt(index);
SXSSFSheet sxSheet = getSXSSFSheet(xSheet);
if (sxSheet == null && xSheet != null) {
return (EmittingSXSSFSheet) createAndRegisterSXSSFSheet(xSheet);
} else {
return (EmittingSXSSFSheet) sxSheet;
}
}
@Override
public SXSSFSheet getSheet(String name) {
throw new RuntimeException("Not supported by EmittingSXSSFWorkbook");
}
public XSSFSheet getXSSFSheet(String name) {
return _wb.getSheet(name);
}
/**
* Gets sheet with the given name for streaming.
*
* @param name the name
* @return the streaming sheet
*/
public EmittingSXSSFSheet getStreamingSheet(String name) {
XSSFSheet xSheet = _wb.getSheet(name);
EmittingSXSSFSheet sxSheet = (EmittingSXSSFSheet) getSXSSFSheet(xSheet);
if (sxSheet == null && xSheet != null) {
return (EmittingSXSSFSheet) createAndRegisterSXSSFSheet(xSheet);
} else {
return sxSheet;
}
}
/**
* Removes sheet at the given index
*
* @param index of the sheet to remove (0-based)
*/
@Override
public void removeSheetAt(int index) {
// Get the sheet to be removed
XSSFSheet xSheet = _wb.getSheetAt(index);
SXSSFSheet sxSheet = getSXSSFSheet(xSheet);
// De-register it
_wb.removeSheetAt(index);
// The sheet may not be a streaming sheet and is not mapped
if (sxSheet != null) {
deregisterSheetMapping(xSheet);
// Clean up temporary resources
try {
sxSheet.dispose();
} catch (IOException e) {
logger.log(POILogger.WARN, e);
}
}
}
}

View File

@ -0,0 +1,37 @@
/* ====================================================================
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 org.apache.poi.util.Beta;
/**
* IRowGenerator for Emitting SXSSF sheets
*
* @see EmittingSXSSFWorkbook
*/
@Beta
public interface IRowGenerator {
/**
* Generate and add rows to the sheet
*
* @param sheet the sheet
* @throws Exception the exception
*/
void generateRows(SXSSFSheet sheet) throws Exception;
}

View File

@ -106,12 +106,14 @@ public final class SXSSFFormulaEvaluator extends BaseXSSFFormulaEvaluator {
// Process the sheets as best we can // Process the sheets as best we can
for (Sheet sheet : wb) { for (Sheet sheet : wb) {
if (sheet instanceof SXSSFSheet) {
// Check if any rows have already been flushed out // Check if any rows have already been flushed out
int lastFlushedRowNum = ((SXSSFSheet) sheet).getLastFlushedRowNum(); int lastFlushedRowNum = ((SXSSFSheet) sheet).getLastFlushedRowNum();
if (lastFlushedRowNum > -1) { if (lastFlushedRowNum > -1) {
if (! skipOutOfWindow) throw new RowFlushedException(0); if (! skipOutOfWindow) throw new RowFlushedException(0);
logger.log(POILogger.INFO, "Rows up to " + lastFlushedRowNum + " have already been flushed, skipping"); logger.log(POILogger.INFO, "Rows up to " + lastFlushedRowNum + " have already been flushed, skipping");
} }
}
// Evaluate what we have // Evaluate what we have
for (Row r : sheet) { for (Row r : sheet) {

View File

@ -24,6 +24,7 @@ import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.ss.usermodel.Picture; import org.apache.poi.ss.usermodel.Picture;
import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Shape; import org.apache.poi.ss.usermodel.Shape;
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.ss.util.ImageUtils; import org.apache.poi.ss.util.ImageUtils;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
@ -201,7 +202,8 @@ public final class SXSSFPicture implements Picture {
// THE FOLLOWING THREE LINES ARE THE MAIN CHANGE compared to the non-streaming version: use the SXSSF sheet, // THE FOLLOWING THREE LINES ARE THE MAIN CHANGE compared to the non-streaming version: use the SXSSF sheet,
// not the XSSF sheet (which never contais rows when using SXSSF) // not the XSSF sheet (which never contais rows when using SXSSF)
XSSFSheet xssfSheet = getSheet(); XSSFSheet xssfSheet = getSheet();
SXSSFSheet sheet = _wb.getSXSSFSheet(xssfSheet); SXSSFSheet sxSheet = _wb.getSXSSFSheet(xssfSheet);
Sheet sheet = sxSheet == null ? xssfSheet : sxSheet;
Row row = sheet.getRow(rowIndex); Row row = sheet.getRow(rowIndex);
float height = row != null ? row.getHeightInPoints() : sheet.getDefaultRowHeightInPoints(); float height = row != null ? row.getHeightInPoints() : sheet.getDefaultRowHeightInPoints();
return height * Units.PIXEL_DPI / Units.POINT_DPI; return height * Units.PIXEL_DPI / Units.POINT_DPI;

View File

@ -65,15 +65,22 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet;
public class SXSSFSheet implements Sheet public class SXSSFSheet implements Sheet
{ {
/*package*/ final XSSFSheet _sh; /*package*/ final XSSFSheet _sh;
private final SXSSFWorkbook _workbook; protected final SXSSFWorkbook _workbook;
private final TreeMap<Integer,SXSSFRow> _rows = new TreeMap<>(); private final TreeMap<Integer,SXSSFRow> _rows = new TreeMap<>();
private final SheetDataWriter _writer; protected SheetDataWriter _writer;
private int _randomAccessWindowSize = SXSSFWorkbook.DEFAULT_WINDOW_SIZE; private int _randomAccessWindowSize = SXSSFWorkbook.DEFAULT_WINDOW_SIZE;
private final AutoSizeColumnTracker _autoSizeColumnTracker; protected final AutoSizeColumnTracker _autoSizeColumnTracker;
private int outlineLevelRow; private int outlineLevelRow;
private int lastFlushedRowNumber = -1; private int lastFlushedRowNumber = -1;
private boolean allFlushed; private boolean allFlushed;
protected SXSSFSheet(SXSSFWorkbook workbook, XSSFSheet xSheet, int randomAccessWindowSize) {
_workbook = workbook;
_sh = xSheet;
setRandomAccessWindowSize(randomAccessWindowSize);
_autoSizeColumnTracker = new AutoSizeColumnTracker(this);
}
public SXSSFSheet(SXSSFWorkbook workbook, XSSFSheet xSheet) throws IOException { public SXSSFSheet(SXSSFWorkbook workbook, XSSFSheet xSheet) throws IOException {
_workbook = workbook; _workbook = workbook;
_sh = xSheet; _sh = xSheet;
@ -1904,7 +1911,7 @@ public class SXSSFSheet implements Sheet
if (!allFlushed) { if (!allFlushed) {
flushRows(); flushRows();
} }
return _writer.dispose(); return _writer == null || _writer.dispose();
} }
@Override @Override

View File

@ -96,13 +96,17 @@ public class SXSSFWorkbook implements Workbook {
public static final int DEFAULT_WINDOW_SIZE = 100; public static final int DEFAULT_WINDOW_SIZE = 100;
private static final POILogger logger = POILogFactory.getLogger(SXSSFWorkbook.class); private static final POILogger logger = POILogFactory.getLogger(SXSSFWorkbook.class);
private final XSSFWorkbook _wb; protected final XSSFWorkbook _wb;
private final Map<SXSSFSheet,XSSFSheet> _sxFromXHash = new HashMap<>(); private final Map<SXSSFSheet,XSSFSheet> _sxFromXHash = new HashMap<>();
private final Map<XSSFSheet,SXSSFSheet> _xFromSxHash = new HashMap<>(); private final Map<XSSFSheet,SXSSFSheet> _xFromSxHash = new HashMap<>();
private int _randomAccessWindowSize = DEFAULT_WINDOW_SIZE; private int _randomAccessWindowSize = DEFAULT_WINDOW_SIZE;
protected static interface ISheetInjector {
void writeSheetData(OutputStream out) throws IOException;
}
/** /**
* whether temp files should be compressed. * whether temp files should be compressed.
*/ */
@ -111,12 +115,12 @@ public class SXSSFWorkbook implements Workbook {
/** /**
* shared string table - a cache of strings in this workbook * shared string table - a cache of strings in this workbook
*/ */
private final SharedStringsTable _sharedStringSource; protected final SharedStringsTable _sharedStringSource;
/** /**
* controls whether Zip64 mode is used - Always became the default in POI 5.0.0 * controls whether Zip64 mode is used - Always became the default in POI 5.0.0
*/ */
private Zip64Mode zip64Mode = Zip64Mode.Always; protected Zip64Mode zip64Mode = Zip64Mode.Always;
/** /**
* Construct a new workbook with default row window size * Construct a new workbook with default row window size
@ -285,7 +289,7 @@ public class SXSSFWorkbook implements Workbook {
return _randomAccessWindowSize; return _randomAccessWindowSize;
} }
private void setRandomAccessWindowSize(int rowAccessWindowSize) { protected void setRandomAccessWindowSize(int rowAccessWindowSize) {
if(rowAccessWindowSize == 0 || rowAccessWindowSize < -1) { if(rowAccessWindowSize == 0 || rowAccessWindowSize < -1) {
throw new IllegalArgumentException("rowAccessWindowSize must be greater than 0 or -1"); throw new IllegalArgumentException("rowAccessWindowSize must be greater than 0 or -1");
} }
@ -377,7 +381,7 @@ public class SXSSFWorkbook implements Workbook {
_xFromSxHash.remove(xSheet); _xFromSxHash.remove(xSheet);
} }
private XSSFSheet getSheetFromZipEntryName(String sheetRef) protected XSSFSheet getSheetFromZipEntryName(String sheetRef)
{ {
for(XSSFSheet sheet : _sxFromXHash.values()) for(XSSFSheet sheet : _sxFromXHash.values())
{ {
@ -408,9 +412,7 @@ public class SXSSFWorkbook implements Workbook {
// See bug 56557, we should not inject data into the special ChartSheets // See bug 56557, we should not inject data into the special ChartSheets
if (xSheet != null && !(xSheet instanceof XSSFChartSheet)) { if (xSheet != null && !(xSheet instanceof XSSFChartSheet)) {
SXSSFSheet sxSheet = getSXSSFSheet(xSheet); SXSSFSheet sxSheet = getSXSSFSheet(xSheet);
try (InputStream xis = sxSheet.getWorksheetXMLInputStream()) { copyStreamAndInjectWorksheet(is, zos, createSheetInjector(sxSheet));
copyStreamAndInjectWorksheet(is, zos, xis);
}
} else { } else {
IOUtils.copy(is, zos); IOUtils.copy(is, zos);
} }
@ -434,7 +436,17 @@ public class SXSSFWorkbook implements Workbook {
} }
} }
private static void copyStreamAndInjectWorksheet(InputStream in, OutputStream out, InputStream worksheetData) throws IOException { protected ISheetInjector createSheetInjector(SXSSFSheet sxSheet) throws IOException {
return (output) -> {
try (InputStream xis = sxSheet.getWorksheetXMLInputStream()) {
// Copy the worksheet data to "output".
IOUtils.copy(xis, output);
}
};
}
// private static void copyStreamAndInjectWorksheet(InputStream in, OutputStream out, InputStream worksheetData) throws IOException {
private static void copyStreamAndInjectWorksheet(InputStream in, OutputStream out, ISheetInjector sheetInjector) throws IOException {
InputStreamReader inReader = new InputStreamReader(in, StandardCharsets.UTF_8); InputStreamReader inReader = new InputStreamReader(in, StandardCharsets.UTF_8);
OutputStreamWriter outWriter = new OutputStreamWriter(out, StandardCharsets.UTF_8); OutputStreamWriter outWriter = new OutputStreamWriter(out, StandardCharsets.UTF_8);
boolean needsStartTag = true; boolean needsStartTag = true;
@ -526,8 +538,7 @@ public class SXSSFWorkbook implements Workbook {
outWriter.write("<sheetData>\n"); outWriter.write("<sheetData>\n");
outWriter.flush(); outWriter.flush();
} }
//Copy the worksheet data to "out". sheetInjector.writeSheetData(out);
IOUtils.copy(worksheetData,out);
outWriter.write("</sheetData>"); outWriter.write("</sheetData>");
outWriter.flush(); outWriter.flush();
//Copy the rest of "in" to "out". //Copy the rest of "in" to "out".
@ -918,7 +929,8 @@ public class SXSSFWorkbook implements Workbook {
for (SXSSFSheet sheet : _xFromSxHash.values()) for (SXSSFSheet sheet : _xFromSxHash.values())
{ {
try { try {
sheet.getSheetDataWriter().close(); SheetDataWriter _writer = sheet.getSheetDataWriter();
if (_writer != null) _writer.close();
} catch (IOException e) { } catch (IOException e) {
logger.log(POILogger.WARN, logger.log(POILogger.WARN,
"An exception occurred while closing sheet data writer for sheet " "An exception occurred while closing sheet data writer for sheet "

View File

@ -55,7 +55,7 @@ public class SheetDataWriter implements Closeable {
private static final POILogger logger = POILogFactory.getLogger(SheetDataWriter.class); private static final POILogger logger = POILogFactory.getLogger(SheetDataWriter.class);
private final File _fd; private final File _fd;
private final Writer _out; protected final Writer _out;
private int _rownum; private int _rownum;
private int _numberOfFlushedRows; private int _numberOfFlushedRows;
private int _lowestIndexOfFlushedRows; // meaningful only of _numberOfFlushedRows>0 private int _lowestIndexOfFlushedRows; // meaningful only of _numberOfFlushedRows>0
@ -73,6 +73,11 @@ public class SheetDataWriter implements Closeable {
_out = createWriter(_fd); _out = createWriter(_fd);
} }
public SheetDataWriter(Writer writer) throws IOException {
_fd = null;
_out = writer;
}
public SheetDataWriter(SharedStringsTable sharedStringsTable) throws IOException { public SheetDataWriter(SharedStringsTable sharedStringsTable) throws IOException {
this(); this();
this._sharedStringSource = sharedStringsTable; this._sharedStringSource = sharedStringsTable;

View File

@ -0,0 +1,84 @@
/* ====================================================================
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.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import org.apache.poi.util.Beta;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
/**
* Unlike SheetDataWriter, this writer does not create a temporary file, it writes data directly
* to the provided OutputStream.
* @since 5.0.0
*/
@Beta
public class StreamingSheetWriter extends SheetDataWriter {
private static final POILogger logger = POILogFactory.getLogger(StreamingSheetWriter.class);
public StreamingSheetWriter() throws IOException {
throw new RuntimeException("StreamingSheetWriter requires OutputStream");
}
public StreamingSheetWriter(OutputStream out) throws IOException {
super(createWriter(out));
logger.log(POILogger.DEBUG, "Preparing SSXSSF sheet writer");
}
@Override
public File createTempFile() throws IOException {
throw new RuntimeException("Not supported with StreamingSheetWriter");
}
@Override
public Writer createWriter(File fd) throws IOException {
throw new RuntimeException("Not supported with StreamingSheetWriter");
}
/**
* Create a writer for the sheet data.
*
* @param out the output stream to write to
*/
protected static Writer createWriter(OutputStream out) throws IOException {
return new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
}
@Override
public void close() throws IOException {
_out.flush();
}
@Override
public InputStream getWorksheetXMLInputStream() throws IOException {
throw new RuntimeException("Not supported with StreamingSheetWriter");
}
@Override
boolean dispose() throws IOException {
_out.close();
return true;
}
}

View File

@ -0,0 +1,488 @@
/*
* ====================================================================
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import org.apache.poi.POIDataSamples;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.ss.usermodel.BaseTestXWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.NullOutputStream;
import org.apache.poi.xssf.SXSSFITestDataProvider;
import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Test;
public final class TestEmittingSXSSFWorkbook extends BaseTestXWorkbook {
public TestEmittingSXSSFWorkbook() {
super(SXSSFITestDataProvider.instance);
}
@After
public void tearDown() {
((SXSSFITestDataProvider) _testDataProvider).cleanup();
}
/**
* cloning of sheets is not supported in SXSSF
*/
@Override
@Test
public void cloneSheet() throws IOException {
try {
super.cloneSheet();
fail("expected exception");
} catch (RuntimeException e) {
assertEquals("Not Implemented", e.getMessage());
}
}
/**
* cloning of sheets is not supported in SXSSF
*/
@Override
@Test
public void sheetClone() throws IOException {
try {
super.sheetClone();
fail("expected exception");
} catch (RuntimeException e) {
assertEquals("Not Implemented", e.getMessage());
}
}
/**
* Skip this test, as SXSSF doesn't update formulas on sheet name changes.
*/
@Override
@Ignore("SXSSF doesn't update formulas on sheet name changes, as most cells probably aren't in memory at the time")
@Test
public void setSheetName() {
}
@Test
public void existingWorkbook() throws IOException {
XSSFWorkbook xssfWb1 = new XSSFWorkbook();
xssfWb1.createSheet("S1");
EmittingSXSSFWorkbook wb1 = new EmittingSXSSFWorkbook(xssfWb1);
XSSFWorkbook xssfWb2 = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb1);
assertTrue(wb1.dispose());
EmittingSXSSFWorkbook wb2 = new EmittingSXSSFWorkbook(xssfWb2);
assertEquals(1, wb2.getNumberOfSheets());
Sheet sheet = wb2.getStreamingSheetAt(0);
assertNotNull(sheet);
assertEquals("S1", sheet.getSheetName());
assertTrue(wb2.dispose());
xssfWb2.close();
xssfWb1.close();
wb2.close();
wb1.close();
}
@Test
public void useSharedStringsTable() throws Exception {
// not supported with EmittingSXSSF
}
@Test
public void addToExistingWorkbook() throws IOException {
XSSFWorkbook xssfWb1 = new XSSFWorkbook();
xssfWb1.createSheet("S1");
Sheet sheet = xssfWb1.createSheet("S2");
Row row = sheet.createRow(1);
Cell cell = row.createCell(1);
cell.setCellValue("value 2_1_1");
EmittingSXSSFWorkbook wb1 = new EmittingSXSSFWorkbook(xssfWb1);
XSSFWorkbook xssfWb2 = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb1);
assertTrue(wb1.dispose());
xssfWb1.close();
EmittingSXSSFWorkbook wb2 = new EmittingSXSSFWorkbook(xssfWb2);
// Add a row to the existing empty sheet
EmittingSXSSFSheet ssheet1 = wb2.getStreamingSheetAt(0);
ssheet1.setRowGenerator((ssxSheet) -> {
Row row1_1 = ssxSheet.createRow(1);
Cell cell1_1_1 = row1_1.createCell(1);
cell1_1_1.setCellValue("value 1_1_1");
});
// Add a row to the existing non-empty sheet
EmittingSXSSFSheet ssheet2 = wb2.getStreamingSheetAt(1);
ssheet2.setRowGenerator((ssxSheet) -> {
Row row2_2 = ssxSheet.createRow(2);
Cell cell2_2_1 = row2_2.createCell(1);
cell2_2_1.setCellValue("value 2_2_1");
});
// Add a sheet with one row
EmittingSXSSFSheet ssheet3 = wb2.createSheet("S3");
ssheet3.setRowGenerator((ssxSheet) -> {
Row row3_1 = ssxSheet.createRow(1);
Cell cell3_1_1 = row3_1.createCell(1);
cell3_1_1.setCellValue("value 3_1_1");
});
XSSFWorkbook xssfWb3 = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb2);
wb2.close();
assertEquals(3, xssfWb3.getNumberOfSheets());
// Verify sheet 1
XSSFSheet sheet1 = xssfWb3.getSheetAt(0);
assertEquals("S1", sheet1.getSheetName());
assertEquals(1, sheet1.getPhysicalNumberOfRows());
XSSFRow row1_1 = sheet1.getRow(1);
assertNotNull(row1_1);
XSSFCell cell1_1_1 = row1_1.getCell(1);
assertNotNull(cell1_1_1);
assertEquals("value 1_1_1", cell1_1_1.getStringCellValue());
// Verify sheet 2
XSSFSheet sheet2 = xssfWb3.getSheetAt(1);
assertEquals("S2", sheet2.getSheetName());
assertEquals(2, sheet2.getPhysicalNumberOfRows());
Row row2_1 = sheet2.getRow(1);
assertNotNull(row2_1);
Cell cell2_1_1 = row2_1.getCell(1);
assertNotNull(cell2_1_1);
assertEquals("value 2_1_1", cell2_1_1.getStringCellValue());
XSSFRow row2_2 = sheet2.getRow(2);
assertNotNull(row2_2);
XSSFCell cell2_2_1 = row2_2.getCell(1);
assertNotNull(cell2_2_1);
assertEquals("value 2_2_1", cell2_2_1.getStringCellValue());
// Verify sheet 3
XSSFSheet sheet3 = xssfWb3.getSheetAt(2);
assertEquals("S3", sheet3.getSheetName());
assertEquals(1, sheet3.getPhysicalNumberOfRows());
XSSFRow row3_1 = sheet3.getRow(1);
assertNotNull(row3_1);
XSSFCell cell3_1_1 = row3_1.getCell(1);
assertNotNull(cell3_1_1);
assertEquals("value 3_1_1", cell3_1_1.getStringCellValue());
xssfWb2.close();
xssfWb3.close();
wb1.close();
}
@Test
public void sheetdataWriter() throws IOException {
EmittingSXSSFWorkbook wb = new EmittingSXSSFWorkbook();
SXSSFSheet sh = wb.createSheet();
assertSame(sh.getClass(), EmittingSXSSFSheet.class);
SheetDataWriter wr = sh.getSheetDataWriter();
assertNull(wr);
wb.close();
}
@Test
public void gzipSheetdataWriter() throws IOException {
EmittingSXSSFWorkbook wb = new EmittingSXSSFWorkbook();
wb.setCompressTempFiles(true);
final int rowNum = 1000;
final int sheetNum = 5;
populateData(wb, 1000, 5);
XSSFWorkbook xwb = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb);
for (int i = 0; i < sheetNum; i++) {
Sheet sh = xwb.getSheetAt(i);
assertEquals("sheet" + i, sh.getSheetName());
for (int j = 0; j < rowNum; j++) {
Row row = sh.getRow(j);
assertNotNull("row[" + j + "]", row);
Cell cell1 = row.getCell(0);
assertEquals(new CellReference(cell1).formatAsString(), cell1.getStringCellValue());
Cell cell2 = row.getCell(1);
assertEquals(i, (int) cell2.getNumericCellValue());
Cell cell3 = row.getCell(2);
assertEquals(j, (int) cell3.getNumericCellValue());
}
}
assertTrue(wb.dispose());
xwb.close();
wb.close();
}
private static void assertWorkbookDispose(EmittingSXSSFWorkbook wb) {
populateData(wb, 1000, 5);
for (Sheet sheet : wb) {
EmittingSXSSFSheet sxSheet = (EmittingSXSSFSheet) sheet;
assertNull(sxSheet.getSheetDataWriter());
}
assertTrue(wb.dispose());
for (Sheet sheet : wb) {
EmittingSXSSFSheet sxSheet = (EmittingSXSSFSheet) sheet;
assertNull(sxSheet.getSheetDataWriter());
}
}
private static void populateData(EmittingSXSSFWorkbook wb, final int rowNum, final int sheetNum) {
for (int i = 0; i < sheetNum; i++) {
EmittingSXSSFSheet sheet = wb.createSheet("sheet" + i);
int index = i;
sheet.setRowGenerator((sh) -> {
for (int j = 0; j < rowNum; j++) {
Row row = sh.createRow(j);
Cell cell1 = row.createCell(0);
cell1.setCellValue(new CellReference(cell1).formatAsString());
Cell cell2 = row.createCell(1);
cell2.setCellValue(index);
Cell cell3 = row.createCell(2);
cell3.setCellValue(j);
}
});
}
}
@Test
public void workbookDispose() throws IOException {
EmittingSXSSFWorkbook wb1 = new EmittingSXSSFWorkbook();
// the underlying writer is SheetDataWriter
assertWorkbookDispose(wb1);
wb1.close();
EmittingSXSSFWorkbook wb2 = new EmittingSXSSFWorkbook();
wb2.setCompressTempFiles(true);
// the underlying writer is GZIPSheetDataWriter
assertWorkbookDispose(wb2);
wb2.close();
}
@Ignore("currently writing the same sheet multiple times is not supported...")
@Test
public void bug53515() throws Exception {
Workbook wb1 = new SXSSFWorkbook(10);
populateWorkbook(wb1);
saveTwice(wb1);
Workbook wb2 = new XSSFWorkbook();
populateWorkbook(wb2);
saveTwice(wb2);
wb2.close();
wb1.close();
}
@Ignore("Crashes the JVM because of documented JVM behavior with concurrent writing/reading of zip-files, "
+ "see http://www.oracle.com/technetwork/java/javase/documentation/overview-156328.html")
@Test
public void bug53515a() throws Exception {
File out = new File("Test.xlsx");
assertTrue(!out.exists() || out.delete());
for (int i = 0; i < 2; i++) {
final SXSSFWorkbook wb;
if (out.exists()) {
wb = new SXSSFWorkbook((XSSFWorkbook) WorkbookFactory.create(out));
} else {
wb = new SXSSFWorkbook(10);
}
try {
FileOutputStream outSteam = new FileOutputStream(out);
if (i == 0) {
populateWorkbook(wb);
} else {
System.gc();
System.gc();
System.gc();
}
wb.write(outSteam);
// assertTrue(wb.dispose());
outSteam.close();
} finally {
assertTrue(wb.dispose());
}
wb.close();
}
assertTrue(out.exists());
assertTrue(out.delete());
}
private static void populateWorkbook(Workbook wb) {
Sheet sh = wb.createSheet();
for (int rownum = 0; rownum < 100; rownum++) {
Row row = sh.createRow(rownum);
for (int cellnum = 0; cellnum < 10; cellnum++) {
Cell cell = row.createCell(cellnum);
String address = new CellReference(cell).formatAsString();
cell.setCellValue(address);
}
}
}
private static void saveTwice(Workbook wb) throws Exception {
for (int i = 0; i < 2; i++) {
try {
NullOutputStream out = new NullOutputStream();
wb.write(out);
out.close();
} catch (Exception e) {
throw new Exception("ERROR: failed on " + (i + 1) + "th time calling " + wb.getClass().getName()
+ ".write() with exception " + e.getMessage(), e);
}
}
}
@Test
public void closeDoesNotModifyWorkbook() throws IOException {
final String filename = "SampleSS.xlsx";
final File file = POIDataSamples.getSpreadSheetInstance().getFile(filename);
// Some tests commented out because close() modifies the file
// See bug 58779
// String
// wb = new SXSSFWorkbook(new XSSFWorkbook(file.getPath()));
// assertCloseDoesNotModifyFile(filename, wb);
// File
// wb = new SXSSFWorkbook(new XSSFWorkbook(file));
// assertCloseDoesNotModifyFile(filename, wb);
// InputStream
try (FileInputStream fis = new FileInputStream(file);
XSSFWorkbook xwb = new XSSFWorkbook(fis);
SXSSFWorkbook wb = new SXSSFWorkbook(xwb)) {
assertCloseDoesNotModifyFile(filename, wb);
}
// OPCPackage
// wb = new SXSSFWorkbook(new XSSFWorkbook(OPCPackage.open(file)));
// assertCloseDoesNotModifyFile(filename, wb);
}
/**
* Bug #59743
*
* this is only triggered on other files apart of sheet[1,2,...].xml as those are either copied uncompressed or with
* the use of GZIPInputStream so we use shared strings
*/
@Test
public void testZipBombNotTriggeredOnUselessContent() throws IOException {
SXSSFWorkbook swb = new SXSSFWorkbook(null, 1, true, true);
SXSSFSheet s = swb.createSheet();
char[] useless = new char[32767];
Arrays.fill(useless, ' ');
for (int row = 0; row < 1; row++) {
Row r = s.createRow(row);
for (int col = 0; col < 10; col++) {
char[] prefix = Integer.toHexString(row * 1000 + col).toCharArray();
Arrays.fill(useless, 0, 10, ' ');
System.arraycopy(prefix, 0, useless, 0, prefix.length);
String ul = new String(useless);
r.createCell(col, CellType.STRING).setCellValue(ul);
}
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
swb.write(bos);
swb.dispose();
swb.close();
}
/**
* To avoid accident changes to the template, you should be able to create a SXSSFWorkbook from a read-only XSSF
* one, then change + save that (only). See bug #60010 TODO Fix this to work!
*/
@Test
@Ignore
public void createFromReadOnlyWorkbook() throws Exception {
String sheetName = "Test SXSSF";
File input = XSSFTestDataSamples.getSampleFile("sample.xlsx");
try (OPCPackage pkg = OPCPackage.open(input, PackageAccess.READ)) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (XSSFWorkbook xssf = new XSSFWorkbook(pkg)) {
try (SXSSFWorkbook wb = new SXSSFWorkbook(xssf, 2)) {
Sheet s = wb.createSheet(sheetName);
for (int i = 0; i < 10; i++) {
Row r = s.createRow(i);
r.createCell(0).setCellValue(true);
r.createCell(1).setCellValue(2.4);
r.createCell(2).setCellValue("Test Row " + i);
}
assertEquals(10, s.getLastRowNum());
wb.write(bos);
wb.dispose();
}
}
try (XSSFWorkbook xssf = new XSSFWorkbook(new ByteArrayInputStream(bos.toByteArray()))) {
Sheet s = xssf.getSheet(sheetName);
assertEquals(10, s.getLastRowNum());
assertTrue(s.getRow(0).getCell(0).getBooleanCellValue());
assertEquals("Test Row 9", s.getRow(9).getCell(2).getStringCellValue());
}
}
}
@Test
public void test56557() throws IOException {
Workbook wb = XSSFTestDataSamples.openSampleWorkbook("56557.xlsx");
// Using streaming XSSFWorkbook makes the output file invalid
wb = new SXSSFWorkbook(((XSSFWorkbook) wb));
// Should not throw POIXMLException: java.io.IOException: Unable to parse xml bean when reading back
Workbook wbBack = XSSFTestDataSamples.writeOutAndReadBack(wb);
assertNotNull(wbBack);
wbBack.close();
wb.close();
}
}