diff options
Diffstat (limited to 'poi/src')
36 files changed, 527 insertions, 96 deletions
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/usermodel/HSSFCellStyle.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java index d1fe755fc3..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 @@ -858,10 +858,11 @@ public final class HSSFCellStyle implements CellStyle, Duplicatable { public void cloneStyleFrom(CellStyle source) { if(source instanceof HSSFCellStyle) { this.cloneStyleFrom((HSSFCellStyle)source); - } else if (_hssfWorkbook != null) { + } else { 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/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/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/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 349e62f044..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); 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/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 91a97b7edf..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 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 d0ee0db55b..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 @@ -280,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); @@ -289,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); } @@ -572,7 +574,7 @@ public final class CellUtil { @Deprecated @Removal(version = "7.0.0") public static void setCellStyleProperties(Cell cell, Map<String, Object> properties) { - EnumMap<CellPropertyType, Object> strPropMap = new EnumMap<>(CellPropertyType.class); + final EnumMap<CellPropertyType, Object> strPropMap = new EnumMap<>(CellPropertyType.class); properties.forEach((k, v) -> strPropMap.put(namePropertyMap.get(k), v)); setCellStyleProperties(cell, strPropMap, false); } @@ -685,10 +687,16 @@ 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) { 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/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/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/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 a900169822..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 @@ -171,7 +171,7 @@ 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 = funcIx; if(!_groupFunctionIndexes.add(funcIxKey)) { 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/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/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()); + } +} |