From bd81f3d5aca0699fd3b2957bf9d0538f4a9c5d95 Mon Sep 17 00:00:00 2001
From: Bengbengbalabalabeng
Date: Wed, 3 Jun 2026 20:51:47 +0800
Subject: [PATCH 1/3] feat: add view-based filtering support for sheet writing
---
.../sheet/annotation/write/ExcelView.java | 65 +++++
.../apache/fesod/sheet/util/ClassUtils.java | 16 +-
.../AbstractExcelWriterParameterBuilder.java | 25 ++
.../write/metadata/WriteBasicParameter.java | 6 +
.../metadata/holder/AbstractWriteHolder.java | 22 ++
.../write/metadata/holder/WriteHolder.java | 6 +
.../write/view/ClassBasedViewMatcher.java | 60 +++++
.../write/view/NameBasedViewMatcher.java | 60 +++++
.../write/view/NoopWriteViewMatcher.java | 42 ++++
.../sheet/write/view/WriteViewMatcher.java | 42 ++++
.../fesod/sheet/util/ClassUtilsTest.java | 52 ++++
.../fesod/sheet/view/WriteMixedViewData.java | 44 ++++
.../fesod/sheet/view/WriteNamedViewsData.java | 41 +++
.../fesod/sheet/view/WriteSheetViewTests.java | 236 ++++++++++++++++++
.../fesod/sheet/view/WriteTypedViewsData.java | 48 ++++
.../fesod/sheet/view/WriteViewStrategy.java | 31 +++
16 files changed, 793 insertions(+), 3 deletions(-)
create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/ExcelView.java
create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/ClassBasedViewMatcher.java
create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NameBasedViewMatcher.java
create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NoopWriteViewMatcher.java
create mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/WriteViewMatcher.java
create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteMixedViewData.java
create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteNamedViewsData.java
create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteSheetViewTests.java
create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteTypedViewsData.java
create mode 100644 fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteViewStrategy.java
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/ExcelView.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/ExcelView.java
new file mode 100644
index 000000000..63c99b380
--- /dev/null
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/ExcelView.java
@@ -0,0 +1,65 @@
+/*
+ * 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.annotation.write;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.fesod.sheet.write.builder.AbstractExcelWriterParameterBuilder;
+
+/**
+ * Annotation used for indicating view(s) that the property
+ * that is defined by field annotated is part of.
+ *
+ * An example annotation would be:
+ *
+ * @ExcelView(asTypes = BasicView.class)
+ * // Or
+ * @ExcelView(asNames = "BasicView")
+ *
+ * which would specify that field annotated would be included
+ * when processing (writing) Sheet identified by BasicView.class (or its subclass) or
+ * "BasicView".
+ * If multiple View class or string identifiers are included, the field will be part of all of them.
+ *
+ *
+ * @see AbstractExcelWriterParameterBuilder#groups(Class[])
+ * @see AbstractExcelWriterParameterBuilder#groups(String[])
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelView {
+
+ /**
+ * View or views that annotated element is part of. Views are identified
+ * by classes, and use expected class inheritance relationship: child
+ * views contain all elements parent views have.
+ */
+ Class>[] asTypes() default {};
+
+ /**
+ * View or views that annotated element is part of. Views are identified
+ * by strings.
+ */
+ String[] asNames() default {};
+}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/ClassUtils.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/ClassUtils.java
index 2e75fc3e3..1fcb8b380 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/ClassUtils.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/ClassUtils.java
@@ -67,6 +67,7 @@
import org.apache.fesod.sheet.metadata.property.NumberFormatProperty;
import org.apache.fesod.sheet.metadata.property.StyleProperty;
import org.apache.fesod.sheet.write.metadata.holder.WriteHolder;
+import org.apache.fesod.sheet.write.view.WriteViewMatcher;
public class ClassUtils {
@@ -346,23 +347,30 @@ private static FieldCache doDeclaredFields(Class> clazz, ConfigurationHolder c
WriteHolder writeHolder = (WriteHolder) configurationHolder;
+ // ignore field by grouping
+ WriteViewMatcher writeViewMatcher = writeHolder.writeViewMatcher();
+ boolean hasViews = writeViewMatcher.hasViews();
+ // ignore field by include*/exclude*
boolean needIgnore = !CollectionUtils.isEmpty(writeHolder.excludeColumnFieldNames())
|| !CollectionUtils.isEmpty(writeHolder.excludeColumnIndexes())
|| !CollectionUtils.isEmpty(writeHolder.includeColumnFieldNames())
- || !CollectionUtils.isEmpty(writeHolder.includeColumnIndexes());
+ || !CollectionUtils.isEmpty(writeHolder.includeColumnIndexes())
+ || hasViews;
if (!needIgnore) {
return fieldCache;
}
- // ignore filed
+ // ignore field
Map tempSortedFieldMap = MapUtils.newHashMap();
int index = 0;
for (Map.Entry entry : sortedFieldMap.entrySet()) {
Integer key = entry.getKey();
FieldWrapper field = entry.getValue();
+ boolean isGroupsMismatch = hasViews && !writeViewMatcher.matches(field.getField());
+
// The current field needs to be ignored
- if (writeHolder.ignore(field.getFieldName(), entry.getKey())) {
+ if (isGroupsMismatch || writeHolder.ignore(field.getFieldName(), entry.getKey())) {
ignoreSet.add(field.getFieldName());
indexFieldMap.remove(index);
} else {
@@ -575,6 +583,7 @@ public static class FieldCacheKey {
private Collection excludeColumnIndexes;
private Collection includeColumnFieldNames;
private Collection includeColumnIndexes;
+ private WriteViewMatcher writeViewMatcher;
FieldCacheKey(Class> clazz, ConfigurationHolder configurationHolder) {
this.clazz = clazz;
@@ -584,6 +593,7 @@ public static class FieldCacheKey {
this.excludeColumnIndexes = writeHolder.excludeColumnIndexes();
this.includeColumnFieldNames = writeHolder.includeColumnFieldNames();
this.includeColumnIndexes = writeHolder.includeColumnIndexes();
+ this.writeViewMatcher = writeHolder.writeViewMatcher();
}
}
}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/AbstractExcelWriterParameterBuilder.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/AbstractExcelWriterParameterBuilder.java
index 012e5b3ae..7cae2d824 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/AbstractExcelWriterParameterBuilder.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/AbstractExcelWriterParameterBuilder.java
@@ -26,11 +26,14 @@
package org.apache.fesod.sheet.write.builder;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import org.apache.fesod.sheet.enums.HeaderMergeStrategy;
import org.apache.fesod.sheet.metadata.AbstractParameterBuilder;
import org.apache.fesod.sheet.write.handler.WriteHandler;
import org.apache.fesod.sheet.write.metadata.WriteBasicParameter;
+import org.apache.fesod.sheet.write.view.ClassBasedViewMatcher;
+import org.apache.fesod.sheet.write.view.NameBasedViewMatcher;
/**
* Build ExcelBuilder
@@ -170,4 +173,26 @@ public T orderByIncludeColumn(Boolean orderByIncludeColumn) {
parameter().setOrderByIncludeColumn(orderByIncludeColumn);
return self();
}
+
+ /**
+ * Only write the fields marked by the following View class identifiers.
+ *
+ * @param types Target View class identifiers
+ * @return this
+ */
+ public T groups(Class>... types) {
+ parameter().setWriteViewMatcher(new ClassBasedViewMatcher(Arrays.asList(types)));
+ return self();
+ }
+
+ /**
+ * Only write to the fields marked by the following View string identifiers.
+ *
+ * @param names Target View string identifiers
+ * @return this
+ */
+ public T groups(String... names) {
+ parameter().setWriteViewMatcher(new NameBasedViewMatcher(Arrays.asList(names)));
+ return self();
+ }
}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/WriteBasicParameter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/WriteBasicParameter.java
index 5dd959e71..3b134df28 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/WriteBasicParameter.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/WriteBasicParameter.java
@@ -34,6 +34,7 @@
import org.apache.fesod.sheet.enums.HeaderMergeStrategy;
import org.apache.fesod.sheet.metadata.BasicParameter;
import org.apache.fesod.sheet.write.handler.WriteHandler;
+import org.apache.fesod.sheet.write.view.WriteViewMatcher;
/**
* Write basic parameter
@@ -92,4 +93,9 @@ public class WriteBasicParameter extends BasicParameter {
* Default is {@code false}.
*/
private Boolean orderByIncludeColumn;
+
+ /**
+ * view-based matcher for sheet writing.
+ */
+ private WriteViewMatcher writeViewMatcher;
}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java
index be35c494e..71a970334 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java
@@ -70,6 +70,8 @@
import org.apache.fesod.sheet.write.style.AbstractVerticalCellStyleStrategy;
import org.apache.fesod.sheet.write.style.column.AbstractHeadColumnWidthStyleStrategy;
import org.apache.fesod.sheet.write.style.row.SimpleRowHeightStyleStrategy;
+import org.apache.fesod.sheet.write.view.NoopWriteViewMatcher;
+import org.apache.fesod.sheet.write.view.WriteViewMatcher;
/**
* Write holder configuration
@@ -134,6 +136,11 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ
*/
private List> customConverterList;
+ /**
+ * view-based matcher for sheet writing.
+ */
+ private WriteViewMatcher writeViewMatcher;
+
/**
* Write handler
*/
@@ -261,6 +268,16 @@ public AbstractWriteHolder(WriteBasicParameter writeBasicParameter, AbstractWrit
this.includeColumnIndexes = writeBasicParameter.getIncludeColumnIndexes();
}
+ if (writeBasicParameter.getWriteViewMatcher() == null) {
+ if (parentAbstractWriteHolder == null) {
+ this.writeViewMatcher = NoopWriteViewMatcher.INSTANCE;
+ } else {
+ this.writeViewMatcher = parentAbstractWriteHolder.getWriteViewMatcher();
+ }
+ } else {
+ this.writeViewMatcher = writeBasicParameter.getWriteViewMatcher();
+ }
+
// Initialization property
this.excelWriteHeadProperty = new ExcelWriteHeadProperty(this, getClazz(), getHead());
@@ -588,4 +605,9 @@ public Collection excludeColumnIndexes() {
public Collection excludeColumnFieldNames() {
return getExcludeColumnFieldNames();
}
+
+ @Override
+ public WriteViewMatcher writeViewMatcher() {
+ return getWriteViewMatcher();
+ }
}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/WriteHolder.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/WriteHolder.java
index 7fdb1bcdc..df13e8ee6 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/WriteHolder.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/WriteHolder.java
@@ -29,6 +29,7 @@
import org.apache.fesod.sheet.enums.HeaderMergeStrategy;
import org.apache.fesod.sheet.metadata.ConfigurationHolder;
import org.apache.fesod.sheet.write.property.ExcelWriteHeadProperty;
+import org.apache.fesod.sheet.write.view.WriteViewMatcher;
/**
* Get the corresponding Holder
@@ -115,4 +116,9 @@ public interface WriteHolder extends ConfigurationHolder {
* @return
*/
Collection excludeColumnFieldNames();
+
+ /**
+ * view-based matcher for sheet writing.
+ */
+ WriteViewMatcher writeViewMatcher();
}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/ClassBasedViewMatcher.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/ClassBasedViewMatcher.java
new file mode 100644
index 000000000..022e36df6
--- /dev/null
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/ClassBasedViewMatcher.java
@@ -0,0 +1,60 @@
+/*
+ * 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.view;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+import lombok.EqualsAndHashCode;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.fesod.sheet.annotation.write.ExcelView;
+
+/**
+ * View matcher that resolves view-based on class
+ * identifiers declared in {@code @ExcelView#asTypes()}.
+ */
+@EqualsAndHashCode
+@RequiredArgsConstructor
+public class ClassBasedViewMatcher implements WriteViewMatcher {
+
+ private final Collection> expectedGroups;
+
+ @Override
+ public boolean hasViews() {
+ return CollectionUtils.isNotEmpty(expectedGroups);
+ }
+
+ @Override
+ public boolean matches(Field field) {
+ Class>[] fieldGroups = Optional.ofNullable(field.getAnnotation(ExcelView.class))
+ .map(ExcelView::asTypes)
+ .orElse(new Class>[0]);
+
+ if (ArrayUtils.isEmpty(fieldGroups)) {
+ return false;
+ }
+
+ return Arrays.stream(fieldGroups).anyMatch(fieldGroup -> expectedGroups.stream()
+ .anyMatch(expectedGroup -> expectedGroup.isAssignableFrom(fieldGroup)));
+ }
+}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NameBasedViewMatcher.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NameBasedViewMatcher.java
new file mode 100644
index 000000000..580df3300
--- /dev/null
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NameBasedViewMatcher.java
@@ -0,0 +1,60 @@
+/*
+ * 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.view;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+import lombok.EqualsAndHashCode;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.fesod.sheet.annotation.write.ExcelView;
+
+/**
+ * View matcher that resolves view-based on string
+ * identifiers declared in {@code @ExcelView#asNames()}.
+ */
+@EqualsAndHashCode
+@RequiredArgsConstructor
+public class NameBasedViewMatcher implements WriteViewMatcher {
+
+ private final Collection expectedGroups;
+
+ @Override
+ public boolean hasViews() {
+ return CollectionUtils.isNotEmpty(expectedGroups);
+ }
+
+ @Override
+ public boolean matches(Field field) {
+ String[] fieldGroups = Optional.ofNullable(field.getAnnotation(ExcelView.class))
+ .map(ExcelView::asNames)
+ .orElse(new String[0]);
+
+ if (ArrayUtils.isEmpty(fieldGroups)) {
+ return false;
+ }
+
+ return Arrays.stream(fieldGroups).anyMatch(fieldGroup -> expectedGroups.stream()
+ .anyMatch(expectedGroup -> expectedGroup.equals(fieldGroup)));
+ }
+}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NoopWriteViewMatcher.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NoopWriteViewMatcher.java
new file mode 100644
index 000000000..ff6113d2c
--- /dev/null
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NoopWriteViewMatcher.java
@@ -0,0 +1,42 @@
+/*
+ * 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.view;
+
+import java.lang.reflect.Field;
+import lombok.EqualsAndHashCode;
+
+/**
+ * The default implementation for {@link WriteViewMatcher}.
+ */
+@EqualsAndHashCode
+public class NoopWriteViewMatcher implements WriteViewMatcher {
+
+ public static final NoopWriteViewMatcher INSTANCE = new NoopWriteViewMatcher();
+
+ @Override
+ public boolean hasViews() {
+ return false;
+ }
+
+ @Override
+ public boolean matches(Field field) {
+ return false;
+ }
+}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/WriteViewMatcher.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/WriteViewMatcher.java
new file mode 100644
index 000000000..66ecfbfa6
--- /dev/null
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/WriteViewMatcher.java
@@ -0,0 +1,42 @@
+/*
+ * 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.view;
+
+import java.lang.reflect.Field;
+
+/**
+ * Strategy interface for determining whether Sheet writing should
+ * apply view-based matcher and whether a given field belongs to
+ * the active view(s).
+ */
+public interface WriteViewMatcher {
+
+ /**
+ * Returns whether any view constraints are active for the current
+ * Sheet writing operation.
+ */
+ boolean hasViews();
+
+ /**
+ * Returns whether the given field is included in the active view(s)
+ * based on its {@code @ExcelView} annotation.
+ */
+ boolean matches(Field field);
+}
diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/ClassUtilsTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/ClassUtilsTest.java
index da41bae8d..458364adc 100644
--- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/ClassUtilsTest.java
+++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/ClassUtilsTest.java
@@ -30,6 +30,7 @@
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.annotation.write.ExcelView;
import org.apache.fesod.sheet.converters.Converter;
import org.apache.fesod.sheet.converters.string.StringStringConverter;
import org.apache.fesod.sheet.enums.CacheLocationEnum;
@@ -42,6 +43,9 @@
import org.apache.fesod.sheet.metadata.property.NumberFormatProperty;
import org.apache.fesod.sheet.metadata.property.StyleProperty;
import org.apache.fesod.sheet.write.metadata.holder.WriteHolder;
+import org.apache.fesod.sheet.write.view.ClassBasedViewMatcher;
+import org.apache.fesod.sheet.write.view.NameBasedViewMatcher;
+import org.apache.fesod.sheet.write.view.NoopWriteViewMatcher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@@ -84,6 +88,18 @@ private static class SimpleEntity {
private Integer age;
}
+ interface BasicView {}
+
+ private static class ViewEntity {
+ @ExcelView(asTypes = BasicView.class)
+ @ExcelProperty("Name")
+ private String name;
+
+ @ExcelView(asNames = "BasicView")
+ @ExcelProperty(value = "Age")
+ private Integer age;
+ }
+
private static class ComplexEntity {
@ExcelProperty(index = 0)
private String id;
@@ -114,6 +130,7 @@ private static class FormatEntity {
@Test
void test_declaredFields_cache_memory() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.MEMORY);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
FieldCache cache1 = ClassUtils.declaredFields(SimpleEntity.class, writeHolder);
Assertions.assertNotNull(cache1);
@@ -127,6 +144,7 @@ void test_declaredFields_cache_memory() {
@Test
void test_declaredFields_cache_ThreadLocal() throws NoSuchFieldException, IllegalAccessException {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.THREAD_LOCAL);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
FieldCache cache1 = ClassUtils.declaredFields(SimpleEntity.class, writeHolder);
Assertions.assertNotNull(cache1);
@@ -140,6 +158,7 @@ void test_declaredFields_cache_ThreadLocal() throws NoSuchFieldException, Illega
@Test
void test_declaredFields_non_cache() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
FieldCache cache1 = ClassUtils.declaredFields(SimpleEntity.class, writeHolder);
FieldCache cache2 = ClassUtils.declaredFields(SimpleEntity.class, writeHolder);
@@ -151,6 +170,7 @@ void test_declaredFields_non_cache() {
@Test
void test_declaredFields_ordering() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
FieldCache fieldCache = ClassUtils.declaredFields(ComplexEntity.class, writeHolder);
Map sortedMap = fieldCache.getSortedFieldMap();
@@ -171,6 +191,7 @@ void test_declaredFields_ordering() {
@Test
void test_declaredFields_ignore() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
FieldCache fieldCache = ClassUtils.declaredFields(ComplexEntity.class, writeHolder);
@@ -183,6 +204,7 @@ void test_declaredFields_ignore() {
@Test
void test_declaredFields_WriteHolder_exclude() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
Mockito.when(writeHolder.excludeColumnFieldNames()).thenReturn(Collections.singleton("name"));
Mockito.when(writeHolder.ignore(Mockito.anyString(), Mockito.anyInt())).thenReturn(false);
@@ -202,6 +224,7 @@ void test_declaredFields_WriteHolder_exclude() {
@Test
void test_declaredFields_resort() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
Mockito.when(writeHolder.orderByIncludeColumn()).thenReturn(true);
List include = Arrays.asList("age", "name");
@@ -221,6 +244,7 @@ void test_declaredFields_resort_byIndex() {
Mockito.when(writeHolder.includeColumnFieldNames()).thenReturn(null);
Mockito.when(writeHolder.includeColumnIndexes()).thenReturn(Arrays.asList(2, 0));
Mockito.when(writeHolder.ignore(Mockito.anyString(), Mockito.anyInt())).thenReturn(false);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
FieldCache fieldCache = ClassUtils.declaredFields(ComplexEntity.class, writeHolder);
Map sortedMap = fieldCache.getSortedFieldMap();
@@ -234,6 +258,34 @@ void test_declaredFields_resort_byIndex() {
Assertions.assertEquals("id", sortedMap.get(1).getFieldName());
}
+ @Test
+ void test_declaredFields_typed_views_write() {
+ Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
+ Mockito.when(writeHolder.writeViewMatcher())
+ .thenReturn(new ClassBasedViewMatcher(Collections.singleton(BasicView.class)));
+
+ FieldCache fieldCache = ClassUtils.declaredFields(ViewEntity.class, writeHolder);
+
+ Map sortedMap = fieldCache.getSortedFieldMap();
+
+ Assertions.assertEquals(1, sortedMap.size());
+ Assertions.assertEquals("name", sortedMap.get(0).getFieldName());
+ }
+
+ @Test
+ void test_declaredFields_named_views_write() {
+ Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
+ Mockito.when(writeHolder.writeViewMatcher())
+ .thenReturn(new NameBasedViewMatcher(Collections.singleton("BasicView")));
+
+ FieldCache fieldCache = ClassUtils.declaredFields(ViewEntity.class, writeHolder);
+
+ Map sortedMap = fieldCache.getSortedFieldMap();
+
+ Assertions.assertEquals(1, sortedMap.size());
+ Assertions.assertEquals("age", sortedMap.get(0).getFieldName());
+ }
+
@Test
void test_declaredExcelContentProperty() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteMixedViewData.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteMixedViewData.java
new file mode 100644
index 000000000..f502c5f9a
--- /dev/null
+++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteMixedViewData.java
@@ -0,0 +1,44 @@
+/*
+ * 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.view;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.fesod.sheet.annotation.write.ExcelView;
+
+@EqualsAndHashCode
+@Data
+public class WriteMixedViewData {
+
+ @ExcelView(
+ asTypes = {WriteViewStrategy.BaseView.class},
+ asNames = {"base"})
+ private String string1;
+
+ @ExcelView(
+ asTypes = {WriteViewStrategy.GroupA.class},
+ asNames = {"detail", "export"})
+ private String string2;
+
+ @ExcelView(asNames = {"detail"})
+ private String string3;
+
+ private String defaultString;
+}
diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteNamedViewsData.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteNamedViewsData.java
new file mode 100644
index 000000000..149c1ab77
--- /dev/null
+++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteNamedViewsData.java
@@ -0,0 +1,41 @@
+/*
+ * 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.view;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.fesod.sheet.annotation.write.ExcelView;
+
+@EqualsAndHashCode
+@Data
+public class WriteNamedViewsData {
+
+ @ExcelView(asNames = {"base"})
+ private String string1;
+
+ @ExcelView(asNames = {"base", "detail"})
+ private String string2;
+
+ @ExcelView(asNames = {"detail"})
+ private String string3;
+
+ @ExcelView(asNames = {"other"})
+ private String string4;
+}
diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteSheetViewTests.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteSheetViewTests.java
new file mode 100644
index 000000000..317855bec
--- /dev/null
+++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteSheetViewTests.java
@@ -0,0 +1,236 @@
+/*
+ * 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.view;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.io.input.BOMInputStream;
+import org.apache.fesod.sheet.FesodSheet;
+import org.apache.fesod.sheet.support.ExcelTypeEnum;
+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.ss.usermodel.WorkbookFactory;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Tests for the view-based export grouping feature using {@code @ExcelView}.
+ */
+class WriteSheetViewTests {
+
+ private File write03;
+ private File write07;
+ private File writeCsv;
+
+ @BeforeEach
+ void setUp(@TempDir Path tempDir) {
+ write03 = createTmpFile(tempDir, "write03.xls");
+ write07 = createTmpFile(tempDir, "write07.xls");
+ writeCsv = createTmpFile(tempDir, "writeCsv.csv");
+ }
+
+ private File createTmpFile(Path dir, String filename) {
+ return new File(dir.resolve(filename).toString());
+ }
+
+ @FunctionalInterface
+ interface WriteExecutor {
+ void execute(File file, ExcelTypeEnum type, String sheetName) throws Exception;
+ }
+
+ private void doTestAllFormatsAndVerify(List expectedHeads, WriteExecutor action) throws Exception {
+ ExcelTypeEnum[] types = {ExcelTypeEnum.XLS, ExcelTypeEnum.XLSX, ExcelTypeEnum.CSV};
+ File[] files = {write03, write07, writeCsv};
+ String sheetName = "TestSheet";
+
+ for (int i = 0; i < types.length; i++) {
+ File currentFile = files[i];
+ ExcelTypeEnum currentType = types[i];
+
+ // Write
+ action.execute(currentFile, currentType, sheetName);
+
+ // Verify
+ verifyHeaders(currentFile, currentType, sheetName, expectedHeads);
+ }
+ }
+
+ private void verifyHeaders(File file, ExcelTypeEnum excelType, String sheetName, List expectedHeads)
+ throws Exception {
+ if (excelType == ExcelTypeEnum.CSV) {
+ try (InputStream is = BOMInputStream.builder()
+ .setInputStream(Files.newInputStream(file.toPath()))
+ .get();
+ Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
+ CSVParser parser = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(reader);
+
+ Map headerMap = parser.getHeaderMap();
+
+ Assertions.assertNotNull(headerMap, "CSV file is empty");
+
+ String[] headers = headerMap.keySet().toArray(new String[0]);
+ Assertions.assertEquals(expectedHeads.size(), headers.length, "CSV Header count mismatch");
+ for (int i = 0; i < expectedHeads.size(); i++) {
+ Assertions.assertEquals(expectedHeads.get(i), headers[i], "CSV Header text mismatch");
+ }
+ }
+ } else {
+ try (Workbook workbook = WorkbookFactory.create(file)) {
+ Sheet sheet = workbook.getSheet(sheetName);
+
+ Row headRow = sheet.getRow(0);
+ Assertions.assertNotNull(headRow, "Excel header row is null");
+ Assertions.assertEquals(
+ expectedHeads.size(),
+ headRow.getPhysicalNumberOfCells(),
+ "Excel Header count mismatch for " + excelType);
+
+ for (int i = 0; i < expectedHeads.size(); i++) {
+ Assertions.assertEquals(
+ expectedHeads.get(i),
+ headRow.getCell(i).getStringCellValue(),
+ "Excel Header text mismatch for " + excelType);
+ }
+ }
+ }
+ }
+
+ // =========================================================================
+ // Test by Class Type
+ // =========================================================================
+
+ @Nested
+ class ClassBasedViewTests {
+
+ @Test
+ void testWriteWithBaseAndSubTypes() throws Exception {
+ List expectedHeads = Arrays.asList("string1", "string2", "string5");
+
+ doTestAllFormatsAndVerify(expectedHeads, (file, type, sheetName) -> FesodSheet.write(file)
+ .head(WriteTypedViewsData.class)
+ .excelType(type)
+ .groups(WriteViewStrategy.BaseView.class)
+ .sheet(sheetName)
+ .doWrite(Collections.emptyList()));
+ }
+
+ @Test
+ void testWriteWithExactViewMatch() throws Exception {
+ List expectedHeads = Arrays.asList("string2", "string3");
+ doTestAllFormatsAndVerify(expectedHeads, (file, type, sheetName) -> FesodSheet.write(file)
+ .head(WriteTypedViewsData.class)
+ .excelType(type)
+ .groups(WriteViewStrategy.GroupA.class)
+ .sheet(sheetName)
+ .doWrite(Collections.emptyList()));
+ }
+
+ @Test
+ void testWriteWithMultipleGroups() throws Exception {
+ List expectedHeads = Arrays.asList("string2", "string3", "string4");
+ doTestAllFormatsAndVerify(expectedHeads, (file, type, sheetName) -> FesodSheet.write(file)
+ .head(WriteTypedViewsData.class)
+ .excelType(type)
+ .groups(WriteViewStrategy.GroupA.class, WriteViewStrategy.GroupB.class)
+ .sheet(sheetName)
+ .doWrite(Collections.emptyList()));
+ }
+ }
+
+ // =========================================================================
+ // Test by String Label Type
+ // =========================================================================
+
+ @Nested
+ class StringBasedViewTests {
+
+ @Test
+ void testWriteWithSingleView() throws Exception {
+ List expectedHeads = Arrays.asList("string1", "string2");
+ doTestAllFormatsAndVerify(expectedHeads, (file, type, sheetName) -> FesodSheet.write(file)
+ .head(WriteNamedViewsData.class)
+ .excelType(type)
+ .groups("base")
+ .sheet(sheetName)
+ .doWrite(Collections.emptyList()));
+ }
+
+ @Test
+ void testWriteWithMultipleViews() throws Exception {
+ List expectedHeads = Arrays.asList("string1", "string2", "string3");
+ doTestAllFormatsAndVerify(expectedHeads, (file, type, sheetName) -> FesodSheet.write(file)
+ .head(WriteNamedViewsData.class)
+ .excelType(type)
+ .groups("base", "detail")
+ .sheet(sheetName)
+ .doWrite(Collections.emptyList()));
+ }
+ }
+
+ // =========================================================================
+ // Test for Conflict and Override Strategies, and Default Behavior
+ // =========================================================================
+
+ @Nested
+ class ConflictAndEdgeCaseTests {
+
+ @Test
+ void testTagOverridesViewWhenCalledLast() throws Exception {
+ // The tags called later should override the previous groups.
+ List expectedHeads = Arrays.asList("string2", "string3");
+
+ doTestAllFormatsAndVerify(expectedHeads, (file, type, sheetName) -> FesodSheet.write(file)
+ .head(WriteMixedViewData.class)
+ .excelType(type)
+ .groups(WriteViewStrategy.BaseView.class)
+ // Take effect
+ .groups("detail")
+ .sheet(sheetName)
+ .doWrite(Collections.emptyList()));
+ }
+
+ @Test
+ void testWriteWithoutViewApi() throws Exception {
+ // Export all fields marked and unmarked with @ExcelView
+ List expectedHeads = Arrays.asList("string1", "string2", "string3", "defaultString");
+
+ doTestAllFormatsAndVerify(expectedHeads, (file, type, sheetName) -> FesodSheet.write(file)
+ .head(WriteMixedViewData.class)
+ .excelType(type)
+ .sheet(sheetName)
+ .doWrite(Collections.emptyList()));
+ }
+ }
+}
diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteTypedViewsData.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteTypedViewsData.java
new file mode 100644
index 000000000..0cad2b8a2
--- /dev/null
+++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteTypedViewsData.java
@@ -0,0 +1,48 @@
+/*
+ * 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.view;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.fesod.sheet.annotation.write.ExcelView;
+import org.apache.fesod.sheet.view.WriteViewStrategy.BaseView;
+import org.apache.fesod.sheet.view.WriteViewStrategy.ExtendGroupC;
+import org.apache.fesod.sheet.view.WriteViewStrategy.GroupA;
+import org.apache.fesod.sheet.view.WriteViewStrategy.GroupB;
+
+@EqualsAndHashCode
+@Data
+public class WriteTypedViewsData {
+
+ @ExcelView(asTypes = {BaseView.class})
+ private String string1;
+
+ @ExcelView(asTypes = {BaseView.class, GroupA.class})
+ private String string2;
+
+ @ExcelView(asTypes = {GroupA.class, GroupB.class})
+ private String string3;
+
+ @ExcelView(asTypes = {GroupB.class})
+ private String string4;
+
+ @ExcelView(asTypes = {ExtendGroupC.class})
+ private String string5;
+}
diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteViewStrategy.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteViewStrategy.java
new file mode 100644
index 000000000..a6dd5d924
--- /dev/null
+++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteViewStrategy.java
@@ -0,0 +1,31 @@
+/*
+ * 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.view;
+
+public interface WriteViewStrategy {
+
+ interface BaseView {}
+
+ interface GroupA {}
+
+ interface GroupB {}
+
+ interface ExtendGroupC extends BaseView {}
+}
From 2fe05e47ea6a040736538723cd2c42a9db69903f Mon Sep 17 00:00:00 2001
From: Bengbengbalabalabeng
Date: Sat, 6 Jun 2026 17:44:46 +0800
Subject: [PATCH 2/3] refactor: address review feedback for ExcelView and
WriteViewMatcher
- Remove @Inherited meta-annotation in @ExcelView
- Correct the javadoc of ExcelView#asTypes
- Refine WriteViewMatcher parameter validation
---
.../sheet/annotation/write/ExcelView.java | 6 +--
.../apache/fesod/sheet/util/ClassUtils.java | 2 +-
.../AbstractExcelWriterParameterBuilder.java | 14 +++++--
.../metadata/holder/AbstractWriteHolder.java | 3 +-
.../write/view/ClassBasedViewMatcher.java | 15 ++++---
.../write/view/NameBasedViewMatcher.java | 15 ++++---
.../write/view/NoopWriteViewMatcher.java | 42 -------------------
.../sheet/write/view/WriteViewMatcher.java | 10 +++--
.../fesod/sheet/util/ClassUtilsTest.java | 18 ++++----
.../fesod/sheet/view/WriteSheetViewTests.java | 2 +-
10 files changed, 52 insertions(+), 75 deletions(-)
delete mode 100644 fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NoopWriteViewMatcher.java
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/ExcelView.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/ExcelView.java
index 63c99b380..579246f0b 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/ExcelView.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/ExcelView.java
@@ -20,7 +20,6 @@
package org.apache.fesod.sheet.annotation.write;
import java.lang.annotation.ElementType;
-import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -47,13 +46,12 @@
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
-@Inherited
public @interface ExcelView {
/**
* View or views that annotated element is part of. Views are identified
- * by classes, and use expected class inheritance relationship: child
- * views contain all elements parent views have.
+ * by classes, when a view type is selected, fields annotated with that type
+ * or any of subtypes are included.
*/
Class>[] asTypes() default {};
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/ClassUtils.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/ClassUtils.java
index 1fcb8b380..dc45e9eca 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/ClassUtils.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/ClassUtils.java
@@ -349,7 +349,7 @@ private static FieldCache doDeclaredFields(Class> clazz, ConfigurationHolder c
// ignore field by grouping
WriteViewMatcher writeViewMatcher = writeHolder.writeViewMatcher();
- boolean hasViews = writeViewMatcher.hasViews();
+ boolean hasViews = (WriteViewMatcher.NOOP != writeViewMatcher);
// ignore field by include*/exclude*
boolean needIgnore = !CollectionUtils.isEmpty(writeHolder.excludeColumnFieldNames())
|| !CollectionUtils.isEmpty(writeHolder.excludeColumnIndexes())
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/AbstractExcelWriterParameterBuilder.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/AbstractExcelWriterParameterBuilder.java
index 7cae2d824..47a530f53 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/AbstractExcelWriterParameterBuilder.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/AbstractExcelWriterParameterBuilder.java
@@ -26,8 +26,8 @@
package org.apache.fesod.sheet.write.builder;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
+import org.apache.commons.lang3.ArrayUtils;
import org.apache.fesod.sheet.enums.HeaderMergeStrategy;
import org.apache.fesod.sheet.metadata.AbstractParameterBuilder;
import org.apache.fesod.sheet.write.handler.WriteHandler;
@@ -178,10 +178,14 @@ public T orderByIncludeColumn(Boolean orderByIncludeColumn) {
* Only write the fields marked by the following View class identifiers.
*
* @param types Target View class identifiers
+ * @throws IllegalArgumentException if the types is empty
* @return this
*/
public T groups(Class>... types) {
- parameter().setWriteViewMatcher(new ClassBasedViewMatcher(Arrays.asList(types)));
+ if (ArrayUtils.isEmpty(types)) {
+ throw new IllegalArgumentException("Types must not be empty");
+ }
+ parameter().setWriteViewMatcher(new ClassBasedViewMatcher(types));
return self();
}
@@ -189,10 +193,14 @@ public T groups(Class>... types) {
* Only write to the fields marked by the following View string identifiers.
*
* @param names Target View string identifiers
+ * @throws IllegalArgumentException if the names is empty
* @return this
*/
public T groups(String... names) {
- parameter().setWriteViewMatcher(new NameBasedViewMatcher(Arrays.asList(names)));
+ if (ArrayUtils.isEmpty(names)) {
+ throw new IllegalArgumentException("Names must not be empty");
+ }
+ parameter().setWriteViewMatcher(new NameBasedViewMatcher(names));
return self();
}
}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java
index 71a970334..aaa2e4e40 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/metadata/holder/AbstractWriteHolder.java
@@ -70,7 +70,6 @@
import org.apache.fesod.sheet.write.style.AbstractVerticalCellStyleStrategy;
import org.apache.fesod.sheet.write.style.column.AbstractHeadColumnWidthStyleStrategy;
import org.apache.fesod.sheet.write.style.row.SimpleRowHeightStyleStrategy;
-import org.apache.fesod.sheet.write.view.NoopWriteViewMatcher;
import org.apache.fesod.sheet.write.view.WriteViewMatcher;
/**
@@ -270,7 +269,7 @@ public AbstractWriteHolder(WriteBasicParameter writeBasicParameter, AbstractWrit
if (writeBasicParameter.getWriteViewMatcher() == null) {
if (parentAbstractWriteHolder == null) {
- this.writeViewMatcher = NoopWriteViewMatcher.INSTANCE;
+ this.writeViewMatcher = WriteViewMatcher.NOOP;
} else {
this.writeViewMatcher = parentAbstractWriteHolder.getWriteViewMatcher();
}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/ClassBasedViewMatcher.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/ClassBasedViewMatcher.java
index 022e36df6..6a38a9a53 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/ClassBasedViewMatcher.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/ClassBasedViewMatcher.java
@@ -22,9 +22,9 @@
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Optional;
import lombok.EqualsAndHashCode;
-import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.fesod.sheet.annotation.write.ExcelView;
@@ -34,14 +34,19 @@
* identifiers declared in {@code @ExcelView#asTypes()}.
*/
@EqualsAndHashCode
-@RequiredArgsConstructor
public class ClassBasedViewMatcher implements WriteViewMatcher {
private final Collection> expectedGroups;
- @Override
- public boolean hasViews() {
- return CollectionUtils.isNotEmpty(expectedGroups);
+ public ClassBasedViewMatcher(Class>... expectedGroups) {
+ this(ArrayUtils.isEmpty(expectedGroups) ? Collections.emptyList() : Arrays.asList(expectedGroups));
+ }
+
+ public ClassBasedViewMatcher(Collection> expectedGroups) {
+ if (CollectionUtils.isEmpty(expectedGroups)) {
+ throw new IllegalArgumentException("Type-based view groups must not be empty");
+ }
+ this.expectedGroups = Collections.unmodifiableCollection(expectedGroups);
}
@Override
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NameBasedViewMatcher.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NameBasedViewMatcher.java
index 580df3300..d7020e349 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NameBasedViewMatcher.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NameBasedViewMatcher.java
@@ -22,9 +22,9 @@
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Optional;
import lombok.EqualsAndHashCode;
-import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.fesod.sheet.annotation.write.ExcelView;
@@ -34,14 +34,19 @@
* identifiers declared in {@code @ExcelView#asNames()}.
*/
@EqualsAndHashCode
-@RequiredArgsConstructor
public class NameBasedViewMatcher implements WriteViewMatcher {
private final Collection expectedGroups;
- @Override
- public boolean hasViews() {
- return CollectionUtils.isNotEmpty(expectedGroups);
+ public NameBasedViewMatcher(String... expectedGroups) {
+ this(ArrayUtils.isEmpty(expectedGroups) ? Collections.emptyList() : Arrays.asList(expectedGroups));
+ }
+
+ public NameBasedViewMatcher(Collection expectedGroups) {
+ if (CollectionUtils.isEmpty(expectedGroups)) {
+ throw new IllegalArgumentException("Name-based view groups must not be empty");
+ }
+ this.expectedGroups = Collections.unmodifiableCollection(expectedGroups);
}
@Override
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NoopWriteViewMatcher.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NoopWriteViewMatcher.java
deleted file mode 100644
index ff6113d2c..000000000
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NoopWriteViewMatcher.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.view;
-
-import java.lang.reflect.Field;
-import lombok.EqualsAndHashCode;
-
-/**
- * The default implementation for {@link WriteViewMatcher}.
- */
-@EqualsAndHashCode
-public class NoopWriteViewMatcher implements WriteViewMatcher {
-
- public static final NoopWriteViewMatcher INSTANCE = new NoopWriteViewMatcher();
-
- @Override
- public boolean hasViews() {
- return false;
- }
-
- @Override
- public boolean matches(Field field) {
- return false;
- }
-}
diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/WriteViewMatcher.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/WriteViewMatcher.java
index 66ecfbfa6..e59992fa2 100644
--- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/WriteViewMatcher.java
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/WriteViewMatcher.java
@@ -29,10 +29,14 @@
public interface WriteViewMatcher {
/**
- * Returns whether any view constraints are active for the current
- * Sheet writing operation.
+ * A noop implementation for {@link WriteViewMatcher}
*/
- boolean hasViews();
+ WriteViewMatcher NOOP = new WriteViewMatcher() {
+ @Override
+ public boolean matches(Field field) {
+ return false;
+ }
+ };
/**
* Returns whether the given field is included in the active view(s)
diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/ClassUtilsTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/ClassUtilsTest.java
index 458364adc..2d81c7a2a 100644
--- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/ClassUtilsTest.java
+++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/ClassUtilsTest.java
@@ -45,7 +45,7 @@
import org.apache.fesod.sheet.write.metadata.holder.WriteHolder;
import org.apache.fesod.sheet.write.view.ClassBasedViewMatcher;
import org.apache.fesod.sheet.write.view.NameBasedViewMatcher;
-import org.apache.fesod.sheet.write.view.NoopWriteViewMatcher;
+import org.apache.fesod.sheet.write.view.WriteViewMatcher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@@ -130,7 +130,7 @@ private static class FormatEntity {
@Test
void test_declaredFields_cache_memory() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.MEMORY);
- Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(WriteViewMatcher.NOOP);
FieldCache cache1 = ClassUtils.declaredFields(SimpleEntity.class, writeHolder);
Assertions.assertNotNull(cache1);
@@ -144,7 +144,7 @@ void test_declaredFields_cache_memory() {
@Test
void test_declaredFields_cache_ThreadLocal() throws NoSuchFieldException, IllegalAccessException {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.THREAD_LOCAL);
- Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(WriteViewMatcher.NOOP);
FieldCache cache1 = ClassUtils.declaredFields(SimpleEntity.class, writeHolder);
Assertions.assertNotNull(cache1);
@@ -158,7 +158,7 @@ void test_declaredFields_cache_ThreadLocal() throws NoSuchFieldException, Illega
@Test
void test_declaredFields_non_cache() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
- Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(WriteViewMatcher.NOOP);
FieldCache cache1 = ClassUtils.declaredFields(SimpleEntity.class, writeHolder);
FieldCache cache2 = ClassUtils.declaredFields(SimpleEntity.class, writeHolder);
@@ -170,7 +170,7 @@ void test_declaredFields_non_cache() {
@Test
void test_declaredFields_ordering() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
- Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(WriteViewMatcher.NOOP);
FieldCache fieldCache = ClassUtils.declaredFields(ComplexEntity.class, writeHolder);
Map sortedMap = fieldCache.getSortedFieldMap();
@@ -191,7 +191,7 @@ void test_declaredFields_ordering() {
@Test
void test_declaredFields_ignore() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
- Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(WriteViewMatcher.NOOP);
FieldCache fieldCache = ClassUtils.declaredFields(ComplexEntity.class, writeHolder);
@@ -204,7 +204,7 @@ void test_declaredFields_ignore() {
@Test
void test_declaredFields_WriteHolder_exclude() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
- Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(WriteViewMatcher.NOOP);
Mockito.when(writeHolder.excludeColumnFieldNames()).thenReturn(Collections.singleton("name"));
Mockito.when(writeHolder.ignore(Mockito.anyString(), Mockito.anyInt())).thenReturn(false);
@@ -224,7 +224,7 @@ void test_declaredFields_WriteHolder_exclude() {
@Test
void test_declaredFields_resort() {
Mockito.when(globalConfiguration.getFiledCacheLocation()).thenReturn(CacheLocationEnum.NONE);
- Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(WriteViewMatcher.NOOP);
Mockito.when(writeHolder.orderByIncludeColumn()).thenReturn(true);
List include = Arrays.asList("age", "name");
@@ -244,7 +244,7 @@ void test_declaredFields_resort_byIndex() {
Mockito.when(writeHolder.includeColumnFieldNames()).thenReturn(null);
Mockito.when(writeHolder.includeColumnIndexes()).thenReturn(Arrays.asList(2, 0));
Mockito.when(writeHolder.ignore(Mockito.anyString(), Mockito.anyInt())).thenReturn(false);
- Mockito.when(writeHolder.writeViewMatcher()).thenReturn(NoopWriteViewMatcher.INSTANCE);
+ Mockito.when(writeHolder.writeViewMatcher()).thenReturn(WriteViewMatcher.NOOP);
FieldCache fieldCache = ClassUtils.declaredFields(ComplexEntity.class, writeHolder);
Map sortedMap = fieldCache.getSortedFieldMap();
diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteSheetViewTests.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteSheetViewTests.java
index 317855bec..973eab791 100644
--- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteSheetViewTests.java
+++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/view/WriteSheetViewTests.java
@@ -57,7 +57,7 @@ class WriteSheetViewTests {
@BeforeEach
void setUp(@TempDir Path tempDir) {
write03 = createTmpFile(tempDir, "write03.xls");
- write07 = createTmpFile(tempDir, "write07.xls");
+ write07 = createTmpFile(tempDir, "write07.xlsx");
writeCsv = createTmpFile(tempDir, "writeCsv.csv");
}
From d134127607fdc55e6d8a5c8212cc0ce6b325c7b5 Mon Sep 17 00:00:00 2001
From: Bengbengbalabalabeng
Date: Fri, 12 Jun 2026 19:05:56 +0800
Subject: [PATCH 3/3] docs: update the usage documentation for the @ExcelView
annotation
---
website/docs/sheet/help/annotation.md | 9 +++++++++
.../current/sheet/help/annotation.md | 9 +++++++++
2 files changed, 18 insertions(+)
diff --git a/website/docs/sheet/help/annotation.md b/website/docs/sheet/help/annotation.md
index 19287de69..d82761ea3 100644
--- a/website/docs/sheet/help/annotation.md
+++ b/website/docs/sheet/help/annotation.md
@@ -157,3 +157,12 @@ Define a freeze pane for an Excel sheet. The parameters are as follows:
| rowSplit | 0 | Vertical position of freeze pane. |
| leftmostColumn | -1 | Left column visible in right pane. By default, it's equal to `colSplit`. |
| topRow | -1 | Top row visible in bottom pane. By default, it's equal to `rowSplit`. |
+
+### `@ExcelView`
+
+Defines the view(s) that the field belongs to. During spreadsheet writing, only fields whose declared views match the active view will be included. The parameters are as follows:
+
+| Name | Default Value | Description |
+|---------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| asTypes | Empty | View or views that annotated element is part of. Views are identified by classes, when
a view type is selected, fields annotated with that type or any of subtypes are included. |
+| asNames | Empty | View or views that annotated element is part of. Views are identified by strings. |
diff --git a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/annotation.md b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/annotation.md
index bcecf7f2b..c72815dda 100644
--- a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/annotation.md
+++ b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/sheet/help/annotation.md
@@ -134,3 +134,12 @@ title: '注解'
| rowSplit | 0 | 冻结窗格的垂直位置(即需要冻结的行数) |
| leftmostColumn | -1 | 右侧窗格中可见的最左侧列。默认情况下,该值等于 `colSplit` |
| topRow | -1 | 底部窗格中可见的最顶部行。默认情况下,该值等于 `rowSplit` |
+
+### `@ExcelView`
+
+定义字段所属视图。在执行电子表格写入时,只有与当前视图匹配的字段才会参与写入。具体参数如下:
+
+| 名称 | 默认值 | 描述 |
+|---------|-----|-----------------------------------------------------------------------|
+| asTypes | 空 | 被注解元素所属的一个或多个视图。视图由类进行标识,当选择一个视图类型时,用该类型
或其任何子类型注解的字段都将被包含在当前视图内。 |
+| asNames | 空 | 该注解元素所属的一个或多个视图,视图由字符串进行标识。 |