Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.liaochong.myexcel.core.AbstractSimpleExcelBuilder Maven / Gradle / Ivy
/*
* Copyright 2019 liaochong
*
* Licensed 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 com.github.liaochong.myexcel.core;
import com.github.liaochong.myexcel.core.annotation.ExcelColumn;
import com.github.liaochong.myexcel.core.annotation.ExcludeColumn;
import com.github.liaochong.myexcel.core.annotation.IgnoreColumn;
import com.github.liaochong.myexcel.core.annotation.MultiColumn;
import com.github.liaochong.myexcel.core.constant.BooleanDropDownList;
import com.github.liaochong.myexcel.core.constant.Constants;
import com.github.liaochong.myexcel.core.constant.DropDownList;
import com.github.liaochong.myexcel.core.constant.ImageFile;
import com.github.liaochong.myexcel.core.constant.LinkEmail;
import com.github.liaochong.myexcel.core.constant.LinkUrl;
import com.github.liaochong.myexcel.core.constant.NumberDropDownList;
import com.github.liaochong.myexcel.core.container.Pair;
import com.github.liaochong.myexcel.core.converter.ConvertContext;
import com.github.liaochong.myexcel.core.converter.WriteConverterContext;
import com.github.liaochong.myexcel.core.converter.writer.LocalTimeWriteConverter;
import com.github.liaochong.myexcel.core.parser.ContentTypeEnum;
import com.github.liaochong.myexcel.core.parser.StyleParser;
import com.github.liaochong.myexcel.core.parser.Table;
import com.github.liaochong.myexcel.core.parser.Td;
import com.github.liaochong.myexcel.core.parser.Tr;
import com.github.liaochong.myexcel.core.reflect.ClassFieldContainer;
import com.github.liaochong.myexcel.core.strategy.WidthStrategy;
import com.github.liaochong.myexcel.utils.ConfigurationUtil;
import com.github.liaochong.myexcel.utils.ReflectUtil;
import com.github.liaochong.myexcel.utils.StringUtil;
import com.github.liaochong.myexcel.utils.TdUtil;
import javax.lang.model.type.NullType;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @author liaochong
* @version 1.0
*/
abstract class AbstractSimpleExcelBuilder {
/**
* 字段展示顺序
*/
protected List fieldDisplayOrder;
/**
* 已排序字段
*/
protected List filteredFields = Collections.emptyList();
/**
* 标题
*/
protected List titles;
/**
* 默认值集合
*/
protected Map defaultValueMap = new HashMap<>();
/**
* 自定义宽度
*/
protected Map customWidthMap = new HashMap<>();
/**
* 标题层级
*/
protected int titleLevel = 0;
/**
* 格式化
*/
private final Map formats = new HashMap<>();
/**
* 是否为Map类型导出
*/
protected boolean isMapBuild;
/**
* 转换上下文
*/
private final ConvertContext convertContext;
protected Configuration configuration;
private final Map excelColumnMappingMap;
protected StyleParser styleParser = new StyleParser(customWidthMap);
/**
* 是否拥有聚合列
*/
protected boolean hasMultiColumn = false;
public AbstractSimpleExcelBuilder(boolean isCsvBuild) {
convertContext = new ConvertContext(isCsvBuild);
configuration = convertContext.configuration;
excelColumnMappingMap = convertContext.excelColumnMappingMap;
}
/**
* Core methods for obtaining export related fields, styles, etc
*
* @param classFieldContainer classFieldContainer
* @param groups 分组
* @return Field
*/
protected List getFilteredFields(ClassFieldContainer classFieldContainer, Class>... groups) {
ConfigurationUtil.parseConfiguration(classFieldContainer, configuration);
this.parseGlobalStyle();
List preElectionFields = this.getPreElectionFields(classFieldContainer);
List buildFields = this.getGroupFields(preElectionFields, groups);
// 初始化标题容器
List titles = new ArrayList<>(buildFields.size());
for (int i = 0, size = buildFields.size(); i < size; i++) {
Field field = buildFields.get(i);
ExcelColumn excelColumn = field.getAnnotation(ExcelColumn.class);
String[] columnStyles = null;
if (excelColumn != null) {
if (configuration.useFieldNameAsTitle && excelColumn.title().isEmpty()) {
titles.add(field.getName());
} else {
titles.add(excelColumn.title());
}
if (!excelColumn.defaultValue().isEmpty()) {
defaultValueMap.put(field, excelColumn.defaultValue());
}
if (excelColumn.width() > -1) {
customWidthMap.putIfAbsent(i, excelColumn.width());
}
if (excelColumn.style().length > 0) {
columnStyles = excelColumn.style();
}
if (!excelColumn.format().isEmpty()) {
formats.put(i, excelColumn.format());
} else if (!excelColumn.decimalFormat().isEmpty()) {
formats.put(i, excelColumn.decimalFormat());
} else if (!excelColumn.dateFormatPattern().isEmpty()) {
formats.put(i, excelColumn.dateFormatPattern());
}
ExcelColumnMapping mapping = ExcelColumnMapping.mapping(excelColumn);
excelColumnMappingMap.put(field, mapping);
} else {
if (configuration.useFieldNameAsTitle) {
titles.add(field.getName());
} else {
titles.add(null);
}
}
styleParser.setColumnStyle(field, i, columnStyles);
setGlobalFormat(i, field);
}
setTitles(titles);
hasMultiColumn = buildFields.stream().anyMatch(field -> field.isAnnotationPresent(MultiColumn.class));
return buildFields;
}
protected void parseGlobalStyle() {
styleParser.parse(configuration.style);
}
private void setGlobalFormat(int i, Field field) {
if (formats.get(i) != null) {
return;
}
if (field.getType() == LocalDate.class) {
formats.put(i, configuration.dateFormat);
} else if (ReflectUtil.isDate(field.getType())) {
formats.put(i, configuration.dateTimeFormat);
} else if (ReflectUtil.isNumber(field.getType())) {
if (configuration.decimalFormat != null) {
formats.put(i, configuration.decimalFormat);
}
}
}
/**
* 创建table
*
* @return table
*/
protected Table createTable() {
Table table = new Table();
table.caption = configuration.sheetName;
table.trList = new LinkedList<>();
return table;
}
/**
* 创建标题行
*
* @return 标题行
*/
protected List createThead() {
if (titles == null || titles.isEmpty()) {
return Collections.emptyList();
}
List> tdLists = new ArrayList<>();
// 初始化位置信息
for (int i = 0; i < titles.size(); i++) {
String title = titles.get(i);
if (title == null) {
continue;
}
List tds = new ArrayList<>();
String[] multiTitles = title.split(configuration.titleSeparator);
if (multiTitles.length > titleLevel) {
titleLevel = multiTitles.length;
}
for (int j = 0; j < multiTitles.length; j++) {
Td td = new Td(j, i);
td.th = true;
td.content = multiTitles[j];
this.setPrompt(td, i);
tds.add(td);
}
tdLists.add(tds);
}
// 调整rowSpan
for (List tdList : tdLists) {
Td last = tdList.get(tdList.size() - 1);
last.setRowSpan(titleLevel - last.row);
}
// 调整colSpan
for (int i = 0; i < titleLevel; i++) {
int level = i;
Map>> groups = tdLists.stream()
.filter(list -> list.size() > level)
.collect(Collectors.groupingBy(list -> list.get(level).content));
groups.forEach((k, v) -> {
if (v.size() == 1) {
return;
}
List tds = v.stream().map(list -> list.get(level))
.sorted(Comparator.comparing(td -> td.col))
.collect(Collectors.toList());
List> subTds = new LinkedList<>();
// 不同跨行分别处理
Map> partitions = tds.stream().collect(Collectors.groupingBy(td -> td.rowSpan));
partitions.forEach((col, subTdList) -> {
// 区分开不连续列
int splitIndex = 0;
for (int j = 0, size = subTdList.size() - 1; j < size; j++) {
Td current = subTdList.get(j);
Td next = subTdList.get(j + 1);
if (current.col + 1 != next.col) {
List sub = subTdList.subList(splitIndex, j + 1);
splitIndex = j + 1;
if (sub.size() <= 1) {
continue;
}
subTds.add(sub);
}
}
subTds.add(subTdList.subList(splitIndex, subTdList.size()));
});
subTds.forEach(val -> {
if (val.size() == 1) {
return;
}
Td t = val.get(0);
t.setColSpan(val.size());
for (int j = 1; j < val.size(); j++) {
val.get(j).row = -1;
}
});
});
}
Map> rowTds = tdLists.stream().flatMap(List::stream).filter(td -> td.row > -1).collect(Collectors.groupingBy(td -> td.row));
List trs = new ArrayList<>();
boolean isComputeAutoWidth = WidthStrategy.isComputeAutoWidth(configuration.widthStrategy);
rowTds.forEach((k, v) -> {
Tr tr = new Tr(k, configuration.titleRowHeight);
tr.colWidthMap = isComputeAutoWidth || !customWidthMap.isEmpty() ? new HashMap<>(titles.size()) : Collections.emptyMap();
tr.tdList = v.stream().sorted(Comparator.comparing(td -> td.col))
.peek(td -> {
if (isComputeAutoWidth) {
tr.colWidthMap.put(td.col, TdUtil.getStringWidth(td.content, 0.25));
}
})
.collect(Collectors.toList());
tr.colWidthMap.putAll(customWidthMap);
trs.add(tr);
});
return trs;
}
/**
* 创建内容行
*
* @param contents 内容集合
* @return 内容行
*/
protected Tr createTr(List> contents) {
Tr tr = new Tr(0, configuration.rowHeight);
if (contents.isEmpty()) {
return tr;
}
tr.colWidthMap = new HashMap<>();
List tdList = IntStream.range(0, contents.size()).mapToObj(index -> {
Td td = new Td(0, index);
Pair extends Class, ?> pair = contents.get(index);
if (pair.getRepeatSize() != null) {
td.setRowSpan(pair.getRepeatSize());
}
this.setTdContent(td, pair);
this.setTdContentType(td, pair.getKey());
if (isMapBuild) {
this.setDateFormatForMap(pair.getKey(), td);
} else {
td.format = formats.get(index);
this.setFormula(index, td);
this.setPrompt(td, index);
}
this.setTdWidth(tr.colWidthMap, td);
return td;
}).collect(Collectors.toList());
tr.colWidthMap.putAll(customWidthMap);
tr.tdList = tdList;
return tr;
}
private void setTdWidth(Map colWidthMap, Td td) {
if (!configuration.computeAutoWidth) {
return;
}
if (td.format == null) {
colWidthMap.put(td.col, TdUtil.getStringWidth(td.content));
} else {
if (td.content != null && td.format.length() > td.content.length()) {
colWidthMap.put(td.col, TdUtil.getStringWidth(td.format));
} else if (td.date != null || td.localDate != null || td.localDateTime != null) {
colWidthMap.put(td.col, TdUtil.getStringWidth(td.format, -0.15));
}
}
}
private void setFormula(int i, Td td) {
if (filteredFields.isEmpty()) {
return;
}
Field field = filteredFields.get(i);
ExcelColumnMapping excelColumnMapping = excelColumnMappingMap.get(field);
if (excelColumnMapping != null && excelColumnMapping.formula) {
td.formula = true;
}
}
protected void setPrompt(Td td, int index) {
if (filteredFields == null || filteredFields.isEmpty()) {
return;
}
Field field = filteredFields.get(index);
ExcelColumnMapping excelColumnMapping = excelColumnMappingMap.get(field);
if (excelColumnMapping != null && excelColumnMapping.promptContainer != null) {
td.promptContainer = excelColumnMapping.promptContainer;
}
}
private void setTdContent(Td td, Pair extends Class, ?> pair) {
Class fieldType = pair.getKey();
if (fieldType == NullType.class) {
return;
}
if (fieldType == Date.class) {
td.date = (Date) pair.getValue();
} else if (fieldType == LocalDateTime.class) {
td.localDateTime = (LocalDateTime) pair.getValue();
} else if (fieldType == LocalDate.class) {
td.localDate = (LocalDate) pair.getValue();
} else if (com.github.liaochong.myexcel.core.constant.File.class.isAssignableFrom(fieldType)) {
if (pair.getValue() instanceof File) {
td.file = (File) pair.getValue();
} else {
td.fileIs = (InputStream) pair.getValue();
}
} else {
td.content = String.valueOf(pair.getValue());
}
}
private void setTdContentType(Td td, Class fieldType) {
if (String.class == fieldType) {
return;
}
if (ReflectUtil.isNumber(fieldType)) {
td.tdContentType = ContentTypeEnum.DOUBLE;
return;
}
if (ReflectUtil.isDate(fieldType)) {
td.tdContentType = ContentTypeEnum.DATE;
return;
}
if (ReflectUtil.isBool(fieldType)) {
td.tdContentType = ContentTypeEnum.BOOLEAN;
return;
}
if (fieldType == DropDownList.class) {
td.tdContentType = ContentTypeEnum.DROP_DOWN_LIST;
return;
}
if (fieldType == NumberDropDownList.class) {
td.tdContentType = ContentTypeEnum.NUMBER_DROP_DOWN_LIST;
return;
}
if (fieldType == BooleanDropDownList.class) {
td.tdContentType = ContentTypeEnum.BOOLEAN_DROP_DOWN_LIST;
return;
}
if (td.content != null && fieldType == LinkUrl.class) {
td.tdContentType = ContentTypeEnum.LINK_URL;
setLinkTd(td);
return;
}
if (td.content != null && fieldType == LinkEmail.class) {
td.tdContentType = ContentTypeEnum.LINK_EMAIL;
setLinkTd(td);
return;
}
if ((td.file != null || td.fileIs != null) && fieldType == ImageFile.class) {
td.tdContentType = ContentTypeEnum.IMAGE;
}
}
private void setLinkTd(Td td) {
String[] splits = td.content.split(Constants.ARROW);
if (splits.length == 1) {
td.link = td.content;
} else {
td.content = splits[0];
td.link = splits[1];
}
}
protected void setTitles(List titles) {
if (this.titles == null) {
boolean hasTitle = titles.stream().anyMatch(StringUtil::isNotBlank);
if (hasTitle) {
this.titles = titles;
}
}
}
protected List getGroupFields(List preElectionFields, Class>[] groups) {
List> selectedGroupList = Objects.nonNull(groups) ? Arrays.stream(groups).filter(Objects::nonNull).collect(Collectors.toList()) : Collections.emptyList();
return preElectionFields.stream()
.filter(field -> (!field.isAnnotationPresent(ExcludeColumn.class) && !field.isAnnotationPresent(IgnoreColumn.class)) && ReflectUtil.isFieldSelected(selectedGroupList, field))
.sorted(ReflectUtil::sortFields)
.collect(Collectors.toList());
}
protected List getPreElectionFields(ClassFieldContainer classFieldContainer) {
if (Objects.nonNull(fieldDisplayOrder) && !fieldDisplayOrder.isEmpty()) {
this.selfAdaption();
return fieldDisplayOrder.stream()
.map(classFieldContainer::getFieldByName)
.collect(Collectors.toList());
}
List preElectionFields;
if (configuration.includeAllField) {
if (configuration.excludeParent) {
preElectionFields = classFieldContainer.getDeclaredFields();
} else {
preElectionFields = classFieldContainer.getFields();
}
} else {
if (configuration.excludeParent) {
preElectionFields = classFieldContainer.getDeclaredFields().stream()
.filter(field -> field.isAnnotationPresent(ExcelColumn.class))
.collect(Collectors.toList());
} else {
preElectionFields = classFieldContainer.getFieldsByAnnotation(ExcelColumn.class);
}
}
if (configuration.ignoreStaticFields) {
preElectionFields = preElectionFields.stream()
.filter(field -> !Modifier.isStatic(field.getModifiers()))
.collect(Collectors.toList());
}
return preElectionFields;
}
/**
* 展示字段order与标题title长度一致性自适应
*/
private void selfAdaption() {
if (titles == null || titles.isEmpty()) {
return;
}
if (fieldDisplayOrder.size() > titles.size()) {
for (int i = 0, size = fieldDisplayOrder.size() - titles.size(); i < size; i++) {
titles.add(null);
}
}
}
/**
* 获取需要被渲染的内容
*
* @param data 数据集合
* @param sortedFields 排序字段
* @param 泛型
* @return 结果集
*/
protected List>> getMultiRenderContent(T data, List sortedFields) {
List> convertResult = this.getOriginalRenderContent(data, sortedFields);
// 获取最大长度
int maxSize = convertResult.stream()
.filter(pair -> pair.getValue() instanceof List)
.map(pair -> ((List>) pair.getValue()).size())
.max(Integer::compareTo)
.orElse(1);
List>> result = new LinkedList<>();
for (int i = 0; i < maxSize; i++) {
List> row = new LinkedList<>();
for (Pair extends Class, ?> pair : convertResult) {
if (!(pair.getValue() instanceof List)) {
if (configuration.autoMerge) {
if (i == 0) {
pair.setRepeatSize(maxSize);
row.add(pair);
} else {
row.add(Pair.of(NullType.class, null));
}
} else {
row.add(pair);
}
continue;
}
List> list = (List>) pair.getValue();
if (list.size() > i) {
row.add((Pair) list.get(i));
} else {
row.add(Constants.NULL_PAIR);
}
}
result.add(row);
}
return result;
}
/**
* 获取需要被渲染的内容
*
* @param data 数据集合
* @param sortedFields 排序字段
* @param 泛型
* @return 结果集
*/
protected LinkedList> getOriginalRenderContent(T data, List sortedFields) {
return sortedFields.stream()
.map(field -> {
Pair extends Class, Object> value = WriteConverterContext.convert(field, data, convertContext);
if (value.getValue() != null) {
return value;
}
String defaultValue = defaultValueMap.get(field);
if (defaultValue != null) {
return Pair.of(String.class, defaultValue);
}
if (configuration.defaultValue != null) {
return Pair.of(String.class, configuration.defaultValue);
}
return value;
})
.collect(Collectors.toCollection(LinkedList::new));
}
protected List> assemblingMapContents(Map data) {
if (data == null || data.isEmpty()) {
return Collections.emptyList();
}
List> contents = new ArrayList<>(data.size());
if (fieldDisplayOrder == null) {
data.forEach((k, v) -> {
this.doAddToContents(contents, v);
});
} else {
for (String fieldName : fieldDisplayOrder) {
Object val = data.get(fieldName);
this.doAddToContents(contents, val);
}
}
return contents;
}
private void doAddToContents(List> contents, Object v) {
if (v instanceof Pair && ((Pair) v).getKey() instanceof Class) {
contents.add((Pair) v);
} else {
if (v == null) {
contents.add(Pair.of(NullType.class, null));
} else if (v.getClass() == LocalTime.class) {
contents.add(LocalTimeWriteConverter.doConvertDate((LocalTime) v, Constants.DEFAULT_LOCAL_TIME_FORMAT));
} else {
contents.add(Pair.of(v.getClass(), v));
}
}
}
private void setDateFormatForMap(Class> objectClass, Td td) {
if (objectClass == LocalDateTime.class || objectClass == Date.class) {
td.format = Constants.DEFAULT_DATE_TIME_FORMAT;
} else if (objectClass == LocalDate.class) {
td.format = Constants.DEFAULT_DATE_FORMAT;
}
}
}