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 | 空 | 该注解元素所属的一个或多个视图,视图由字符串进行标识。 |