This is an automated email from the ASF dual-hosted git repository.

delei pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fesod.git


The following commit(s) were added to refs/heads/main by this push:
     new d973557c fix: resolve Windows fill template copy failures (#1021) 
(#1034)
d973557c is described below

commit d973557ce2587d2e5fa43da2d5d73b4cf024d2a6
Author: Mikkey-f <[email protected]>
AuthorDate: Mon Aug 24 12:51:11 2026 +0800

    fix: resolve Windows fill template copy failures (#1021) (#1034)
    
    * fix: close output stream when WriteWorkbookHolder init fails (#1021)
    
    The constructor opens the output file stream before copying the template.
    If initialization fails (e.g. missing template), the stream was never
    closed, leaving the target file locked on Windows. Close streams opened
    by the holder on failure; caller-provided streams remain the caller's
    responsibility.
    
    * fix: decode percent-encoded resource URLs in ExampleFileUtil (#1021)
    
    URL.getPath() returns the encoded path. On Windows, template file names
    built with File.separator ('\') are percent-encoded as %5C in the
    resource URL, so the returned path pointed at a non-existent file and
    fill examples failed with 'Copy template failure'. Decode via
    URL.toURI() instead.
    
    * fix: honor autoCloseStream when closing output stream on init failure 
(#1021)
    
    ---------
    
    Co-authored-by: Mikkey-f <[email protected]>
    Co-authored-by: DeleiGuo <[email protected]>
---
 .../fesod/sheet/examples/util/ExampleFileUtil.java | 21 ++++++-
 .../write/metadata/holder/WriteWorkbookHolder.java | 23 +++++++-
 .../WriteWorkbookHolderOutputStreamTest.java       | 67 ++++++++++++++++++++++
 3 files changed, 107 insertions(+), 4 deletions(-)

diff --git 
a/fesod-examples/fesod-sheet-examples/src/main/java/org/apache/fesod/sheet/examples/util/ExampleFileUtil.java
 
b/fesod-examples/fesod-sheet-examples/src/main/java/org/apache/fesod/sheet/examples/util/ExampleFileUtil.java
index 0bfbc75d..000f5927 100644
--- 
a/fesod-examples/fesod-sheet-examples/src/main/java/org/apache/fesod/sheet/examples/util/ExampleFileUtil.java
+++ 
b/fesod-examples/fesod-sheet-examples/src/main/java/org/apache/fesod/sheet/examples/util/ExampleFileUtil.java
@@ -54,7 +54,7 @@ public class ExampleFileUtil {
         if (resource == null) {
             throw new IllegalStateException("Cannot find classpath root 
resource");
         }
-        return resource.getPath();
+        return toFilePath(resource);
     }
 
     /**
@@ -66,12 +66,29 @@ public class ExampleFileUtil {
     public static String getExamplePath(String fileName) {
         java.net.URL resource = 
ExampleFileUtil.class.getClassLoader().getResource(EXAMPLE + "/" + fileName);
         if (resource != null) {
-            return resource.getPath();
+            return toFilePath(resource);
         }
         // Fallback to classpath root + example path
         return getPath() + EXAMPLE + File.separator + fileName;
     }
 
+    /**
+     * Convert a resource URL to a file path, decoding percent-encoded 
characters.
+     * <p>
+     * {@link java.net.URL#getPath()} returns the encoded path (e.g. {@code 
%5C} for a backslash on Windows),
+     * which is not usable with {@link java.io.File}. {@link 
java.net.URL#toURI()} decodes it correctly.
+     *
+     * @param resource the resource URL to convert
+     * @return the absolute file path
+     */
+    private static String toFilePath(java.net.URL resource) {
+        try {
+            return new File(resource.toURI()).getAbsolutePath();
+        } catch (java.net.URISyntaxException e) {
+            throw new IllegalStateException("Invalid resource URL: " + 
resource, e);
+        }
+    }
+
     /**
      * Get the path to write output files in the system temp directory.
      *
diff --git 
a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/WriteWorkbookHolder.java
 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/WriteWorkbookHolder.java
index 7750e80a..021dd852 100644
--- 
a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/WriteWorkbookHolder.java
+++ 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/WriteWorkbookHolder.java
@@ -240,12 +240,15 @@ public class WriteWorkbookHolder extends 
AbstractWriteHolder {
         }
 
         // init handler
-        initHandler(writeWorkbook, null);
-
         try {
+            initHandler(writeWorkbook, null);
             copyTemplate();
         } catch (IOException e) {
+            closeOutputStreamOnFailure();
             throw new ExcelGenerateException("Copy template failure.", e);
+        } catch (RuntimeException e) {
+            closeOutputStreamOnFailure();
+            throw e;
         }
         if (writeWorkbook.getMandatoryUseInputStream() == null) {
             this.mandatoryUseInputStream = Boolean.FALSE;
@@ -270,6 +273,22 @@ public class WriteWorkbookHolder extends 
AbstractWriteHolder {
         this.dataFormatMap = MapUtils.newHashMap();
     }
 
+    /**
+     * Close the output stream when initialization fails, so that the target 
file is not held open.
+     * Mirrors the close behavior of the success path, which is also gated by 
{@code autoCloseStream};
+     * callers that opt out of automatic closing via {@code 
autoCloseStream(false)} keep that behavior
+     * here as well.
+     */
+    private void closeOutputStreamOnFailure() {
+        if (autoCloseStream && outputStream != null) {
+            try {
+                outputStream.close();
+            } catch (IOException e) {
+                log.warn("Failed to close output stream after workbook 
initialization failure.", e);
+            }
+        }
+    }
+
     private void copyTemplate() throws IOException {
         if (writeWorkbook.getTemplateFile() == null && 
writeWorkbook.getTemplateInputStream() == null) {
             return;
diff --git 
a/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/metadata/holder/WriteWorkbookHolderOutputStreamTest.java
 
b/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/metadata/holder/WriteWorkbookHolderOutputStreamTest.java
new file mode 100644
index 00000000..b39c56c3
--- /dev/null
+++ 
b/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/metadata/holder/WriteWorkbookHolderOutputStreamTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.fesod.sheet.write.metadata.holder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import org.apache.fesod.sheet.exception.ExcelGenerateException;
+import org.apache.fesod.sheet.write.metadata.WriteWorkbook;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Tests for the output stream lifecycle of {@link WriteWorkbookHolder} when 
workbook
+ * initialization fails.
+ */
+public class WriteWorkbookHolderOutputStreamTest {
+
+    @TempDir
+    Path tempDir;
+
+    @Test
+    void constructorClosesOutputFileStreamWhenTemplateCopyFails() throws 
IOException {
+        File outputFile = tempDir.resolve("output.xlsx").toFile();
+        WriteWorkbook writeWorkbook = new WriteWorkbook();
+        writeWorkbook.setFile(outputFile);
+        
writeWorkbook.setTemplateFile(tempDir.resolve("missing-template.xlsx").toFile());
+
+        Assertions.assertThrows(ExcelGenerateException.class, () -> new 
WriteWorkbookHolder(writeWorkbook));
+        // On Windows an open FileOutputStream locks the file, so deletion 
fails if the holder
+        // leaked the stream it opened. On Unix the deletion succeeds either 
way, keeping the
+        // assertion portable.
+        Assertions.assertTrue(outputFile.delete(), "output stream should be 
closed after initialization failure");
+    }
+
+    @Test
+    void 
constructorDoesNotCloseCallerProvidedOutputStreamWhenAutoCloseDisabled() throws 
IOException {
+        WriteWorkbook writeWorkbook = new WriteWorkbook();
+        writeWorkbook.setAutoCloseStream(false);
+        writeWorkbook.setOutputStream(new ByteArrayOutputStream());
+        
writeWorkbook.setTemplateFile(tempDir.resolve("missing-template.xlsx").toFile());
+
+        Assertions.assertThrows(ExcelGenerateException.class, () -> new 
WriteWorkbookHolder(writeWorkbook));
+        // autoCloseStream(false) opts into manual stream management, so the 
holder must not close
+        // caller-provided streams — mirroring the success-path contract.
+        Assertions.assertDoesNotThrow(() -> 
writeWorkbook.getOutputStream().write(1));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to