From 260479081f49511625b80604062c008e54bdb79d Mon Sep 17 00:00:00 2001 From: baicaixiaozhan Date: Sat, 1 Nov 2025 00:01:55 +0800 Subject: [PATCH 1/4] fix: support immutable lists in dynamic header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This prevents UnsupportedOperationException when using immutable lists such as Arrays.asList、List.of or Collections.unmodifiableList ... --- .../metadata/AbstractParameterBuilder.java | 17 +++- .../head/ImmutableListHeadDataListener.java | 64 +++++++++++++ .../excel/head/ImmutableListHeadDataTest.java | 95 +++++++++++++++++++ .../ImmutableListHeadDataWriteHandler.java | 39 ++++++++ 4 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataListener.java create mode 100644 fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataTest.java create mode 100644 fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataWriteHandler.java diff --git a/fesod/src/main/java/org/apache/fesod/excel/metadata/AbstractParameterBuilder.java b/fesod/src/main/java/org/apache/fesod/excel/metadata/AbstractParameterBuilder.java index 54eb7092f..43e78a2f5 100644 --- a/fesod/src/main/java/org/apache/fesod/excel/metadata/AbstractParameterBuilder.java +++ b/fesod/src/main/java/org/apache/fesod/excel/metadata/AbstractParameterBuilder.java @@ -19,9 +19,11 @@ package org.apache.fesod.excel.metadata; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.stream.Collectors; import org.apache.fesod.excel.converters.Converter; import org.apache.fesod.excel.enums.CacheLocationEnum; import org.apache.fesod.excel.util.ListUtils; @@ -39,10 +41,23 @@ public abstract class AbstractParameterBuilder> head) { - parameter().setHead(head); + parameter().setHead(toMutableListIfNecessary(head)); return self(); } + /** + * Ensures and returns a fully mutable deep copy of head list. + * + * @param head head The source list to create a mutable copy from. + * @return A new, fully mutable deep copy, or the original list if the input is null or empty. + */ + private List> toMutableListIfNecessary(List> head) { + if (null == head || head.isEmpty()) { + return head; + } + return head.stream().map(ArrayList::new).collect(Collectors.toList()); + } + /** * You can only choose one of the {@link #head(List)} and {@link #head(Class)} * diff --git a/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataListener.java b/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataListener.java new file mode 100644 index 000000000..080cb4572 --- /dev/null +++ b/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataListener.java @@ -0,0 +1,64 @@ +/* + * 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.excel.head; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.apache.fesod.excel.context.AnalysisContext; +import org.apache.fesod.excel.metadata.data.ReadCellData; +import org.apache.fesod.excel.read.listener.ReadListener; +import org.junit.jupiter.api.Assertions; + +public class ImmutableListHeadDataListener implements ReadListener> { + + private List> list = new ArrayList>(); + + @Override + public void invokeHead(Map> headMap, AnalysisContext context) { + Assertions.assertNotNull(context.readRowHolder().getRowIndex()); + headMap.forEach((key, value) -> { + Assertions.assertEquals(value.getRowIndex(), context.readRowHolder().getRowIndex()); + Assertions.assertEquals(value.getColumnIndex(), key); + }); + } + + @Override + public void invoke(Map data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + List> head = context.readSheetHolder().getHead(); + + Assertions.assertInstanceOf(ArrayList.class, head); + for (List item : head) { + Assertions.assertInstanceOf(ArrayList.class, item); + } + + Assertions.assertEquals(1, list.size()); + Map data = list.get(0); + Assertions.assertEquals("字符串0", data.get(0)); + Assertions.assertEquals("1", data.get(1)); + Assertions.assertEquals("2025-10-31 01:01:01", data.get(2)); + Assertions.assertEquals("额外数据", data.get(3)); + } +} diff --git a/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataTest.java b/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataTest.java new file mode 100644 index 000000000..476103ed3 --- /dev/null +++ b/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataTest.java @@ -0,0 +1,95 @@ +/* + * 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.excel.head; + +import java.io.File; +import java.text.ParseException; +import java.util.*; +import org.apache.fesod.excel.FastExcel; +import org.apache.fesod.excel.util.DateUtils; +import org.apache.fesod.excel.util.TestFileUtil; +import org.junit.jupiter.api.*; + +@TestMethodOrder(MethodOrderer.MethodName.class) +public class ImmutableListHeadDataTest { + + private static File file07; + private static File file03; + private static File fileCsv; + + @BeforeAll + public static void init() { + file07 = TestFileUtil.createNewFile("listHead07.xlsx"); + file03 = TestFileUtil.createNewFile("listHead03.xls"); + fileCsv = TestFileUtil.createNewFile("listHeadCsv.csv"); + } + + @Test + public void t01ReadAndWrite07() throws Exception { + readAndWrite(file07); + } + + @Test + public void t02ReadAndWrite03() throws Exception { + readAndWrite(file03); + } + + @Test + public void t03ReadAndWriteCsv() throws Exception { + readAndWrite(fileCsv); + } + + private void readAndWrite(File file) throws Exception { + FastExcel.write(file) + .head(head()) + .registerWriteHandler(new ImmutableListHeadDataWriteHandler()) + .sheet() + .doWrite(data()); + + FastExcel.read(file) + .head(head()) + .registerReadListener(new ImmutableListHeadDataListener()) + .sheet() + .doRead(); + } + + private List> head() { + List> list = new ArrayList>(); + List head0 = Arrays.asList("字符串"); + List head1 = new ArrayList(); + head1.add("数字"); + + list.add(head0); + list.add(Collections.unmodifiableList(head1)); + list.add(Collections.singletonList("日期")); + return list; + } + + private List> data() throws ParseException { + List> list = new ArrayList>(); + List data0 = new ArrayList(); + data0.add("字符串0"); + data0.add(1); + data0.add(DateUtils.parseDate("2025-10-31 01:01:01")); + data0.add("额外数据"); + list.add(data0); + return list; + } +} diff --git a/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataWriteHandler.java b/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataWriteHandler.java new file mode 100644 index 000000000..77752355c --- /dev/null +++ b/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataWriteHandler.java @@ -0,0 +1,39 @@ +/* + * 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.excel.head; + +import java.util.ArrayList; +import java.util.List; +import org.apache.fesod.excel.write.handler.SheetWriteHandler; +import org.apache.fesod.excel.write.handler.context.SheetWriteHandlerContext; +import org.junit.jupiter.api.Assertions; + +public class ImmutableListHeadDataWriteHandler implements SheetWriteHandler { + + @Override + public void afterSheetDispose(SheetWriteHandlerContext context) { + List> head = context.getWriteContext().writeSheetHolder().getHead(); + + Assertions.assertInstanceOf(ArrayList.class, head); + for (List item : head) { + Assertions.assertInstanceOf(ArrayList.class, item); + } + } +} From bf9c0d9f949b05bd9acc66961e8f70a5a0120287 Mon Sep 17 00:00:00 2001 From: baicaixiaozhan Date: Sat, 1 Nov 2025 08:57:04 +0800 Subject: [PATCH 2/4] test: update test based on review feedback. - Use English to describe - Avoid use imports * --- .../head/ImmutableListHeadDataListener.java | 4 ++-- .../excel/head/ImmutableListHeadDataTest.java | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataListener.java b/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataListener.java index 080cb4572..4d6534164 100644 --- a/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataListener.java +++ b/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataListener.java @@ -56,9 +56,9 @@ public void doAfterAllAnalysed(AnalysisContext context) { Assertions.assertEquals(1, list.size()); Map data = list.get(0); - Assertions.assertEquals("字符串0", data.get(0)); + Assertions.assertEquals("stringData", data.get(0)); Assertions.assertEquals("1", data.get(1)); Assertions.assertEquals("2025-10-31 01:01:01", data.get(2)); - Assertions.assertEquals("额外数据", data.get(3)); + Assertions.assertEquals("extraData", data.get(3)); } } diff --git a/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataTest.java b/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataTest.java index 476103ed3..f8c636331 100644 --- a/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataTest.java +++ b/fesod/src/test/java/org/apache/fesod/excel/head/ImmutableListHeadDataTest.java @@ -21,11 +21,17 @@ import java.io.File; import java.text.ParseException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import org.apache.fesod.excel.FastExcel; import org.apache.fesod.excel.util.DateUtils; import org.apache.fesod.excel.util.TestFileUtil; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; @TestMethodOrder(MethodOrderer.MethodName.class) public class ImmutableListHeadDataTest { @@ -72,23 +78,24 @@ private void readAndWrite(File file) throws Exception { private List> head() { List> list = new ArrayList>(); - List head0 = Arrays.asList("字符串"); + List head0 = Arrays.asList("stringTitle"); List head1 = new ArrayList(); - head1.add("数字"); + head1.add("numberTitle1"); + head1.add("numberTitle2"); list.add(head0); list.add(Collections.unmodifiableList(head1)); - list.add(Collections.singletonList("日期")); + list.add(Collections.singletonList("datetimeTitle")); return list; } private List> data() throws ParseException { List> list = new ArrayList>(); List data0 = new ArrayList(); - data0.add("字符串0"); + data0.add("stringData"); data0.add(1); data0.add(DateUtils.parseDate("2025-10-31 01:01:01")); - data0.add("额外数据"); + data0.add("extraData"); list.add(data0); return list; } From 3e73c384ee4d6e28db04e136b52126f2fb920ac2 Mon Sep 17 00:00:00 2001 From: baicaixiaozhan Date: Wed, 12 Nov 2025 21:31:23 +0800 Subject: [PATCH 3/4] refactor: replace stream implementation with explicit loop, and update JavaDoc. --- .../sheet/metadata/AbstractParameterBuilder.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fesod/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java b/fesod/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java index 90ce3dc4c..eaef44ccb 100644 --- a/fesod/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java +++ b/fesod/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java @@ -46,16 +46,20 @@ public T head(List> head) { } /** - * Ensures and returns a fully mutable deep copy of head list. + * Ensures and returns a mutable head list. * - * @param head head The source list to create a mutable copy from. - * @return A new, fully mutable deep copy, or the original list if the input is null or empty. + * @param head The source list to create a mutable from. + * @return A new mutable list, or the original list if the input is null or empty. */ private List> toMutableListIfNecessary(List> head) { if (null == head || head.isEmpty()) { return head; } - return head.stream().map(ArrayList::new).collect(Collectors.toList()); + List> result = new ArrayList<>(); + for (List headColumn : head) { + result.add(new ArrayList<>(headColumn)); + } + return result; } /** From ff7d62283b11656baa2450e978115dc7c7e5b7e3 Mon Sep 17 00:00:00 2001 From: baicaixiaozhan Date: Wed, 12 Nov 2025 21:33:23 +0800 Subject: [PATCH 4/4] chore: remove unnecessary imports. --- .../apache/fesod/sheet/metadata/AbstractParameterBuilder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/fesod/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java b/fesod/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java index eaef44ccb..dea79713b 100644 --- a/fesod/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java +++ b/fesod/src/main/java/org/apache/fesod/sheet/metadata/AbstractParameterBuilder.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Locale; import java.util.Objects; -import java.util.stream.Collectors; import org.apache.fesod.sheet.converters.Converter; import org.apache.fesod.sheet.enums.CacheLocationEnum; import org.apache.fesod.sheet.util.ListUtils;