diff --git a/src/main/java/com/alibaba/excel/util/ClassUtils.java b/src/main/java/com/alibaba/excel/util/ClassUtils.java index 936c325d..16643de4 100644 --- a/src/main/java/com/alibaba/excel/util/ClassUtils.java +++ b/src/main/java/com/alibaba/excel/util/ClassUtils.java @@ -6,6 +6,8 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -208,4 +210,51 @@ public class ClassUtils { return ignoreMap; } } + + + /** + *

Gets a {@code List} of all interfaces implemented by the given + * class and its superclasses.

+ * + *

The order is determined by looking through each interface in turn as + * declared in the source file and following its hierarchy up. Then each + * superclass is considered in the same way. Later duplicates are ignored, + * so the order is maintained.

+ * + * @param cls the class to look up, may be {@code null} + * @return the {@code List} of interfaces in order, + * {@code null} if null input + */ + public static List> getAllInterfaces(final Class cls) { + if (cls == null) { + return null; + } + + final LinkedHashSet> interfacesFound = new LinkedHashSet<>(); + getAllInterfaces(cls, interfacesFound); + + return new ArrayList<>(interfacesFound); + } + + + /** + * Gets the interfaces for the specified class. + * + * @param cls the class to look up, may be {@code null} + * @param interfacesFound the {@code Set} of interfaces for the class + */ + private static void getAllInterfaces(Class cls, final HashSet> interfacesFound) { + while (cls != null) { + final Class[] interfaces = cls.getInterfaces(); + + for (final Class i : interfaces) { + if (interfacesFound.add(i)) { + getAllInterfaces(i, interfacesFound); + } + } + + cls = cls.getSuperclass(); + } + } + } diff --git a/src/main/java/com/alibaba/excel/util/FieldUtils.java b/src/main/java/com/alibaba/excel/util/FieldUtils.java index 1a4d5287..1e788907 100644 --- a/src/main/java/com/alibaba/excel/util/FieldUtils.java +++ b/src/main/java/com/alibaba/excel/util/FieldUtils.java @@ -1,6 +1,8 @@ package com.alibaba.excel.util; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + /** * Field utils @@ -48,4 +50,96 @@ public class FieldUtils { private static String buildFieldName(char firstChar, String fieldName) { return firstChar + fieldName.substring(1); } + + + /** + * Gets an accessible {@link Field} by name respecting scope. Superclasses/interfaces will be considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @return the Field object + * @throws IllegalArgumentException + * if the class is {@code null}, or the field name is blank or empty + */ + public static Field getField(final Class cls, final String fieldName) { + final Field field = getField(cls, fieldName, false); + MemberUtils.setAccessibleWorkaround(field); + return field; + } + + + + /** + * Gets an accessible {@link Field} by name, breaking scope if requested. Superclasses/interfaces will be + * considered. + * + * @param cls + * the {@link Class} to reflect, must not be {@code null} + * @param fieldName + * the field name to obtain + * @param forceAccess + * whether to break scope restrictions using the + * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only + * match {@code public} fields. + * @return the Field object + * @throws NullPointerException if the class is {@code null} + * @throws IllegalArgumentException if the field name is blank or empty or is matched at multiple places + * in the inheritance hierarchy + */ + public static Field getField(final Class cls, final String fieldName, final boolean forceAccess) { + Validate.isTrue(cls != null, "The class must not be null"); + Validate.isTrue(StringUtils.isNotBlank(fieldName), "The field name must not be blank/empty"); + // FIXME is this workaround still needed? lang requires Java 6 + // Sun Java 1.3 has a bugged implementation of getField hence we write the + // code ourselves + + // getField() will return the Field object with the declaring class + // set correctly to the class that declares the field. Thus requesting the + // field on a subclass will return the field from the superclass. + // + // priority order for lookup: + // searchclass private/protected/package/public + // superclass protected/package/public + // private/different package blocks access to further superclasses + // implementedinterface public + + // check up the superclass hierarchy + for (Class acls = cls; acls != null; acls = acls.getSuperclass()) { + try { + final Field field = acls.getDeclaredField(fieldName); + // getDeclaredField checks for non-public scopes as well + // and it returns accurate results + if (!Modifier.isPublic(field.getModifiers())) { + if (forceAccess) { + field.setAccessible(true); + } else { + continue; + } + } + return field; + } catch (final NoSuchFieldException ex) { // NOPMD + // ignore + } + } + // check the public interface case. This must be manually searched for + // incase there is a public supersuperclass field hidden by a private/package + // superclass field. + Field match = null; + for (final Class class1 : ClassUtils.getAllInterfaces(cls)) { + try { + final Field test = class1.getField(fieldName); + Validate.isTrue(match == null, "Reference to field %s is ambiguous relative to %s" + + "; a matching field exists on two or more implemented interfaces.", fieldName, cls); + match = test; + } catch (final NoSuchFieldException ex) { // NOPMD + // ignore + } + } + return match; + } + + + } diff --git a/src/main/java/com/alibaba/excel/util/MemberUtils.java b/src/main/java/com/alibaba/excel/util/MemberUtils.java new file mode 100644 index 00000000..6bcfaac6 --- /dev/null +++ b/src/main/java/com/alibaba/excel/util/MemberUtils.java @@ -0,0 +1,54 @@ +package com.alibaba.excel.util; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Member; +import java.lang.reflect.Modifier; + +/** + * Member utils. + * + * @author Jiaju Zhuang + */ +public class MemberUtils { + + private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE; + + + /** + * XXX Default access superclass workaround. + * + * When a {@code public} class has a default access superclass with {@code public} members, + * these members are accessible. Calling them from compiled code works fine. + * Unfortunately, on some JVMs, using reflection to invoke these members + * seems to (wrongly) prevent access even when the modifier is {@code public}. + * Calling {@code setAccessible(true)} solves the problem but will only work from + * sufficiently privileged code. Better workarounds would be gratefully + * accepted. + * @param o the AccessibleObject to set as accessible + * @return a boolean indicating whether the accessibility of the object was set to true. + */ + static boolean setAccessibleWorkaround(final AccessibleObject o) { + if (o == null || o.isAccessible()) { + return false; + } + final Member m = (Member) o; + if (!o.isAccessible() && Modifier.isPublic(m.getModifiers()) && isPackageAccess(m.getDeclaringClass().getModifiers())) { + try { + o.setAccessible(true); + return true; + } catch (final SecurityException e) { // NOPMD + // ignore in favor of subsequent IllegalAccessException + } + } + return false; + } + + /** + * Returns whether a given set of modifiers implies package access. + * @param modifiers to test + * @return {@code true} unless {@code package}/{@code protected}/{@code private} modifier detected + */ + static boolean isPackageAccess(final int modifiers) { + return (modifiers & ACCESS_TEST) == 0; + } +} diff --git a/src/main/java/com/alibaba/excel/util/StringUtils.java b/src/main/java/com/alibaba/excel/util/StringUtils.java index a67e46be..667233dc 100644 --- a/src/main/java/com/alibaba/excel/util/StringUtils.java +++ b/src/main/java/com/alibaba/excel/util/StringUtils.java @@ -69,4 +69,29 @@ public class StringUtils { return true; } + + /** + *

Checks if a CharSequence is not empty (""), not null and not whitespace only.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.isNotBlank(null)      = false
+     * StringUtils.isNotBlank("")        = false
+     * StringUtils.isNotBlank(" ")       = false
+     * StringUtils.isNotBlank("bob")     = true
+     * StringUtils.isNotBlank("  bob  ") = true
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is + * not empty and not null and not whitespace only + * @since 2.0 + * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence) + */ + public static boolean isNotBlank(final CharSequence cs) { + return !isBlank(cs); + } + + } diff --git a/src/main/java/com/alibaba/excel/util/Validate.java b/src/main/java/com/alibaba/excel/util/Validate.java new file mode 100644 index 00000000..6207bb1a --- /dev/null +++ b/src/main/java/com/alibaba/excel/util/Validate.java @@ -0,0 +1,151 @@ +package com.alibaba.excel.util; + +import java.util.Objects; + +/** + * Validate + * + * @author Jiaju Zhuang + */ +public class Validate { + + private static final String DEFAULT_IS_TRUE_EX_MESSAGE = "The validated expression is false"; + private static final String DEFAULT_IS_NULL_EX_MESSAGE = "The validated object is null"; + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
Validate.isTrue(i > 0.0, "The value must be greater than zero: %d", i);
+ * + *

For performance reasons, the long value is passed as a separate parameter and + * appended to the exception message only in the case of an error.

+ * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param value the value to append to the message when invalid + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, double) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression, final String message, final long value) { + if (!expression) { + throw new IllegalArgumentException(String.format(message, Long.valueOf(value))); + } + } + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
Validate.isTrue(d > 0.0, "The value must be greater than zero: %s", d);
+ * + *

