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..579246f0b
--- /dev/null
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/annotation/write/ExcelView.java
@@ -0,0 +1,63 @@
+/*
+ * 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.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)
+public @interface ExcelView {
+
+ /**
+ * 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.
+ */
+ 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..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
@@ -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.NOOP != writeViewMatcher);
+ // 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..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
@@ -27,10 +27,13 @@
import java.util.ArrayList;
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;
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,34 @@ 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
+ * @throws IllegalArgumentException if the types is empty
+ * @return this
+ */
+ public T groups(Class>... types) {
+ if (ArrayUtils.isEmpty(types)) {
+ throw new IllegalArgumentException("Types must not be empty");
+ }
+ parameter().setWriteViewMatcher(new ClassBasedViewMatcher(types));
+ return self();
+ }
+
+ /**
+ * 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) {
+ 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/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 293f18adc..01db93eb2 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
@@ -72,6 +72,7 @@
import org.apache.fesod.sheet.write.style.SheetFreezePaneStrategy;
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.WriteViewMatcher;
/**
* Write holder configuration
@@ -136,6 +137,11 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ
*/
private List> customConverterList;
+ /**
+ * view-based matcher for sheet writing.
+ */
+ private WriteViewMatcher writeViewMatcher;
+
/**
* Write handler
*/
@@ -263,6 +269,16 @@ public AbstractWriteHolder(WriteBasicParameter writeBasicParameter, AbstractWrit
this.includeColumnIndexes = writeBasicParameter.getIncludeColumnIndexes();
}
+ if (writeBasicParameter.getWriteViewMatcher() == null) {
+ if (parentAbstractWriteHolder == null) {
+ this.writeViewMatcher = WriteViewMatcher.NOOP;
+ } else {
+ this.writeViewMatcher = parentAbstractWriteHolder.getWriteViewMatcher();
+ }
+ } else {
+ this.writeViewMatcher = writeBasicParameter.getWriteViewMatcher();
+ }
+
// Initialization property
this.excelWriteHeadProperty = new ExcelWriteHeadProperty(this, getClazz(), getHead());
@@ -599,4 +615,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..6a38a9a53
--- /dev/null
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/ClassBasedViewMatcher.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.write.view;
+
+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 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
+public class ClassBasedViewMatcher implements WriteViewMatcher {
+
+ private final Collection> 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
+ 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..d7020e349
--- /dev/null
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/NameBasedViewMatcher.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.write.view;
+
+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 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
+public class NameBasedViewMatcher implements WriteViewMatcher {
+
+ private final Collection 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
+ 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/WriteViewMatcher.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/WriteViewMatcher.java
new file mode 100644
index 000000000..e59992fa2
--- /dev/null
+++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/view/WriteViewMatcher.java
@@ -0,0 +1,46 @@
+/*
+ * 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 {
+
+ /**
+ * A noop implementation for {@link WriteViewMatcher}
+ */
+ WriteViewMatcher NOOP = new WriteViewMatcher() {
+ @Override
+ public boolean matches(Field field) {
+ return false;
+ }
+ };
+
+ /**
+ * 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..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
@@ -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.WriteViewMatcher;
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(WriteViewMatcher.NOOP);
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(WriteViewMatcher.NOOP);
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(WriteViewMatcher.NOOP);
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(WriteViewMatcher.NOOP);
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(WriteViewMatcher.NOOP);
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(WriteViewMatcher.NOOP);
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(WriteViewMatcher.NOOP);
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(WriteViewMatcher.NOOP);
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..973eab791
--- /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.xlsx");
+ 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 {}
+}
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 | 空 | 该注解元素所属的一个或多个视图,视图由字符串进行标识。 |