[Practical] EasyExcel uses interceptors to set cell drop-downs (using excel formulas)

1. Brief introduction

During the system development process, we will encounter various export and import requirements. EasyExcel is currently a popular export and import framework in addition to POI. The advantages and disadvantages of EasyExcel and POI will not be introduced here. The main introduction is The following is a demand scenario: the attribute value source in the export template is an enumeration value or other columns that require drop-down constraints need to be set to drop-down

2. Entity class preparation

State enumeration class State:

public enum State {<!-- -->
GRADUATE("graduate", "graduate"),
INSTUDY("Reading", "studying"),
LEAVEL("leave", "leavel")
\t
private String cn;
private String en;
\t
State(String cn, String en) {<!-- -->
this.cn = cn;
this.en = en;
}
}

New annotation class SelectValueFrom (used to identify the source of drop-down attribute values):

@Target({<!-- -->ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SelectValueFrom {<!-- -->
    //The position of the drop-down column in the excel table
    int index();

    //The drop-down value is the system enumeration value
    Class[] enumValue() default {<!-- -->};

    //Custom drop-down string
    String[] stringValue() default {<!-- -->};

}

Entity class Student:

@Entity
@Data
public class Student {<!-- -->
private Long id;
private String name;
private String grade;
private Integer age;
\t//state
private State state;
}

Export class StudentExcelDto:

@Data
public class StudentExcelDto {<!-- -->
@ExcelProperty("name")
private String name;
@ExcelProperty("grade")
private String grade;
@ExcelProperty("Age")
private Integer age;
@SelectValueFrom(index = 4, enumValue = State.class)
@ExcelProperty("status")
private State state;
}

3. Set attribute column drop-down interceptor writing

@Slf4j
public class SelectColumnWriteHandler implements SheetWriteHandler {<!-- -->

    private Map<Integer, List<String>> selectMap;
    //Start rendering the drop-down list from a certain row
    private int index;

    private char[] alphabetArr = new char[]{<!-- -->'A', 'B', 'C', 'D', 'E', ' F', 'G', 'H', 'I', 'J', 'K', 'L',
            'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', ' U', 'V', 'W', 'X', 'Y', 'Z'};

    public SelectColumnWriteHandler(Map<Integer, List<String>> selectMap, int index) {<!-- -->
        this.selectMap = selectMap;
        this.index = index;
    }

    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {<!-- -->
    }


    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {<!-- -->
        if (selectMap == null || selectMap.size() == 0) {<!-- -->
            return;
        }
        // Get the current sheet page
        Sheet curSheet = writeSheetHolder.getSheet();
        DataValidationHelper helper = curSheet.getDataValidationHelper();
        for (Map.Entry<Integer, List<String>> entry : selectMap.entrySet()) {<!-- -->
            //Define the name of the data source sheet
            String hiddenName = "hidden" + curSheet.getSheetName() + entry.getKey();
            //Create a sheet page to store the data source, and finally need to hide it
            Workbook workbook= writeWorkbookHolder.getWorkbook();
            Sheet hidden = workbook.createSheet(hiddenName);

            //Set the first row, last row, first column, and last column of the drop-down cell
            CellRangeAddressList rangeAddressList = new CellRangeAddressList(index, 65533, entry.getKey(), entry.getKey());
            int rowLen = entry.getValue().size();
            //Set the value of the dictionary sheet page, one dictionary item for each column
            for (int i = 0; i < rowLen; i + + ) {<!-- -->
                Row row = hidden.getRow(i);
                if (row == null) {<!-- -->
                    row = hidden.createRow(i);
                }
                row.createCell(entry.getKey()).setCellValue(entry.getValue().get(i));
            }
            String excelColumn = getExcelColumnEn(entry.getKey());
            // Drop-down box data source eg: dictionary sheet!$B1:$B2
            String refers = hiddenName + "!$" + excelColumn + "$1:$" + excelColumn + "$" + rowLen;
            //Create a name that can be referenced by other cells
            Name name = workbook.createName();
            // Set the name of the name
            name.setNameName("dict" + curSheet.getSheetName() + entry.getKey());
            //Set formula
            name.setRefersToFormula(refers);
            //Set reference constraints
            DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + curSheet.getSheetName() + entry.getKey());
            //Set constraints
            DataValidation validation = helper.createValidation(constraint, rangeAddressList);
            if (validation instanceof HSSFDataValidation) {<!-- -->
                validation.setSuppressDropDownArrow(false);
            } else {<!-- -->
                validation.setSuppressDropDownArrow(true);
                validation.setShowErrorBox(true);
            }
            //Prevent input of non-drop-down box values
            validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
            validation.createErrorBox("Prompt", "This value is inconsistent with the cell definition format!");
            //Add drop-down box constraints
            writeSheetHolder.getSheet().addValidationData(validation);

            //Set the data source sheet page to be hidden
            int hiddenIndex = workbook.getSheetIndex(hiddenName);
            if (!workbook.isSheetHidden(hiddenIndex)) {<!-- -->
                workbook.setSheetHidden(hiddenIndex, true);
            }
        }
    }

    /**
     * Obtain the corresponding English column name through the index of the column in the Excel table
     * @param num
     * @return
     */
    private String getExcelColumnEn(int num) {<!-- -->
        String column = "";
        int len = alphabetArr.length - 1;
        if (num <= len) {<!-- -->
            column = alphabetArr[num] + "";
        } else {<!-- -->
            int first = num / len;
            int second = num % len;
            column = alphabetArr[first - 1] + "";
            if (second == 0) {<!-- -->
                column = column + alphabetArr[len] + "";
            } else {<!-- -->
                column = column + alphabetArr[second - 1] + "";
            }
        }
        return column;
    }
}

Export tool class ExcelUtil:

public class ExcelUtil {<!-- -->
    /**Use reflection to obtain the annotations of the class attributes, and then put the attribute value source into the Map and return it to the caller
    *@param tclass exports the Class object of the entity class (StudentExcelDto)
    *@param supplier functional interface, returns a customized drop-down map
    **/
    public static <T> Map<Integer, List<String>> buildSelectMap (Class<T> tClass, Supplier<Map<Integer, List<String>>> supplier) {<!-- -->
        Map<Integer, List<String>> res = new HashMap<>();
        if (null != supplier) {<!-- -->
            res.putAll(supplier.get());
        }
        Field[] fields = tClass.getDeclaredFields();
        try {<!-- -->
            for (Field field : fields) {<!-- -->
                SelectValueFrom selectValueFrom = field.getAnnotation(SelectValueFrom.class);
                if (null == selectValueFrom) {<!-- -->
                    continue;
                }
                int index = selectValueFrom.index();
                String[] strValues = selectValueFrom.stringValue();
                List<String> valueList = res.getOrDefault(index, new ArrayList<>());
                if (strValues.length != 0) {<!-- -->
                    valueList.addAll(Arrays.asList(strValues));
                }
                Class[] enumValues = selectValueFrom.enumValue();
                if (enumValues.length != 0) {<!-- -->
                    for (Class enumValue : enumValues) {<!-- -->
                        Object[] enumConstants = enumValue.getEnumConstants();
                        Field cnField = enumValue.getDeclaredField("cn");
                        cnField.setAccessible(Boolean.TRUE);
                        for (Object enumConstant : enumConstants) {<!-- -->
                            valueList.add(cnField.get(enumConstant).toString());
                        }
                    }
                }
                if (CollectionUtils.isNotEmpty(valueList)) {<!-- -->
                    res.put(index, valueList);
                }
            }
        } catch (Exception e) {<!-- -->
        }
        return res;
    }
}

Test classTest:

public class Test {<!-- -->
public static void main(String[] args) throws Exception {<!-- -->
String fileName ="C:\\workspace\template.xlsx";
Map<Integer, List<String>> selectValueMap = ExcelUtil.buildSelectMap(StudentExcelDto.class, null);
EasyExcel.write(fileName, StudentExcelDto.class)
.sheet("Sheet1")
.head(StudentExcelDto.class)
.registerWriteHandler(new SelectColumnWriteHandler(selectValueMap, 1))
.doWrite(new ArrayList<>());
}