For performance reasons, the double value is passed as a separate parameter and + * appended to the exception message only in the case of an error.

+ * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param value the value to append to the message when invalid + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression, final String message, final double value) { + if (!expression) { + throw new IllegalArgumentException(String.format(message, Double.valueOf(value))); + } + } + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception with the specified message. This method is useful when + * validating according to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
+     * Validate.isTrue(i >= min && i <= max, "The value must be between %d and %d", min, max);
+     * Validate.isTrue(myObject.isOk(), "The object is not okay");
+ * + * @param expression the boolean expression to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message, null array not recommended + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean) + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, double) + */ + public static void isTrue(final boolean expression, final String message, final Object... values) { + if (!expression) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + /** + *

Validate that the argument condition is {@code true}; otherwise + * throwing an exception. This method is useful when validating according + * to an arbitrary boolean expression, such as validating a + * primitive number or using your own custom validation expression.

+ * + *
+     * Validate.isTrue(i > 0);
+     * Validate.isTrue(myObject.isOk());
+ * + *

The message of the exception is "The validated expression is + * false".

+ * + * @param expression the boolean expression to check + * @throws IllegalArgumentException if expression is {@code false} + * @see #isTrue(boolean, String, long) + * @see #isTrue(boolean, String, double) + * @see #isTrue(boolean, String, Object...) + */ + public static void isTrue(final boolean expression) { + if (!expression) { + throw new IllegalArgumentException(DEFAULT_IS_TRUE_EX_MESSAGE); + } + } + + + /** + *

Validate that the specified argument is not {@code null}; + * otherwise throwing an exception. + * + *

Validate.notNull(myObject, "The object must not be null");
+ * + *

The message of the exception is "The validated object is + * null".

+ * + * @param the object type + * @param object the object to check + * @return the validated object (never {@code null} for method chaining) + * @throws NullPointerException if the object is {@code null} + * @see #notNull(Object, String, Object...) + */ + public static T notNull(final T object) { + return notNull(object, DEFAULT_IS_NULL_EX_MESSAGE); + } + + /** + *

Validate that the specified argument is not {@code null}; + * otherwise throwing an exception with the specified message. + * + *

Validate.notNull(myObject, "The object must not be null");
+ * + * @param the object type + * @param object the object to check + * @param message the {@link String#format(String, Object...)} exception message if invalid, not null + * @param values the optional values for the formatted exception message + * @return the validated object (never {@code null} for method chaining) + * @throws NullPointerException if the object is {@code null} + * @see #notNull(Object) + */ + public static T notNull(final T object, final String message, final Object... values) { + return Objects.requireNonNull(object, () -> String.format(message, values)); + } +} diff --git a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java index 0c7158ed..2acd5769 100644 --- a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java +++ b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java @@ -1,13 +1,18 @@ package com.alibaba.excel.write; +import java.lang.reflect.Field; import java.util.List; +import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFSheet; import com.alibaba.excel.context.WriteContext; import com.alibaba.excel.context.WriteContextImpl; import com.alibaba.excel.enums.WriteTypeEnum; import com.alibaba.excel.exception.ExcelGenerateException; +import com.alibaba.excel.util.FieldUtils; import com.alibaba.excel.util.FileUtils; import com.alibaba.excel.write.executor.ExcelWriteAddExecutor; import com.alibaba.excel.write.executor.ExcelWriteFillExecutor; diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java index 6e99a9a9..529f5d22 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java @@ -49,9 +49,10 @@ public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor { // BeanMap is out of order,so use sortedAllFiledMap Map sortedAllFiledMap = new TreeMap(); int relativeRowIndex = 0; + int lastRowIndex = 0; for (Object oneRowData : data) { - int n = relativeRowIndex + newRowIndex; - addOneRowOfDataToExcel(oneRowData, n, relativeRowIndex, sortedAllFiledMap); + lastRowIndex = relativeRowIndex + newRowIndex; + addOneRowOfDataToExcel(oneRowData, lastRowIndex, relativeRowIndex, sortedAllFiledMap); relativeRowIndex++; } } @@ -108,6 +109,7 @@ public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor { Object value = oneRowData.get(dataIndex); CellData cellData = converterAndSet(writeContext.currentWriteHolder(), value == null ? null : value.getClass(), cell, value, null, head, relativeRowIndex); + writeContext.writeSheetHolder().setLastRowIndex(cellData.getRowIndex()); WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, head, relativeRowIndex, Boolean.FALSE); } diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index b2f3362b..7accb669 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -93,7 +93,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { Object realData; if (data instanceof FillWrapper) { - FillWrapper fillWrapper = (FillWrapper) data; + FillWrapper fillWrapper = (FillWrapper)data; currentDataPrefix = fillWrapper.getName(); realData = fillWrapper.getCollectionData(); } else { @@ -105,7 +105,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { // processing data if (realData instanceof Collection) { List analysisCellList = readTemplateData(templateCollectionAnalysisCache); - Collection collectionData = (Collection) realData; + Collection collectionData = (Collection)realData; if (CollectionUtils.isEmpty(collectionData)) { return; } @@ -183,7 +183,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } Map dataMap; if (oneRowData instanceof Map) { - dataMap = (Map) oneRowData; + dataMap = (Map)oneRowData; } else { dataMap = BeanMap.create(oneRowData); } @@ -293,7 +293,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } Row row = createRowIfNecessary(sheet, cachedSheet, lastRowIndex, fillConfig, analysisCell, isOriginalCell); - Cell cell = createCellIfNecessary(row,lastColumnIndex); + Cell cell = createCellIfNecessary(row, lastColumnIndex); Map collectionFieldStyleMap = collectionFieldStyleCache.get(currentUniqueDataFlag); if (collectionFieldStyleMap == null) { @@ -335,7 +335,8 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { row = cachedSheet.createRow(lastRowIndex); } else { // The last row of the middle disk inside empty rows, resulting in cachedSheet can not get inside. - // Will throw Attempting to write a row[" + rownum + "] " + "in the range [0," + this._sh.getLastRowNum() + "] that is already written to disk. + // Will throw Attempting to write a row[" + rownum + "] " + "in the range [0," + this._sh + // .getLastRowNum() + "] that is already written to disk. try { row = sheet.createRow(lastRowIndex); } catch (IllegalArgumentException ignore) { @@ -414,7 +415,8 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { int startIndex = 0; int length = value.length(); int lastPrepareDataIndex = 0; - out: while (startIndex < length) { + out: + while (startIndex < length) { int prefixIndex = value.indexOf(FILL_PREFIX, startIndex); if (prefixIndex < 0) { break out; diff --git a/src/main/java/com/alibaba/excel/write/handler/AbstractCellWriteHandler.java b/src/main/java/com/alibaba/excel/write/handler/AbstractCellWriteHandler.java index b649af7a..963abc87 100644 --- a/src/main/java/com/alibaba/excel/write/handler/AbstractCellWriteHandler.java +++ b/src/main/java/com/alibaba/excel/write/handler/AbstractCellWriteHandler.java @@ -14,7 +14,9 @@ import com.alibaba.excel.write.metadata.holder.WriteTableHolder; * Abstract cell write handler * * @author Jiaju Zhuang + * @deprecated Please use it directly {@link CellWriteHandler} **/ +@Deprecated public abstract class AbstractCellWriteHandler implements CellWriteHandler { @Override diff --git a/src/main/java/com/alibaba/excel/write/handler/AbstractRowWriteHandler.java b/src/main/java/com/alibaba/excel/write/handler/AbstractRowWriteHandler.java index d1e8c3a9..286d8757 100644 --- a/src/main/java/com/alibaba/excel/write/handler/AbstractRowWriteHandler.java +++ b/src/main/java/com/alibaba/excel/write/handler/AbstractRowWriteHandler.java @@ -1,15 +1,17 @@ package com.alibaba.excel.write.handler; -import org.apache.poi.ss.usermodel.Row; - import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteTableHolder; +import org.apache.poi.ss.usermodel.Row; + /** * Abstract row write handler * * @author Jiaju Zhuang + * @deprecated Please use it directly {@link RowWriteHandler} **/ +@Deprecated public abstract class AbstractRowWriteHandler implements RowWriteHandler { @Override public void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer rowIndex, diff --git a/src/main/java/com/alibaba/excel/write/handler/AbstractSheetWriteHandler.java b/src/main/java/com/alibaba/excel/write/handler/AbstractSheetWriteHandler.java index 76f4c846..d24f9010 100644 --- a/src/main/java/com/alibaba/excel/write/handler/AbstractSheetWriteHandler.java +++ b/src/main/java/com/alibaba/excel/write/handler/AbstractSheetWriteHandler.java @@ -7,7 +7,9 @@ import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; * Abstract sheet write handler * * @author Jiaju Zhuang + * @deprecated Please use it directly {@link SheetWriteHandler} **/ +@Deprecated public abstract class AbstractSheetWriteHandler implements SheetWriteHandler { @Override public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { diff --git a/src/main/java/com/alibaba/excel/write/handler/AbstractWorkbookWriteHandler.java b/src/main/java/com/alibaba/excel/write/handler/AbstractWorkbookWriteHandler.java index baad3920..5bb2fc5f 100644 --- a/src/main/java/com/alibaba/excel/write/handler/AbstractWorkbookWriteHandler.java +++ b/src/main/java/com/alibaba/excel/write/handler/AbstractWorkbookWriteHandler.java @@ -6,7 +6,9 @@ import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; * Abstract workbook write handler * * @author Jiaju Zhuang + * @deprecated Please use it directly {@link WorkbookWriteHandler} **/ +@Deprecated public abstract class AbstractWorkbookWriteHandler implements WorkbookWriteHandler { @Override diff --git a/src/main/java/com/alibaba/excel/write/handler/DefaultWriteHandlerLoader.java b/src/main/java/com/alibaba/excel/write/handler/DefaultWriteHandlerLoader.java index dff84d7a..4afefea6 100644 --- a/src/main/java/com/alibaba/excel/write/handler/DefaultWriteHandlerLoader.java +++ b/src/main/java/com/alibaba/excel/write/handler/DefaultWriteHandlerLoader.java @@ -3,12 +3,14 @@ package com.alibaba.excel.write.handler; import java.util.ArrayList; import java.util.List; -import org.apache.poi.ss.usermodel.IndexedColors; - +import com.alibaba.excel.write.handler.impl.DefaultRowWriteHandler; +import com.alibaba.excel.write.handler.impl.DimensionWorkbookWriteHandler; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; +import org.apache.poi.ss.usermodel.IndexedColors; + /** * Load default handler * @@ -16,13 +18,20 @@ import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; */ public class DefaultWriteHandlerLoader { + public static final List DEFAULT_WRITE_HANDLER_LIST = new ArrayList<>(); + + static { + DEFAULT_WRITE_HANDLER_LIST.add(new DimensionWorkbookWriteHandler()); + DEFAULT_WRITE_HANDLER_LIST.add(new DefaultRowWriteHandler()); + } + /** * Load default handler * * @return */ public static List loadDefaultHandler(Boolean useDefaultStyle) { - List handlerList = new ArrayList(); + List handlerList = new ArrayList<>(); if (useDefaultStyle) { WriteCellStyle headWriteCellStyle = new WriteCellStyle(); headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); @@ -31,8 +40,9 @@ public class DefaultWriteHandlerLoader { headWriteFont.setFontHeightInPoints((short)14); headWriteFont.setBold(true); headWriteCellStyle.setWriteFont(headWriteFont); - handlerList.add(new HorizontalCellStyleStrategy(headWriteCellStyle, new ArrayList())); + handlerList.add(new HorizontalCellStyleStrategy(headWriteCellStyle, new ArrayList<>())); } + handlerList.addAll(DEFAULT_WRITE_HANDLER_LIST); return handlerList; } diff --git a/src/main/java/com/alibaba/excel/write/handler/RowWriteHandler.java b/src/main/java/com/alibaba/excel/write/handler/RowWriteHandler.java index 29ac10b0..e12d98f6 100644 --- a/src/main/java/com/alibaba/excel/write/handler/RowWriteHandler.java +++ b/src/main/java/com/alibaba/excel/write/handler/RowWriteHandler.java @@ -1,10 +1,10 @@ package com.alibaba.excel.write.handler; -import org.apache.poi.ss.usermodel.Row; - import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteTableHolder; +import org.apache.poi.ss.usermodel.Row; + /** * intercepts handle row creation * @@ -16,44 +16,35 @@ public interface RowWriteHandler extends WriteHandler { * Called before create the row * * @param writeSheetHolder - * @param writeTableHolder - * Nullable.It is null without using table writes. + * @param writeTableHolder Nullable.It is null without using table writes. * @param rowIndex - * @param relativeRowIndex - * Nullable.It is null in the case of fill data. - * @param isHead - * Nullable.It is null in the case of fill data. + * @param relativeRowIndex Nullable.It is null in the case of fill data. + * @param isHead Nullable.It is null in the case of fill data. */ - void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer rowIndex, - Integer relativeRowIndex, Boolean isHead); + default void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer rowIndex, + Integer relativeRowIndex, Boolean isHead) {} /** * Called after the row is created * * @param writeSheetHolder - * @param writeTableHolder - * Nullable.It is null without using table writes. + * @param writeTableHolder Nullable.It is null without using table writes. * @param row - * @param relativeRowIndex - * Nullable.It is null in the case of fill data. - * @param isHead - * Nullable.It is null in the case of fill data. + * @param relativeRowIndex Nullable.It is null in the case of fill data. + * @param isHead Nullable.It is null in the case of fill data. */ - void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, - Integer relativeRowIndex, Boolean isHead); + default void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Integer relativeRowIndex, Boolean isHead) {} /** * Called after all operations on the row have been completed.This method is not called when fill the data. * * @param writeSheetHolder - * @param writeTableHolder - * Nullable.It is null without using table writes. + * @param writeTableHolder Nullable.It is null without using table writes. * @param row - * @param relativeRowIndex - * Nullable.It is null in the case of fill data. - * @param isHead - * Nullable.It is null in the case of fill data. + * @param relativeRowIndex Nullable.It is null in the case of fill data. + * @param isHead Nullable.It is null in the case of fill data. */ - void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, - Integer relativeRowIndex, Boolean isHead); + default void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Integer relativeRowIndex, Boolean isHead) {} } diff --git a/src/main/java/com/alibaba/excel/write/handler/WorkbookWriteHandler.java b/src/main/java/com/alibaba/excel/write/handler/WorkbookWriteHandler.java index 8dba9646..c3b758f5 100644 --- a/src/main/java/com/alibaba/excel/write/handler/WorkbookWriteHandler.java +++ b/src/main/java/com/alibaba/excel/write/handler/WorkbookWriteHandler.java @@ -12,19 +12,19 @@ public interface WorkbookWriteHandler extends WriteHandler { /** * Called before create the workbook */ - void beforeWorkbookCreate(); + default void beforeWorkbookCreate() {} /** * Called after the workbook is created * * @param writeWorkbookHolder */ - void afterWorkbookCreate(WriteWorkbookHolder writeWorkbookHolder); + default void afterWorkbookCreate(WriteWorkbookHolder writeWorkbookHolder) {} /** * Called after all operations on the workbook have been completed * * @param writeWorkbookHolder */ - void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder); + default void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) {} } diff --git a/src/main/java/com/alibaba/excel/write/handler/impl/DefaultRowWriteHandler.java b/src/main/java/com/alibaba/excel/write/handler/impl/DefaultRowWriteHandler.java new file mode 100644 index 00000000..36f5ed09 --- /dev/null +++ b/src/main/java/com/alibaba/excel/write/handler/impl/DefaultRowWriteHandler.java @@ -0,0 +1,22 @@ +package com.alibaba.excel.write.handler.impl; + +import com.alibaba.excel.write.handler.RowWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteTableHolder; + +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Row; + +/** + * Default row handler. + * + * @author Jiaju Zhuang + */ +@Slf4j +public class DefaultRowWriteHandler implements RowWriteHandler { + @Override + public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Integer relativeRowIndex, Boolean isHead) { + writeSheetHolder.setLastRowIndex(row.getRowNum()); + } +} diff --git a/src/main/java/com/alibaba/excel/write/handler/impl/DimensionWorkbookWriteHandler.java b/src/main/java/com/alibaba/excel/write/handler/impl/DimensionWorkbookWriteHandler.java new file mode 100644 index 00000000..6e2c8bac --- /dev/null +++ b/src/main/java/com/alibaba/excel/write/handler/impl/DimensionWorkbookWriteHandler.java @@ -0,0 +1,76 @@ +package com.alibaba.excel.write.handler.impl; + +import java.lang.reflect.Field; +import java.util.Map; + +import com.alibaba.excel.util.FieldUtils; +import com.alibaba.excel.write.handler.WorkbookWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.MapUtils; +import org.apache.poi.ss.util.CellReference; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet; + +/** + * Handle the problem of unable to write dimension. + * + * https://github.com/alibaba/easyexcel/issues/1282 + * + * @author Jiaju Zhuang + */ +@Slf4j +public class DimensionWorkbookWriteHandler implements WorkbookWriteHandler { + + private static final String XSSF_SHEET_MEMBER_VARIABLE_NAME = "_sh"; + private static final Field XSSF_SHEET_FIELD = FieldUtils.getField(SXSSFSheet.class, XSSF_SHEET_MEMBER_VARIABLE_NAME, + true); + + @Override + public void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) { + if (writeWorkbookHolder == null || writeWorkbookHolder.getWorkbook() == null) { + return; + } + if (!(writeWorkbookHolder.getWorkbook() instanceof SXSSFWorkbook)) { + return; + } + + Map writeSheetHolderMap = writeWorkbookHolder.getHasBeenInitializedSheetIndexMap(); + if (MapUtils.isEmpty(writeSheetHolderMap)) { + return; + } + for (WriteSheetHolder writeSheetHolder : writeSheetHolderMap.values()) { + if (writeSheetHolder.getSheet() == null || !(writeSheetHolder.getSheet() instanceof SXSSFSheet)) { + continue; + } + SXSSFSheet sxssfSheet = ((SXSSFSheet)writeSheetHolder.getSheet()); + XSSFSheet xssfSheet; + try { + xssfSheet = (XSSFSheet)XSSF_SHEET_FIELD.get(sxssfSheet); + } catch (IllegalAccessException e) { + log.debug("Can not found _sh.", e); + continue; + } + if (xssfSheet == null) { + continue; + } + CTWorksheet ctWorksheet = xssfSheet.getCTWorksheet(); + if (ctWorksheet == null) { + continue; + } + int headSize = 0; + if (MapUtils.isNotEmpty(writeSheetHolder.getExcelWriteHeadProperty().getHeadMap())) { + headSize = writeSheetHolder.getExcelWriteHeadProperty().getHeadMap().size(); + if (headSize > 0) { + headSize--; + } + } + ctWorksheet.getDimension().setRef( + "A1:" + CellReference.convertNumToColString(headSize) + (writeSheetHolder.getLastRowIndex() + 1)); + } + } +} diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java index 56fc7e4e..afae52b8 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java @@ -3,21 +3,25 @@ package com.alibaba.excel.write.metadata.holder; import java.util.HashMap; import java.util.Map; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.xssf.streaming.SXSSFSheet; -import org.apache.poi.xssf.usermodel.XSSFSheet; - import com.alibaba.excel.enums.HolderEnum; import com.alibaba.excel.enums.WriteLastRowTypeEnum; import com.alibaba.excel.util.StringUtils; import com.alibaba.excel.write.metadata.WriteSheet; +import lombok.Getter; +import lombok.Setter; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFSheet; + /** * sheet holder * * @author Jiaju Zhuang */ +@Getter +@Setter public class WriteSheetHolder extends AbstractWriteHolder { /** * current param @@ -65,6 +69,11 @@ public class WriteSheetHolder extends AbstractWriteHolder { */ private WriteLastRowTypeEnum writeLastRowTypeEnum; + /** + * last row index + */ + private Integer lastRowIndex; + public WriteSheetHolder(WriteSheet writeSheet, WriteWorkbookHolder writeWorkbookHolder) { super(writeSheet, writeWorkbookHolder, writeWorkbookHolder.getWriteWorkbook().getConvertAllFiled()); this.writeSheet = writeSheet; @@ -75,76 +84,13 @@ public class WriteSheetHolder extends AbstractWriteHolder { } this.sheetName = writeSheet.getSheetName(); this.parentWriteWorkbookHolder = writeWorkbookHolder; - this.hasBeenInitializedTable = new HashMap(); + this.hasBeenInitializedTable = new HashMap<>(); if (writeWorkbookHolder.getTempTemplateInputStream() != null) { writeLastRowTypeEnum = WriteLastRowTypeEnum.TEMPLATE_EMPTY; } else { writeLastRowTypeEnum = WriteLastRowTypeEnum.COMMON_EMPTY; } - } - - public WriteSheet getWriteSheet() { - return writeSheet; - } - - public void setWriteSheet(WriteSheet writeSheet) { - this.writeSheet = writeSheet; - } - - public Sheet getSheet() { - return sheet; - } - - public void setSheet(Sheet sheet) { - this.sheet = sheet; - } - - public Integer getSheetNo() { - return sheetNo; - } - - public Sheet getCachedSheet() { - return cachedSheet; - } - - public void setCachedSheet(Sheet cachedSheet) { - this.cachedSheet = cachedSheet; - } - - public void setSheetNo(Integer sheetNo) { - this.sheetNo = sheetNo; - } - - public String getSheetName() { - return sheetName; - } - - public void setSheetName(String sheetName) { - this.sheetName = sheetName; - } - - public WriteWorkbookHolder getParentWriteWorkbookHolder() { - return parentWriteWorkbookHolder; - } - - public void setParentWriteWorkbookHolder(WriteWorkbookHolder parentWriteWorkbookHolder) { - this.parentWriteWorkbookHolder = parentWriteWorkbookHolder; - } - - public Map getHasBeenInitializedTable() { - return hasBeenInitializedTable; - } - - public void setHasBeenInitializedTable(Map hasBeenInitializedTable) { - this.hasBeenInitializedTable = hasBeenInitializedTable; - } - - public WriteLastRowTypeEnum getWriteLastRowTypeEnum() { - return writeLastRowTypeEnum; - } - - public void setWriteLastRowTypeEnum(WriteLastRowTypeEnum writeLastRowTypeEnum) { - this.writeLastRowTypeEnum = writeLastRowTypeEnum; + lastRowIndex = 0; } /** diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java index 2a907c30..98e77527 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java @@ -14,6 +14,7 @@ import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.WriteTable; import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; import net.sf.cglib.beans.BeanMap; import org.junit.Ignore; import org.junit.Test; @@ -26,6 +27,7 @@ import org.slf4j.LoggerFactory; * @author Jiaju Zhuang **/ @Ignore +@Slf4j public class Wirte { private static final Logger LOGGER = LoggerFactory.getLogger(Wirte.class); @@ -42,6 +44,7 @@ public class Wirte { @Test public void simpleWrite() { + log.info("t5"); // 写法1 String fileName = TestFileUtil.getPath() + "t22" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