aboutsummaryrefslogtreecommitdiffstats
path: root/poi
diff options
context:
space:
mode:
Diffstat (limited to 'poi')
-rw-r--r--poi/build.gradle2
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java2
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/extractor/ExcelExtractor.java2
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/record/EscherAggregate.java6
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/record/TextObjectRecord.java2
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/record/WriteAccessRecord.java14
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCell.java10
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java19
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFName.java2
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFSheet.java2
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java5
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/usermodel/HeaderFooter.java2
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java16
-rw-r--r--poi/src/main/java/org/apache/poi/hssf/util/HSSFColor.java66
-rw-r--r--poi/src/main/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java17
-rw-r--r--poi/src/main/java/org/apache/poi/poifs/filesystem/DocumentDescriptor.java2
-rw-r--r--poi/src/main/java/org/apache/poi/poifs/property/PropertyTable.java13
-rw-r--r--poi/src/main/java/org/apache/poi/ss/format/CellFormatPart.java6
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/EvaluationConditionalFormatRule.java2
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/FormulaParser.java2
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/WorkbookEvaluator.java2
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java62
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/atp/TextJoinFunction.java2
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java6
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java2
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/functions/Code.java2
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/functions/Complex.java2
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/functions/Countif.java2
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/functions/Imaginary.java2
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/functions/Rept.java3
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/functions/Sheet.java89
-rw-r--r--poi/src/main/java/org/apache/poi/ss/formula/functions/Value.java2
-rw-r--r--poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java43
-rw-r--r--poi/src/main/java/org/apache/poi/ss/util/AreaReference.java4
-rw-r--r--poi/src/main/java/org/apache/poi/ss/util/CellReference.java10
-rw-r--r--poi/src/main/java/org/apache/poi/ss/util/CellUtil.java62
-rw-r--r--poi/src/main/java/org/apache/poi/ss/util/PropertyTemplate.java47
-rw-r--r--poi/src/main/java/org/apache/poi/util/DefaultTempFileCreationStrategy.java22
-rw-r--r--poi/src/main/java/org/apache/poi/util/IOUtils.java36
-rw-r--r--poi/src/main/java/org/apache/poi/util/TempFile.java64
-rw-r--r--poi/src/test/java/org/apache/poi/POIDataSamples.java2
-rw-r--r--poi/src/test/java/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java4
-rw-r--r--poi/src/test/java/org/apache/poi/hssf/model/TestDrawingAggregate.java12
-rw-r--r--poi/src/test/java/org/apache/poi/hssf/model/TestEscherRecordFactory.java6
-rw-r--r--poi/src/test/java/org/apache/poi/hssf/model/TestWorkbook.java54
-rw-r--r--poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java6
-rw-r--r--poi/src/test/java/org/apache/poi/hssf/record/TestWriteAccessRecord.java43
-rw-r--r--poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java6
-rw-r--r--poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java26
-rw-r--r--poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRowCopyRowFrom.java130
-rw-r--r--poi/src/test/java/org/apache/poi/hssf/util/TestHSSFColor.java12
-rw-r--r--poi/src/test/java/org/apache/poi/ss/formula/atp/TestAnalysisToolPak.java75
-rw-r--r--poi/src/test/java/org/apache/poi/ss/formula/function/ExcelCetabFunctionExtractor.java2
-rw-r--r--poi/src/test/java/org/apache/poi/ss/formula/function/ExcelFileFormatDocFunctionExtractor.java16
-rw-r--r--poi/src/test/java/org/apache/poi/ss/formula/functions/TestNumericFunction.java10
-rw-r--r--poi/src/test/java/org/apache/poi/ss/formula/functions/TestSheet.java89
-rw-r--r--poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestCell.java35
-rw-r--r--poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestConditionalFormatting.java2
-rw-r--r--poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java2
-rw-r--r--poi/src/test/java/org/apache/poi/ss/usermodel/TestExcelStyleDateFormatter.java17
-rw-r--r--poi/src/test/java/org/apache/poi/ss/util/BaseTestCellUtil.java5
-rw-r--r--poi/src/test/java/org/apache/poi/ss/util/TestCellUtil.java36
-rw-r--r--poi/src/test/java/org/apache/poi/ss/util/TestDateFormatConverter.java2
-rw-r--r--poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java106
-rw-r--r--poi/src/test/java/org/apache/poi/util/ExceptionUtilTest.java2
-rw-r--r--poi/src/test/java/org/apache/poi/util/TestIOUtils.java21
-rw-r--r--poi/src/test/java/org/apache/poi/util/TestThreadLocalTempFile.java68
67 files changed, 1212 insertions, 233 deletions
diff --git a/poi/build.gradle b/poi/build.gradle
index b755139a1d..2b1076840e 100644
--- a/poi/build.gradle
+++ b/poi/build.gradle
@@ -37,7 +37,7 @@ sourceSets {
dependencies {
api "commons-codec:commons-codec:${commonsCodecVersion}"
- api 'org.apache.commons:commons-collections4:4.4'
+ api 'org.apache.commons:commons-collections4:4.5.0'
api "org.apache.commons:commons-math3:${commonsMathVersion}"
api "commons-io:commons-io:${commonsIoVersion}"
api 'com.zaxxer:SparseBitSet:1.3'
diff --git a/poi/src/main/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java b/poi/src/main/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java
index f9af178a5a..aa58173db6 100644
--- a/poi/src/main/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java
+++ b/poi/src/main/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java
@@ -58,7 +58,7 @@ import org.apache.poi.poifs.filesystem.POIFSFileSystem;
* the XLS2CSVmra example
* </p>
*
- * @see <a href="http://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java">XLS2CSVmra</a>
+ * @see <a href="https://github.com/apache/poi/blob/trunk/poi-examples/src/main/java/org/apache/poi/examples/hssf/eventusermodel/XLS2CSVmra.java">XLS2CSVmra</a>
*/
public class EventBasedExcelExtractor implements POIOLE2TextExtractor, org.apache.poi.ss.extractor.ExcelExtractor {
private final POIFSFileSystem poifs;
diff --git a/poi/src/main/java/org/apache/poi/hssf/extractor/ExcelExtractor.java b/poi/src/main/java/org/apache/poi/hssf/extractor/ExcelExtractor.java
index 3f413b0643..fc0e7aa43c 100644
--- a/poi/src/main/java/org/apache/poi/hssf/extractor/ExcelExtractor.java
+++ b/poi/src/main/java/org/apache/poi/hssf/extractor/ExcelExtractor.java
@@ -51,7 +51,7 @@ import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
* the XLS2CSVmra example
* </p>
*
- * @see <a href="http://svn.apache.org/repos/asf/poi/trunk/poi-examples/src/main/java/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java">XLS2CSVmra</a>
+ * @see <a href="https://github.com/apache/poi/blob/trunk/poi-examples/src/main/java/org/apache/poi/examples/hssf/eventusermodel/XLS2CSVmra.java">XLS2CSVmra</a>
*/
public class ExcelExtractor implements POIOLE2TextExtractor, org.apache.poi.ss.extractor.ExcelExtractor {
private final HSSFWorkbook _wb;
diff --git a/poi/src/main/java/org/apache/poi/hssf/record/EscherAggregate.java b/poi/src/main/java/org/apache/poi/hssf/record/EscherAggregate.java
index 2c71372ee9..67beb21207 100644
--- a/poi/src/main/java/org/apache/poi/hssf/record/EscherAggregate.java
+++ b/poi/src/main/java/org/apache/poi/hssf/record/EscherAggregate.java
@@ -1037,11 +1037,7 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
final UnsynchronizedByteArrayOutputStream buffer = UnsynchronizedByteArrayOutputStream.builder().get();
void addBytes(byte[] data) {
- try {
- buffer.write(data);
- } catch (IOException e) {
- throw new IllegalStateException("Couldn't get data from drawing/continue records", e);
- }
+ buffer.write(data);
}
@Override
diff --git a/poi/src/main/java/org/apache/poi/hssf/record/TextObjectRecord.java b/poi/src/main/java/org/apache/poi/hssf/record/TextObjectRecord.java
index 17bcce71e7..d4a303f873 100644
--- a/poi/src/main/java/org/apache/poi/hssf/record/TextObjectRecord.java
+++ b/poi/src/main/java/org/apache/poi/hssf/record/TextObjectRecord.java
@@ -212,7 +212,7 @@ public final class TextObjectRecord extends ContinuableRecord {
protected void serialize(ContinuableRecordOutput out) {
serializeTXORecord(out);
- if (_text.getString().length() > 0) {
+ if (!_text.getString().isEmpty()) {
serializeTrailingRecords(out);
}
}
diff --git a/poi/src/main/java/org/apache/poi/hssf/record/WriteAccessRecord.java b/poi/src/main/java/org/apache/poi/hssf/record/WriteAccessRecord.java
index 88f8affd2d..712693554c 100644
--- a/poi/src/main/java/org/apache/poi/hssf/record/WriteAccessRecord.java
+++ b/poi/src/main/java/org/apache/poi/hssf/record/WriteAccessRecord.java
@@ -108,10 +108,19 @@ public final class WriteAccessRecord extends StandardRecord {
data = IOUtils.safelyAllocate(in.remaining(), STRING_SIZE);
in.readFully(data);
if (UTF16FLAG.isSet(is16BitFlag)) {
- byteCnt = Math.min(nChars * 2, data.length);
+ // the spec only allows up to 109 bytes for the string in this record, but it seems some broken
+ // software out there will generate invalid records
+ int min = Math.min(nChars * 2, data.length);
+
+ // make sure byteCnt is divisible by 2 as we read UTF-16LE
+ byteCnt = min - (min % 2);
+
charset = StandardCharsets.UTF_16LE;
} else {
+ // the spec only allows up to 109 bytes for the string in this record, but it seems some broken
+ // software out there will generate invalid records
byteCnt = Math.min(nChars, data.length);
+
charset = StandardCharsets.ISO_8859_1;
}
}
@@ -130,7 +139,8 @@ public final class WriteAccessRecord extends StandardRecord {
boolean is16bit = StringUtil.hasMultibyte(username);
int encodedByteCount = username.length() * (is16bit ? 2 : 1);
if (encodedByteCount > STRING_SIZE) {
- throw new IllegalArgumentException("Name is too long: " + username);
+ throw new IllegalArgumentException("Name is too long, expecting up to " + STRING_SIZE +
+ " bytes, but had: " + encodedByteCount + " bytes: " + username);
}
field_1_username = username;
diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCell.java
index 1569553682..fd4f97365d 100644
--- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCell.java
+++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCell.java
@@ -1020,8 +1020,6 @@ public class HSSFCell extends CellBase {
_sheet.getSheet().setActiveCellCol(col);
}
- private static final DataFormatter DATA_FORMATTER = new DataFormatter();
-
/**
* Returns a string representation of the cell
*
@@ -1045,8 +1043,14 @@ public class HSSFCell extends CellBase {
case FORMULA:
return getCellFormula();
case NUMERIC:
+ if (DateUtil.isCellDateFormatted(this)) {
+ DataFormatter df = new DataFormatter();
+ df.setUseCachedValuesForFormulaCells(true);
+ return df.formatCellValue(this);
+ }
+ return Double.toString(getNumericCellValue());
case STRING:
- return DATA_FORMATTER.formatCellValue(this);
+ return getRichStringCellValue().toString();
default:
return "Unknown Cell Type: " + getCellType();
}
diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java
index 0983c45497..20d3bcaf6e 100644
--- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java
+++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java
@@ -35,6 +35,7 @@ import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.util.CellUtil;
import org.apache.poi.util.Removal;
import org.apache.poi.util.ThreadLocalUtil;
@@ -49,22 +50,29 @@ public final class HSSFCellStyle implements CellStyle, Duplicatable {
private final ExtendedFormatRecord _format;
private final short _index;
private final InternalWorkbook _workbook;
+ private final HSSFWorkbook _hssfWorkbook;
-
- /** Creates new HSSFCellStyle why would you want to do this?? */
protected HSSFCellStyle(short index, ExtendedFormatRecord rec, HSSFWorkbook workbook)
{
- this(index, rec, workbook.getWorkbook());
+ _workbook = workbook.getInternalWorkbook();
+ _hssfWorkbook = workbook;
+ _index = index;
+ _format = rec;
}
+
+ @Deprecated
+ @Removal(version = "7.0.0")
protected HSSFCellStyle(short index, ExtendedFormatRecord rec, InternalWorkbook workbook)
{
_workbook = workbook;
+ _hssfWorkbook = null;
_index = index;
- _format = rec;
+ _format = rec;
}
protected HSSFCellStyle(HSSFCellStyle other) {
_workbook = other._workbook;
+ _hssfWorkbook = other._hssfWorkbook;
_index = other._index;
_format = other._format;
}
@@ -851,9 +859,10 @@ public final class HSSFCellStyle implements CellStyle, Duplicatable {
if(source instanceof HSSFCellStyle) {
this.cloneStyleFrom((HSSFCellStyle)source);
} else {
- throw new IllegalArgumentException("Can only clone from one HSSFCellStyle to another, not between HSSFCellStyle and XSSFCellStyle");
+ CellUtil.cloneStyle(source, this, _hssfWorkbook);
}
}
+
public void cloneStyleFrom(HSSFCellStyle source) {
// First we need to clone the extended format
// record
diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFName.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFName.java
index 75d8c0a303..60fdffa599 100644
--- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFName.java
+++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFName.java
@@ -180,7 +180,7 @@ public final class HSSFName implements Name {
*/
private static void validateName(String name) {
- if (name.length() == 0) {
+ if (name.isEmpty()) {
throw new IllegalArgumentException("Name cannot be blank");
}
if (name.length() > 255) {
diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFSheet.java
index bba313f57d..9e56f37714 100644
--- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFSheet.java
+++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFSheet.java
@@ -645,7 +645,7 @@ public final class HSSFSheet implements Sheet {
}
ExtendedFormatRecord xf = _book.getExFormatAt(styleIndex);
- return new HSSFCellStyle(styleIndex, xf, _book);
+ return new HSSFCellStyle(styleIndex, xf, _workbook);
}
/**
diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
index 0fea271340..6c7bb93a2b 100644
--- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
+++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
@@ -234,7 +234,8 @@ public final class HSSFWorkbook extends POIDocument implements Workbook {
* @return the max image length allowed for HSSFWorkbook
*/
public static int getMaxImageLength() {
- return MAX_IMAGE_LENGTH;
+ final int ioMaxSize = IOUtils.getByteArrayMaxOverride();
+ return ioMaxSize < 0 ? MAX_IMAGE_LENGTH : Math.min(MAX_IMAGE_LENGTH, ioMaxSize);
}
/**
@@ -1978,7 +1979,7 @@ public final class HSSFWorkbook extends POIDocument implements Workbook {
case PICTURE_TYPE_WMF:
// remove first 22 bytes if file starts with the WMF placeable header
if (FileMagic.valueOf(pictureData) == FileMagic.WMF) {
- pictureData = IOUtils.safelyClone(pictureData, 22, pictureData.length - 22, MAX_IMAGE_LENGTH);
+ pictureData = IOUtils.safelyClone(pictureData, 22, pictureData.length - 22, getMaxImageLength());
}
// fall through
case PICTURE_TYPE_EMF:
diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HeaderFooter.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HeaderFooter.java
index aa883dd44e..3884462587 100644
--- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HeaderFooter.java
+++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HeaderFooter.java
@@ -282,7 +282,7 @@ public abstract class HeaderFooter implements org.apache.poi.ss.usermodel.Header
int pos;
// Check we really got something to work on
- if (pText == null || pText.length() == 0) {
+ if (pText == null || pText.isEmpty()) {
return pText;
}
diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java
index 698f1d7279..dc68c77dcb 100644
--- a/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java
+++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java
@@ -62,19 +62,19 @@ import static org.apache.logging.log4j.util.Unbox.box;
}
/**
- * Update the formulas in specified row using the formula shifting policy specified by shifter
+ * Update the formulas in the specified row using the formula shifting policy specified by shifter
*
* @param row the row to update the formulas on
* @param formulaShifter the formula shifting policy
*/
/*package*/ static void updateRowFormulas(HSSFRow row, FormulaShifter formulaShifter) {
- HSSFSheet sheet = row.getSheet();
- for (Cell c : row) {
- HSSFCell cell = (HSSFCell) c;
- String formula = cell.getCellFormula();
- if (formula.length() > 0) {
- String shiftedFormula = shiftFormula(row, formula, formulaShifter);
- cell.setCellFormula(shiftedFormula);
+ for (Cell cell : row) {
+ if (cell.getCellType() == CellType.FORMULA) {
+ String formula = cell.getCellFormula();
+ if (formula != null && !formula.isEmpty()) {
+ String shiftedFormula = shiftFormula(row, formula, formulaShifter);
+ cell.setCellFormula(shiftedFormula);
+ }
}
}
}
diff --git a/poi/src/main/java/org/apache/poi/hssf/util/HSSFColor.java b/poi/src/main/java/org/apache/poi/hssf/util/HSSFColor.java
index 263a7c0da9..107b116798 100644
--- a/poi/src/main/java/org/apache/poi/hssf/util/HSSFColor.java
+++ b/poi/src/main/java/org/apache/poi/hssf/util/HSSFColor.java
@@ -25,6 +25,7 @@ import java.util.Map;
import java.util.Objects;
import org.apache.poi.ss.usermodel.Color;
+import org.apache.poi.util.Removal;
/**
@@ -34,7 +35,7 @@ import org.apache.poi.ss.usermodel.Color;
* Each color has an index (for the standard palette in Excel (tm) ),
* native (RGB) triplet and string triplet. The string triplet is as the
* color would be represented by Gnumeric. Having (string) this here is a bit of a
- * collision of function between HSSF and the HSSFSerializer but I think its
+ * collision of function between HSSF and the HSSFSerializer but I think it's
* a reasonable one in this case.
*/
public class HSSFColor implements Color {
@@ -42,7 +43,7 @@ public class HSSFColor implements Color {
private static Map<Integer,HSSFColor> indexHash;
private static Map<HSSFColorPredefined,HSSFColor> enumList;
- private final java.awt.Color color;
+ private final int rgb;
private final int index;
private final int index2;
@@ -110,7 +111,7 @@ public class HSSFColor implements Color {
private final HSSFColor color;
HSSFColorPredefined(int index, int index2, int rgb) {
- this.color = new HSSFColor(index, index2, new java.awt.Color(rgb));
+ this.color = new HSSFColor(index, index2, rgb);
}
/**
@@ -145,7 +146,7 @@ public class HSSFColor implements Color {
* @return (a copy of) the HSSFColor assigned to the enum
*/
public HSSFColor getColor() {
- return new HSSFColor(getIndex(), getIndex2(), color.color);
+ return new HSSFColor(getIndex(), getIndex2(), color.rgb);
}
}
@@ -153,13 +154,40 @@ public class HSSFColor implements Color {
/** Creates a new instance of HSSFColor */
public HSSFColor() {
// automatic index
- this(0x40, -1, java.awt.Color.BLACK);
+ this(0x40, -1, 0x000000);
}
+ /** Constructs new instance of {@code HSSFColor} by
+ * extracting RGB from {@link java.awt.Color}. The code is equivalent
+ * to calling:
+ * <pre>
+ * new HSSFColor(index, index2, color.getRGB());
+ * </pre>
+ * or specifying {@link #HSSFColor(int, int, int) RGB directly}.
+ *
+ * @param index
+ * @param index2
+ * @param color color to extract RGB from
+ * @deprecated use {@link #HSSFColor(int, int, int)} instead
+ */
+ @Removal(version = "7.0.0")
public HSSFColor(int index, int index2, java.awt.Color color) {
+ this(index, index2, color.getRGB());
+ }
+
+ /** Constructs new instance of {@code HSSFColor} by
+ * specifying RGB as an {@code int} value. Given {@code blue}, {@code green} and
+ * {@code blue} being byte values between {@code 0x00 to 0xFF}, then
+ * {@code rgb = blue + (green >> 8) + (red >> 16)}.
+ * @param index
+ * @param index2
+ * @param rgb combined value of RGB
+ * @since POI 5.4.2
+ */
+ public HSSFColor(int index, int index2, int rgb) {
this.index = index;
this.index2 = index2;
- this.color = color;
+ this.rgb = 0xFF000000 | rgb;
}
/**
@@ -203,7 +231,7 @@ public class HSSFColor implements Color {
}
/**
- * this function returns all colors in a hastable. It's not implemented as a
+ * This function returns all colors in a map. It's not implemented as a
* static member/statically initialized because that would be dirty in a
* server environment as it is intended. This means you'll eat the time
* it takes to create it once per request but you will not hold onto it
@@ -235,7 +263,7 @@ public class HSSFColor implements Color {
private static synchronized Map<HSSFColorPredefined,HSSFColor> mapEnumToColorClass() {
if (enumList == null) {
enumList = new EnumMap<>(HSSFColorPredefined.class);
- // AUTOMATIC is not add to list
+ // AUTOMATIC is not added to list
addHSSFColorPredefined(HSSFColorPredefined.BLACK);
addHSSFColorPredefined(HSSFColorPredefined.BROWN);
addHSSFColorPredefined(HSSFColorPredefined.OLIVE_GREEN);
@@ -315,7 +343,7 @@ public class HSSFColor implements Color {
*/
public short [] getTriplet() {
- return new short[] { (short)color.getRed(), (short)color.getGreen(), (short)color.getBlue() };
+ return new short[] { getRed(), getGreen(), getBlue() };
}
/**
@@ -324,9 +352,19 @@ public class HSSFColor implements Color {
*/
public String getHexString() {
- return (Integer.toHexString(color.getRed()*0x101) + ":" +
- Integer.toHexString(color.getGreen()*0x101) + ":" +
- Integer.toHexString(color.getBlue()*0x101)).toUpperCase(Locale.ROOT);
+ return (Integer.toHexString(getRed()*0x101) + ":" +
+ Integer.toHexString(getGreen()*0x101) + ":" +
+ Integer.toHexString(getBlue()*0x101)).toUpperCase(Locale.ROOT);
+ }
+
+ private final short getBlue() {
+ return (short) (rgb & 0xFF);
+ }
+ private final short getGreen() {
+ return (short) ((rgb >> 8) & 0xFF);
+ }
+ private final short getRed() {
+ return (short) ((rgb >> 16) & 0xFF);
}
@Override
@@ -338,12 +376,12 @@ public class HSSFColor implements Color {
if (index != hssfColor.index) return false;
if (index2 != hssfColor.index2) return false;
- return Objects.equals(color, hssfColor.color);
+ return Objects.equals(rgb, hssfColor.rgb);
}
@Override
public int hashCode() {
- return Objects.hash(color,index,index2);
+ return Objects.hash(rgb, index, index2);
}
/**
diff --git a/poi/src/main/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java b/poi/src/main/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java
index b8d443aab4..4cb3ebb4e2 100644
--- a/poi/src/main/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java
+++ b/poi/src/main/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java
@@ -141,22 +141,23 @@ public class POIFSReader
* assumed
* @param name the document name
*
- * @throws NullPointerException if listener is null or name is
- * null or empty
+ * @throws NullPointerException if listener is null or name is null
* @throws IllegalStateException if read() has already been
- * called
+ * called or name is empty
*/
-
public void registerListener(final POIFSReaderListener listener,
final POIFSDocumentPath path,
final String name) {
- if ((listener == null) || (name == null) || (name.length() == 0)) {
- throw new NullPointerException();
+ if (listener == null || name == null) {
+ throw new NullPointerException("invalid null parameter");
+ }
+ if (name.isEmpty()) {
+ throw new IllegalStateException("name must not be empty");
}
if (registryClosed) {
- throw new IllegalStateException();
+ throw new IllegalStateException("registry closed");
}
- registry.registerListener(listener, (path == null) ? new POIFSDocumentPath() : path, name);
+ registry.registerListener(listener, path == null ? new POIFSDocumentPath() : path, name);
}
/**
diff --git a/poi/src/main/java/org/apache/poi/poifs/filesystem/DocumentDescriptor.java b/poi/src/main/java/org/apache/poi/poifs/filesystem/DocumentDescriptor.java
index b8d783638e..40ffdffaec 100644
--- a/poi/src/main/java/org/apache/poi/poifs/filesystem/DocumentDescriptor.java
+++ b/poi/src/main/java/org/apache/poi/poifs/filesystem/DocumentDescriptor.java
@@ -46,7 +46,7 @@ public class DocumentDescriptor
{
throw new NullPointerException("name must not be null");
}
- if (name.length() == 0)
+ if (name.isEmpty())
{
throw new IllegalArgumentException("name cannot be empty");
}
diff --git a/poi/src/main/java/org/apache/poi/poifs/property/PropertyTable.java b/poi/src/main/java/org/apache/poi/poifs/property/PropertyTable.java
index 203f23cd46..062627ae52 100644
--- a/poi/src/main/java/org/apache/poi/poifs/property/PropertyTable.java
+++ b/poi/src/main/java/org/apache/poi/poifs/property/PropertyTable.java
@@ -83,11 +83,14 @@ public final class PropertyTable implements BATManaged {
for (ByteBuffer bb : dataSource) {
// Turn it into an array
- byte[] data;
- if (bb.hasArray() && bb.arrayOffset() == 0 &&
- bb.array().length == _bigBigBlockSize.getBigBlockSize()) {
- data = bb.array();
- } else {
+ byte[] data = null;
+ if (bb.hasArray() && bb.arrayOffset() == 0) {
+ final byte[] array = bb.array();
+ if (array.length == _bigBigBlockSize.getBigBlockSize()) {
+ data = array;
+ }
+ }
+ if (data == null) {
data = IOUtils.safelyAllocate(_bigBigBlockSize.getBigBlockSize(), POIFSFileSystem.getMaxRecordLength());
int toRead = data.length;
diff --git a/poi/src/main/java/org/apache/poi/ss/format/CellFormatPart.java b/poi/src/main/java/org/apache/poi/ss/format/CellFormatPart.java
index 990181baad..ea8eaa37b4 100644
--- a/poi/src/main/java/org/apache/poi/ss/format/CellFormatPart.java
+++ b/poi/src/main/java/org/apache/poi/ss/format/CellFormatPart.java
@@ -251,7 +251,7 @@ public class CellFormatPart {
*/
private static Color getColor(Matcher m) {
String cdesc = m.group(COLOR_GROUP);
- if (cdesc == null || cdesc.length() == 0)
+ if (cdesc == null || cdesc.isEmpty())
return null;
Color c = NAMED_COLORS.get(cdesc);
if (c == null) {
@@ -270,7 +270,7 @@ public class CellFormatPart {
*/
private CellFormatCondition getCondition(Matcher m) {
String mdesc = m.group(CONDITION_OPERATOR_GROUP);
- if (mdesc == null || mdesc.length() == 0)
+ if (mdesc == null || mdesc.isEmpty())
return null;
return CellFormatCondition.getInstance(m.group(
CONDITION_OPERATOR_GROUP), m.group(CONDITION_VALUE_GROUP));
@@ -509,7 +509,7 @@ public class CellFormatPart {
StringBuffer fmt = new StringBuffer();
while (m.find()) {
String part = group(m, 0);
- if (part.length() > 0) {
+ if (!part.isEmpty()) {
String repl = partHandler.handlePart(m, part, type, fmt);
if (repl == null) {
switch (part.charAt(0)) {
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/EvaluationConditionalFormatRule.java b/poi/src/main/java/org/apache/poi/ss/formula/EvaluationConditionalFormatRule.java
index 54199cbfca..3d0e441dbc 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/EvaluationConditionalFormatRule.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/EvaluationConditionalFormatRule.java
@@ -357,7 +357,7 @@ public class EvaluationConditionalFormatRule implements Comparable<EvaluationCon
String f2 = rule.getFormula2();
ValueEval eval2 = BlankEval.instance;
- if (f2 != null && f2.length() > 0) {
+ if (f2 != null && !f2.isEmpty()) {
eval2 = unwrapEval(workbookEvaluator.evaluate(f2, ConditionalFormattingEvaluator.getRef(cell), region));
}
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/FormulaParser.java b/poi/src/main/java/org/apache/poi/ss/formula/FormulaParser.java
index 020ae38d8c..c748231838 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/FormulaParser.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/FormulaParser.java
@@ -483,7 +483,7 @@ public final class FormulaParser {
} else {
// Is it a named range?
String name = parseAsName();
- if (name.length() == 0) {
+ if (name.isEmpty()) {
throw new FormulaParseException("Cell reference or Named Range "
+ "expected after sheet name at index " + _pointer + ".");
}
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/poi/src/main/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
index 11ae822a21..4b703fc143 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
@@ -971,7 +971,7 @@ public final class WorkbookEvaluator {
}
/**
- * Register a ATP function in runtime.
+ * Register an ATP function in runtime.
*
* @param name the function name
* @param func the function to register
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java b/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
index dd165383ee..d7e2db2236 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
@@ -1,12 +1,19 @@
-/*
- * ==================================================================== 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.
- * ====================================================================
- */
+/* ====================================================================
+ 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.ss.formula.atp;
@@ -17,6 +24,8 @@ import java.util.Locale;
import java.util.Map;
import java.util.TreeSet;
+import org.apache.logging.log4j.Logger;
+import org.apache.poi.logging.PoiLogManager;
import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.NotImplementedFunctionException;
import org.apache.poi.ss.formula.eval.ValueEval;
@@ -32,6 +41,8 @@ public final class AnalysisToolPak implements UDFFinder {
public static final UDFFinder instance = new AnalysisToolPak();
+ private static final Logger LOG = PoiLogManager.getLogger(AnalysisToolPak.class);
+
private static final class NotImplemented implements FreeRefFunction {
private final String _functionName;
@@ -180,6 +191,7 @@ public final class AnalysisToolPak implements UDFFinder {
r(m, "RECEIVED", null);
r(m, "RTD", null);
r(m, "SERIESSUM", null);
+ r(m, "SHEET", Sheet.instance);
r(m, "SINGLE", Single.instance);
r(m, "SQRTPI", Sqrtpi.instance);
r(m, "STDEV.S", Stdevs.instance);
@@ -265,9 +277,26 @@ public final class AnalysisToolPak implements UDFFinder {
* @throws IllegalArgumentException if the function is unknown or already registered.
* @since 3.8 beta6
*/
- public static void registerFunction(String name, FreeRefFunction func){
+ public static void registerFunction(String name, FreeRefFunction func) {
+ registerFunction(name, func, false);
+ }
+
+ /**
+ * Register an ATP function in runtime.
+ *
+ * @param name the function name
+ * @param func the function to register
+ * @param force force registration even if the function is already registered or unknown to POI
+ * @throws IllegalArgumentException if the function is unknown or already registered (and `force` is not true).
+ * @since POI 5.4.2
+ */
+ public static void registerFunction(String name, FreeRefFunction func, boolean force) {
AnalysisToolPak inst = (AnalysisToolPak)instance;
- if(!isATPFunction(name)) {
+ if (force) {
+ // Excel regularly adds new functions, so the ones registered in POI
+ // can be well out of date - allow users who know what they are doing
+ // to force their update
+ } else if(!isATPFunction(name)) {
FunctionMetadata metaData = FunctionMetadataRegistry.getFunctionByName(name);
if(metaData != null) {
throw new IllegalArgumentException(name + " is a built-in Excel function. " +
@@ -276,13 +305,20 @@ public final class AnalysisToolPak implements UDFFinder {
throw new IllegalArgumentException(name + " is not a function from the Excel Analysis Toolpack.");
}
+
FreeRefFunction f = inst.findFunction(name);
if(f != null && !(f instanceof NotImplemented)) {
- throw new IllegalArgumentException("POI already implements " + name +
- ". You cannot override POI's implementations of Excel functions");
+ if (force) {
+ LOG.info("POI already implements " + name +
+ ". You are overriding the implementation.");
+ } else {
+ throw new IllegalArgumentException("POI already implements " + name +
+ ". You cannot override POI's implementations of Excel functions");
+ }
}
// FIXME: inconsistent case-sensitivity
inst._functionsByName.put(name, func);
}
+
}
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/atp/TextJoinFunction.java b/poi/src/main/java/org/apache/poi/ss/formula/atp/TextJoinFunction.java
index 5e7ffe8b87..45b3ec31ac 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/atp/TextJoinFunction.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/atp/TextJoinFunction.java
@@ -86,7 +86,7 @@ final class TextJoinFunction implements FreeRefFunction {
String textValue = OperandResolver.coerceValueToString(textArg);
// If we're not ignoring empty values or if our value is not empty, add it to the list
- if (!ignoreEmpty || (textValue != null && textValue.length() > 0)) {
+ if (!ignoreEmpty || (textValue != null && !textValue.isEmpty())) {
textValues.add(textValue);
}
}
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java b/poi/src/main/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java
index b03c732e89..4b6e6fa2e7 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java
@@ -124,7 +124,7 @@ final class FunctionMetadataReader {
byte returnClassCode = parseReturnTypeCode(parts[4]);
byte[] parameterClassCodes = parseOperandTypeCodes(parts[5]);
// 6 isVolatile
- boolean hasNote = parts[7].length() > 0;
+ boolean hasNote = !parts[7].isEmpty();
validateFunctionName(functionName);
// TODO - make POI use isVolatile
@@ -134,7 +134,7 @@ final class FunctionMetadataReader {
private static byte parseReturnTypeCode(String code) {
- if(code.length() == 0) {
+ if(code.isEmpty()) {
return Ptg.CLASS_REF; // happens for GETPIVOTDATA
}
return parseOperandTypeCode(code);
@@ -155,7 +155,7 @@ final class FunctionMetadataReader {
// (all unspecified params are assumed to be the same as the last)
nItems --;
}
- byte[] result = IOUtils.safelyAllocate(nItems, MAX_RECORD_LENGTH);
+ byte[] result = IOUtils.safelyAllocate(nItems, getMaxRecordLength());
for (int i = 0; i < nItems; i++) {
result[i] = parseOperandTypeCode(array[i]);
}
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java
index c39d082e10..db583f3229 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java
@@ -23,7 +23,7 @@ public class BaseNumberUtils {
public static double convertToDecimal(String value, int base, int maxNumberOfPlaces) throws IllegalArgumentException {
- if (value == null || value.length() == 0) {
+ if (value == null || value.isEmpty()) {
return 0.0;
}
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Code.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Code.java
index 96f2a13204..30f2d3257f 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Code.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Code.java
@@ -41,7 +41,7 @@ public class Code extends Fixed1ArgFunction {
}
String text = OperandResolver.coerceValueToString(veText1);
- if (text.length() == 0) {
+ if (text.isEmpty()) {
return ErrorEval.VALUE_INVALID;
}
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Complex.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Complex.java
index a10c54a758..a4fac2685e 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Complex.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Complex.java
@@ -90,7 +90,7 @@ public class Complex extends Var2or3ArgFunction implements FreeRefFunction {
}
String suffixValue = OperandResolver.coerceValueToString(suffix);
- if (suffixValue.length() == 0) {
+ if (suffixValue.isEmpty()) {
suffixValue = DEFAULT_SUFFIX;
}
if (suffixValue.equals(DEFAULT_SUFFIX.toUpperCase(Locale.ROOT)) ||
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Countif.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Countif.java
index 93fdf033a4..ecb57e5e1a 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Countif.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Countif.java
@@ -339,7 +339,7 @@ public final class Countif extends Fixed2ArgFunction {
switch(getCode()) {
case CmpOp.NONE:
case CmpOp.EQ:
- return _value.length() == 0;
+ return _value.isEmpty();
case CmpOp.NE:
// pred '<>' matches empty string but not blank cell
// pred '<>ABC' matches blank and 'not ABC'
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Imaginary.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Imaginary.java
index 012aacdd01..f478c95d5b 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Imaginary.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Imaginary.java
@@ -74,7 +74,7 @@ public class Imaginary extends Fixed1ArgFunction implements FreeRefFunction {
String imaginaryGroup = m.group(5);
boolean hasImaginaryPart = imaginaryGroup.equals("i") || imaginaryGroup.equals("j");
- if (imaginaryGroup.length() == 0) {
+ if (imaginaryGroup.isEmpty()) {
return new StringEval(String.valueOf(0));
}
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Rept.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Rept.java
index c3be177ce3..33b2c46366 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Rept.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Rept.java
@@ -40,7 +40,6 @@ import org.apache.poi.ss.formula.eval.ValueEval;
*/
public class Rept extends Fixed2ArgFunction {
-
@Override
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval text, ValueEval number_times) {
@@ -64,7 +63,7 @@ public class Rept extends Fixed2ArgFunction {
strb.append(strText1);
}
- if (strb.toString().length() > 32767) {
+ if (strb.length() > 32767) {
return ErrorEval.VALUE_INVALID;
}
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Sheet.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Sheet.java
new file mode 100644
index 0000000000..37b78dbb89
--- /dev/null
+++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Sheet.java
@@ -0,0 +1,89 @@
+/* ====================================================================
+ 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.ss.formula.functions;
+
+import org.apache.poi.ss.formula.EvaluationWorkbook;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.formula.eval.*;
+
+/**
+ * Implementation for Excel SHEET() function.
+ * <p>
+ * <b>Syntax</b>:<br> <b>SHEET</b>([value])<br>
+ * </p>
+ * <p>
+ * Returns the sheet number of the referenced sheet or the current sheet if no argument is provided.
+ * </p>
+ * <p>
+ * Examples:
+ * </p>
+ * <ul>
+ * <li><code>=SHEET()</code> → returns the current sheet number (1-based)</li>
+ * <li><code>=SHEET(A1)</code> → returns the sheet number of the reference A1</li>
+ * <li><code>=SHEET(A1:B5)</code> → returns the sheet number of the range A1:B5</li>
+ * <li><code>=SHEET("Sheet3")</code> → returns the sheet number of the sheet named "Sheet3"</li>
+ * </ul>
+ * <p>
+ * See <a href="https://support.microsoft.com/en-us/office/sheet-function-44718b6f-8b87-47a1-a9d6-b701c06cff24">Microsoft Documentation</a>
+ * </p>
+ */
+public class Sheet implements FreeRefFunction {
+
+ public static final Sheet instance = new Sheet();
+
+ @Override
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+ try {
+ if (args.length == 0) {
+ // No argument provided → return the current sheet index +1 (Excel uses 1-based index)
+ return new NumberEval((double) ec.getSheetIndex() + 1);
+ } else {
+ ValueEval arg = args[0];
+
+ if (arg instanceof RefEval) {
+ // Argument is a single cell reference → return the sheet index of that reference +1
+ RefEval ref = (RefEval) arg;
+ int sheetIndex = ref.getFirstSheetIndex();
+ return new NumberEval((double) sheetIndex + 1);
+ } else if (arg instanceof AreaEval) {
+ // Argument is a cell range → return the sheet index of that area +1
+ AreaEval area = (AreaEval) arg;
+ int sheetIndex = area.getFirstSheetIndex();
+ return new NumberEval((double) sheetIndex + 1);
+ } else if (arg instanceof StringEval) {
+ // Argument is a string (sheet name, e.g., "Sheet3") → look up the sheet index by name
+ String sheetName = ((StringEval) arg).getStringValue();
+ EvaluationWorkbook wb = ec.getWorkbook();
+ int sheetIndex = wb.getSheetIndex(sheetName);
+ if (sheetIndex >= 0) {
+ return new NumberEval((double) sheetIndex + 1);
+ } else {
+ // Sheet name not found → return #N/A error
+ return ErrorEval.NA;
+ }
+ } else {
+ // Unsupported argument type → return #N/A error
+ return ErrorEval.NA;
+ }
+ }
+ } catch (Exception e) {
+ // Any unexpected exception (e.g., null pointers) → return #VALUE! error
+ return ErrorEval.VALUE_INVALID;
+ }
+ }
+}
diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Value.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Value.java
index 451f0a2fab..87edae4b0e 100644
--- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Value.java
+++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Value.java
@@ -152,7 +152,7 @@ public final class Value extends Fixed1ArgFunction implements ArrayFunction {
foundPercentage = true;
break;
}
- if (remainingTextTrimmed.length() > 0) {
+ if (!remainingTextTrimmed.isEmpty()) {
// intervening spaces not allowed once the digits start
return null;
}
diff --git a/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java b/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java
index 8018267a26..401b1c47a1 100644
--- a/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java
+++ b/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java
@@ -70,7 +70,7 @@ public class DateUtil {
private static final Pattern date_ptrn2 = Pattern.compile("^\\[[a-zA-Z]+]");
private static final Pattern date_ptrn3a = Pattern.compile("[yYmMdDhHsS]");
// add "\u5e74 \u6708 \u65e5" for Chinese/Japanese date format:2017 \u5e74 2 \u6708 7 \u65e5
- private static final Pattern date_ptrn3b = Pattern.compile("^[\\[\\]yYmMdDhHsS\\-T/\u5e74\u6708\u65e5,. :\"\\\\]+0*[ampAMP/]*$");
+ private static final Pattern date_ptrn3b = Pattern.compile("^[\\[\\]yYmMdDhHsS\\-T/\u5e74\u6708\u65e5,. :\"\\\\]+0* ?[ampAMP/]*$");
// elapsed time patterns: [h],[m] and [s]
private static final Pattern date_ptrn4 = Pattern.compile("^\\[([hH]+|[mM]+|[sS]+)]");
@@ -548,6 +548,7 @@ public class DateUtil {
// avoid re-checking DateUtil.isADateFormat(int, String) if a given format
// string represents a date format if the same string is passed multiple times.
// see https://issues.apache.org/bugzilla/show_bug.cgi?id=55611
+ private static boolean maintainCache = true;
private static final ThreadLocal<Integer> lastFormatIndex = ThreadLocal.withInitial(() -> -1);
private static final ThreadLocal<String> lastFormatString = new ThreadLocal<>();
private static final ThreadLocal<Boolean> lastCachedResult = new ThreadLocal<>();
@@ -561,22 +562,24 @@ public class DateUtil {
}
private static boolean isCached(String formatString, int formatIndex) {
- return formatIndex == lastFormatIndex.get()
+ return maintainCache && formatIndex == lastFormatIndex.get()
&& formatString.equals(lastFormatString.get());
}
private static void cache(String formatString, int formatIndex, boolean cached) {
- if (formatString == null || "".equals(formatString)) {
- lastFormatString.remove();
- } else {
- lastFormatString.set(formatString);
- }
- if (formatIndex == -1) {
- lastFormatIndex.remove();
- } else {
- lastFormatIndex.set(formatIndex);
+ if (maintainCache) {
+ if (formatString == null || "".equals(formatString)) {
+ lastFormatString.remove();
+ } else {
+ lastFormatString.set(formatString);
+ }
+ if (formatIndex == -1) {
+ lastFormatIndex.remove();
+ } else {
+ lastFormatIndex.set(formatIndex);
+ }
+ lastCachedResult.set(cached);
}
- lastCachedResult.set(cached);
}
/**
@@ -624,7 +627,7 @@ public class DateUtil {
}
// If we didn't get a real string, don't even cache it as we can always find this out quickly
- if(formatString == null || formatString.length() == 0) {
+ if(formatString == null || formatString.isEmpty()) {
return false;
}
@@ -997,4 +1000,18 @@ public class DateUtil {
return tm;
}
+
+ /**
+ * Enable or disable the thread-local cache for date format checking.
+ * If enabled, the date format checking will be cached per thread,
+ * which can improve performance when checking the same format multiple times.
+ * If disabled, the cache will not be used and each check will be performed independently.
+ *
+ * @param enable true to enable the cache, false to disable it (enabled, by default)
+ * @since POI 5.4.2
+ */
+ public static void enableThreadLocalCache(final boolean enable) {
+ // enable thread-local cache for date format checking
+ maintainCache = enable;
+ }
}
diff --git a/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java b/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java
index fc45d81b4a..fd1cb4ff51 100644
--- a/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java
+++ b/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java
@@ -326,7 +326,7 @@ public class AreaReference {
String currentSegment = "";
StringTokenizer st = new StringTokenizer(reference, ",");
while(st.hasMoreTokens()) {
- if (currentSegment.length() > 0) {
+ if (!currentSegment.isEmpty()) {
currentSegment += ",";
}
currentSegment += st.nextToken();
@@ -336,7 +336,7 @@ public class AreaReference {
currentSegment = "";
}
}
- if (currentSegment.length() > 0) {
+ if (!currentSegment.isEmpty()) {
results.add(currentSegment);
}
return results.toArray(new String[0]);
diff --git a/poi/src/main/java/org/apache/poi/ss/util/CellReference.java b/poi/src/main/java/org/apache/poi/ss/util/CellReference.java
index 1749878163..da1202c283 100644
--- a/poi/src/main/java/org/apache/poi/ss/util/CellReference.java
+++ b/poi/src/main/java/org/apache/poi/ss/util/CellReference.java
@@ -116,22 +116,22 @@ public class CellReference implements GenericRecord {
_sheetName = parts.sheetName;
String colRef = parts.colRef;
- _isColAbs = (colRef.length() > 0) && colRef.charAt(0) == '$';
+ _isColAbs = (!colRef.isEmpty()) && colRef.charAt(0) == '$';
if (_isColAbs) {
colRef = colRef.substring(1);
}
- if (colRef.length() == 0) {
+ if (colRef.isEmpty()) {
_colIndex = -1;
} else {
_colIndex = convertColStringToIndex(colRef);
}
String rowRef=parts.rowRef;
- _isRowAbs = (rowRef.length() > 0) && rowRef.charAt(0) == '$';
+ _isRowAbs = (!rowRef.isEmpty()) && rowRef.charAt(0) == '$';
if (_isRowAbs) {
rowRef = rowRef.substring(1);
}
- if (rowRef.length() == 0) {
+ if (rowRef.isEmpty()) {
_rowIndex = -1;
} else {
// throws NumberFormatException if rowRef is not convertible to an int
@@ -451,7 +451,7 @@ public class CellReference implements GenericRecord {
}
/**
- * Takes in a 0-based base-10 column and returns a ALPHA-26
+ * Takes in a 0-based base-10 column and returns an ALPHA-26
* representation.
* eg {@code convertNumToColString(3)} returns {@code "D"}
*/
diff --git a/poi/src/main/java/org/apache/poi/ss/util/CellUtil.java b/poi/src/main/java/org/apache/poi/ss/util/CellUtil.java
index 64ad761e2e..b2879fb7fd 100644
--- a/poi/src/main/java/org/apache/poi/ss/util/CellUtil.java
+++ b/poi/src/main/java/org/apache/poi/ss/util/CellUtil.java
@@ -18,6 +18,7 @@
package org.apache.poi.ss.util;
import java.util.Collections;
+import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Locale;
@@ -46,6 +47,7 @@ import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.util.Beta;
+import org.apache.poi.util.Internal;
import org.apache.poi.util.Removal;
/**
@@ -278,6 +280,7 @@ public final class CellUtil {
map.put(LEFT_BORDER_COLOR, CellPropertyType.LEFT_BORDER_COLOR);
map.put(RIGHT_BORDER_COLOR, CellPropertyType.RIGHT_BORDER_COLOR);
map.put(TOP_BORDER_COLOR, CellPropertyType.TOP_BORDER_COLOR);
+ map.put(DATA_FORMAT, CellPropertyType.DATA_FORMAT);
map.put(FILL_BACKGROUND_COLOR, CellPropertyType.FILL_BACKGROUND_COLOR);
map.put(FILL_FOREGROUND_COLOR, CellPropertyType.FILL_FOREGROUND_COLOR);
map.put(FILL_BACKGROUND_COLOR_COLOR, CellPropertyType.FILL_BACKGROUND_COLOR_COLOR);
@@ -287,10 +290,11 @@ public final class CellUtil {
map.put(HIDDEN, CellPropertyType.HIDDEN);
map.put(INDENTION, CellPropertyType.INDENTION);
map.put(LOCKED, CellPropertyType.LOCKED);
+ map.put(QUOTE_PREFIXED, CellPropertyType.QUOTE_PREFIXED);
map.put(ROTATION, CellPropertyType.ROTATION);
- map.put(VERTICAL_ALIGNMENT, CellPropertyType.VERTICAL_ALIGNMENT);
map.put(SHRINK_TO_FIT, CellPropertyType.SHRINK_TO_FIT);
- map.put(QUOTE_PREFIXED, CellPropertyType.QUOTE_PREFIXED);
+ map.put(VERTICAL_ALIGNMENT, CellPropertyType.VERTICAL_ALIGNMENT);
+ map.put(WRAP_TEXT, CellPropertyType.WRAP_TEXT);
namePropertyMap = Collections.unmodifiableMap(map);
}
@@ -570,7 +574,7 @@ public final class CellUtil {
@Deprecated
@Removal(version = "7.0.0")
public static void setCellStyleProperties(Cell cell, Map<String, Object> properties) {
- Map<CellPropertyType, Object> strPropMap = new HashMap<>(properties.size());
+ final EnumMap<CellPropertyType, Object> strPropMap = new EnumMap<>(CellPropertyType.class);
properties.forEach((k, v) -> strPropMap.put(namePropertyMap.get(k), v));
setCellStyleProperties(cell, strPropMap, false);
}
@@ -610,7 +614,7 @@ public final class CellUtil {
CellStyle originalStyle = cell.getCellStyle();
CellStyle newStyle = null;
- Map<CellPropertyType, Object> values = getFormatProperties(originalStyle);
+ EnumMap<CellPropertyType, Object> values = getFormatProperties(originalStyle);
if (properties.containsKey(CellPropertyType.FILL_FOREGROUND_COLOR_COLOR) && properties.get(CellPropertyType.FILL_FOREGROUND_COLOR_COLOR) == null) {
values.remove(CellPropertyType.FILL_FOREGROUND_COLOR);
}
@@ -627,11 +631,11 @@ public final class CellUtil {
// index seems like what index the cellstyle is in the list of styles for a workbook.
// not good to compare on!
- int numberCellStyles = workbook.getNumCellStyles();
+ final int numberCellStyles = workbook.getNumCellStyles();
for (int i = 0; i < numberCellStyles; i++) {
CellStyle wbStyle = workbook.getCellStyleAt(i);
- Map<CellPropertyType, Object> wbStyleMap = getFormatProperties(wbStyle);
+ EnumMap<CellPropertyType, Object> wbStyleMap = getFormatProperties(wbStyle);
// the desired style already exists in the workbook. Use the existing style.
if (styleMapsMatch(wbStyleMap, values, disableNullColorCheck)) {
@@ -651,8 +655,8 @@ public final class CellUtil {
private static boolean styleMapsMatch(final Map<CellPropertyType, Object> newProps,
final Map<CellPropertyType, Object> storedProps, final boolean disableNullColorCheck) {
- final Map<CellPropertyType, Object> map1Copy = new HashMap<>(newProps);
- final Map<CellPropertyType, Object> map2Copy = new HashMap<>(storedProps);
+ final EnumMap<CellPropertyType, Object> map1Copy = new EnumMap<>(newProps);
+ final EnumMap<CellPropertyType, Object> map2Copy = new EnumMap<>(storedProps);
final Object backColor1 = map1Copy.remove(CellPropertyType.FILL_BACKGROUND_COLOR_COLOR);
final Object backColor2 = map2Copy.remove(CellPropertyType.FILL_BACKGROUND_COLOR_COLOR);
final Object foreColor1 = map1Copy.remove(CellPropertyType.FILL_FOREGROUND_COLOR_COLOR);
@@ -683,20 +687,26 @@ public final class CellUtil {
* @param cell The cell that is to be changed.
* @param property The name of the property that is to be changed.
* @param propertyValue The value of the property that is to be changed.
- *
+ * @throws NullPointerException if {@code cell} or {@code property} is null
* @since POI 5.4.0
*/
public static void setCellStyleProperty(Cell cell, CellPropertyType property, Object propertyValue) {
+ if (cell == null) {
+ throw new NullPointerException("Cell must not be null");
+ }
+ if (property == null) {
+ throw new NullPointerException("CellPropertyType must not be null");
+ }
boolean disableNullColorCheck = false;
final Map<CellPropertyType, Object> propMap;
if (CellPropertyType.FILL_FOREGROUND_COLOR_COLOR.equals(property) && propertyValue == null) {
disableNullColorCheck = true;
- propMap = new HashMap<>();
+ propMap = new EnumMap<>(CellPropertyType.class);
propMap.put(CellPropertyType.FILL_FOREGROUND_COLOR_COLOR, null);
propMap.put(CellPropertyType.FILL_FOREGROUND_COLOR, null);
} else if (CellPropertyType.FILL_BACKGROUND_COLOR_COLOR.equals(property) && propertyValue == null) {
disableNullColorCheck = true;
- propMap = new HashMap<>();
+ propMap = new EnumMap<>(CellPropertyType.class);
propMap.put(CellPropertyType.FILL_BACKGROUND_COLOR_COLOR, null);
propMap.put(CellPropertyType.FILL_BACKGROUND_COLOR, null);
} else {
@@ -739,8 +749,8 @@ public final class CellUtil {
* @return map of format properties (CellPropertyType -> Object)
* @see #setFormatProperties(CellStyle, Workbook, Map)
*/
- private static Map<CellPropertyType, Object> getFormatProperties(CellStyle style) {
- Map<CellPropertyType, Object> properties = new HashMap<>();
+ private static EnumMap<CellPropertyType, Object> getFormatProperties(CellStyle style) {
+ EnumMap<CellPropertyType, Object> properties = new EnumMap<>(CellPropertyType.class);
put(properties, CellPropertyType.ALIGNMENT, style.getAlignment());
put(properties, CellPropertyType.VERTICAL_ALIGNMENT, style.getVerticalAlignment());
put(properties, CellPropertyType.BORDER_BOTTOM, style.getBorderBottom());
@@ -776,7 +786,6 @@ public final class CellUtil {
*
* @param src the property map to copy from (read-only)
* @param dest the property map to copy into
- * @since POI 3.15 beta 3
*/
private static void putAll(final Map<CellPropertyType, Object> src, Map<CellPropertyType, Object> dest) {
for (final CellPropertyType key : src.keySet()) {
@@ -806,7 +815,7 @@ public final class CellUtil {
* Sets the format properties of the given style based on the given map.
*
* @param style cell style
- * @param workbook parent workbook
+ * @param workbook parent workbook (can be null but some fomt info will not be copied if null is passed)
* @param properties map of format properties (CellPropertyType -> Object)
* @see #getFormatProperties(CellStyle)
*/
@@ -848,7 +857,9 @@ public final class CellUtil {
}
}
- style.setFont(workbook.getFontAt(getInt(properties, CellPropertyType.FONT)));
+ if (workbook != null) {
+ style.setFont(workbook.getFontAt(getInt(properties, CellPropertyType.FONT)));
+ }
style.setHidden(getBoolean(properties, CellPropertyType.HIDDEN));
style.setIndention(getShort(properties, CellPropertyType.INDENTION));
style.setLeftBorderColor(getShort(properties, CellPropertyType.LEFT_BORDER_COLOR));
@@ -862,6 +873,25 @@ public final class CellUtil {
}
/**
+ * Clones the style from one cell to another. For internal use only.
+ * Users should use the cloneStyleFrom method on CellStyle instead.
+ *
+ * @param src source cell style
+ * @param dest destination cell style
+ * @param destWorkbook destination workbook (can be null but some font info will not be copied if null is passed)
+ * @throws IllegalArgumentException if source or destination styles are null
+ * @since POI 5.4.2
+ */
+ @Internal
+ public static void cloneStyle(CellStyle src, CellStyle dest, Workbook destWorkbook) {
+ if (src == null || dest == null) {
+ throw new IllegalArgumentException("Source and destination styles must not be null");
+ }
+ EnumMap<CellPropertyType, Object> properties = getFormatProperties(src);
+ setFormatProperties(dest, destWorkbook, properties);
+ }
+
+ /**
* Utility method that returns the named short value from the given map.
*
* @param properties map of named properties (CellPropertyType -> Object)
diff --git a/poi/src/main/java/org/apache/poi/ss/util/PropertyTemplate.java b/poi/src/main/java/org/apache/poi/ss/util/PropertyTemplate.java
index 2f74af55d7..e00b785070 100644
--- a/poi/src/main/java/org/apache/poi/ss/util/PropertyTemplate.java
+++ b/poi/src/main/java/org/apache/poi/ss/util/PropertyTemplate.java
@@ -28,9 +28,11 @@ import org.apache.poi.ss.usermodel.IndexedColors;
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.util.Removal;
+import java.util.EnumMap;
+import java.util.EnumSet;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -42,9 +44,9 @@ import java.util.Set;
* sheet in any workbook.
*
* This class requires the full spreadsheet to be in memory, so
- * {@link org.apache.poi.xssf.streaming.SXSSFWorkbook} Spreadsheets are not
+ * <code>SXSSFWorkbook</code> Spreadsheets are not
* supported. The same PropertyTemplate can, however, be applied to both
- * {@link HSSFWorkbook} and {@link org.apache.poi.xssf.usermodel.XSSFWorkbook}
+ * {@link HSSFWorkbook} and <code>XSSFWorkbook</code>
* objects if necessary. Portions of the border that fall outside the max range
* of the {@link Workbook} sheet are ignored.
* </p>
@@ -59,7 +61,7 @@ public final class PropertyTemplate {
* This is a list of cell properties for one shot application to a range of
* cells at a later time.
*/
- private final Map<CellAddress, Map<CellPropertyType, Object>> _propertyTemplate;
+ private final Map<CellAddress, EnumMap<CellPropertyType, Object>> _propertyTemplate;
/**
* Create a PropertyTemplate object
@@ -75,17 +77,17 @@ public final class PropertyTemplate {
*/
public PropertyTemplate(PropertyTemplate template) {
this();
- for (Map.Entry<CellAddress, Map<CellPropertyType, Object>> entry : template.getTemplate().entrySet()) {
+ for (Map.Entry<CellAddress, EnumMap<CellPropertyType, Object>> entry : template.getTemplate().entrySet()) {
_propertyTemplate.put(new CellAddress(entry.getKey()), cloneCellProperties(entry.getValue()));
}
}
- private Map<CellAddress, Map<CellPropertyType, Object>> getTemplate() {
+ private Map<CellAddress, EnumMap<CellPropertyType, Object>> getTemplate() {
return _propertyTemplate;
}
- private static Map<CellPropertyType, Object> cloneCellProperties(Map<CellPropertyType, Object> properties) {
- return new HashMap<>(properties);
+ private static EnumMap<CellPropertyType, Object> cloneCellProperties(EnumMap<CellPropertyType, Object> properties) {
+ return new EnumMap<>(properties);
}
/**
@@ -409,11 +411,11 @@ public final class PropertyTemplate {
* @param range - {@link CellRangeAddress} range of cells to remove borders.
*/
private void removeBorders(CellRangeAddress range) {
- Set<CellPropertyType> properties = new HashSet<>();
- properties.add(CellPropertyType.BORDER_TOP);
- properties.add(CellPropertyType.BORDER_BOTTOM);
- properties.add(CellPropertyType.BORDER_LEFT);
- properties.add(CellPropertyType.BORDER_RIGHT);
+ EnumSet<CellPropertyType> properties = EnumSet.of(
+ CellPropertyType.BORDER_TOP,
+ CellPropertyType.BORDER_BOTTOM,
+ CellPropertyType.BORDER_LEFT,
+ CellPropertyType.BORDER_RIGHT);
for (int row = range.getFirstRow(); row <= range.getLastRow(); row++) {
for (int col = range.getFirstColumn(); col <= range
.getLastColumn(); col++) {
@@ -433,7 +435,7 @@ public final class PropertyTemplate {
*/
public void applyBorders(Sheet sheet) {
Workbook wb = sheet.getWorkbook();
- for (Map.Entry<CellAddress, Map<CellPropertyType, Object>> entry : _propertyTemplate
+ for (Map.Entry<CellAddress, EnumMap<CellPropertyType, Object>> entry : _propertyTemplate
.entrySet()) {
CellAddress cellAddress = entry.getKey();
if (cellAddress.getRow() < wb.getSpreadsheetVersion().getMaxRows()
@@ -756,11 +758,11 @@ public final class PropertyTemplate {
* @param range - {@link CellRangeAddress} range of cells to remove borders.
*/
private void removeBorderColors(CellRangeAddress range) {
- Set<CellPropertyType> properties = new HashSet<>();
- properties.add(CellPropertyType.TOP_BORDER_COLOR);
- properties.add(CellPropertyType.BOTTOM_BORDER_COLOR);
- properties.add(CellPropertyType.LEFT_BORDER_COLOR);
- properties.add(CellPropertyType.RIGHT_BORDER_COLOR);
+ Set<CellPropertyType> properties = EnumSet.of(
+ CellPropertyType.TOP_BORDER_COLOR,
+ CellPropertyType.BOTTOM_BORDER_COLOR,
+ CellPropertyType.LEFT_BORDER_COLOR,
+ CellPropertyType.RIGHT_BORDER_COLOR);
for (int row = range.getFirstRow(); row <= range.getLastRow(); row++) {
for (int col = range.getFirstColumn(); col <= range
.getLastColumn(); col++) {
@@ -781,9 +783,9 @@ public final class PropertyTemplate {
*/
private void addProperty(int row, int col, CellPropertyType property, Object value) {
CellAddress cell = new CellAddress(row, col);
- Map<CellPropertyType, Object> cellProperties = _propertyTemplate.get(cell);
+ EnumMap<CellPropertyType, Object> cellProperties = _propertyTemplate.get(cell);
if (cellProperties == null) {
- cellProperties = new HashMap<>();
+ cellProperties = new EnumMap<>(CellPropertyType.class);
}
cellProperties.put(property, value);
_propertyTemplate.put(cell, cellProperties);
@@ -795,7 +797,7 @@ public final class PropertyTemplate {
*/
private void removeProperties(int row, int col, Set<CellPropertyType> properties) {
CellAddress cell = new CellAddress(row, col);
- Map<CellPropertyType, Object> cellProperties = _propertyTemplate.get(cell);
+ EnumMap<CellPropertyType, Object> cellProperties = _propertyTemplate.get(cell);
if (cellProperties != null) {
cellProperties.keySet().removeAll(properties);
if (cellProperties.isEmpty()) {
@@ -946,6 +948,7 @@ public final class PropertyTemplate {
* @deprecated See {@link #getTemplateProperty(int, int, CellPropertyType)}
*/
@Deprecated
+ @Removal(version = "7.0.0")
public short getTemplateProperty(int row, int col, String propertyName) {
return getTemplateProperty(new CellAddress(row, col), CellUtil.namePropertyMap.get(propertyName));
}
diff --git a/poi/src/main/java/org/apache/poi/util/DefaultTempFileCreationStrategy.java b/poi/src/main/java/org/apache/poi/util/DefaultTempFileCreationStrategy.java
index 9b2e711ed4..584f7dec4c 100644
--- a/poi/src/main/java/org/apache/poi/util/DefaultTempFileCreationStrategy.java
+++ b/poi/src/main/java/org/apache/poi/util/DefaultTempFileCreationStrategy.java
@@ -52,6 +52,9 @@ public class DefaultTempFileCreationStrategy implements TempFileCreationStrategy
/** The directory where the temporary files will be created (<code>null</code> to use the default directory). */
private volatile File dir;
+ /** The directory where that was passed to the constructor (<code>null</code> to use the default directory). */
+ private final File initDir;
+
/** The lock to make dir initialized only once. */
private final Lock dirLock = new ReentrantLock();
@@ -65,14 +68,23 @@ public class DefaultTempFileCreationStrategy implements TempFileCreationStrategy
}
/**
- * Creates the strategy allowing to set the
+ * Creates the strategy allowing to set a custom directory for the temporary files.
+ * <p>
+ * If you provide a non-null dir as input, it must be a directory (if it already exists).
+ * Since POI 5.4.2, this is checked at construction time. In previous versions, it was checked
+ * at the first call to {@link #createTempFile(String, String)} or {@link #createTempDirectory(String)}.
+ * </p>
*
* @param dir The directory where the temporary files will be created (<code>null</code> to use the default directory).
- *
+ * @throws IllegalArgumentException if the provided directory does not exist or is not a directory
* @see Files#createTempFile(Path, String, String, FileAttribute[])
*/
public DefaultTempFileCreationStrategy(File dir) {
+ this.initDir = dir;
this.dir = dir;
+ if (dir != null && dir.exists() && !dir.isDirectory()) {
+ throw new IllegalArgumentException("The provided file is not a directory: " + dir);
+ }
}
@Override
@@ -117,7 +129,11 @@ public class DefaultTempFileCreationStrategy implements TempFileCreationStrategy
}
protected Path getPOIFilesDirectoryPath() throws IOException {
- return Paths.get(getJavaIoTmpDir(), POIFILES);
+ if (initDir == null) {
+ return Paths.get(getJavaIoTmpDir(), POIFILES);
+ } else {
+ return initDir.toPath();
+ }
}
// Create our temp dir only once by double-checked locking
diff --git a/poi/src/main/java/org/apache/poi/util/IOUtils.java b/poi/src/main/java/org/apache/poi/util/IOUtils.java
index 430e895557..ff86043a54 100644
--- a/poi/src/main/java/org/apache/poi/util/IOUtils.java
+++ b/poi/src/main/java/org/apache/poi/util/IOUtils.java
@@ -108,6 +108,14 @@ public final class IOUtils {
}
/**
+ * @return The maximum number of bytes that should be possible to be allocated in one step.
+ * @since 5.4.1
+ */
+ public static int getByteArrayMaxOverride() {
+ return BYTE_ARRAY_MAX_OVERRIDE;
+ }
+
+ /**
* Peeks at the first 8 bytes of the stream. Returns those bytes, but
* with the stream unaffected. Requires a stream that supports mark/reset,
* or a PushbackInputStream. If the stream has &gt;0 but &lt;8 bytes,
@@ -209,6 +217,27 @@ public final class IOUtils {
}
/**
+ * Reads up to {@code length} bytes from the input stream, and returns the bytes read.
+ *
+ * @param stream The byte stream of data to read.
+ * @param length The maximum length to read, use {@link Integer#MAX_VALUE} to read the stream
+ * until EOF
+ * @param maxLength if the input is equal to/longer than {@code maxLength} bytes,
+ * then throw an {@link IOException} complaining about the length.
+ * use {@link Integer#MAX_VALUE} to disable the check - if {@link #setByteArrayMaxOverride(int)} is
+ * set then that max of that value and this maxLength is used
+ * @return A byte array with the read bytes.
+ * @throws IOException If reading data fails or EOF is encountered too early for the given length.
+ * @throws RecordFormatException If the requested length is invalid.
+ * @since POI 5.4.1
+ */
+ public static byte[] toByteArray(InputStream stream, final long length, final int maxLength) throws IOException {
+ return toByteArray(stream,
+ length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length,
+ maxLength, true, length != Integer.MAX_VALUE);
+ }
+
+ /**
* Reads the input stream, and returns the bytes read.
*
* @param stream The byte stream of data to read.
@@ -227,15 +256,12 @@ public final class IOUtils {
private static byte[] toByteArray(InputStream stream, final int length, final int maxLength,
final boolean checkEOFException, final boolean isLengthKnown) throws IOException {
- if (length < 0 || maxLength < 0) {
- throw new RecordFormatException("Can't allocate an array of length < 0");
- }
final int derivedMaxLength = Math.max(maxLength, BYTE_ARRAY_MAX_OVERRIDE);
if ((length != Integer.MAX_VALUE) || (derivedMaxLength != Integer.MAX_VALUE)) {
checkLength(length, derivedMaxLength);
}
- final int derivedLen = isLengthKnown ? Math.min(length, derivedMaxLength) : derivedMaxLength;
+ final int derivedLen = isLengthKnown && length >= 0 ? Math.min(length, derivedMaxLength) : derivedMaxLength;
final int byteArrayInitLen = calculateByteArrayInitLength(isLengthKnown, length, derivedMaxLength);
final int internalBufferLen = DEFAULT_BUFFER_SIZE;
try (UnsynchronizedByteArrayOutputStream baos = UnsynchronizedByteArrayOutputStream.builder().setBufferSize(byteArrayInitLen).get()) {
@@ -254,7 +280,7 @@ public final class IOUtils {
throwRecordTruncationException(derivedMaxLength);
}
- if (checkEOFException && derivedLen != Integer.MAX_VALUE && totalBytes < derivedLen) {
+ if (checkEOFException && length >= 0 && derivedLen != Integer.MAX_VALUE && totalBytes < derivedLen) {
throw new EOFException("unexpected EOF - expected len: " + derivedLen + " - actual len: " + totalBytes);
}
diff --git a/poi/src/main/java/org/apache/poi/util/TempFile.java b/poi/src/main/java/org/apache/poi/util/TempFile.java
index 46bf8ccc8b..2b668dc885 100644
--- a/poi/src/main/java/org/apache/poi/util/TempFile.java
+++ b/poi/src/main/java/org/apache/poi/util/TempFile.java
@@ -19,6 +19,8 @@ package org.apache.poi.util;
import java.io.File;
import java.io.IOException;
+import java.util.Objects;
+import java.util.function.Supplier;
/**
* Interface for creating temporary files. Collects them all into one directory by default.
@@ -27,7 +29,14 @@ public final class TempFile {
/** The strategy used by {@link #createTempFile(String, String)} to create the temporary files. */
private static TempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy();
- /** Define a constant for this property as it is sometimes mistypes as "tempdir" otherwise */
+ /** If set for the thread, this is used instead of the strategy variable above. */
+ private static final ThreadLocal<TempFileCreationStrategy> threadLocalStrategy = new ThreadLocal<>();
+ static {
+ // allow to clear all thread-locals via ThreadLocalUtil
+ ThreadLocalUtil.registerCleaner(threadLocalStrategy::remove);
+ }
+
+ /** Define a constant for this property as it is sometimes mistyped as "tempdir" otherwise */
public static final String JAVA_IO_TMPDIR = "java.io.tmpdir";
private TempFile() {
@@ -47,6 +56,21 @@ public final class TempFile {
}
TempFile.strategy = strategy;
}
+
+ /**
+ * Configures the strategy used by {@link #createTempFile(String, String)} to create the temporary files.
+ *
+ * @param strategy The new strategy to be used to create the temporary files for this thread.
+ * <code>null</code> can be used to reset the strategy for this thread to the default one.
+ * @since POI 5.4.2
+ */
+ public static void setThreadLocalTempFileCreationStrategy(TempFileCreationStrategy strategy) {
+ if (strategy == null) {
+ threadLocalStrategy.remove();
+ } else {
+ threadLocalStrategy.set(strategy);
+ }
+ }
/**
* Creates a new and empty temporary file. By default, files are collected into one directory and are not
@@ -64,10 +88,44 @@ public final class TempFile {
* @throws IOException If no temporary file could be created.
*/
public static File createTempFile(String prefix, String suffix) throws IOException {
- return strategy.createTempFile(prefix, suffix);
+ return getStrategy().createTempFile(prefix, suffix);
}
public static File createTempDirectory(String name) throws IOException {
- return strategy.createTempDirectory(name);
+ return getStrategy().createTempDirectory(name);
+ }
+
+ /**
+ * Executes the given task ensuring that POI will use the given temp file creation strategy
+ * within the scope of the given task. The change of strategy is not visible to other threads,
+ * and the previous strategy is restored after the task completed (normally or exceptionally).
+ *
+ * @param newStrategy the temp file strategy to be used in the scope of the given task
+ * @param task the task to be executed with the given temp file strategy
+ * @return the result of the given task
+ *
+ * @since POI 5.4.2
+ */
+ public static <R> R withStrategy(TempFileCreationStrategy newStrategy, Supplier<? extends R> task) {
+ Objects.requireNonNull(newStrategy, "newStrategy");
+ Objects.requireNonNull(task, "task");
+
+ TempFileCreationStrategy oldStrategy = threadLocalStrategy.get();
+ try {
+ threadLocalStrategy.set(newStrategy);
+ return task.get();
+ } finally {
+ setThreadLocalTempFileCreationStrategy(oldStrategy);
+ }
+ }
+
+ private static TempFileCreationStrategy getStrategy() {
+ final TempFileCreationStrategy s = threadLocalStrategy.get();
+ if (s == null) {
+ threadLocalStrategy.remove();
+ return strategy;
+ } else {
+ return s;
+ }
}
}
diff --git a/poi/src/test/java/org/apache/poi/POIDataSamples.java b/poi/src/test/java/org/apache/poi/POIDataSamples.java
index e15843f377..f976cd0c91 100644
--- a/poi/src/test/java/org/apache/poi/POIDataSamples.java
+++ b/poi/src/test/java/org/apache/poi/POIDataSamples.java
@@ -169,7 +169,7 @@ public final class POIDataSamples {
+ "' not found in data dir '" + _resolvedDataDir.getAbsolutePath() + "'");
}
try {
- if(sampleFileName.length() > 0) {
+ if(!sampleFileName.isEmpty()) {
String fn = sampleFileName;
if(fn.indexOf('/') > 0) {
fn = fn.substring(fn.indexOf('/')+1);
diff --git a/poi/src/test/java/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java b/poi/src/test/java/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java
index 2a99b3b395..3ef75d7f3d 100644
--- a/poi/src/test/java/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java
+++ b/poi/src/test/java/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java
@@ -16,7 +16,9 @@
==================================================================== */
package org.apache.poi.hssf.eventusermodel;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -96,7 +98,7 @@ final class TestFormatTrackingHSSFListener {
// Should always give us a string
String s = listener.formatNumberDateCell(cvr);
assertNotNull(s);
- assertTrue(s.length() > 0);
+ assertNotEquals(0, s.length());
}
}
diff --git a/poi/src/test/java/org/apache/poi/hssf/model/TestDrawingAggregate.java b/poi/src/test/java/org/apache/poi/hssf/model/TestDrawingAggregate.java
index 12854be920..8d07fc4d0e 100644
--- a/poi/src/test/java/org/apache/poi/hssf/model/TestDrawingAggregate.java
+++ b/poi/src/test/java/org/apache/poi/hssf/model/TestDrawingAggregate.java
@@ -118,11 +118,7 @@ class TestDrawingAggregate {
UnsynchronizedByteArrayOutputStream out = UnsynchronizedByteArrayOutputStream.builder().get();
for (RecordBase rb : aggRecords) {
Record r = (org.apache.poi.hssf.record.Record) rb;
- try {
- out.write(r.serialize());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ out.write(r.serialize());
}
return out.toByteArray();
}
@@ -263,11 +259,7 @@ class TestDrawingAggregate {
UnsynchronizedByteArrayOutputStream out = UnsynchronizedByteArrayOutputStream.builder().get();
for (RecordBase rb : records) {
Record r = (org.apache.poi.hssf.record.Record) rb;
- try {
- out.write(r.serialize());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ out.write(r.serialize());
}
return out.toByteArray();
}
diff --git a/poi/src/test/java/org/apache/poi/hssf/model/TestEscherRecordFactory.java b/poi/src/test/java/org/apache/poi/hssf/model/TestEscherRecordFactory.java
index 091802098c..f641b6eeb3 100644
--- a/poi/src/test/java/org/apache/poi/hssf/model/TestEscherRecordFactory.java
+++ b/poi/src/test/java/org/apache/poi/hssf/model/TestEscherRecordFactory.java
@@ -42,11 +42,7 @@ class TestEscherRecordFactory {
UnsynchronizedByteArrayOutputStream out = UnsynchronizedByteArrayOutputStream.builder().get();
for (RecordBase rb : records) {
Record r = (org.apache.poi.hssf.record.Record) rb;
- try {
- out.write(r.serialize());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ out.write(r.serialize());
}
return out.toByteArray();
}
diff --git a/poi/src/test/java/org/apache/poi/hssf/model/TestWorkbook.java b/poi/src/test/java/org/apache/poi/hssf/model/TestWorkbook.java
index 76b306b1d9..5b277e52cc 100644
--- a/poi/src/test/java/org/apache/poi/hssf/model/TestWorkbook.java
+++ b/poi/src/test/java/org/apache/poi/hssf/model/TestWorkbook.java
@@ -22,10 +22,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
+import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.apache.poi.hssf.record.CountryRecord;
import org.apache.poi.hssf.record.FontRecord;
import org.apache.poi.hssf.record.RecalcIdRecord;
@@ -170,4 +172,56 @@ final class TestWorkbook {
int newRecordsCount = iwb.getNumRecords();
assertEquals(oldRecordsCount, newRecordsCount, "records count after getWriteAccess");
}
+
+ @Test
+ void testSetUserName() throws IOException {
+ // normal username
+ setAndReadUserName("username", false);
+
+ // 109 characters max if no "multibyte" character
+ setAndReadUserName("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", false);
+
+ // 54 max if there is at least one "multibyte" character
+ setAndReadUserName("€23456789012345678901234567890123456789012345678901234", false);
+
+ // also works for very strange characters
+ setAndReadUserName("\uD801\uDC37\uD852\uDF62€$ⵃⵥꭓꭃꭦ﹄4Uカ\uD800\uDFB1\uD800\uDFC9\uD834\uDF45\uD834\uDF4B\uD83E\uDF22\uD83E\uDF53\uD83E\uDFB5\uD83E\uDFF6\uD801\uDC37\uD852\uDF62€$ⵃⵥꭓꭃꭦ﹄4Uカ\uD800\uDFB1\uD800\uDFC9\uD834\uDF45\uD834\uDF4B", false);
+
+ // fails with longer strings
+ setAndReadUserName("12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890",
+ true);
+ setAndReadUserName("€234567890123456789012345678901234567890123456789012345", true);
+ setAndReadUserName("\uD801\uDC37\uD852\uDF62€$ⵃⵥꭓꭃꭦ﹄4Uカ\uD800\uDFB1\uD800\uDFC9\uD834\uDF45\uD834\uDF4B\uD83E\uDF22\uD83E\uDF53\uD83E\uDFB5\uD83E\uDFF6\uD801\uDC37\uD852\uDF62€$ⵃⵥꭓꭃꭦ﹄4Uカ\uD800\uDFB1\uD800\uDFC9\uD834\uDF45\uD834\uDF4B\uDF4B",
+ true);
+ }
+
+ private static void setAndReadUserName(String username, boolean fails) throws IOException {
+ try (HSSFWorkbook wb = new HSSFWorkbook()) {
+ InternalWorkbook iwb = wb.getInternalWorkbook();
+
+ String prev = iwb.getWriteAccess().getUsername();
+ if (fails) {
+ assertThrows(IllegalArgumentException.class,
+ () -> iwb.getWriteAccess().setUsername(username),
+ "Expected to fail with username " + username);
+ assertEquals(prev, iwb.getWriteAccess().getUsername(),
+ "Username should not have been changed, but had: " + prev + " and " +
+ iwb.getWriteAccess().getUsername());
+
+ // cannot test more if username is too long
+ return;
+ } else {
+ iwb.getWriteAccess().setUsername(username);
+ }
+
+ assertEquals(username, iwb.getWriteAccess().getUsername());
+ try (UnsynchronizedByteArrayOutputStream os = UnsynchronizedByteArrayOutputStream.builder().get()) {
+ wb.write(os);
+ try (HSSFWorkbook wb2 = new HSSFWorkbook(os.toInputStream())) {
+ InternalWorkbook iwb2 = wb2.getInternalWorkbook();
+ assertEquals(username, iwb2.getWriteAccess().getUsername());
+ }
+ }
+ }
+ }
}
diff --git a/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java b/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java
index a44cce0e42..fb3f44eb6c 100644
--- a/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java
+++ b/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java
@@ -211,11 +211,7 @@ final class TestRecordFactory {
};
UnsynchronizedByteArrayOutputStream baos = UnsynchronizedByteArrayOutputStream.builder().get();
for (org.apache.poi.hssf.record.Record rec : recs) {
- try {
- baos.write(rec.serialize());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ baos.write(rec.serialize());
}
//simulate the bad padding at the end of the workbook stream in attachment 23483 of bug 46987
baos.write(0x00);
diff --git a/poi/src/test/java/org/apache/poi/hssf/record/TestWriteAccessRecord.java b/poi/src/test/java/org/apache/poi/hssf/record/TestWriteAccessRecord.java
index ee3cfec38b..8286f442c0 100644
--- a/poi/src/test/java/org/apache/poi/hssf/record/TestWriteAccessRecord.java
+++ b/poi/src/test/java/org/apache/poi/hssf/record/TestWriteAccessRecord.java
@@ -93,4 +93,47 @@ final class TestWriteAccessRecord {
confirmRecordEncoding(WriteAccessRecord.sid, expectedEncoding, rec.serialize());
}
+
+ @Test
+ void testUTF16LE() {
+ byte[] data = HexRead.readFromString(""
+ + "5c 00 70 00 1C 00 01 "
+ + "44 00 61 00 74 00 61 00 20 00 44 00 79 00 6e 00 "
+ + "61 00 6d 00 69 00 63 00 73 00 27 00 20 00 53 00 "
+ + "70 00 72 00 65 00 61 00 64 00 42 00 75 00 69 00 "
+ + "6c 00 64 00 65 00 72 00 20 00 20 00 20 00 20 00 "
+ + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 "
+ + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 "
+ + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 "
+ + "20 00 20 00 20 00"
+ );
+ RecordInputStream in = TestcaseRecordInputStream.create(data);
+
+ WriteAccessRecord rec = new WriteAccessRecord(in);
+
+ assertEquals("Data Dynamics' SpreadBuilder", rec.getUsername());
+ }
+
+ @Test
+ void testUTF16LE_wrong_size() {
+ // "0x51" on position 5 is an incorrect size, as it would require 162 bytes to encode as UTF-16LE
+ // the spec only allows up to 109 bytes for the string in this record, but it seems some broken
+ // software out there will generate such a file
+ byte[] data = HexRead.readFromString(""
+ + "5c 00 70 00 51 00 01 "
+ + "44 00 61 00 74 00 61 00 20 00 44 00 79 00 6e 00 "
+ + "61 00 6d 00 69 00 63 00 73 00 27 00 20 00 53 00 "
+ + "70 00 72 00 65 00 61 00 64 00 42 00 75 00 69 00 "
+ + "6c 00 64 00 65 00 72 00 20 00 20 00 20 00 20 00 "
+ + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 "
+ + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 "
+ + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 "
+ + "20 00 20 00 20 00"
+ );
+ RecordInputStream in = TestcaseRecordInputStream.create(data);
+
+ WriteAccessRecord rec = new WriteAccessRecord(in);
+
+ assertEquals("Data Dynamics' SpreadBuilder", rec.getUsername());
+ }
}
diff --git a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java
index 3b026b2ce1..df7da70e19 100644
--- a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java
+++ b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java
@@ -26,6 +26,8 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
@@ -2316,12 +2318,12 @@ final class TestBugs extends BaseTestBugzillaIssues {
}
@Test
- void test46515() throws IOException {
+ void test46515() throws IOException, URISyntaxException {
try (Workbook wb = openSampleWorkbook("46515.xls")) {
// Get structure from webservice
String urlString = "https://poi.apache.org/components/spreadsheet/images/calendar.jpg";
- URL structURL = new URL(urlString);
+ URL structURL = new URI(urlString).toURL();
BufferedImage bimage;
try {
bimage = ImageIO.read(structURL);
diff --git a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
index 2949dbeeee..61cd1aba69 100644
--- a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
+++ b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
@@ -27,6 +27,7 @@ import java.util.TimeZone;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.model.InternalWorkbook;
+import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.util.LocaleUtil;
import org.junit.jupiter.api.AfterAll;
@@ -60,7 +61,7 @@ class TestHSSFDateUtil {
HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook("DateFormats.xls");
HSSFSheet sheet = workbook.getSheetAt(0);
- InternalWorkbook wb = workbook.getWorkbook();
+ InternalWorkbook wb = workbook.getWorkbook();
assertNotNull(wb);
HSSFRow row;
@@ -115,4 +116,27 @@ class TestHSSFDateUtil {
workbook.close();
}
+
+ @Test
+ void testIsADateFormat() throws IOException {
+ try (HSSFWorkbook workbook = new HSSFWorkbook()) {
+ HSSFSheet sheet = workbook.createSheet();
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell cell = row.createCell(0);
+ cell.setCellValue(45825.5); // 2025-06-17 (midday)
+ HSSFCellStyle style = workbook.createCellStyle();
+ style.setDataFormat(workbook.createDataFormat().getFormat("DD MMMM, YYYY hh:mm:ss.000 AM/PM"));
+ cell.setCellStyle(style);
+ DateUtil.enableThreadLocalCache(false);
+ try {
+ assertTrue(DateUtil.isCellDateFormatted(cell), "cell is date formatted?");
+ DataFormatter formatter = new DataFormatter();
+ String formattedValue = formatter.formatCellValue(cell);
+ assertEquals("17 June, 2025 12:00:00.000 PM", formattedValue);
+ } finally {
+ DateUtil.enableThreadLocalCache(true);
+ }
+ }
+ }
+
}
diff --git a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRowCopyRowFrom.java b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRowCopyRowFrom.java
new file mode 100644
index 0000000000..67dcc38f46
--- /dev/null
+++ b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRowCopyRowFrom.java
@@ -0,0 +1,130 @@
+/* ====================================================================
+ 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.hssf.usermodel;
+
+import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellCopyContext;
+import org.apache.poi.ss.usermodel.CellCopyPolicy;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CreationHelper;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Iterator;
+
+public class TestHSSFRowCopyRowFrom {
+ @Test
+ void testCopyFrom() throws IOException {
+ CellCopyPolicy cellCopyPolicy = new CellCopyPolicy.Builder()
+ .cellFormula(false) // NOTE: setting to false allows for copying the evaluated formula value.
+ .cellStyle(CellCopyPolicy.DEFAULT_COPY_CELL_STYLE_POLICY)
+ .cellValue(CellCopyPolicy.DEFAULT_COPY_CELL_VALUE_POLICY)
+ .condenseRows(CellCopyPolicy.DEFAULT_CONDENSE_ROWS_POLICY)
+ .copyHyperlink(CellCopyPolicy.DEFAULT_COPY_HYPERLINK_POLICY)
+ .mergeHyperlink(CellCopyPolicy.DEFAULT_MERGE_HYPERLINK_POLICY)
+ .mergedRegions(CellCopyPolicy.DEFAULT_COPY_MERGED_REGIONS_POLICY)
+ .rowHeight(CellCopyPolicy.DEFAULT_COPY_ROW_HEIGHT_POLICY)
+ .build();
+
+ final LocalDateTime localDateTime = LocalDateTime.of(2023, 1, 1, 0, 0, 0);
+ final LocalDateTime nonValidExcelDate = LocalDateTime.of(1899, 12, 31, 0, 0, 0);
+ final Object[][] data = {
+ {"transaction_id", "transaction_date", "transaction_time"},
+ {75, localDateTime, nonValidExcelDate.plusHours(9).plusMinutes(53).plusSeconds(44).toLocalTime()},
+ {78, localDateTime, nonValidExcelDate.plusHours(9).plusMinutes(55).plusSeconds(16).toLocalTime()}
+ };
+
+ final UnsynchronizedByteArrayOutputStream workbookOutputStream =
+ UnsynchronizedByteArrayOutputStream.builder().get();
+ try (Workbook workbook = new HSSFWorkbook()) {
+ final Sheet sheet = workbook.createSheet("SomeSheetName");
+ populateSheet(sheet, data);
+ setCellStyles(sheet, workbook);
+ workbook.write(workbookOutputStream);
+ }
+
+ try (HSSFWorkbook originalWorkbook = new HSSFWorkbook(workbookOutputStream.toInputStream())) {
+ final Iterator<Sheet> originalSheetsIterator = originalWorkbook.sheetIterator();
+ final CellCopyContext cellCopyContext = new CellCopyContext();
+
+ while (originalSheetsIterator.hasNext()) {
+ final HSSFSheet originalSheet = (HSSFSheet) originalSheetsIterator.next();
+ final String originalSheetName = originalSheet.getSheetName();
+ final Iterator<Row> originalRowsIterator = originalSheet.rowIterator();
+
+ try (HSSFWorkbook newWorkbook = new HSSFWorkbook()) {
+ final HSSFSheet newSheet = newWorkbook.createSheet(originalSheetName);
+ while (originalRowsIterator.hasNext()) {
+ HSSFRow originalRow = (HSSFRow) originalRowsIterator.next();
+ HSSFRow newRow = newSheet.createRow(originalRow.getRowNum());
+ newRow.copyRowFrom(originalRow, cellCopyPolicy, cellCopyContext);
+ }
+ }
+ }
+ }
+ }
+
+ private static void populateSheet(Sheet sheet, Object[][] data) {
+ int rowCount = 0;
+ for (Object[] dataRow : data) {
+ Row row = sheet.createRow(rowCount++);
+ int columnCount = 0;
+
+ for (Object field : dataRow) {
+ Cell cell = row.createCell(columnCount++);
+ if (field instanceof String) {
+ cell.setCellValue((String) field);
+ } else if (field instanceof Integer) {
+ cell.setCellValue((Integer) field);
+ } else if (field instanceof Long) {
+ cell.setCellValue((Long) field);
+ } else if (field instanceof LocalDateTime) {
+ cell.setCellValue((LocalDateTime) field);
+ } else if (field instanceof LocalTime) {
+ cell.setCellValue(DateUtil.convertTime(DateTimeFormatter.ISO_LOCAL_TIME.format((LocalTime) field)));
+ }
+ }
+ }
+ }
+
+ void setCellStyles(Sheet sheet, Workbook workbook) {
+ CreationHelper creationHelper = workbook.getCreationHelper();
+ CellStyle dayMonthYearCellStyle = workbook.createCellStyle();
+ dayMonthYearCellStyle.setDataFormat(creationHelper.createDataFormat().getFormat("dd/mm/yyyy"));
+ CellStyle hourMinuteSecond = workbook.createCellStyle();
+ hourMinuteSecond.setDataFormat((short) 21); // 21 represents format h:mm:ss
+ for (int rowNum = sheet.getFirstRowNum() + 1; rowNum < sheet.getLastRowNum() + 1; rowNum++) {
+ Row row = sheet.getRow(rowNum);
+ row.getCell(1).setCellStyle(dayMonthYearCellStyle);
+ row.getCell(2).setCellStyle(hourMinuteSecond);
+ }
+ }
+}
diff --git a/poi/src/test/java/org/apache/poi/hssf/util/TestHSSFColor.java b/poi/src/test/java/org/apache/poi/hssf/util/TestHSSFColor.java
index 059dbb28b3..1bde058114 100644
--- a/poi/src/test/java/org/apache/poi/hssf/util/TestHSSFColor.java
+++ b/poi/src/test/java/org/apache/poi/hssf/util/TestHSSFColor.java
@@ -17,6 +17,7 @@
package org.apache.poi.hssf.util;
+import java.awt.Color;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -56,4 +57,15 @@ final class TestHSSFColor {
triplets.get(HSSFColorPredefined.YELLOW.getHexString())
);
}
+
+ @Test
+ void testRgbAndColorWorkTheSame() {
+ Color color = java.awt.Color.YELLOW;
+ @SuppressWarnings("deprecation")
+ HSSFColor fromColor = new HSSFColor(10, 20, color);
+ HSSFColor fromRgb = new HSSFColor(10, 20, color.getRGB());
+
+ assertEquals(fromColor, fromRgb, "Both colors are equal");
+ assertEquals(fromColor.hashCode(), fromRgb.hashCode(), "Both have the same hash code");
+ }
}
diff --git a/poi/src/test/java/org/apache/poi/ss/formula/atp/TestAnalysisToolPak.java b/poi/src/test/java/org/apache/poi/ss/formula/atp/TestAnalysisToolPak.java
new file mode 100644
index 0000000000..2861cab7f0
--- /dev/null
+++ b/poi/src/test/java/org/apache/poi/ss/formula/atp/TestAnalysisToolPak.java
@@ -0,0 +1,75 @@
+/* ====================================================================
+ 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.ss.formula.atp;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class TestAnalysisToolPak {
+ private static class NullFunction implements FreeRefFunction {
+ @Override
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+ return ErrorEval.DIV_ZERO;
+ }
+ }
+
+ @Test
+ void testOverrideKnownButUnimplemented() {
+ // JIS is a known function in Excel, but it is not implemented in POI
+ AnalysisToolPak.registerFunction("JIS", new NullFunction());
+ try (HSSFWorkbook workbook = new HSSFWorkbook()) {
+ HSSFSheet sheet = workbook.createSheet("Sheet1");
+ HSSFCell cell = sheet.createRow(0).createCell(0);
+ cell.setCellFormula("JIS(\"test\")");
+ workbook.getCreationHelper().createFormulaEvaluator().evaluateAll();
+ assertEquals(ErrorEval.DIV_ZERO.getErrorCode(), cell.getErrorCellValue());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ void testOverrideUnknown() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ AnalysisToolPak.registerFunction("UNKNOWN", new NullFunction());
+ });
+ }
+
+ @Test
+ void testOverrideUnknownButForceAllowed() {
+ AnalysisToolPak.registerFunction("FAKE", new NullFunction(), true);
+ try (HSSFWorkbook workbook = new HSSFWorkbook()) {
+ HSSFSheet sheet = workbook.createSheet("Sheet1");
+ HSSFCell cell = sheet.createRow(0).createCell(0);
+ cell.setCellFormula("FAKE(\"test\")");
+ workbook.getCreationHelper().createFormulaEvaluator().evaluateAll();
+ assertEquals(ErrorEval.DIV_ZERO.getErrorCode(), cell.getErrorCellValue());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelCetabFunctionExtractor.java b/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelCetabFunctionExtractor.java
index 87c0dcd1f3..3d6f525d2b 100644
--- a/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelCetabFunctionExtractor.java
+++ b/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelCetabFunctionExtractor.java
@@ -149,7 +149,7 @@ public final class ExcelCetabFunctionExtractor {
public void addFunction(int funcIx, boolean hasFootnote, String funcName, int minParams, int maxParams,
String returnClass, String paramClasses, String volatileFlagStr) {
- boolean isVolatile = volatileFlagStr.length() > 0;
+ boolean isVolatile = !volatileFlagStr.isEmpty();
Integer funcIxKey = Integer.valueOf(funcIx);
if(!_groupFunctionIndexes.add(funcIxKey)) {
diff --git a/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelFileFormatDocFunctionExtractor.java b/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelFileFormatDocFunctionExtractor.java
index b131071abf..11dca94104 100644
--- a/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelFileFormatDocFunctionExtractor.java
+++ b/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelFileFormatDocFunctionExtractor.java
@@ -26,6 +26,8 @@ import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
@@ -169,9 +171,9 @@ public final class ExcelFileFormatDocFunctionExtractor {
public void addFunction(int funcIx, boolean hasFootnote, String funcName, int minParams, int maxParams,
String returnClass, String paramClasses, String volatileFlagStr) {
- boolean isVolatile = volatileFlagStr.length() > 0;
+ boolean isVolatile = !volatileFlagStr.isEmpty();
- Integer funcIxKey = Integer.valueOf(funcIx);
+ Integer funcIxKey = funcIx;
if(!_groupFunctionIndexes.add(funcIxKey)) {
throw new RuntimeException("Duplicate function index (" + funcIx + ")");
}
@@ -209,7 +211,7 @@ public final class ExcelFileFormatDocFunctionExtractor {
throw new RuntimeException("changing function '"
+ funcName + "' definition without foot-note");
}
- _allFunctionsByIndex.remove(Integer.valueOf(fdPrev.getIndex()));
+ _allFunctionsByIndex.remove(fdPrev.getIndex());
}
}
@@ -326,7 +328,7 @@ public final class ExcelFileFormatDocFunctionExtractor {
processTableRow(cellData, noteFlags);
} else if(matchesRelPath(TABLE_CELL_RELPATH_NAMES)) {
_rowData.add(_textNodeBuffer.toString().trim());
- _rowNoteFlags.add(Boolean.valueOf(_cellHasNote));
+ _rowNoteFlags.add(_cellHasNote);
_textNodeBuffer.setLength(0);
}
}
@@ -350,7 +352,7 @@ public final class ExcelFileFormatDocFunctionExtractor {
}
int funcIx = parseInt(funcIxStr);
- boolean hasFootnote = noteFlags[i + 1].booleanValue();
+ boolean hasFootnote = noteFlags[i + 1];
String funcName = cellData[i + 1];
int minParams = parseInt(cellData[i + 2]);
int maxParams = parseInt(cellData[i + 3]);
@@ -577,8 +579,8 @@ public final class ExcelFileFormatDocFunctionExtractor {
private static File downloadSourceFile() {
URL url;
try {
- url = new URL("http://sc.openoffice.org/" + SOURCE_DOC_FILE_NAME);
- } catch (MalformedURLException e) {
+ url = new URI("http://sc.openoffice.org/" + SOURCE_DOC_FILE_NAME).toURL();
+ } catch (MalformedURLException | URISyntaxException e) {
throw new RuntimeException(e);
}
diff --git a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestNumericFunction.java b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestNumericFunction.java
index 632c53ad39..6343cb1a06 100644
--- a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestNumericFunction.java
+++ b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestNumericFunction.java
@@ -69,7 +69,7 @@ final class TestNumericFunction {
void testDOLLAR() {
Locale defaultLocale = LocaleUtil.getUserLocale();
try {
- LocaleUtil.setUserLocale(new Locale("en", "US"));
+ LocaleUtil.setUserLocale(new Locale.Builder().setLanguage("en").setRegion("US").build());
HSSFWorkbook wb = new HSSFWorkbook();
HSSFCell cell = wb.createSheet().createRow(0).createCell(0);
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
@@ -89,7 +89,7 @@ final class TestNumericFunction {
void testDOLLARIreland() {
Locale defaultLocale = LocaleUtil.getUserLocale();
try {
- LocaleUtil.setUserLocale(new Locale("en", "IE"));
+ LocaleUtil.setUserLocale(new Locale.Builder().setLanguage("en").setRegion("IE").build());
HSSFWorkbook wb = new HSSFWorkbook();
HSSFCell cell = wb.createSheet().createRow(0).createCell(0);
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
@@ -104,7 +104,7 @@ final class TestNumericFunction {
void testDOLLARSpain() {
Locale defaultLocale = LocaleUtil.getUserLocale();
try {
- LocaleUtil.setUserLocale(new Locale("es", "ES"));
+ LocaleUtil.setUserLocale(new Locale.Builder().setLanguage("es").setRegion("ES").build());
HSSFWorkbook wb = new HSSFWorkbook();
HSSFCell cell = wb.createSheet().createRow(0).createCell(0);
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
@@ -119,7 +119,7 @@ final class TestNumericFunction {
void testDOLLARJapan() {
Locale defaultLocale = LocaleUtil.getUserLocale();
try {
- LocaleUtil.setUserLocale(new Locale("ja", "JP"));
+ LocaleUtil.setUserLocale(new Locale.Builder().setLanguage("ja").setRegion("JP").build());
HSSFWorkbook wb = new HSSFWorkbook();
HSSFCell cell = wb.createSheet().createRow(0).createCell(0);
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
@@ -135,7 +135,7 @@ final class TestNumericFunction {
void testDOLLARDenmark() {
Locale defaultLocale = LocaleUtil.getUserLocale();
try {
- LocaleUtil.setUserLocale(new Locale("da", "DK"));
+ LocaleUtil.setUserLocale(new Locale.Builder().setLanguage("da").setRegion("DK").build());
HSSFWorkbook wb = new HSSFWorkbook();
HSSFCell cell = wb.createSheet().createRow(0).createCell(0);
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
diff --git a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestSheet.java b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestSheet.java
new file mode 100644
index 0000000000..894db09814
--- /dev/null
+++ b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestSheet.java
@@ -0,0 +1,89 @@
+/* ====================================================================
+ 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.ss.formula.functions;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.FormulaError;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+final class TestSheet {
+
+ private static final OperationEvaluationContext ec = new OperationEvaluationContext(null, null, 2, 0, 2, null);
+
+ @Test
+ void testSheetFunctionWithRealWorkbook() throws IOException {
+ try (HSSFWorkbook wb = new HSSFWorkbook()) {
+ // Add three sheets: Sheet1, Sheet2, Sheet3
+ HSSFSheet sheet1 = wb.createSheet("Sheet1");
+ HSSFSheet sheet2 = wb.createSheet("Sheet2");
+ HSSFSheet sheet3 = wb.createSheet("Sheet3");
+
+ // Add data
+ sheet1.createRow(0).createCell(0).setCellValue(123); // A1 in Sheet1
+ sheet2.createRow(1).createCell(0).setCellValue(456); // A2 in Sheet2
+
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+
+ // Define formulas and expected results
+ String[] formulas = {
+ "SHEET()",
+ "SHEET(A1)",
+ "SHEET(A1:B5)",
+ "SHEET(Sheet2!A2)",
+ "SHEET(\"Sheet3\")",
+ "SHEET(\"invalid\")"
+ };
+
+ Object[] expected = {
+ 1.0, // current sheet
+ 1.0, // A1 in same sheet
+ 1.0, // A1:B5 in same sheet
+ 2.0, // Sheet2!A2
+ 3.0, // Sheet3
+ FormulaError.NA.getCode() // unknown sheet → #N/A
+ };
+
+ // Write formulas to separate cells and evaluate
+ HSSFRow formulaRow = sheet1.createRow(1);
+ for (int i = 0; i < formulas.length; i++) {
+ String formula = formulas[i];
+ HSSFCell cell = formulaRow.createCell(i);
+ cell.setCellFormula(formula);
+ CellType resultType = fe.evaluateFormulaCell(cell);
+
+ if (expected[i] instanceof Double) {
+ assertEquals(CellType.NUMERIC, resultType,
+ "Unexpected cell type for formula: " + formula);
+ assertEquals((Double) expected[i], cell.getNumericCellValue(),
+ "Unexpected numeric result for formula: " + formula);
+ } else if (expected[i] instanceof Byte) {
+ assertEquals(CellType.ERROR, resultType,
+ "Unexpected cell type for formula: " + formula);
+ assertEquals((byte) expected[i], cell.getErrorCellValue(),
+ "Unexpected error code for formula: " + formula);
+ }
+ }
+ }
+ }
+}
diff --git a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestCell.java b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestCell.java
index de2c4ad7c6..e42a662813 100644
--- a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestCell.java
+++ b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestCell.java
@@ -365,6 +365,9 @@ public abstract class BaseTestCell {
dateStyle.setDataFormat(formatId);
r.getCell(7).setCellStyle(dateStyle);
+ // null rich text
+ r.createCell(8).setCellValue(factory.createRichTextString(null)); // blank
+
assertEquals("FALSE", r.getCell(0).toString(), "Boolean");
assertEquals("TRUE", r.getCell(1).toString(), "Boolean");
assertEquals("1.5", r.getCell(2).toString(), "Numeric");
@@ -375,6 +378,7 @@ public abstract class BaseTestCell {
// toString on a date-formatted cell displays dates as dd-MMM-yyyy, which has locale problems with the month
String dateCell1 = r.getCell(7).toString();
assertEquals("2/2/10 0:00", dateCell1);
+ assertEquals("", r.getCell(8).toString(), "Blank");
//Write out the file, read it in, and then check cell values
try (Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb1)) {
@@ -388,6 +392,7 @@ public abstract class BaseTestCell {
assertEquals("", r.getCell(6).toString(), "Blank");
String dateCell2 = r.getCell(7).toString();
assertEquals(dateCell1, dateCell2, "Date");
+ assertEquals("", r.getCell(8).toString(), "Blank");
}
}
}
@@ -1468,6 +1473,36 @@ public abstract class BaseTestCell {
verify(cell).setBlank();
}
+ @Test
+ protected void setCellNullString() throws IOException {
+ try (Workbook wb = _testDataProvider.createWorkbook()) {
+ Cell cell = getInstance(wb);
+
+ cell.setCellValue((String)null);
+
+ // setting string "null" leads to a BLANK cell
+ assertEquals(CellType.BLANK, cell.getCellType());
+ assertEquals("", cell.getStringCellValue());
+ assertEquals("", cell.toString());
+
+ cell.setCellType(CellType.STRING);
+
+ // forcing to string type leads to STRING cell, but still empty strings
+ assertEquals(CellType.STRING, cell.getCellType());
+ assertEquals("", cell.getStringCellValue());
+ assertEquals("", cell.toString());
+
+ try (Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb)) {
+ // read first sheet, first row, first cell
+ Cell cellBack = wb2.iterator().next().iterator().next().iterator().next();
+
+ assertEquals(CellType.STRING, cell.getCellType());
+ assertEquals("", cell.getStringCellValue());
+ assertEquals("", cell.toString());
+ }
+ }
+ }
+
private Cell getInstance(Workbook wb) {
return wb.createSheet().createRow(0).createCell(0);
}
diff --git a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestConditionalFormatting.java b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestConditionalFormatting.java
index 29f62da235..64f585608a 100644
--- a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestConditionalFormatting.java
+++ b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestConditionalFormatting.java
@@ -531,7 +531,7 @@ public abstract class BaseTestConditionalFormatting {
// Sanity check data
assertEquals("Values", s.getRow(0).getCell(0).toString());
- assertEquals("10", s.getRow(2).getCell(0).toString());
+ assertEquals(10.0, s.getRow(2).getCell(0).getNumericCellValue());
// Check we found all the conditional formatting rules we should have
SheetConditionalFormatting sheetCF = s.getSheetConditionalFormatting();
diff --git a/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java b/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java
index 0d49bee3ab..b1fced30a5 100644
--- a/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java
+++ b/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java
@@ -937,7 +937,7 @@ class TestDataFormatter {
// 2017-12-01 09:54:33 which is 42747.412892397523 as double
DataFormatter dfDE = new DataFormatter(Locale.GERMANY);
DataFormatter dfZH = new DataFormatter(Locale.PRC);
- DataFormatter dfIE = new DataFormatter(new Locale("GA", "IE"));
+ DataFormatter dfIE = new DataFormatter(new Locale.Builder().setLanguage("GA").setRegion("IE").build());
double date = 42747.412892397523;
String format = "dd MMMM yyyy HH:mm:ss";
assertEquals("12 Januar 2017 09:54:33", dfDE.formatRawCellContents(date, -1, format));
diff --git a/poi/src/test/java/org/apache/poi/ss/usermodel/TestExcelStyleDateFormatter.java b/poi/src/test/java/org/apache/poi/ss/usermodel/TestExcelStyleDateFormatter.java
index b3ccd06a4c..f905470f88 100644
--- a/poi/src/test/java/org/apache/poi/ss/usermodel/TestExcelStyleDateFormatter.java
+++ b/poi/src/test/java/org/apache/poi/ss/usermodel/TestExcelStyleDateFormatter.java
@@ -58,11 +58,12 @@ class TestExcelStyleDateFormatter {
formatter.setDateFormatSymbols(DateFormatSymbols.getInstance(locale));
String result = formatter.format(d, sb, fp).toString();
- String msg = "Failed testDates for locale " + locale + ", provider: " + provider +
- " and date " + d + ", having: " + result;
int actIdx = localeIndex(locale);
+ String msg = "Failed testDates for locale " + locale + ", provider: " + provider +
+ " and date " + d + ", having actIdx: " + actIdx + " and result '" + result + "' (" + result.length() + ")";
+
assertNotNull(result, msg);
assertTrue(result.length() > actIdx, msg);
assertEquals(expected.charAt(month), result.charAt(actIdx), msg);
@@ -102,15 +103,15 @@ class TestExcelStyleDateFormatter {
public static Stream<Arguments> initializeLocales() throws ParseException {
Object[][] locExps = {
{ Locale.GERMAN, "JFMAMJJASOND" },
- { new Locale("de", "AT"), "JFMAMJJASOND" },
+ { new Locale.Builder().setLanguage("de").setRegion("AT").build(), "JFMAMJJASOND" },
{ Locale.UK, "JFMAMJJASOND" },
- { new Locale("en", "IN"), "JFMAMJJASOND" },
- { new Locale("in", "ID"), "JFMAMJJASOND" },
+ { new Locale.Builder().setLanguage("en").setRegion("IN").build(), "JFMAMJJASOND" },
+ { new Locale.Builder().setLanguage("in").setRegion("ID").build(), "JFMAMJJASOND" },
{ Locale.FRENCH, "jfmamjjasond" },
- { new Locale("ru", "RU"), "\u044f\u0444\u043c\u0430\u043c\u0438\u0438\u0430\u0441\u043e\u043d\u0434" },
+ { new Locale.Builder().setLanguage("ru").setRegion("RU").build(), "\u044f\u0444\u043c\u0430\u043c\u0438\u0438\u0430\u0441\u043e\u043d\u0434" },
{ Locale.CHINESE, localeIndex(Locale.CHINESE) == 0 ? "\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u5341\u5341" : "123456789111" },
- { new Locale("tr", "TR"), "\u004f\u015e\u004d\u004e\u004d\u0048\u0054\u0041\u0045\u0045\u004b\u0041" },
- { new Locale("hu", "HU"), "\u006a\u0066\u006d\u00e1\u006d\u006a\u006a\u0061\u0073\u006f\u006e\u0064" }
+ { new Locale.Builder().setLanguage("tr").setRegion("TR").build(), "\u004f\u015e\u004d\u004e\u004d\u0048\u0054\u0041\u0045\u0045\u004b\u0041" },
+ { new Locale.Builder().setLanguage("hu").setRegion("HU").build(), "\u006a\u0066\u006d\u00e1\u006d\u006a\u006a\u0061\u0073\u006f\u006e\u0064" }
};
String[] dates = {
diff --git a/poi/src/test/java/org/apache/poi/ss/util/BaseTestCellUtil.java b/poi/src/test/java/org/apache/poi/ss/util/BaseTestCellUtil.java
index 9784e080b5..fb2200ba57 100644
--- a/poi/src/test/java/org/apache/poi/ss/util/BaseTestCellUtil.java
+++ b/poi/src/test/java/org/apache/poi/ss/util/BaseTestCellUtil.java
@@ -22,6 +22,7 @@ import org.apache.poi.ss.usermodel.*;
import org.junit.jupiter.api.Test;
import java.io.IOException;
+import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
@@ -339,7 +340,7 @@ public abstract class BaseTestCellUtil {
// Add multiple border properties to cell should create a single new style
int styCnt1 = wb.getNumCellStyles();
- Map<CellPropertyType, Object> props = new HashMap<>();
+ EnumMap<CellPropertyType, Object> props = new EnumMap<>(CellPropertyType.class);
props.put(CellPropertyType.BORDER_TOP, BorderStyle.THIN);
props.put(CellPropertyType.BORDER_BOTTOM, BorderStyle.THIN);
props.put(CellPropertyType.BORDER_LEFT, BorderStyle.THIN);
@@ -574,7 +575,7 @@ public abstract class BaseTestCellUtil {
protected void setFillForegroundColorBeforeFillBackgroundColorEnumByEnum() throws IOException {
try (Workbook wb1 = _testDataProvider.createWorkbook()) {
Cell A1 = wb1.createSheet().createRow(0).createCell(0);
- Map<CellPropertyType, Object> properties = new HashMap<>();
+ EnumMap<CellPropertyType, Object> properties = new EnumMap<>(CellPropertyType.class);
properties.put(CellPropertyType.FILL_PATTERN, FillPatternType.BRICKS);
properties.put(CellPropertyType.FILL_FOREGROUND_COLOR, IndexedColors.BLUE.index);
properties.put(CellPropertyType.FILL_BACKGROUND_COLOR, IndexedColors.RED.index);
diff --git a/poi/src/test/java/org/apache/poi/ss/util/TestCellUtil.java b/poi/src/test/java/org/apache/poi/ss/util/TestCellUtil.java
new file mode 100644
index 0000000000..b7defbef99
--- /dev/null
+++ b/poi/src/test/java/org/apache/poi/ss/util/TestCellUtil.java
@@ -0,0 +1,36 @@
+/* ====================================================================
+ 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.ss.util;
+
+import org.apache.poi.ss.usermodel.CellPropertyType;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test for CellUtil constants
+ */
+class TestCellUtil {
+ @Test
+ void testNamePropertyMap() {
+ Arrays.stream(CellPropertyType.values()).forEach(cellPropertyType ->
+ assertTrue(CellUtil.namePropertyMap.containsValue(cellPropertyType),
+ "missing " + cellPropertyType));
+ }
+}
diff --git a/poi/src/test/java/org/apache/poi/ss/util/TestDateFormatConverter.java b/poi/src/test/java/org/apache/poi/ss/util/TestDateFormatConverter.java
index 059426289e..59c8acf695 100644
--- a/poi/src/test/java/org/apache/poi/ss/util/TestDateFormatConverter.java
+++ b/poi/src/test/java/org/apache/poi/ss/util/TestDateFormatConverter.java
@@ -130,7 +130,7 @@ final class TestDateFormatConverter {
void testJDK8EmptyLocale() {
// JDK 8 seems to add an empty locale-string to the list returned via DateFormat.getAvailableLocales()
// therefore we now cater for this special locale as well
- String prefix = getPrefixForLocale(new Locale(""));
+ String prefix = getPrefixForLocale(new Locale.Builder().build());
assertEquals("", prefix);
}
diff --git a/poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java b/poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java
index 4e4f6b779e..d60e4da8ac 100644
--- a/poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java
+++ b/poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java
@@ -20,11 +20,14 @@ package org.apache.poi.util;
import static org.apache.poi.util.DefaultTempFileCreationStrategy.DELETE_FILES_ON_EXIT;
import static org.apache.poi.util.DefaultTempFileCreationStrategy.POIFILES;
import static org.apache.poi.util.TempFile.JAVA_IO_TMPDIR;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.AfterEach;
@@ -59,16 +62,62 @@ class DefaultTempFileCreationStrategyTest {
checkGetFile(strategy);
}
- private static void checkGetFile(DefaultTempFileCreationStrategy strategy) throws IOException {
- File file = strategy.createTempFile("POITest", ".tmp");
+ @Test
+ void testProvidedDir() throws IOException {
+ DefaultTempFileCreationStrategy parentStrategy = new DefaultTempFileCreationStrategy();
+ File dir = parentStrategy.createTempDirectory("testProvidedDir");
+ assertNotNull(dir, "Failed to create temp directory");
try {
- assertTrue(file.getParentFile().exists(),
- "Failed for " + file.getParentFile());
+ assertTrue(Files.isDirectory(dir.toPath()), "File is not a directory: " + dir);
+ DefaultTempFileCreationStrategy testStrategy = new DefaultTempFileCreationStrategy(dir);
+ checkGetFileAndPath(testStrategy, dir.toPath());
+ } finally {
+ // Clean up the directory after the test
+ FileUtils.deleteDirectory(dir);
+ }
+ }
- assertTrue(file.exists(),
- "Failed for " + file);
+ @Test
+ void testProvidedDirThreadLocal() throws IOException {
+ DefaultTempFileCreationStrategy parentStrategy = new DefaultTempFileCreationStrategy();
+ File dir = parentStrategy.createTempDirectory("testProvidedDir");
+ assertNotNull(dir, "Failed to create temp directory");
+ try {
+ assertTrue(Files.isDirectory(dir.toPath()), "File is not a directory: " + dir);
+ DefaultTempFileCreationStrategy testStrategy = new DefaultTempFileCreationStrategy(dir);
+ TempFile.setThreadLocalTempFileCreationStrategy(testStrategy);
+ checkGetFileAndPath(dir.toPath());
} finally {
- assertTrue(file.delete());
+ // Clean up the directory after the test
+ FileUtils.deleteDirectory(dir);
+ TempFile.setThreadLocalTempFileCreationStrategy(null);
+ }
+ }
+
+ @Test
+ void testProvidedDirNotExists() throws IOException {
+ DefaultTempFileCreationStrategy parentStrategy = new DefaultTempFileCreationStrategy();
+ File dir = parentStrategy.createTempDirectory("testProvidedDir");
+ assertNotNull(dir, "Failed to create temp directory");
+ assertTrue(dir.delete(), "directory not deleted: " + dir);
+ try {
+ DefaultTempFileCreationStrategy testStrategy = new DefaultTempFileCreationStrategy(dir);
+ checkGetFileAndPath(testStrategy, dir.toPath());
+ } finally {
+ // Clean up the directory after the test
+ FileUtils.deleteDirectory(dir);
+ }
+ }
+
+ @Test
+ void testProvidedDirIsActuallyAPlainFile() throws IOException {
+ DefaultTempFileCreationStrategy parentStrategy = new DefaultTempFileCreationStrategy();
+ File dir = parentStrategy.createTempFile("test123", ".tmp");
+ assertNotNull(dir, "Failed to create temp file");
+ try {
+ assertThrows(IllegalArgumentException.class, () -> new DefaultTempFileCreationStrategy(dir));
+ } finally {
+ dir.delete();
}
}
@@ -106,10 +155,11 @@ class DefaultTempFileCreationStrategyTest {
}
@Test
- void testCustomDir() throws IOException {
+ void testCustomDirExists() throws IOException {
File dirTest = File.createTempFile("POITest", ".dir");
try {
assertTrue(dirTest.delete());
+ assertTrue(dirTest.mkdir());
DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(dirTest);
checkGetFile(strategy);
@@ -119,11 +169,12 @@ class DefaultTempFileCreationStrategyTest {
}
@Test
- void testCustomDirExists() throws IOException {
+ void testCustomDirAndPoiFilesExists() throws IOException {
File dirTest = File.createTempFile("POITest", ".dir");
try {
assertTrue(dirTest.delete());
assertTrue(dirTest.mkdir());
+ assertTrue(new File(dirTest, POIFILES).mkdir());
DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(dirTest);
checkGetFile(strategy);
@@ -132,18 +183,35 @@ class DefaultTempFileCreationStrategyTest {
}
}
- @Test
- void testCustomDirAndPoiFilesExists() throws IOException {
- File dirTest = File.createTempFile("POITest", ".dir");
+ private static void checkGetFile(DefaultTempFileCreationStrategy strategy) throws IOException {
+ checkGetFileAndPath(strategy, null);
+ }
+
+ private static void checkGetFileAndPath(DefaultTempFileCreationStrategy strategy,
+ Path path) throws IOException {
+ File file = strategy.createTempFile("POITest", ".tmp");
+ testFileAndPath(file, path);
+ }
+
+ private static void checkGetFileAndPath(Path path) throws IOException {
+ File file = TempFile.createTempFile("POITest", ".tmp");
+ testFileAndPath(file, path);
+ }
+
+ private static void testFileAndPath(File file, Path path) throws IOException {
try {
- assertTrue(dirTest.delete());
- assertTrue(dirTest.mkdir());
- assertTrue(new File(dirTest, POIFILES).mkdir());
+ if (path != null) {
+ assertTrue(file.toPath().startsWith(path),
+ "File path does not start with expected path: " + path);
+ }
- DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(dirTest);
- checkGetFile(strategy);
+ assertTrue(file.getParentFile().exists(),
+ "Failed for " + file.getParentFile());
+
+ assertTrue(file.exists(),
+ "Failed for " + file);
} finally {
- FileUtils.deleteDirectory(dirTest);
+ assertTrue(file.delete());
}
}
-} \ No newline at end of file
+}
diff --git a/poi/src/test/java/org/apache/poi/util/ExceptionUtilTest.java b/poi/src/test/java/org/apache/poi/util/ExceptionUtilTest.java
index d5d4fdd77a..d78e679789 100644
--- a/poi/src/test/java/org/apache/poi/util/ExceptionUtilTest.java
+++ b/poi/src/test/java/org/apache/poi/util/ExceptionUtilTest.java
@@ -42,7 +42,7 @@ class ExceptionUtilTest {
assertTrue(ExceptionUtil.isFatal(new VirtualMachineError(){}));
}
-
+ @SuppressForbidden("Test with ThreadDeath on purpose here")
@Test
void testThreadDeath() {
assertTrue(ExceptionUtil.isFatal(new ThreadDeath()));
diff --git a/poi/src/test/java/org/apache/poi/util/TestIOUtils.java b/poi/src/test/java/org/apache/poi/util/TestIOUtils.java
index bb12f9932e..7f026adc74 100644
--- a/poi/src/test/java/org/apache/poi/util/TestIOUtils.java
+++ b/poi/src/test/java/org/apache/poi/util/TestIOUtils.java
@@ -110,8 +110,25 @@ final class TestIOUtils {
}
@Test
- void testToByteArrayNegativeLength() {
- assertThrows(RecordFormatException.class, () -> IOUtils.toByteArray(data123(), -1));
+ void testToByteArrayNegativeLength() throws IOException {
+ final byte[] array = new byte[]{1, 2, 3, 4, 5, 6, 7};
+ IOUtils.setByteArrayMaxOverride(30 * 1024 * 1024);
+ try (ByteArrayInputStream is = new ByteArrayInputStream(array)) {
+ assertArrayEquals(array, IOUtils.toByteArray(is, -1, 100));
+ } finally {
+ IOUtils.setByteArrayMaxOverride(-1);
+ }
+ }
+
+ @Test
+ void testToByteArrayNegativeLength2() throws IOException {
+ final byte[] array = new byte[]{1, 2, 3, 4, 5, 6, 7};
+ IOUtils.setByteArrayMaxOverride(30 * 1024 * 1024);
+ try (ByteArrayInputStream is = new ByteArrayInputStream(array)) {
+ assertArrayEquals(array, IOUtils.toByteArray(is, -1));
+ } finally {
+ IOUtils.setByteArrayMaxOverride(-1);
+ }
}
@Test
diff --git a/poi/src/test/java/org/apache/poi/util/TestThreadLocalTempFile.java b/poi/src/test/java/org/apache/poi/util/TestThreadLocalTempFile.java
new file mode 100644
index 0000000000..86161e59ac
--- /dev/null
+++ b/poi/src/test/java/org/apache/poi/util/TestThreadLocalTempFile.java
@@ -0,0 +1,68 @@
+/* ====================================================================
+ 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.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.io.TempDir;
+
+class TestThreadLocalTempFile {
+ @AfterEach
+ void tearDown() {
+ TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy());
+ }
+
+ @RepeatedTest(2) // Repeat it to ensure testing the case
+ void testThreadLocalStrategy(@TempDir Path tmpDir) {
+ Path rootDir = tmpDir.toAbsolutePath().normalize();
+ Path globalTmpDir = rootDir.resolve("global-tmp-dir");
+ Path localTmpDir1 = rootDir.resolve("local-tmp-dir1");
+ Path localTmpDir2 = rootDir.resolve("local-tmp-dir2");
+
+ TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy(globalTmpDir.toFile()));
+ assertTempFileIn(globalTmpDir);
+
+ String result = TempFile.withStrategy(new DefaultTempFileCreationStrategy(localTmpDir1.toFile()), () -> {
+ assertTempFileIn(localTmpDir1);
+ String nestedResult = TempFile.withStrategy(new DefaultTempFileCreationStrategy(localTmpDir2.toFile()), () -> {
+ assertTempFileIn(localTmpDir2);
+ return "nested-test-result";
+ });
+ assertTempFileIn(localTmpDir1);
+ return "my-test-result-" + nestedResult;
+ });
+ assertTempFileIn(globalTmpDir);
+ assertEquals("my-test-result-nested-test-result", result);
+ }
+
+ private static void assertTempFileIn(Path expectedDir) {
+ Path tempFile;
+ try {
+ tempFile = TempFile.createTempFile("tmp-prefix", ".tmp").toPath();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ assertTrue(tempFile.startsWith(expectedDir), tempFile.toString());
+ }
+}