fyeeme opened a new issue, #889:
URL: https://github.com/apache/fesod/issues/889

   ### Search before asking
   
   - [x] I searched in the [issues](https://github.com/apache/fesod/issues) and 
found nothing similar.
   
   
   ### Fesod version
   
   2.0.1-incubating
   
   ### JDK version
   
   21
   
   ### Operating system
   
   macos 26.4
   
   ### Steps To Reproduce
   
   ## Summary
   
   Using the native POJO write path in 
`org.apache.fesod:fesod-sheet:2.0.1-incubating`
   can trigger a `StackOverflowError` when:
   
   - the DTO contains a `ZonedDateTime` field
   - a custom converter is registered for `ZonedDateTime`
   - the DTO also uses normal field metadata such as:
     - `@DateTimeFormat`
     - `@NumberFormat`
     - `@ExcelProperty(converter = ...)`
   
   In our project this blocks switching back to the pure native Fesod POJO 
export path
   for `ZonedDateTime`-based exports.
   
   ## Environment
   
   - Java: project currently runs on Java 21
   - Library: `org.apache.fesod:fesod-sheet:2.0.1-incubating`
   - Output format: XLSX
   
   ## Minimal Reproduction
   
   Repository repro artifact:
   
   - Test file: 
   
   ```java 
   package com.linzi.pitpat.excel;
   
   import lombok.Data;
   import org.apache.fesod.sheet.FesodSheet;
   import org.apache.fesod.sheet.annotation.ExcelProperty;
   import org.apache.fesod.sheet.annotation.format.DateTimeFormat;
   import org.apache.fesod.sheet.annotation.format.NumberFormat;
   import org.apache.fesod.sheet.converters.Converter;
   import org.apache.fesod.sheet.enums.CellDataTypeEnum;
   import org.apache.fesod.sheet.metadata.GlobalConfiguration;
   import org.apache.fesod.sheet.metadata.data.WriteCellData;
   import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
   import org.junit.jupiter.api.Disabled;
   import org.junit.jupiter.api.DisplayName;
   import org.junit.jupiter.api.Test;
   
   import java.io.ByteArrayOutputStream;
   import java.math.BigDecimal;
   import java.time.ZoneId;
   import java.time.ZonedDateTime;
   import java.util.List;
   
   import static org.junit.jupiter.api.Assertions.assertThrows;
   
   /**
    * Documents an upstream Fesod bug we can reproduce reliably.
    *
    * <p>When using the native POJO write path with a DTO containing 
ZonedDateTime,
    * Fesod 2.0.1-incubating triggers a StackOverflowError inside its write 
holder hashCode chain.
    * We keep this test disabled because enabling it would break CI, but it 
provides a stable
    * reproduction if we need to validate an upstream fix or a local dependency 
patch.</p>
    */
   class FesodNativeMetadataRegressionTest {
   
       @Test
       @Disabled("Stable repro for upstream Fesod StackOverflowError on native 
POJO write path")
       @DisplayName("native Fesod POJO export should reproduce 
StackOverflowError with ZonedDateTime metadata")
       void nativePojoExport_shouldReproduceStackOverflowError() {
           ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
           
           assertThrows(StackOverflowError.class, () -> 
FesodSheet.write(outputStream, NativeMixedMetadataRow.class)
                   .registerConverter(new NativeZonedDateStringConverter())
                   .sheet("NativeMixedMetadata")
                   .doWrite(List.of(createRow())));
       }
   
       private NativeMixedMetadataRow createRow() {
           NativeMixedMetadataRow row = new NativeMixedMetadataRow();
           row.setStatus("ACTIVE");
           row.setAmount(new BigDecimal("1234.5"));
           row.setCreatedAt(ZonedDateTime.of(2026, 3, 24, 15, 30, 45, 0, 
ZoneId.of("Asia/Shanghai")));
           return row;
       }
   
       @Data
       public static class NativeMixedMetadataRow {
           @ExcelProperty(value = "Status", converter = 
NativeStatusLabelConverter.class)
           private String status;
   
           @ExcelProperty("Amount")
           @NumberFormat("¥#,##0.00")
           private BigDecimal amount;
   
           @ExcelProperty("Created At")
           @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
           private ZonedDateTime createdAt;
       }
   
       public static class NativeStatusLabelConverter implements 
Converter<String> {
           @Override
           public Class<?> supportJavaTypeKey() {
               return String.class;
           }
   
           @Override
           public CellDataTypeEnum supportExcelTypeKey() {
               return CellDataTypeEnum.STRING;
           }
   
           @Override
           public WriteCellData<?> convertToExcelData(
                   String value, ExcelContentProperty contentProperty, 
GlobalConfiguration globalConfiguration) {
               return new WriteCellData<>("STATUS:" + value);
           }
       }
   
       public static class NativeZonedDateStringConverter implements 
Converter<ZonedDateTime> {
           @Override
           public Class<?> supportJavaTypeKey() {
               return ZonedDateTime.class;
           }
   
           @Override
           public CellDataTypeEnum supportExcelTypeKey() {
               return CellDataTypeEnum.STRING;
           }
   
           @Override
           public WriteCellData<?> convertToExcelData(
                   ZonedDateTime value, ExcelContentProperty contentProperty, 
GlobalConfiguration globalConfiguration) {
               String format = contentProperty != null && 
contentProperty.getDateTimeFormatProperty() != null
                       ? contentProperty.getDateTimeFormatProperty().getFormat()
                       : "yyyy-MM-dd HH:mm:ss";
               return new 
WriteCellData<>(value.toLocalDateTime().format(java.time.format.DateTimeFormatter.ofPattern(format)));
           }
       }
   }
   ```
   
   The test is intentionally marked `@Disabled` so CI stays green, but it is a 
stable reproducer.
   
   ### Repro DTO
   
   ```java
   @Data
   public static class NativeMixedMetadataRow {
       @ExcelProperty(value = "Status", converter = 
NativeStatusLabelConverter.class)
       private String status;
   
       @ExcelProperty("Amount")
       @NumberFormat("¥#,##0.00")
       private BigDecimal amount;
   
       @ExcelProperty("Created At")
       @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
       private ZonedDateTime createdAt;
   }
   ```
   
   ### Repro Export Code
   
   ```java
   ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
   
   FesodSheet.write(outputStream, NativeMixedMetadataRow.class)
           .registerConverter(new NativeZonedDateStringConverter())
           .sheet("NativeMixedMetadata")
           .doWrite(List.of(row));
   ```
   
   ### Expected Result
   
   Workbook is exported successfully, with native Fesod metadata handling 
preserved:
   
   - `@ExcelProperty(converter = ...)` is applied
   - `@NumberFormat` is applied
   - `@DateTimeFormat` is applied
   
   ### Actual Result
   
   `StackOverflowError`
   
   ## Observed Stack Pattern
   
   The stack repeatedly cycles through `hashCode()` calls on Fesod write holder 
objects:
   
   ```text
   java.lang.StackOverflowError
     at java.util.ArrayList.hashCode(...)
     at org.apache.fesod.sheet.metadata.Head.hashCode(...)
     at org.apache.fesod.sheet.metadata.property.ExcelHeadProperty.hashCode(...)
     at 
org.apache.fesod.sheet.write.property.ExcelWriteHeadProperty.hashCode(...)
     at 
org.apache.fesod.sheet.write.metadata.holder.AbstractWriteHolder.hashCode(...)
     at 
org.apache.fesod.sheet.write.metadata.holder.WriteWorkbookHolder.hashCode(...)
     at 
org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder.hashCode(...)
     ...
   ```
   
   ## Suspected Root Cause
   
   This is an inference based on source inspection plus stable reproduction.
   
   `WriteContextImpl.initSheet()` stores `WriteSheetHolder` into maps owned by 
`WriteWorkbookHolder`:
   
   ```java
   
writeWorkbookHolder.getHasBeenInitializedSheetIndexMap().put(writeSheetHolder.getSheetNo(),
 writeSheetHolder);
   
writeWorkbookHolder.getHasBeenInitializedSheetNameMap().put(writeSheetHolder.getSheetName(),
 writeSheetHolder);
   ```
   
   At the same time:
   
   - `WriteSheetHolder` holds `parentWriteWorkbookHolder`
   - `WriteWorkbookHolder` holds maps of `WriteSheetHolder`
   - `AbstractWriteHolder`, `WriteWorkbookHolder`, and `WriteSheetHolder` use 
Lombok `@EqualsAndHashCode`
   
   That appears to create a recursive object graph for structural `hashCode()` 
evaluation:
   
   ```text
   WriteWorkbookHolder
     -> hasBeenInitializedSheetIndexMap / hasBeenInitializedSheetNameMap
     -> WriteSheetHolder
     -> parentWriteWorkbookHolder
     -> WriteWorkbookHolder
     -> ...
   ```
   
   If Fesod evaluates structural `hashCode()` on these holder objects, 
recursion does not terminate.
   
   ## Project Impact
   
   In our project:
   
   - the plain native POJO path would be the preferred implementation
   - but this Fesod issue prevents using it safely for `ZonedDateTime`
   - we currently use a compatibility path that still reuses Fesod field 
metadata instead of fully hardcoding formatting logic
   
   Compatibility implementation reference:
   
   - 
[ExcelExportUtils.java](/Users/Young/Projects/profressional/linzikg/pitpat-framework/pitpat-common/pitpat-excel/src/main/java/com/linzi/pitpat/excel/util/ExcelExportUtils.java#L228)
   - fallback branch starts at 
[ExcelExportUtils.java](/Users/Young/Projects/profressional/linzikg/pitpat-framework/pitpat-common/pitpat-excel/src/main/java/com/linzi/pitpat/excel/util/ExcelExportUtils.java#L388)
   
   ## Suggested Fix Direction
   
   Likely fix directions in Fesod:
   
   - exclude recursive holder references from `equals/hashCode`
   - or avoid structural `equals/hashCode` for runtime write holders entirely
   - or replace the relevant holder equality semantics with identity-based 
handling
   
   ## Local Verification
   
   Project-level verification after documenting the repro:
   
   ```bash
   mvn -q -pl pitpat-common/pitpat-excel test
   mvn -q -pl demo-service -am -DskipTests compile
   ```
   
   
   ### Current Behavior
   
   StackOverflowError
   
   ### Expected Behavior
   
   work normally
   
   ### Anything else?
   
   _No response_
   
   ### Are you willing to submit a PR?
   
   - [ ] I'm willing to submit a PR!


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to