├── LICENSE
├── README-zh.md
├── README.md
├── notes
├── pictures
├── applicant_example.png
├── auto_drawing_header.png
├── convering_data.png
├── simplest_example.png
└── wrong_data.png
├── pom.xml
└── src
├── main
├── java
│ └── cn
│ │ └── chenhuanming
│ │ └── octopus
│ │ ├── Octopus.java
│ │ ├── config
│ │ ├── AbstractConfigFactory.java
│ │ ├── AbstractXMLConfigFactory.java
│ │ ├── Config.java
│ │ ├── ConfigFactory.java
│ │ ├── Field.java
│ │ ├── FieldCellStyle.java
│ │ ├── ImportValidation.java
│ │ ├── XmlConfigFactory.java
│ │ └── annotation
│ │ │ ├── AnnotationConfigFactory.java
│ │ │ ├── Field.java
│ │ │ ├── Formatter.java
│ │ │ ├── Header.java
│ │ │ ├── Shareable.java
│ │ │ └── Sheet.java
│ │ ├── exception
│ │ ├── CanNotBeBlankException.java
│ │ ├── NotAllowValueException.java
│ │ ├── ParseException.java
│ │ ├── PatternNotMatchException.java
│ │ └── SheetNotFoundException.java
│ │ ├── formatter
│ │ ├── AbstractFormatter.java
│ │ ├── BigDecimalFormatter.java
│ │ ├── BooleanFormatter.java
│ │ ├── DateFormatter.java
│ │ ├── DefaultFormatterContainer.java
│ │ ├── DoubleFormatter.java
│ │ ├── FloatFormatter.java
│ │ ├── Formatter.java
│ │ ├── FormatterContainer.java
│ │ ├── IntegerFormatter.java
│ │ ├── LongFormatter.java
│ │ ├── ShortFormatter.java
│ │ └── StringFormatter.java
│ │ ├── model
│ │ ├── CellPosition.java
│ │ ├── CellPositions.java
│ │ ├── CheckedData.java
│ │ ├── DefaultCellPosition.java
│ │ └── WorkbookContext.java
│ │ ├── reader
│ │ ├── AbstractSheetReader.java
│ │ ├── CheckedSheetReader.java
│ │ ├── DefaultSheetReader.java
│ │ └── SheetReader.java
│ │ ├── util
│ │ ├── CellUtils.java
│ │ ├── ColorUtils.java
│ │ ├── MethodHandleUtil.java
│ │ ├── ReflectionUtils.java
│ │ ├── StringUtils.java
│ │ └── ValidationUtils.java
│ │ └── writer
│ │ ├── AbstractSheetWriter.java
│ │ ├── AutoSizeSheetWriter.java
│ │ ├── DefaultExcelWriter.java
│ │ ├── DefaultHeaderWriter.java
│ │ ├── DefaultSheetWriter.java
│ │ ├── ExcelWriter.java
│ │ ├── HeaderWriter.java
│ │ ├── NoHeaderSheetWriter.java
│ │ └── SheetWriter.java
└── resources
│ ├── log4j2.xml
│ └── octopus.xsd
└── test
├── java
└── cn
│ └── chenhuanming
│ └── octopus
│ ├── config
│ └── ConfigFactoryTest.java
│ ├── entity
│ ├── Address.java
│ ├── Applicants.java
│ ├── Company.java
│ └── Job.java
│ ├── example
│ ├── AddressExample.java
│ ├── AdvancedExample.java
│ ├── ApplicantExample.java
│ └── CompanyExample.java
│ ├── formatter
│ ├── AddressFormatter.java
│ ├── BigDecimalFormatter.java
│ └── WorkingFormatter.java
│ ├── model
│ └── CellPositionsTest.java
│ ├── test
│ ├── AbstractTest.java
│ └── AutoTest.java
│ └── util
│ └── Timer.java
└── resources
├── address.xml
├── applicants.xml
├── company.xml
├── company2.xml
├── company3.xml
├── import.xlsx
└── wrongCompany.xlsx
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 zerouwar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - [Octopus](#octopus)
5 | - [从Maven导入](#从maven导入)
6 | - [导出Excel](#导出excel)
7 | - [从最简单的例子开始](#从最简单的例子开始)
8 | - [自动绘制表头](#自动绘制表头)
9 | - [转换数据](#转换数据)
10 | - [导入Excel](#导入excel)
11 | - [导入校验数据](#导入校验数据)
12 | - [注解](#注解)
13 | - [Q&A](#qa)
14 | - [需要操作Apache POI?](#需要操作apache-poi)
15 | - [有建议或者想法?](#有建议或者想法)
16 |
17 |
18 | # Octopus
19 |
20 | [ ](https://github.com/mockito/mockito/blob/master/LICENSE)
21 | [](https://search.maven.org/search?q=g:cn.chenhuanming%20AND%20a:octopus)
22 |
23 | `Octopus` 是一个简单的java excel导入导出工具。目的是不用接触Apache POI的API就可以完成简单的Excel导出导入。
24 | 同时,可以自定义表格样式,导入检验数据和转换数据
25 |
26 | **不BB,直接上图**
27 |
28 | 
29 |
30 | ## 从Maven导入
31 | ```
32 |
33 | cn.chenhuanming
34 | octopus
35 | 1.1.4
36 |
37 | ```
38 |
39 | ## 导出Excel
40 |
41 | ### 从最简单的例子开始
42 | 我们从最简单的例子开始,导出一些地址数据
43 |
44 | 
45 |
46 | 定义`Address`类
47 |
48 | ```java
49 | @Data
50 | @AllArgsConstructor
51 | @NoArgsConstructor
52 | public class Address {
53 | private String city;
54 | private String detail;
55 | }
56 | ```
57 |
58 | 用XML文件定义怎么导出
59 |
60 | ```xml
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | ```
69 |
70 | `Root`标签的`class`属性,代表我们要导出的类全限定名
71 |
72 | 一个`Field`标签代表Excel里的一列数据
73 |
74 | `name`属性值就是`Address`里的属性名,实际上Octopus调用其getter方法获取值,所以要确保有getter方法
75 |
76 | `description`属性会被用来绘制表头
77 |
78 | 我们可以开始做最后一件事,编写Java代码
79 |
80 | ```java
81 | public class AddressExample {
82 | List
addresses;
83 |
84 | /**
85 | * preparing testing data
86 | */
87 | @Before
88 | public void prepare() {
89 | addresses = new ArrayList<>();
90 | DataFactory df = new DataFactory();
91 | for (int i = 0; i < df.getNumberBetween(5, 10); i++) {
92 | addresses.add(new Address(df.getCity(), df.getAddress()));
93 | }
94 | }
95 |
96 | @Test
97 | public void export() throws Exception {
98 |
99 | //导出文件的is
100 | String rootPath = this.getClass().getClassLoader().getResource("").getPath();
101 | FileOutputStream os = new FileOutputStream(rootPath + "/address.xlsx");
102 |
103 | //从XML读取配置,建议单例模式
104 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("address.xml");
105 | Config config = new XmlConfigFactory(is).getConfig();
106 |
107 | //用Octopus,只需要一行代码
108 | Octopus.writeOneSheet(os, config, "address", addresses);
109 | }
110 | }
111 | ```
112 |
113 | 这是一个完整的单元测试,可以在[单测测试路径](https://github.com/zerouwar/Octopus/blob/master/src/test/java/cn/chenhuanming/octopus/example/AddressExample.java)找到它
114 |
115 | ### 自动绘制表头
116 | Octopus支持导出复杂对象时自动绘制表头
117 |
118 | 这次我们来导出一些公司数据,这里是`Company`类
119 |
120 | ```java
121 | @Data
122 | @AllArgsConstructor
123 | @NoArgsConstructor
124 | public class Company {
125 | private String name;
126 | private Address address;
127 | }
128 | ```
129 |
130 | 然后我们创建一个 *company.xml* 配置文件
131 |
132 | ```xml
133 |
134 |
135 |
136 |
139 |
140 |
144 |
145 |
146 | ```
147 |
148 | 我们用`Header`元素代表要导出`Company`的一个复杂属性,同时设置字体颜色是红色
149 |
150 | Java代码基本跟之前的一样
151 |
152 | ```java
153 | public class CompanyExample {
154 | List companies;
155 |
156 | /**
157 | * preparing testing data
158 | */
159 | @Before
160 | public void prepare() {
161 | companies = new ArrayList<>();
162 | DataFactory df = new DataFactory();
163 | for (int i = 0; i < df.getNumberBetween(5, 10); i++) {
164 | companies.add(new Company(df.getBusinessName(), new Address(df.getCity(), df.getAddress())));
165 | }
166 | }
167 |
168 | @Test
169 | public void export() throws Exception {
170 |
171 | //导出文件的is
172 | String rootPath = this.getClass().getClassLoader().getResource("").getPath();
173 | FileOutputStream os = new FileOutputStream(rootPath + "/company.xlsx");
174 |
175 | //从XML读取配置,建议单例模式
176 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("company.xml");
177 | Config config = new XmlConfigFactory(is).getConfig();
178 |
179 | Octopus.writeOneSheet(os, config, "company", companies);
180 | }
181 | }
182 | ```
183 |
184 | 最后是导出的Excel文件
185 |
186 | 
187 |
188 | Octopus可以处理更复杂的数据,你可以在`cn.chenhuanming.octopus.example.ApplicantExample`查看这个更复杂的例子
189 |
190 | 
191 |
192 | ### 转换数据
193 | 有时你想转换导出的数据。例如,在上一个例子中,我们不想导出整个`Address`对象,把它当做一个一列数据导出
194 |
195 | 我们所需要做的只是实现一个`Formatter`
196 |
197 | ```java
198 | public class AddressFormatter implements Formatter {
199 | @Override
200 | public String format(Address address) {
201 | return address.getCity() + "," + address.getDetail();
202 | }
203 |
204 | @Override
205 | public Address parse(String str) {
206 | String[] split = str.split(",");
207 | if (split.length != 2) {
208 | return null;
209 | }
210 | return new Address(split[0], split[1]);
211 | }
212 | }
213 | ```
214 |
215 | `parse`方法用于导入Excel,只要关注`format`方法。这里接受一个Address对象,返回一个字符串。
216 |
217 | 最后,配置`AddressFormatter`到XML文件
218 |
219 | ```xml
220 |
223 |
224 |
227 | ```
228 |
229 | 最后导出的结果
230 |
231 | 
232 |
233 |
234 | ## 导入Excel
235 | 我们直接拿上一个例子的导出结果来演示导入,共用同一个`Config`对象,直接编写导入的代码
236 |
237 | ```java
238 | SheetReader importData = Octopus.readFirstSheet(fis, config, new DefaultCellPosition(1, 0));
239 |
240 | for (Company company : importData) {
241 | System.out.println(company);
242 | }
243 | ```
244 |
245 | 在控制台可以看到打印导入结果,可以看到,之前的`AddressFormatter`也完成了数据的转换工作
246 |
247 | ```
248 | Company(name=Graham Motor Services, address=Address(city=Monroe, detail=666 Bonnair Ave))
249 | Company(name=Social Circle Engineering, address=Address(city=Fort Gaines, detail=956 Third Ridge))
250 | Company(name=Enigma Cafe, address=Address(city=Mcdonough, detail=1278 Midway Trail))
251 | Company(name=Hapeville Studios, address=Address(city=Riceboro, detail=823 Tuscarawas Blvd))
252 | Company(name=Thalman Gymnasium, address=Address(city=Ebenezer, detail=1225 Blackwood Avenue))
253 | Company(name=Sparks Pro Services, address=Address(city=Darien, detail=1362 Woodlawn Lane))
254 | Company(name=Toccoa Development, address=Address(city=Ridgeville, detail=1790 Lawn Ave))
255 | ```
256 |
257 | ### 导入校验数据
258 | 有时候我们对导入的数据有一定的要求,Octopus提供简单的数据校验配置
259 |
260 | 首先给我们的`Company`增加一个`status`属性,只能是 *good*,*bad*和*closed* 三个值其中一个,同时`name`不可以为空,看一下XML配置文件
261 |
262 | ```xml
263 |
264 |
266 |
267 |
268 |
272 |
273 |
277 |
278 |
281 |
282 |
283 |
284 | ```
285 |
286 | 这是我们要导入的Excel,可以看到里面有非法数据
287 |
288 | 
289 |
290 | 看一下怎么编写Java代码
291 |
292 | ```java
293 | @Test
294 | public void importCheckedData() throws IOException, InvalidFormatException {
295 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("wrongCompany.xlsx");
296 |
297 | Config config = new XmlConfigFactory(this.getClass().getClassLoader().getResourceAsStream("company3.xml")).getConfig();
298 |
299 | final SheetReader> sheetReader = Octopus.readFirstSheetWithValidation(is,config,new DefaultCellPosition(1,0));
300 |
301 | for (CheckedData checkedData : sheetReader) {
302 | System.out.println(checkedData);
303 | }
304 | }
305 | ```
306 |
307 | 这里我们调用`Octopus.readFirstSheetWithValidation`,获取带校验结果的`SheetReader`,看一下导入的结果
308 |
309 | ```
310 | CheckedData(data=Company(name=Graham Motor Services, address=Address(city=Monroe, detail=666 Bonnair Ave), status=good), exceptions=[])
311 | CheckedData(data=Company(name=Social Circle Engineering, address=Address(city=Fort Gaines, detail=956 Third Ridge), status=null), exceptions=[cn.chenhuanming.octopus.exception.NotAllowValueException])
312 | CheckedData(data=Company(name=null, address=Address(city=Mcdonough, detail=1278 Midway Trail), status=null), exceptions=[cn.chenhuanming.octopus.exception.CanNotBeBlankException, cn.chenhuanming.octopus.exception.NotAllowValueException])
313 | ```
314 |
315 | 可以看到每一个`CheckData`有一个`data`属性和一个`exceptions`列表。
316 | 这个异常列表存放着导入时每一个单元格可能出现的校验错误,异常类型都是`ParseException`
317 |
318 | 除了`is-blankable`和`options`,还可以通过`regex`配置正则表达式检查。当校验错误时,会抛出对应的`ParseException`子类
319 |
320 | * `is-blankable`:抛出 `CanNotBeBlankException`
321 | * `options`:抛出 `NotAllowValueException`
322 | * `regex`:抛出 `PatternNotMatchException`
323 |
324 | 你通过这些异常来进行跟进一步的处理。如果上面三种校验方式不能满足需求,在`Formatter`的`parse`抛出自定义的`ParseException`。Octopus会捕获它们放到`exceptions`列表中,并自动把单元格位置和你的配置内容塞到`ParseException`中
325 |
326 | ***以上代码都可以在测试路径`cn.chenhuanming.octopus.example`找到,通过这些例子可以感受下Octopus的魅力***
327 |
328 | ## 注解
329 | 我们推荐使用 xml 方式配置导入导出格式,因为 xml 配置与类不相耦合,相比注解更加灵活。
330 | 不过有时使用方可能不太在意灵活性,希望把配置和数据类放在一起,那么可以使用注解版本。
331 | 注解与 xml 文件的使用方法类似,主要有 `@Sheet`,`@Formatter`,`@Header`,`@Field` 这几个。
332 | - `@Sheet` 注解在数据类上,可选 `formatters` 属性, 表示全局转换器
333 | - `@Formatter` 作为 `@Sheet` 的 formatters 属性值,表示一个转换器
334 | - `@Header` 注解在数据类的字段上,表示该字段是一个复合字段
335 | - `@Field` 注解在数据类的字段上,表示该字段是一个单一字段
336 |
337 | 注解的的属性取值请参考 xml 文件。下面是一个数据类的注解示例:
338 | ```java
339 | @Sheet(formatters = {
340 | @Formatter(target = BigDecimal.class, format = BigDecimalFormatter.class),
341 | })
342 | public class Applicants {
343 | @Field(description = "Value", color = "#74f441")
344 | private int id;
345 | @Field(description = "Name", fontSize = 20, border = "0,2,0,2", borderColor = ",#4242f4,,#4242f4")
346 | private String name;
347 | @Header(description = "Job", headerColor = "#4286f4")
348 | private Job job;
349 | @Field(description = "Entry Date", dateFormat = "yyyy-MM-dd")
350 | private Date entryDate;
351 | @Field(description = "Working/Leaved", options = "Working|Leaved",
352 | formatter = cn.chenhuanming.octopus.formatter.WorkingFormatter.class, color = "#42f4b9")
353 | private boolean working = true;
354 | }
355 |
356 | ```
357 | 使用方法:
358 | ```java
359 | // 构造方法必须传入一个带有 @Sheet 注解的类
360 | Config config = new AnnotationConfigFactory(Applicants.class).getConfig();
361 | // ... 使用 config 就像 xml 的方式一样
362 | ```
363 |
364 | ## Q&A
365 |
366 | ### 需要操作Apache POI?
367 | `Octopus`类可以提供一行代码式的API,让你不用碰Apache POI的API。但是如果你确实需要用到Apache POI,可以先看一下Octopus核心类`SheetWriter`和`SheetReader`代码。我在设计的时候尽量考虑扩展,并且完全基于接口实现,实在不行可以选择继承重写,属性基本都是protected,或者直接自己实现接口
368 |
369 | ### 有建议或者问题?
370 | 提Issue或者email我**chenhuanming.cn@gmail.com**
371 |
372 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | - [Octopus](#octopus)
4 | - [Import from Maven](#import-from-maven)
5 | - [Export Excel](#export-excel)
6 | - [Simplest Example](#simplest-example)
7 | - [Auto Drawing Header](#auto-drawing-header)
8 | - [Converting Data](#converting-data)
9 | - [Import Excel](#import-excel)
10 | - [Data Validation](#data-validation)
11 | - [Annotation](#annotation)
12 | - [Q&A](#qa)
13 | - [Need Apache POI?](#need-apache-poi)
14 | - [Have Advice or idea?](#have-advice-or-idea)
15 |
16 |
17 | [跳去中文版](https://github.com/zerouwar/Octopus/blob/master/README-zh.md)
18 |
19 | # Octopus
20 |
21 | [ ](https://github.com/mockito/mockito/blob/master/LICENSE)
22 | [](https://search.maven.org/search?q=g:cn.chenhuanming%20AND%20a:octopus)
23 |
24 | **Simple** Java excel import and export tool.You can finish work with some object instead of annoying `Apache POI` API.
25 |
26 | Besides,some additional function like customizing cell style,converting data during export,validating data during import,etc.
27 |
28 | ***A complicated excel exporting with Octopus***
29 | 
30 |
31 | ## Import from Maven
32 |
33 | ```xml
34 |
35 | cn.chenhuanming
36 | octopus
37 | 1.1.4
38 |
39 | ```
40 |
41 |
42 | ## Export Excel
43 |
44 | ### Simplest Example
45 | Let's begin with a simple example,exporting some address information
46 |
47 | 
48 |
49 | Define class `Address`
50 |
51 | ```java
52 | @Data
53 | @AllArgsConstructor
54 | public class Address {
55 | private String city;
56 | private String detail;
57 | }
58 | ```
59 |
60 | We need a xml config to define what and how to export
61 |
62 | ```xml
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | ```
71 |
72 | Element `Field` represents one column in excel.
73 | Attribute `name` is the field name of class `Address`.Attribute `description` is the column header in excel
74 |
75 | Actually `Octopus` get value from getter method,so make sure there is a getter method.
76 |
77 | Last,writing code to export
78 |
79 | ```java
80 | public class AddressExample {
81 | List addresses;
82 |
83 | /**
84 | * preparing testing data
85 | */
86 | @Before
87 | public void prepare() {
88 | addresses = new ArrayList<>();
89 | DataFactory df = new DataFactory();
90 | for (int i = 0; i < df.getNumberBetween(5, 10); i++) {
91 | addresses.add(new Address(df.getCity(), df.getAddress()));
92 | }
93 | }
94 |
95 | @Test
96 | public void export() throws Exception {
97 |
98 | //where to export
99 | String rootPath = this.getClass().getClassLoader().getResource("").getPath();
100 | FileOutputStream os = new FileOutputStream(rootPath + "/address.xlsx");
101 |
102 | //get config from xml file.Singleton pattern is recommending
103 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("address.xml");
104 | Config config = new XmlConfigFactory(is).getConfig();
105 |
106 | //just one line of code with Octopus facade
107 | Octopus.writeOneSheet(os, config, "address", addresses);
108 | }
109 | }
110 | ```
111 |
112 | This is a complete unit test.You can find it in [test classpath](https://github.com/zerouwar/Octopus/blob/master/src/test/java/cn/chenhuanming/octopus/example/AddressExample.java)
113 |
114 | ### Auto Drawing Header
115 | You can exporting complex structure data.`Octopus` will drawing header automatically.
116 |
117 | Now,we will export some company information.Here is the class `Company`
118 |
119 | ```java
120 | @Data
121 | @AllArgsConstructor
122 | @NoArgsConstructor
123 | public class Company {
124 | private String name;
125 | private Address address;
126 | }
127 | ```
128 |
129 | XML config file is different from first example.
130 |
131 | ```xml
132 |
133 |
134 |
135 |
138 |
139 |
143 |
144 |
145 | ```
146 |
147 | This time we set font color of column name to red in excel.
148 | Element `Header` represents a complicated field in company object,
149 |
150 | The Java code is almost the same as before
151 |
152 | ```java
153 | public class CompanyExample {
154 | List companies;
155 |
156 | /**
157 | * preparing testing data
158 | */
159 | @Before
160 | public void prepare() {
161 | companies = new ArrayList<>();
162 | DataFactory df = new DataFactory();
163 | for (int i = 0; i < df.getNumberBetween(5, 10); i++) {
164 | companies.add(new Company(df.getBusinessName(), new Address(df.getCity(), df.getAddress())));
165 | }
166 | }
167 |
168 | @Test
169 | public void export() throws Exception {
170 |
171 | //where to export
172 | String rootPath = this.getClass().getClassLoader().getResource("").getPath();
173 | FileOutputStream os = new FileOutputStream(rootPath + "/company.xlsx");
174 |
175 | //get config from xml file.Singleton pattern is recommending
176 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("company.xml");
177 | Config config = new XmlConfigFactory(is).getConfig();
178 |
179 | Octopus.writeOneSheet(os, config, "company", companies);
180 | }
181 | }
182 | ```
183 |
184 | Following is the exported excel
185 |
186 | 
187 |
188 | Octopus can handle more complicated data,you can check this from `cn.chenhuanming.octopus.example.ApplicantExample` in test classpath
189 |
190 | 
191 |
192 | ### Converting Data
193 | Sometimes you want to convert data during export.
194 | For example,in previous example,we want export address as one column.
195 |
196 | We need to define a `Formatter`
197 |
198 | ```java
199 | public class AddressFormatter implements Formatter {
200 | @Override
201 | public String format(Address address) {
202 | return address.getCity() + "," + address.getDetail();
203 | }
204 |
205 | /**
206 | * called during import
207 | */
208 | @Override
209 | public Address parse(String str) {
210 | String[] split = str.split(",");
211 | if (split.length != 2) {
212 | return null;
213 | }
214 | return new Address(split[0], split[1]);
215 | }
216 | }
217 | ```
218 |
219 | `parse` method is used in importing excel,so just pay attention on `format
220 | method.It accepts a `Address` object and returns a `String` object.
221 |
222 | At last,put this into XML config file
223 |
224 | ```xml
225 |
228 |
229 |
232 | ```
233 |
234 | Exporting excel will be like this
235 |
236 | 
237 |
238 | ## Import Excel
239 | Let's how to import a excel.Reuse xml config file in [Converting Data](#Converting Data)
240 |
241 | ```java
242 | SheetReader importData = Octopus.readFirstSheet(fis, config, new DefaultCellPosition(1, 0));
243 |
244 | for (Company company : importData) {
245 | System.out.println(company);
246 | }
247 | ```
248 |
249 | Check terminal.See,`AddressFormatter` works!
250 |
251 | ```
252 | Company(name=Graham Motor Services, address=Address(city=Monroe, detail=666 Bonnair Ave))
253 | Company(name=Social Circle Engineering, address=Address(city=Fort Gaines, detail=956 Third Ridge))
254 | Company(name=Enigma Cafe, address=Address(city=Mcdonough, detail=1278 Midway Trail))
255 | Company(name=Hapeville Studios, address=Address(city=Riceboro, detail=823 Tuscarawas Blvd))
256 | Company(name=Thalman Gymnasium, address=Address(city=Ebenezer, detail=1225 Blackwood Avenue))
257 | Company(name=Sparks Pro Services, address=Address(city=Darien, detail=1362 Woodlawn Lane))
258 | Company(name=Toccoa Development, address=Address(city=Ridgeville, detail=1790 Lawn Ave))
259 | ```
260 |
261 | ### Data Validation
262 | SomeTimes we have restrictions for importing data.Octopus provides simple validation config.
263 |
264 | we add a property `status` for class `Company`.
265 |
266 | ```java
267 | @Data
268 | @AllArgsConstructor
269 | @NoArgsConstructor
270 | public class Company {
271 | private String name;
272 | private Address address;
273 | private String status;
274 | }
275 | ```
276 |
277 | We want restrictions during import.
278 |
279 | * Value of `status` is only one of *good*,*bad* and *closed*.
280 | * `name` can not be empty.
281 |
282 | ```xml
283 |
284 |
285 |
286 |
290 |
291 |
295 |
296 |
299 |
300 |
301 |
302 | ```
303 |
304 | Here is the excel we will import,there are three wrong.
305 |
306 | 
307 |
308 | Java code
309 |
310 | ```java
311 | @Test
312 | public void importCheckedData() throws IOException, InvalidFormatException {
313 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("wrongCompany.xlsx");
314 |
315 | Config config = new XmlConfigFactory(this.getClass().getClassLoader().getResourceAsStream("company3.xml")).getConfig();
316 |
317 | final SheetReader> sheetReader = Octopus.readFirstSheetWithValidation(is,config,new DefaultCellPosition(1,0));
318 |
319 | for (CheckedData checkedData : sheetReader) {
320 | System.out.println(checkedData);
321 | }
322 | }
323 | ```
324 |
325 | We call `Octopus.readFirstSheetWithValidation` and get `SheetReader` which implements `Iterable`.Check the terminal
326 |
327 | ```
328 | CheckedData(data=Company(name=Graham Motor Services, address=Address(city=Monroe, detail=666 Bonnair Ave), status=good), exceptions=[])
329 | CheckedData(data=Company(name=Social Circle Engineering, address=Address(city=Fort Gaines, detail=956 Third Ridge), status=null), exceptions=[cn.chenhuanming.octopus.exception.NotAllowValueException])
330 | CheckedData(data=Company(name=null, address=Address(city=Mcdonough, detail=1278 Midway Trail), status=null), exceptions=[cn.chenhuanming.octopus.exception.CanNotBeBlankException, cn.chenhuanming.octopus.exception.NotAllowValueException])
331 | ```
332 |
333 | `CheckData` has `data` and `exceptions`.In `exceptions`,it saves all exceptions of every cell occurred during import.All of them are subclass of `ParseException`.
334 |
335 | Besides `is-blankable` and `options`,you can apply regular expression validation through `regex`.When validate fails,it will throw corresponding `ParseException`
336 |
337 | * `is-blankable`:throws `CanNotBeBlankException`
338 | * `options`:throws `NotAllowValueException`
339 | * `regex`:throws `PatternNotMatchException`
340 |
341 | You can handle more with these exceptions.If it is not satisfied with you,throws `ParseException` in `paese` method of `Formatter`.
342 | Octopus will catch them,put into `exceptions` and fill with position of cell and config info at the same time.
343 |
344 | ***All example could be found at `cn.chenhuanming.octopus.example`,you can run and check these examples***
345 |
346 | ## Annotation
347 | We recommend using XML to configure the import and export, because XML configuration is not coupled with classes and is more flexible than annotations.
348 | Sometimes, you may be less concerned about flexibility and want to put configuration and data classes together, so annotation can be used.
349 | Annotations are similar to XML files, There are `@Sheet`,`@Formatter`,`@Header`,`@Field`:
350 | - `@Sheet` on class, with a optional `formatters` attribute represents the global formatter
351 | - `@Formatter` represents a formatter as the `formatters` attribute value of `@Sheet`
352 | - `@Header` on a field of the data class, indicating that the field is a composite field
353 | - `@Field` on a field of the data class, indicating that the field is a single field
354 |
355 | Please refer to the XML file for the attribute values of the annotations(show as above examples). The following is an annotation example of a data class:
356 | ```java
357 | @Sheet(formatters = {
358 | @Formatter(target = BigDecimal.class, format = BigDecimalFormatter.class),
359 | })
360 | public class Applicants {
361 | @Field(description = "Value", color = "#74f441")
362 | private int id;
363 | @Field(description = "Name", fontSize = 20, border = "0,2,0,2", borderColor = ",#4242f4,,#4242f4")
364 | private String name;
365 | @Header(description = "Job", headerColor = "#4286f4")
366 | private Job job;
367 | @Field(description = "Entry Date", dateFormat = "yyyy-MM-dd")
368 | private Date entryDate;
369 | @Field(description = "Working/Leaved", options = "Working|Leaved",
370 | formatter = cn.chenhuanming.octopus.formatter.WorkingFormatter.class, color = "#42f4b9")
371 | private boolean working = true;
372 | }
373 |
374 | ```
375 | Usage:
376 | ```java
377 | Config config = new AnnotationConfigFactory(Applicants.class).getConfig();
378 | // ... use config ...
379 | ```
380 |
381 | ## Q&A
382 |
383 | ### Need Apache POI?
384 | `Octopus` provides one-code-api,get rid of Apache API。If you really need Apache POI,check core class `SheetWriter`和`SheetReader`
385 |
386 | ### Have Advice or Question?
387 | New a issue or email **chenhuanming.cn@gmail.com**
388 |
389 |
--------------------------------------------------------------------------------
/notes:
--------------------------------------------------------------------------------
1 | 每一个field属性:
2 | height
3 | width
4 |
5 | 分配height行,width列
6 |
7 | 如果不是数据节点(贪婪),自身占用第一行,分配给孩子height-1行,width列,等孩子用完,占用可用的最大空间,width不变,只要知道孩子用了多少行就好
8 | 数据节点,根据自身占用空间
--------------------------------------------------------------------------------
/pictures/applicant_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerouwar/Octopus/d07183a8515e205c36c92c6832c2d884f7a902ec/pictures/applicant_example.png
--------------------------------------------------------------------------------
/pictures/auto_drawing_header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerouwar/Octopus/d07183a8515e205c36c92c6832c2d884f7a902ec/pictures/auto_drawing_header.png
--------------------------------------------------------------------------------
/pictures/convering_data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerouwar/Octopus/d07183a8515e205c36c92c6832c2d884f7a902ec/pictures/convering_data.png
--------------------------------------------------------------------------------
/pictures/simplest_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerouwar/Octopus/d07183a8515e205c36c92c6832c2d884f7a902ec/pictures/simplest_example.png
--------------------------------------------------------------------------------
/pictures/wrong_data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerouwar/Octopus/d07183a8515e205c36c92c6832c2d884f7a902ec/pictures/wrong_data.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | cn.chenhuanming
8 | octopus
9 | 1.1.5
10 |
11 | Octopus
12 | A simple excel import and export tool
13 | https://github.com/zerouwar/Octopus
14 |
15 |
16 | 4.1.2
17 | 1.16.14
18 | 1.7.25
19 | 2.13.3
20 |
21 |
22 |
23 |
24 |
25 | org.apache.poi
26 | poi
27 | ${poi-version}
28 |
29 |
30 | org.apache.poi
31 | poi-ooxml
32 | ${poi-version}
33 |
34 |
35 |
36 | org.projectlombok
37 | lombok
38 | ${lombok-version}
39 | true
40 |
41 |
42 |
43 | org.fluttercode.datafactory
44 | datafactory
45 | 0.8
46 | jar
47 | test
48 |
49 |
50 | junit
51 | junit
52 | 4.13.1
53 | test
54 |
55 |
56 |
57 |
58 | org.slf4j
59 | slf4j-api
60 | ${slf4j-version}
61 |
62 |
63 | org.apache.logging.log4j
64 | log4j-api
65 | ${log4j2-version}
66 | true
67 |
68 |
69 | org.apache.logging.log4j
70 | log4j-core
71 | ${log4j2-version}
72 | true
73 |
74 |
75 | org.apache.logging.log4j
76 | log4j-slf4j-impl
77 | ${log4j2-version}
78 | true
79 |
80 |
81 |
82 |
83 |
84 |
85 | ossrh
86 | https://oss.sonatype.org/content/repositories/snapshots
87 |
88 |
89 | ossrh
90 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
91 |
92 |
93 |
94 |
95 |
96 |
97 | org.apache.maven.plugins
98 | maven-compiler-plugin
99 |
100 | 1.7
101 | 1.7
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | release
110 |
111 |
112 |
113 | org.sonatype.plugins
114 | nexus-staging-maven-plugin
115 | 1.6.7
116 | true
117 |
118 | ossrh
119 | https://oss.sonatype.org/
120 | true
121 |
122 |
123 |
124 | org.apache.maven.plugins
125 | maven-source-plugin
126 | 2.2.1
127 |
128 |
129 | attach-sources
130 |
131 | jar-no-fork
132 |
133 |
134 |
135 |
136 |
137 | org.apache.maven.plugins
138 | maven-javadoc-plugin
139 | 2.9.1
140 |
141 |
142 | attach-javadocs
143 |
144 | jar
145 |
146 |
147 |
148 |
149 |
150 | org.apache.maven.plugins
151 | maven-gpg-plugin
152 | 1.5
153 |
154 |
155 | sign-artifacts
156 | verify
157 |
158 | sign
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 | MIT License
170 | https://github.com/zerouwar/Octopus/blob/master/LICENSE
171 | repo
172 |
173 |
174 |
175 |
176 | https://github.com/zerouwar/Octopus.git
177 | https://github.com/zerouwar/Octopus.git
178 | chenhuanming.cn@gmail.com
179 |
180 |
181 |
182 |
183 | zerouwar
184 | chenhuanming.cn@gmail.com
185 | http://github.chenhuanming.cn
186 |
187 |
188 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/Octopus.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus;
2 |
3 | import cn.chenhuanming.octopus.config.Config;
4 | import cn.chenhuanming.octopus.config.ConfigFactory;
5 | import cn.chenhuanming.octopus.config.XmlConfigFactory;
6 | import cn.chenhuanming.octopus.config.annotation.AnnotationConfigFactory;
7 | import cn.chenhuanming.octopus.exception.SheetNotFoundException;
8 | import cn.chenhuanming.octopus.model.CellPosition;
9 | import cn.chenhuanming.octopus.model.CheckedData;
10 | import cn.chenhuanming.octopus.reader.CheckedSheetReader;
11 | import cn.chenhuanming.octopus.reader.DefaultSheetReader;
12 | import cn.chenhuanming.octopus.reader.SheetReader;
13 | import cn.chenhuanming.octopus.writer.DefaultExcelWriter;
14 | import cn.chenhuanming.octopus.writer.DefaultSheetWriter;
15 | import cn.chenhuanming.octopus.writer.ExcelWriter;
16 | import cn.chenhuanming.octopus.writer.SheetWriter;
17 | import org.apache.poi.EncryptedDocumentException;
18 | import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
19 | import org.apache.poi.ss.usermodel.Sheet;
20 | import org.apache.poi.ss.usermodel.Workbook;
21 | import org.apache.poi.ss.usermodel.WorkbookFactory;
22 | import org.apache.poi.xssf.streaming.SXSSFWorkbook;
23 |
24 | import java.io.IOException;
25 | import java.io.InputStream;
26 | import java.io.OutputStream;
27 | import java.util.Collection;
28 |
29 | /**
30 | * Excel operate facade.
31 | *
32 | * @author chenhuanming
33 | * Created at 2018/12/20
34 | * @see SheetReader
35 | * @see ExcelWriter
36 | * @see SheetWriter
37 | * @see Config
38 | * @see XmlConfigFactory
39 | * @see AnnotationConfigFactory
40 | */
41 | public final class Octopus {
42 | /**
43 | * Create excel workbook and write a sheet data.
44 | *
45 | * @param os write out this workbook to this output stream
46 | * @param config get config through @{{@link ConfigFactory}}
47 | * @param sheetName name of sheet
48 | * @param data data
49 | * @param class type of data
50 | * @throws IOException when writing excel file failed
51 | */
52 | public static void writeOneSheet(OutputStream os, Config config, String sheetName, Collection data) throws IOException {
53 | ExcelWriter writer = new DefaultExcelWriter(new SXSSFWorkbook(), os);
54 | writer.write(sheetName, new DefaultSheetWriter(config), data);
55 | writer.close();
56 | }
57 |
58 | /**
59 | * Open excel workbook and read data from the first sheet.
60 | */
61 | public static SheetReader readFirstSheet(InputStream is, Config config, CellPosition startPosition) throws IOException, InvalidFormatException, EncryptedDocumentException {
62 | return readOneSheet(is, 0, config, startPosition);
63 | }
64 |
65 | /**
66 | * Open excel workbook and read data from the special index sheet.
67 | *
68 | * @param is excel file
69 | * @param index position,starting from 0
70 | * @param config get config through @{{@link ConfigFactory}}
71 | * @param startPosition where to start read,starts from 0
72 | * @param class type of data
73 | * @return sheet reader
74 | * @throws IOException if an error occurs while reading the data
75 | * @throws InvalidFormatException if the contents of the file cannot be parsed into a {@link Workbook}
76 | * @throws EncryptedDocumentException If the workbook is password protected
77 | */
78 | public static SheetReader readOneSheet(InputStream is, int index, Config config, CellPosition startPosition) throws IOException, InvalidFormatException, EncryptedDocumentException {
79 | Workbook workbook = WorkbookFactory.create(is);
80 | return new DefaultSheetReader(workbook.getSheetAt(index), config, startPosition);
81 | }
82 |
83 | /**
84 | * Read the sheet data with the specified name.
85 | */
86 | public static SheetReader readBySheetName(InputStream is, String sheetName, Config config, CellPosition startPosition) throws IOException, InvalidFormatException, EncryptedDocumentException, SheetNotFoundException {
87 | Workbook workbook = WorkbookFactory.create(is);
88 | for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
89 | Sheet sheet = workbook.getSheetAt(i);
90 | if (sheet.getSheetName().equals(sheetName)) {
91 | return new DefaultSheetReader<>(sheet, config, startPosition);
92 | }
93 | }
94 | throw new SheetNotFoundException("not found:" + sheetName);
95 | }
96 |
97 | /**
98 | * Read and validate data from the special index sheet.
99 | */
100 | public static SheetReader> readOneSheetWithValidation(InputStream is, int index, Config config, CellPosition startPosition) throws IOException, InvalidFormatException, EncryptedDocumentException {
101 | Workbook workbook = WorkbookFactory.create(is);
102 | return new CheckedSheetReader<>(workbook.getSheetAt(index), config, startPosition);
103 | }
104 |
105 | public static SheetReader> readFirstSheetWithValidation(InputStream is, Config config, CellPosition startPosition) throws IOException, InvalidFormatException, EncryptedDocumentException {
106 | return readOneSheetWithValidation(is, 0, config, startPosition);
107 | }
108 |
109 | public static SheetReader> readBySheetNameWithValidation(InputStream is, String sheetName, Config config, CellPosition startPosition) throws IOException, InvalidFormatException, EncryptedDocumentException, SheetNotFoundException {
110 | Workbook workbook = WorkbookFactory.create(is);
111 | for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
112 | Sheet sheet = workbook.getSheetAt(i);
113 | if (sheet.getSheetName().equals(sheetName)) {
114 | return new CheckedSheetReader<>(sheet, config, startPosition);
115 | }
116 | }
117 | throw new SheetNotFoundException("not found:" + sheetName);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/AbstractConfigFactory.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config;
2 |
3 | import cn.chenhuanming.octopus.util.ColorUtils;
4 | import cn.chenhuanming.octopus.util.StringUtils;
5 | import org.apache.poi.ss.usermodel.BorderStyle;
6 |
7 | import java.awt.*;
8 |
9 | /**
10 | * @author chenhuanming
11 | * Created at 2018/12/10
12 | */
13 | public abstract class AbstractConfigFactory implements ConfigFactory {
14 | protected BorderStyle[] convertBorder(String border) {
15 | BorderStyle[] result = new BorderStyle[4];
16 | String[] split = border.split(",");
17 | for (int i = 0; i < split.length; i++) {
18 | short val = Short.parseShort(split[i]);
19 | BorderStyle style = BorderStyle.valueOf(val);
20 | result[i] = style;
21 | }
22 | return result;
23 | }
24 |
25 | protected Color[] convertBorderColor(String borderColor) {
26 | Color[] result = new Color[4];
27 | String[] split = borderColor.split(",");
28 |
29 | for (int i = 0; i < split.length; i++) {
30 | String c = split[i];
31 | Color color = ColorUtils.hex2Rgb(StringUtils.isEmpty(c) ? "#000000" : c);
32 | result[i] = color;
33 | }
34 | return result;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/AbstractXMLConfigFactory.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config;
2 |
3 |
4 | import cn.chenhuanming.octopus.util.StringUtils;
5 | import org.apache.poi.util.IOUtils;
6 | import org.w3c.dom.NamedNodeMap;
7 | import org.w3c.dom.Node;
8 |
9 | import javax.xml.XMLConstants;
10 | import javax.xml.transform.Source;
11 | import javax.xml.transform.stream.StreamSource;
12 | import javax.xml.validation.Schema;
13 | import javax.xml.validation.SchemaFactory;
14 | import javax.xml.validation.Validator;
15 | import java.io.ByteArrayInputStream;
16 | import java.io.File;
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.net.URL;
20 |
21 | /**
22 | * @author chenhuanming
23 | * Created at 2018/12/17
24 | */
25 | public abstract class AbstractXMLConfigFactory extends AbstractConfigFactory {
26 |
27 | protected final ByteArrayInputStream is;
28 |
29 |
30 | public AbstractXMLConfigFactory(InputStream is) {
31 | try {
32 | this.is = new ByteArrayInputStream(IOUtils.toByteArray(is));
33 | } catch (IOException e) {
34 | throw new IllegalArgumentException(e);
35 | }
36 | }
37 |
38 | protected void validateXML(Source source, String schemaUri) throws Exception {
39 | is.reset();
40 | SchemaFactory schemaFactory = SchemaFactory
41 | .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
42 | Schema schema = null;
43 | if (StringUtils.isEmpty(schemaUri)) {
44 | //default use xsd in this jar
45 | schema = schemaFactory.newSchema(new StreamSource(this.getClass().getClassLoader().getResourceAsStream("octopus.xsd")));
46 | } else {
47 | if (schemaUri.startsWith("http:") || schemaUri.startsWith("https:")) {
48 | schema = schemaFactory.newSchema(new URL(schemaUri));
49 | } else if (schemaUri.startsWith("file:")) {
50 | schema = schemaFactory.newSchema(new File(schemaUri));
51 | } else {
52 | schema = schemaFactory.newSchema(new StreamSource(this.getClass().getClassLoader().getResourceAsStream(schemaUri)));
53 | }
54 | }
55 | Validator validator = schema.newValidator();
56 | validator.validate(source);
57 | is.reset();
58 | }
59 |
60 | protected String getAttribute(Node node, String name) {
61 | if (node == null) {
62 | return null;
63 | }
64 | NamedNodeMap attributes = node.getAttributes();
65 | Node item = attributes.getNamedItem(name);
66 | if (item == null) {
67 | return null;
68 | }
69 | return item.getNodeValue();
70 | }
71 |
72 |
73 | /**
74 | * xml config constant
75 | */
76 | protected interface XmlNode {
77 |
78 | interface Root {
79 | String nodeName = "Root";
80 |
81 | interface Attribute {
82 | String CLASS = "class";
83 | }
84 | }
85 |
86 | interface Header {
87 | String nodeName = "Header";
88 |
89 | interface Attribute {
90 | String NAME = "name";
91 | String DESCRIPTION = "description";
92 | String HEADER_FONT_SIZE = "header-font-size";
93 | String HEADER_COLOR = "header-color";
94 | String IS_HEADER_BOLD = "header-is-bold";
95 | String HEADER_FOREGROUND_COLOR = "header-foreground-color";
96 | String HEADER_BORDER = "header-border";
97 | String HEADER_BORDER_COLOR = "header-border-color";
98 | }
99 | }
100 |
101 | interface Field {
102 | String nodeName = "Field";
103 |
104 | interface Attribute extends Header.Attribute {
105 | String FONT_SIZE = "font-size";
106 | String COLOR = "color";
107 | String IS_BOLD = "is-bold";
108 | String FOREGROUND_COLOR = "foreground-color";
109 | String BORDER = "border";
110 | String BORDER_COLOR = "border-color";
111 | String WIDTH = "width";
112 |
113 | String DATE_FORMAT = "date-format";
114 | String FORMATTER = "formatter";
115 | String IS_BLANKABLE = "is-blankable";
116 | String REGEX = "regex";
117 | String OPTIONS = "options";
118 | }
119 | }
120 |
121 | interface Formatters {
122 | String nodeName = "Formatters";
123 |
124 | interface Formatter {
125 | String nodeName = "Formatter";
126 |
127 | interface Attribute {
128 | String TARGET = "target";
129 | String CLASS = "class";
130 | }
131 | }
132 |
133 | interface Attribute {
134 | String DATE_FORMAT = "date-format";
135 | }
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/Config.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config;
2 |
3 | import cn.chenhuanming.octopus.formatter.FormatterContainer;
4 | import lombok.NonNull;
5 | import lombok.Value;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * @author chenhuanming
11 | * Created at 2018/12/7
12 | */
13 | @Value(staticConstructor = "of")
14 | public class Config {
15 | @NonNull
16 | private Class classType;
17 | @NonNull
18 | private FormatterContainer formatterContainer;
19 | @NonNull
20 | private List fields;
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/ConfigFactory.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config;
2 |
3 | /**
4 | * Factory of #{@link Config}
5 | * @author chenhuanming
6 | * Created at 2018/12/10
7 | */
8 | public interface ConfigFactory {
9 | Config getConfig();
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/Field.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config;
2 |
3 | import cn.chenhuanming.octopus.formatter.Formatter;
4 | import lombok.Builder;
5 | import lombok.NonNull;
6 | import lombok.Value;
7 |
8 | import java.lang.reflect.Method;
9 | import java.util.List;
10 |
11 | /**
12 | * @author chenhuanming
13 | * Created at 2018/12/15
14 | */
15 | @Value
16 | @Builder
17 | public class Field {
18 |
19 | /**
20 | * Used to access from data.
21 | */
22 | @NonNull
23 | private String name;
24 |
25 | /**
26 | * attribute description used to write into sheet's header
27 | */
28 | @NonNull
29 | private String description;
30 |
31 | /**
32 | * default value
33 | */
34 | private String defaultValue;
35 |
36 | /**
37 | * format content which is wrote into excel or read from excel
38 | */
39 | private Formatter formatter;
40 |
41 | /**
42 | * method that access value from data
43 | */
44 | @NonNull
45 | private Method picker;
46 |
47 | /**
48 | * method that set value into data
49 | */
50 | @NonNull
51 | private Method pusher;
52 |
53 | /**
54 | * children of field,normally represent tree construct of data.
55 | * It will be used to write and read excel
56 | */
57 | private List children;
58 |
59 | /**
60 | * cell style of table body when exporting excel
61 | */
62 | private FieldCellStyle fieldCellStyle;
63 |
64 | /**
65 | * cell style of table header when exporting excel
66 | */
67 | @NonNull
68 | private FieldCellStyle headerFieldCellStyle;
69 |
70 | /**
71 | * validation config when importing excel
72 | */
73 | private ImportValidation importValidation;
74 |
75 | public boolean isLeaf() {
76 | return getChildren() == null || getChildren().size() == 0;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/FieldCellStyle.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config;
2 |
3 | import lombok.Builder;
4 | import lombok.Value;
5 | import org.apache.poi.ss.usermodel.BorderStyle;
6 |
7 | import java.awt.*;
8 |
9 |
10 | /**
11 | * cell style config of field
12 | * @author guangdao
13 | * Created at 2019-02-25
14 | */
15 | @Value
16 | @Builder
17 | public class FieldCellStyle {
18 | private short fontSize;
19 | private Color color;
20 | private boolean bold;
21 | private Color foregroundColor;
22 | private BorderStyle[] border;
23 | private Color[] borderColor;
24 | private int width;
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/ImportValidation.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config;
2 |
3 | import lombok.Value;
4 |
5 | import java.util.List;
6 | import java.util.regex.Pattern;
7 |
8 | /**
9 | * @author chenhuanming
10 | * Created at 2019-01-09
11 | */
12 | @Value(staticConstructor = "of")
13 | public class ImportValidation {
14 | protected boolean blankable;
15 | protected List options;
16 | protected Pattern regex;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/XmlConfigFactory.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config;
2 |
3 | import cn.chenhuanming.octopus.formatter.DateFormatter;
4 | import cn.chenhuanming.octopus.formatter.DefaultFormatterContainer;
5 | import cn.chenhuanming.octopus.formatter.FormatterContainer;
6 | import cn.chenhuanming.octopus.util.ColorUtils;
7 | import cn.chenhuanming.octopus.util.ReflectionUtils;
8 | import cn.chenhuanming.octopus.util.StringUtils;
9 | import cn.chenhuanming.octopus.util.ValidationUtils;
10 | import lombok.extern.slf4j.Slf4j;
11 | import org.w3c.dom.Document;
12 | import org.w3c.dom.Element;
13 | import org.w3c.dom.Node;
14 | import org.w3c.dom.NodeList;
15 |
16 | import javax.xml.parsers.DocumentBuilderFactory;
17 | import javax.xml.transform.stream.StreamSource;
18 | import java.io.InputStream;
19 | import java.lang.reflect.Method;
20 | import java.util.ArrayList;
21 | import java.util.Arrays;
22 | import java.util.Date;
23 | import java.util.List;
24 | import java.util.regex.Pattern;
25 |
26 | /**
27 | * read config from xml file,construct {@link Config}
28 | *
29 | * @author chenhuanming
30 | * Created at 2018/12/10
31 | */
32 | @Slf4j
33 | public class XmlConfigFactory extends AbstractXMLConfigFactory {
34 |
35 | public XmlConfigFactory(InputStream is) {
36 | super(is);
37 | }
38 |
39 | @Override
40 | public Config getConfig() {
41 | Document document;
42 | try {
43 | document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
44 | String schemaUri = document.getDocumentElement().getAttribute("xsi:noNamespaceSchemaLocation");
45 | validateXML(new StreamSource(is), schemaUri);
46 | } catch (Exception e) {
47 | throw new IllegalArgumentException("xml file is not valid", e);
48 | }
49 |
50 | Element root = document.getDocumentElement();
51 |
52 | if (!XmlNode.Root.nodeName.equals(root.getTagName())) {
53 | throw new IllegalArgumentException("xml config file: must has a root tag named " + XmlNode.Root.nodeName);
54 | }
55 | String className = root.getAttribute(XmlNode.Root.Attribute.CLASS);
56 |
57 | Class> classType = null;
58 | try {
59 | classType = Class.forName(className);
60 | } catch (ClassNotFoundException e) {
61 | throw new IllegalArgumentException(e);
62 | }
63 |
64 | Node formattersNode = root.getElementsByTagName(XmlNode.Formatters.nodeName).item(0);
65 |
66 | return Config.of(classType, readFormatter(formattersNode), getFields(root, classType));
67 | }
68 |
69 | private FormatterContainer readFormatter(Node formatNode) {
70 | DefaultFormatterContainer container = new DefaultFormatterContainer();
71 |
72 | String dateFormat = getAttribute(formatNode, XmlNode.Formatters.Attribute.DATE_FORMAT);
73 | if (StringUtils.isEmpty(dateFormat)) {
74 | container.addFormat(Date.class, new DateFormatter("yyyy-MM-dd HH:mm:ss"));
75 | } else {
76 | container.addFormat(Date.class, new DateFormatter(dateFormat));
77 | }
78 |
79 | if (formatNode != null && formatNode.hasChildNodes()) {
80 | NodeList children = formatNode.getChildNodes();
81 | for (int i = 0; i < children.getLength(); i++) {
82 | Node item = children.item(i);
83 | if (item.getNodeType() == Node.ELEMENT_NODE || !item.getNodeName().equals(XmlNode.Formatters.Formatter.nodeName)) {
84 | continue;
85 | }
86 | String targetClass = getAttribute(item, XmlNode.Formatters.Formatter.Attribute.TARGET);
87 | String formatClass = getAttribute(item, XmlNode.Formatters.Formatter.Attribute.CLASS);
88 |
89 | try {
90 | Class target = Class.forName(targetClass);
91 | Class format = Class.forName(formatClass);
92 | container.addFormat(target, (cn.chenhuanming.octopus.formatter.Formatter) format.newInstance());
93 | } catch (Exception e) {
94 | throw new IllegalArgumentException(e);
95 | }
96 |
97 | }
98 | }
99 | return container;
100 | }
101 |
102 | private List getFields(Node node, Class classType) {
103 |
104 | List children = new ArrayList<>();
105 |
106 | for (int i = 0; i < node.getChildNodes().getLength(); i++) {
107 | Node item = node.getChildNodes().item(i);
108 | if (item.getNodeType() != Node.ELEMENT_NODE || (!item.getNodeName().equals(XmlNode.Field.nodeName) && !item.getNodeName().equals(XmlNode.Header.nodeName))) {
109 | continue;
110 | }
111 |
112 | Field.FieldBuilder field = Field.builder();
113 |
114 | String name = getAttribute(item, XmlNode.Field.Attribute.NAME);
115 | ValidationUtils.notEmpty(name, XmlNode.Field.Attribute.NAME);
116 | field.name(name);
117 |
118 | String description = getAttribute(item, XmlNode.Field.Attribute.DESCRIPTION);
119 | ValidationUtils.notEmpty(description, XmlNode.Field.Attribute.DESCRIPTION);
120 | field.description(description);
121 |
122 | setFormatter(field, item);
123 |
124 | Method picker = null;
125 | if (classType != null) {
126 | //set picker
127 | picker = ReflectionUtils.readMethod(classType, name);
128 | field.picker(picker);
129 |
130 | //set pusher
131 | Method pusher = ReflectionUtils.writeMethod(classType, name);
132 | field.pusher(pusher);
133 | }
134 |
135 | setHeaderCellStyleConfig(field, item);
136 |
137 | if (item.getNodeName().equals(XmlNode.Field.nodeName)) {
138 | setCellStyleConfig(field, item);
139 | setImportValidation(field, item);
140 | } else {
141 | Class headerType = item.getNodeName().equals(XmlNode.Root.nodeName) ? classType : (picker != null ? picker.getReturnType() : null);
142 | field.children(getFields(item, headerType));
143 | }
144 | children.add(field.build());
145 | }
146 | return children;
147 | }
148 |
149 | private void setFormatter(Field.FieldBuilder field, Node node) {
150 | String formatterStr = getAttribute(node, XmlNode.Field.Attribute.FORMATTER);
151 | if (StringUtils.isNotEmpty(formatterStr)) {
152 | try {
153 | Class formatterClass = Class.forName(formatterStr);
154 | if (!cn.chenhuanming.octopus.formatter.Formatter.class.isAssignableFrom(formatterClass)) {
155 | throw new IllegalArgumentException(formatterStr + " is not subclass of cn.chenhuanming.octopus.formatter.Formatters");
156 | } else {
157 | field.formatter((cn.chenhuanming.octopus.formatter.Formatter) formatterClass.newInstance());
158 | }
159 | } catch (Exception e) {
160 | throw new IllegalArgumentException(formatterStr + " may not have a default constructor");
161 | }
162 | } else {
163 | String dateFormat = getAttribute(node, XmlNode.Field.Attribute.DATE_FORMAT);
164 | if (StringUtils.isNotEmpty(dateFormat)) {
165 | field.formatter(new DateFormatter(dateFormat));
166 | }
167 | }
168 | }
169 |
170 | private void setCellStyleConfig(Field.FieldBuilder field, Node node) {
171 | FieldCellStyle.FieldCellStyleBuilder builder = FieldCellStyle.builder();
172 |
173 | String fontSize = getAttribute(node, XmlNode.Field.Attribute.FONT_SIZE);
174 | builder.fontSize(Short.parseShort(StringUtils.defaultIfEmpty(fontSize, "14")));
175 |
176 | String color = getAttribute(node, XmlNode.Field.Attribute.COLOR);
177 | builder.color(ColorUtils.hex2Rgb(StringUtils.defaultIfEmpty(color, "#000000")));
178 |
179 | String isBold = getAttribute(node, XmlNode.Field.Attribute.IS_BOLD);
180 | builder.bold(Boolean.parseBoolean(StringUtils.defaultIfEmpty(isBold, "false")));
181 |
182 | String foregroundColor = getAttribute(node, XmlNode.Field.Attribute.FOREGROUND_COLOR);
183 | builder.foregroundColor(ColorUtils.hex2Rgb(StringUtils.defaultIfEmpty(foregroundColor, null)));
184 |
185 | String border = getAttribute(node, XmlNode.Field.Attribute.BORDER);
186 | builder.border(super.convertBorder(StringUtils.defaultIfEmpty(border, "0,0,0,0")));
187 |
188 | String borderColor = getAttribute(node, XmlNode.Field.Attribute.BORDER_COLOR);
189 | builder.borderColor(super.convertBorderColor(StringUtils.defaultIfEmpty(borderColor, "#000000,#000000,#000000,#000000")));
190 |
191 | String width = getAttribute(node, XmlNode.Field.Attribute.WIDTH);
192 | builder.width(Integer.parseInt(StringUtils.defaultIfEmpty(width, "0")));
193 |
194 | field.fieldCellStyle(builder.build());
195 | }
196 |
197 | private void setHeaderCellStyleConfig(Field.FieldBuilder field, Node node) {
198 | FieldCellStyle.FieldCellStyleBuilder builder = FieldCellStyle.builder();
199 |
200 | String fontSize = getAttribute(node, XmlNode.Header.Attribute.HEADER_FONT_SIZE);
201 | builder.fontSize(Short.parseShort(StringUtils.defaultIfEmpty(fontSize, "15")));
202 |
203 | String color = getAttribute(node, XmlNode.Header.Attribute.HEADER_COLOR);
204 | builder.color(ColorUtils.hex2Rgb(StringUtils.defaultIfEmpty(color, "#000000")));
205 |
206 | String isBold = getAttribute(node, XmlNode.Header.Attribute.IS_HEADER_BOLD);
207 | builder.bold(Boolean.parseBoolean(StringUtils.defaultIfEmpty(isBold, "true")));
208 |
209 | String foregroundColor = getAttribute(node, XmlNode.Header.Attribute.HEADER_FOREGROUND_COLOR);
210 | builder.foregroundColor(ColorUtils.hex2Rgb(StringUtils.defaultIfEmpty(foregroundColor, "#FFFFFF")));
211 |
212 | String border = getAttribute(node, XmlNode.Header.Attribute.HEADER_BORDER);
213 | builder.border(super.convertBorder(StringUtils.defaultIfEmpty(border, "1,1,1,1")));
214 |
215 | String borderColor = getAttribute(node, XmlNode.Header.Attribute.HEADER_BORDER_COLOR);
216 | builder.borderColor(super.convertBorderColor(StringUtils.defaultIfEmpty(borderColor, "#000000,#000000,#000000,#000000")));
217 |
218 | //handle width in cellStyle rather than headerCellStyle
219 | builder.width(0);
220 |
221 | field.headerFieldCellStyle(builder.build());
222 | }
223 |
224 | private void setImportValidation(Field.FieldBuilder field, Node node) {
225 | String isBlankable = getAttribute(node, XmlNode.Field.Attribute.IS_BLANKABLE);
226 | boolean blankable = true;
227 | List options = null;
228 | Pattern regex = null;
229 |
230 | if (StringUtils.isNotEmpty(isBlankable)) {
231 | blankable = Boolean.parseBoolean(isBlankable);
232 | }
233 |
234 | String regexStr = getAttribute(node, XmlNode.Field.Attribute.REGEX);
235 | if (!StringUtils.isEmpty(regexStr)) {
236 | regex = Pattern.compile(regexStr);
237 | }
238 | String optionsStr = getAttribute(node, XmlNode.Field.Attribute.OPTIONS);
239 | if (!StringUtils.isEmpty(optionsStr)) {
240 | String[] split = optionsStr.split(StringUtils.OPTION_SPLITTER_VERTICAL);
241 | blankable = false;//can not blank when options were set
242 | options = Arrays.asList(split);
243 | }
244 |
245 | field.importValidation(ImportValidation.of(blankable, options, regex));
246 | }
247 |
248 | }
249 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/annotation/AnnotationConfigFactory.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config.annotation;
2 |
3 | import cn.chenhuanming.octopus.config.AbstractConfigFactory;
4 | import cn.chenhuanming.octopus.config.Config;
5 | import cn.chenhuanming.octopus.config.Field;
6 | import cn.chenhuanming.octopus.config.FieldCellStyle;
7 | import cn.chenhuanming.octopus.config.ImportValidation;
8 | import cn.chenhuanming.octopus.formatter.DateFormatter;
9 | import cn.chenhuanming.octopus.formatter.DefaultFormatterContainer;
10 | import cn.chenhuanming.octopus.formatter.FormatterContainer;
11 | import cn.chenhuanming.octopus.util.ColorUtils;
12 | import cn.chenhuanming.octopus.util.ReflectionUtils;
13 | import cn.chenhuanming.octopus.util.StringUtils;
14 | import lombok.Getter;
15 |
16 | import java.lang.annotation.Annotation;
17 | import java.lang.reflect.Method;
18 | import java.util.ArrayList;
19 | import java.util.Arrays;
20 | import java.util.Date;
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.concurrent.ConcurrentHashMap;
24 | import java.util.regex.Pattern;
25 |
26 | /**
27 | * @author : youthlin.chen @ 2019-04-26 12:00
28 | */
29 | public class AnnotationConfigFactory extends AbstractConfigFactory {
30 | private Map instanceMap = new ConcurrentHashMap<>();
31 | @Getter
32 | private final Class> modelClass;
33 |
34 | public AnnotationConfigFactory(Class modelClass) {
35 | if (modelClass.getAnnotation(Sheet.class) == null) {
36 | throw new IllegalArgumentException("the modelClass must have @Sheet annotation:" + modelClass);
37 | }
38 | this.modelClass = modelClass;
39 | }
40 |
41 | @Override
42 | public Config getConfig() {
43 | return Config.of(modelClass, readFormatter(), getFields(modelClass));
44 | }
45 |
46 | private FormatterContainer readFormatter() {
47 | DefaultFormatterContainer container = new DefaultFormatterContainer();
48 | Sheet sheet = modelClass.getAnnotation(Sheet.class);
49 | String dateFormatter = Sheet.DEFAULT_DATE_FORMATTER;
50 | if (sheet != null) {
51 | if (StringUtils.isNotEmpty(sheet.dateFormatter())) {
52 | dateFormatter = sheet.dateFormatter();
53 | }
54 | }
55 | container.addFormat(Date.class, new DateFormatter(dateFormatter));
56 | if (sheet != null) {
57 | for (Formatter formatter : sheet.formatters()) {
58 | Class target = formatter.target();
59 | Class extends cn.chenhuanming.octopus.formatter.Formatter> format = formatter.format();
60 | container.addFormat(target, getFormatterInstance(format));
61 | }
62 | }
63 | return container;
64 | }
65 |
66 | private cn.chenhuanming.octopus.formatter.Formatter getFormatterInstance(
67 | Class extends cn.chenhuanming.octopus.formatter.Formatter> formatClass) {
68 | try {
69 | cn.chenhuanming.octopus.formatter.Formatter instance;
70 | if (formatClass.getAnnotation(Shareable.class) != null) {
71 | instance = instanceMap.get(formatClass);
72 | if (instance == null) {
73 | instance = formatClass.newInstance();
74 | instanceMap.put(formatClass, instance);
75 | }
76 | } else {
77 | instance = formatClass.newInstance();
78 | }
79 | return instance;
80 | } catch (Exception e) {
81 | throw new IllegalArgumentException(e);
82 | }
83 | }
84 |
85 | private List getFields(Class clazz) {
86 | List fields = new ArrayList<>();
87 | for (java.lang.reflect.Field declaredField : clazz.getDeclaredFields()) {
88 | cn.chenhuanming.octopus.config.annotation.Field fieldAnnotation =
89 | declaredField.getAnnotation(cn.chenhuanming.octopus.config.annotation.Field.class);
90 | Header header = declaredField.getAnnotation(Header.class);
91 | if (fieldAnnotation != null && header != null) {
92 | throw new IllegalArgumentException("a field can not have @Field and @Header both:" + declaredField);
93 | }
94 | if (fieldAnnotation != null) {
95 | Field.FieldBuilder field = Field.builder();
96 | field.name(declaredField.getName());
97 | field.description(fieldAnnotation.description());
98 | setFormatter(field, fieldAnnotation);
99 | setCellStyleConfig(field, fieldAnnotation);
100 | setHeaderCellStyleConfig(field, fieldAnnotation);
101 | setInvoker(field, declaredField.getName(), declaredField.getDeclaringClass());
102 | setImportValidation(field, fieldAnnotation);
103 | fields.add(field.build());
104 | }
105 | if (header != null) {
106 | Field.FieldBuilder field = Field.builder();
107 | field.name(declaredField.getName());
108 | field.description(header.description());
109 | setHeaderCellStyleConfig(field, header);
110 | setInvoker(field, declaredField.getName(), declaredField.getDeclaringClass());
111 | field.children(getFields(declaredField.getType()));
112 | fields.add(field.build());
113 | }
114 | }
115 | return fields;
116 | }
117 |
118 | private void setFormatter(Field.FieldBuilder field, cn.chenhuanming.octopus.config.annotation.Field fieldAnnotation) {
119 | Class extends cn.chenhuanming.octopus.formatter.Formatter> formatter = fieldAnnotation.formatter();
120 | if (!formatter.equals(cn.chenhuanming.octopus.formatter.Formatter.class)) {
121 | field.formatter(getFormatterInstance(formatter));
122 | } else {
123 | if (StringUtils.isNotEmpty(fieldAnnotation.dateFormat())) {
124 | field.formatter(new DateFormatter(fieldAnnotation.dateFormat()));
125 | }
126 | }
127 | }
128 |
129 | private void setCellStyleConfig(Field.FieldBuilder field, cn.chenhuanming.octopus.config.annotation.Field fieldAnnotation) {
130 | FieldCellStyle.FieldCellStyleBuilder builder = FieldCellStyle.builder();
131 |
132 | short fontSize = fieldAnnotation.fontSize();
133 | builder.fontSize(fontSize);
134 |
135 | String color = fieldAnnotation.color();
136 | builder.color(ColorUtils.hex2Rgb(StringUtils.defaultIfEmpty(color, "#000000")));
137 |
138 | boolean isBold = fieldAnnotation.isBold();
139 | builder.bold(isBold);
140 |
141 | String foregroundColor = fieldAnnotation.foregroundColor();
142 | builder.foregroundColor(ColorUtils.hex2Rgb(StringUtils.defaultIfEmpty(foregroundColor, null)));
143 |
144 | String border = fieldAnnotation.border();
145 | builder.border(super.convertBorder(StringUtils.defaultIfEmpty(border, "0,0,0,0")));
146 |
147 | String borderColor = fieldAnnotation.borderColor();
148 | borderColor = StringUtils.defaultIfEmpty(borderColor, "#000000,#000000,#000000,#000000");
149 | builder.borderColor(super.convertBorderColor(borderColor));
150 |
151 | builder.width(fieldAnnotation.width());
152 |
153 | field.fieldCellStyle(builder.build());
154 | }
155 |
156 | private void setHeaderCellStyleConfig(Field.FieldBuilder field, Annotation headerOrField) {
157 | FieldCellStyle.FieldCellStyleBuilder builder = FieldCellStyle.builder();
158 | if (headerOrField instanceof Header
159 | || headerOrField instanceof cn.chenhuanming.octopus.config.annotation.Field) {
160 | short fontSize = 0;
161 | String color = null;
162 | boolean isBold = false;
163 | String foregroundColor = null;
164 | String border = null;
165 | String borderColor = null;
166 |
167 | if (headerOrField instanceof Header) {
168 | fontSize = ((Header) headerOrField).headerFontSize();
169 | color = ((Header) headerOrField).headerColor();
170 | isBold = ((Header) headerOrField).headerIsBold();
171 | foregroundColor = ((Header) headerOrField).headerForegroundColor();
172 | border = ((Header) headerOrField).headerBorder();
173 | borderColor = ((Header) headerOrField).headerBorderColor();
174 | }
175 | if (headerOrField instanceof cn.chenhuanming.octopus.config.annotation.Field) {
176 | fontSize = ((cn.chenhuanming.octopus.config.annotation.Field) headerOrField).headerFontSize();
177 | color = ((cn.chenhuanming.octopus.config.annotation.Field) headerOrField).headerColor();
178 | isBold = ((cn.chenhuanming.octopus.config.annotation.Field) headerOrField).headerIsBold();
179 | foregroundColor = ((cn.chenhuanming.octopus.config.annotation.Field) headerOrField)
180 | .headerForegroundColor();
181 | border = ((cn.chenhuanming.octopus.config.annotation.Field) headerOrField).headerBorder();
182 | borderColor = ((cn.chenhuanming.octopus.config.annotation.Field) headerOrField).headerBorderColor();
183 | }
184 | builder.fontSize(fontSize);
185 | builder.color(ColorUtils.hex2Rgb(StringUtils.defaultIfEmpty(color, "#000000")));
186 | builder.bold(isBold);
187 | builder.foregroundColor(ColorUtils.hex2Rgb(StringUtils.defaultIfEmpty(foregroundColor, "#FFFFFF")));
188 | builder.border(super.convertBorder(StringUtils.defaultIfEmpty(border, "1,1,1,1")));
189 | borderColor = StringUtils.defaultIfEmpty(borderColor, "#000000,#000000,#000000,#000000");
190 | builder.borderColor(super.convertBorderColor(borderColor));
191 | builder.width(0);
192 |
193 | field.headerFieldCellStyle(builder.build());
194 | } else {
195 | throw new IllegalArgumentException("unknown annotation, @Header or @Field required:" + headerOrField);
196 | }
197 | }
198 |
199 | private void setInvoker(Field.FieldBuilder field, String name, Class classType) {
200 | //getter
201 | Method picker = ReflectionUtils.readMethod(classType, name);
202 | field.picker(picker);
203 | //setter
204 | Method pusher = ReflectionUtils.writeMethod(classType, name);
205 | field.pusher(pusher);
206 | }
207 |
208 | private void setImportValidation(Field.FieldBuilder field, cn.chenhuanming.octopus.config.annotation.Field fieldAnnotation) {
209 | boolean blankable = fieldAnnotation.isBlankable();
210 | List options = null;
211 | Pattern regex = null;
212 |
213 | String regexStr = fieldAnnotation.regex();
214 | if (StringUtils.isNotEmpty(regexStr)) {
215 | regex = Pattern.compile(regexStr);
216 | }
217 |
218 | String optionsStr = fieldAnnotation.options();
219 | if (StringUtils.isNotEmpty(optionsStr)) {
220 | String[] split = optionsStr.split(StringUtils.OPTION_SPLITTER_VERTICAL);
221 | blankable = false;//can not blank when options were set
222 | options = Arrays.asList(split);
223 | }
224 |
225 | field.importValidation(ImportValidation.of(blankable, options, regex));
226 | }
227 |
228 | }
229 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/annotation/Field.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config.annotation;
2 |
3 | import cn.chenhuanming.octopus.formatter.Formatter;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | /**
11 | * @author : youthlin.chen @ 2019-04-26 11:50
12 | */
13 | @Retention(RetentionPolicy.RUNTIME)
14 | @Target(ElementType.FIELD)
15 | public @interface Field {
16 |
17 | String description();
18 |
19 | short headerFontSize() default 15;
20 |
21 | String headerColor() default "#000000";
22 |
23 | boolean headerIsBold() default true;
24 |
25 | String headerForegroundColor() default "#FFFFFF";
26 |
27 | String headerBorder() default "1,1,1,1";
28 |
29 | String headerBorderColor() default "#000000,#000000,#000000,#000000";
30 |
31 | short fontSize() default 14;
32 |
33 | String color() default "#000000";
34 |
35 | boolean isBold() default false;
36 |
37 | String foregroundColor() default "";
38 |
39 | String border() default "0,0,0,0";
40 |
41 | String borderColor() default "#000000,#000000,#000000,#000000";
42 |
43 | int width() default 0;
44 |
45 | String dateFormat() default "";
46 |
47 | Class extends Formatter> formatter() default Formatter.class;
48 |
49 | boolean isBlankable() default true;
50 |
51 | String regex() default "";
52 |
53 | String options() default "";
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/annotation/Formatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * @author : youthlin.chen @ 2019-04-26 12:19
10 | */
11 | @Retention(RetentionPolicy.RUNTIME)
12 | @Target(ElementType.ANNOTATION_TYPE)
13 | public @interface Formatter {
14 | Class target();
15 |
16 | Class extends cn.chenhuanming.octopus.formatter.Formatter> format();
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/annotation/Header.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * @author : youthlin.chen @ 2019-04-26 11:46
10 | */
11 | @Retention(RetentionPolicy.RUNTIME)
12 | @Target(ElementType.FIELD)
13 | public @interface Header {
14 |
15 | String description();
16 |
17 | short headerFontSize() default 15;
18 |
19 | String headerColor() default "#000000";
20 |
21 | boolean headerIsBold() default true;
22 |
23 | String headerForegroundColor() default "#FFFFFF";
24 |
25 | String headerBorder() default "1,1,1,1";
26 |
27 | String headerBorderColor() default "#000000,#000000,#000000,#000000";
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/annotation/Shareable.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * thread unsafe, only used when the formatter is stateless
10 | *
11 | * @author : youthlin.chen @ 2019-04-26 12:21
12 | */
13 | @Retention(RetentionPolicy.RUNTIME)
14 | @Target(ElementType.ANNOTATION_TYPE)
15 | public @interface Shareable {
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/config/annotation/Sheet.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * @author : youthlin.chen @ 2019-04-26 11:41
10 | */
11 | @Retention(RetentionPolicy.RUNTIME)
12 | @Target(ElementType.TYPE)
13 | public @interface Sheet {
14 | String DEFAULT_DATE_FORMATTER = "yyyy-MM-dd HH:mm:ss";
15 |
16 | Formatter[] formatters() default {};
17 |
18 | String dateFormatter() default DEFAULT_DATE_FORMATTER;
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/exception/CanNotBeBlankException.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.exception;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2019-01-09
6 | */
7 | public class CanNotBeBlankException extends ParseException {
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/exception/NotAllowValueException.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.exception;
2 |
3 | import lombok.Getter;
4 |
5 | import java.util.Collections;
6 | import java.util.List;
7 |
8 | /**
9 | * @author chenhuanming
10 | * Created at 2019-01-09
11 | */
12 | public class NotAllowValueException extends ParseException {
13 | @Getter
14 | private final List options;
15 |
16 | public NotAllowValueException(List options) {
17 | this.options = Collections.unmodifiableList(options);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/exception/ParseException.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.exception;
2 |
3 | import cn.chenhuanming.octopus.config.Field;
4 | import cn.chenhuanming.octopus.model.CellPosition;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 |
8 | /**
9 | * @author chenhuanming
10 | * Created at 2019-01-08
11 | */
12 | @Getter
13 | @Setter
14 | public class ParseException extends Exception {
15 | private CellPosition cellPosition;
16 | private Field field;
17 |
18 | ParseException() {
19 | }
20 |
21 | public ParseException(String message) {
22 | super(message);
23 | }
24 |
25 | public ParseException(String message, Throwable cause) {
26 | super(message, cause);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/exception/PatternNotMatchException.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.exception;
2 |
3 | import lombok.Getter;
4 |
5 | import java.util.regex.Pattern;
6 |
7 | /**
8 | * @author chenhuanming
9 | * Created at 2019-01-09
10 | */
11 | public class PatternNotMatchException extends ParseException {
12 | @Getter
13 | private final Pattern pattern;
14 |
15 | public PatternNotMatchException(Pattern pattern) {
16 | this.pattern = pattern;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/exception/SheetNotFoundException.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.exception;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2019-01-10
6 | */
7 | public class SheetNotFoundException extends Exception {
8 | public SheetNotFoundException(String message) {
9 | super(message);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/AbstractFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 |
4 | import cn.chenhuanming.octopus.exception.ParseException;
5 | import cn.chenhuanming.octopus.util.StringUtils;
6 |
7 | import java.util.Objects;
8 |
9 | /**
10 | * @author chenhuanming
11 | * Created at 2019-01-08
12 | */
13 | public abstract class AbstractFormatter implements Formatter {
14 | @Override
15 | public T parse(String str) throws ParseException {
16 | try {
17 | if (this.isEmptyWhenParse(str)) {
18 | return defaultValueWhenParseEmpty();
19 | }
20 | return parseImpl(str);
21 | } catch (Exception e) {
22 | throw new ParseException(e.getMessage(), e);
23 | }
24 | }
25 |
26 | public abstract T parseImpl(String str) throws Exception;
27 |
28 | protected T defaultValueWhenParseEmpty() {
29 | return null;
30 | }
31 |
32 | protected boolean isEmptyWhenParse(String str) {
33 | return StringUtils.isEmpty(str) || Objects.equals(str, "null");
34 | }
35 |
36 | @Override
37 | public boolean equals(Object obj) {
38 | if (obj == null) {
39 | return false;
40 | }
41 | return this.getClass().equals(obj.getClass());
42 | }
43 |
44 | @Override
45 | public int hashCode() {
46 | return this.getClass().hashCode();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/BigDecimalFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | import java.math.BigDecimal;
4 |
5 | /**
6 | * @author huiyadanli
7 | * Created at 2019-06-22
8 | */
9 | public class BigDecimalFormatter extends AbstractFormatter {
10 | @Override
11 | public BigDecimal parseImpl(String str) throws Exception {
12 | return new BigDecimal(str);
13 | }
14 |
15 | @Override
16 | public String format(BigDecimal bigDecimal) {
17 | return bigDecimal.toString();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/BooleanFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2019-01-08
6 | */
7 | public class BooleanFormatter extends AbstractFormatter {
8 | @Override
9 | public Boolean parseImpl(String str) throws Exception {
10 | return Boolean.valueOf(str);
11 | }
12 |
13 | @Override
14 | public String format(Boolean aBoolean) {
15 | return String.valueOf(aBoolean);
16 | }
17 |
18 | static class PrimitiveFormatter extends BooleanFormatter {
19 | @Override
20 | protected Boolean defaultValueWhenParseEmpty() {
21 | return Boolean.FALSE;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/DateFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | import java.text.DateFormat;
4 | import java.text.SimpleDateFormat;
5 | import java.util.Date;
6 |
7 | /**
8 | * @author chenhuanming
9 | * Created at 2018/12/17
10 | */
11 | public class DateFormatter extends AbstractFormatter {
12 |
13 | private final DateFormat FORMAT;
14 |
15 | public DateFormatter(String format) {
16 | this.FORMAT = new SimpleDateFormat(format);
17 | }
18 |
19 | @Override
20 | public String format(Date date) {
21 | if (date == null) {
22 | return null;
23 | }
24 | return FORMAT.format(date);
25 | }
26 |
27 | @Override
28 | public Date parseImpl(String str) throws Exception {
29 | return FORMAT.parse(str);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/DefaultFormatterContainer.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | import lombok.EqualsAndHashCode;
4 |
5 | import java.math.BigDecimal;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | /**
10 | * @author chenhuanming
11 | * Created at 2018/12/17
12 | */
13 | @EqualsAndHashCode
14 | public class DefaultFormatterContainer implements FormatterContainer {
15 |
16 | private Map formatMap;
17 |
18 | public DefaultFormatterContainer() {
19 | formatMap = new HashMap<>();
20 | formatMap.put(Integer.class, new IntegerFormatter());
21 | formatMap.put(Integer.TYPE, new IntegerFormatter.PrimitiveFormatter());
22 | formatMap.put(Double.class, new DoubleFormatter());
23 | formatMap.put(Double.TYPE, new DoubleFormatter.PrimitiveFormatter());
24 | formatMap.put(Long.class, new LongFormatter());
25 | formatMap.put(Long.TYPE, new LongFormatter.PrimitiveFormatter());
26 | formatMap.put(Float.class, new FloatFormatter());
27 | formatMap.put(Float.TYPE, new FloatFormatter.PrimitiveFormatter());
28 | formatMap.put(Boolean.class, new BooleanFormatter());
29 | formatMap.put(Boolean.TYPE, new BooleanFormatter.PrimitiveFormatter());
30 | formatMap.put(Short.class, new ShortFormatter());
31 | formatMap.put(Short.TYPE, new ShortFormatter.PrimitiveFormatter());
32 | formatMap.put(BigDecimal.class, new BigDecimalFormatter());
33 | formatMap.put(String.class, new StringFormatter());
34 | }
35 |
36 | public void addFormat(Class clazz, Formatter formatter) {
37 | formatMap.put(clazz, formatter);
38 | }
39 |
40 |
41 | @Override
42 | public Formatter get(Class tClass) {
43 | return formatMap.get(tClass);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/DoubleFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2019-01-08
6 | */
7 | public class DoubleFormatter extends AbstractFormatter {
8 | @Override
9 | public Double parseImpl(String str) throws Exception {
10 | return Double.valueOf(str);
11 | }
12 |
13 | @Override
14 | public String format(Double aDouble) {
15 | return String.valueOf(aDouble);
16 | }
17 |
18 | static class PrimitiveFormatter extends DoubleFormatter {
19 | @Override
20 | protected Double defaultValueWhenParseEmpty() {
21 | return 0.0;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/FloatFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2019-01-08
6 | */
7 | public class FloatFormatter extends AbstractFormatter {
8 | @Override
9 | public Float parseImpl(String str) throws Exception {
10 | return Float.valueOf(str);
11 | }
12 |
13 | @Override
14 | public String format(Float aFloat) {
15 | return String.valueOf(aFloat);
16 | }
17 |
18 | static class PrimitiveFormatter extends FloatFormatter {
19 | @Override
20 | protected Float defaultValueWhenParseEmpty() {
21 | return 0.0f;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/Formatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | import cn.chenhuanming.octopus.exception.ParseException;
4 | import cn.chenhuanming.octopus.reader.CheckedSheetReader;
5 |
6 | /**
7 | * @author chenhuanming
8 | * Created at 2018/12/17
9 | */
10 | public interface Formatter {
11 |
12 | /**
13 | * Format data from T to String when exporting
14 | * @param t your data object
15 | * @return string for writing into excel
16 | */
17 | String format(T t);
18 |
19 | /**
20 | * Read String type data from excel and get T when importing
21 | * If data is not valid,then throw ParseException.It will catch in return value of @{# {@link CheckedSheetReader}}
22 | * @param str string value from excel
23 | * @return T your data object
24 | * @throws ParseException when failed or is invalid data
25 | * @see CheckedSheetReader
26 | * @see cn.chenhuanming.octopus.model.CheckedData
27 | */
28 | T parse(String str) throws ParseException;
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/FormatterContainer.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2018/12/17
6 | */
7 | public interface FormatterContainer {
8 | Formatter get(Class tClass);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/IntegerFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2019-01-08
6 | */
7 | public class IntegerFormatter extends AbstractFormatter {
8 |
9 | @Override
10 | public String format(Integer integer) {
11 | return String.valueOf(integer);
12 | }
13 |
14 |
15 | @Override
16 | public Integer parseImpl(String str) throws Exception {
17 | return Integer.valueOf(str);
18 | }
19 |
20 | static class PrimitiveFormatter extends IntegerFormatter {
21 | @Override
22 | protected Integer defaultValueWhenParseEmpty() {
23 | return 0;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/LongFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2019-01-08
6 | */
7 | public class LongFormatter extends AbstractFormatter {
8 | @Override
9 | public Long parseImpl(String str) throws Exception {
10 | return Long.valueOf(str);
11 | }
12 |
13 | @Override
14 | public String format(Long aLong) {
15 | return String.valueOf(aLong);
16 | }
17 |
18 | static class PrimitiveFormatter extends LongFormatter {
19 | @Override
20 | protected Long defaultValueWhenParseEmpty() {
21 | return 0L;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/ShortFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2019-01-08
6 | */
7 | public class ShortFormatter extends AbstractFormatter {
8 | @Override
9 | public String format(Short aShort) {
10 | return String.valueOf(aShort);
11 | }
12 |
13 |
14 | @Override
15 | public Short parseImpl(String str) throws Exception {
16 | return Short.valueOf(str);
17 | }
18 |
19 | static class PrimitiveFormatter extends ShortFormatter {
20 | @Override
21 | protected Short defaultValueWhenParseEmpty() {
22 | return 0;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/formatter/StringFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2019-01-08
6 | */
7 | public class StringFormatter extends AbstractFormatter {
8 | @Override
9 | public String format(String aShort) {
10 | return String.valueOf(aShort);
11 | }
12 |
13 |
14 | @Override
15 | public String parseImpl(String str) throws Exception {
16 | return str;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/model/CellPosition.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.model;
2 |
3 | /**
4 | * Cell position.
5 | *
6 | * @author chenhuanming
7 | * Created at 2018/12/14
8 | * @see CellPositions
9 | */
10 |
11 | public interface CellPosition {
12 | /**
13 | * Begin with 0,same as java poi
14 | *
15 | * @return row index
16 | */
17 | int getRow();
18 |
19 | /**
20 | * Begin with 0,same as java poi
21 | *
22 | * @return column index
23 | */
24 | int getCol();
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/model/CellPositions.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.model;
2 |
3 | import cn.chenhuanming.octopus.config.Config;
4 | import cn.chenhuanming.octopus.config.Field;
5 | import lombok.AccessLevel;
6 | import lombok.NoArgsConstructor;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * Static utility methods pertaining to {@link CellPosition} instances.
12 | *
13 | * @author guangdao
14 | * Created at 2021-08-19
15 | * @since 1.1.5
16 | */
17 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
18 | public final class CellPositions {
19 |
20 | public static final CellPosition POSITION_ZERO_ZERO = new DefaultCellPosition(0, 0);
21 |
22 | public static CellPosition of(int row, int col) {
23 | return new DefaultCellPosition(row, col);
24 | }
25 |
26 | /**
27 | * Get content start position by header start position and config.
28 | *
29 | * @param headerStartPos header start position
30 | * @param config config
31 | * @return content start position
32 | */
33 | public static CellPosition getContentStartPosition(CellPosition headerStartPos, Config config) {
34 | int height = getHeight(config.getFields());
35 | return of(headerStartPos.getRow() + height, headerStartPos.getCol());
36 | }
37 |
38 | private static int getHeight(List fields) {
39 | if (fields == null || fields.size() == 0) {
40 | return 0;
41 | }
42 | int height = 0;
43 | for (Field field : fields) {
44 | height = Math.max(height, getHeight(field.getChildren()));
45 | }
46 | return height + 1;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/model/CheckedData.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.model;
2 |
3 | import cn.chenhuanming.octopus.exception.ParseException;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | import java.util.LinkedList;
8 | import java.util.List;
9 |
10 | /**
11 | * @author chenhuanming
12 | * Created at 2019-01-09
13 | */
14 | @Data
15 | @NoArgsConstructor
16 | public class CheckedData {
17 | private D data;
18 | private List exceptions = new LinkedList<>();
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/model/DefaultCellPosition.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chenhuanming
8 | * Created at 2018/12/15
9 | */
10 | @Data
11 | @AllArgsConstructor
12 | public class DefaultCellPosition implements CellPosition {
13 | private int row;
14 | private int col;
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/model/WorkbookContext.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.model;
2 |
3 | import cn.chenhuanming.octopus.config.Field;
4 | import cn.chenhuanming.octopus.config.FieldCellStyle;
5 | import cn.chenhuanming.octopus.util.ColorUtils;
6 | import org.apache.poi.ss.usermodel.BorderStyle;
7 | import org.apache.poi.ss.usermodel.CellStyle;
8 | import org.apache.poi.ss.usermodel.Font;
9 | import org.apache.poi.ss.usermodel.HorizontalAlignment;
10 | import org.apache.poi.ss.usermodel.VerticalAlignment;
11 | import org.apache.poi.ss.usermodel.Workbook;
12 |
13 | import java.util.HashMap;
14 | import java.util.Map;
15 |
16 | /**
17 | * context of one workbook
18 | * manage limited resources of workbook,such as color,fieldCellStyle
19 | *
20 | * @author chenhuanming
21 | * Created at 2019-02-18
22 | */
23 | public class WorkbookContext {
24 | private Workbook book;
25 | private Map cellStyleMap;
26 | private Map headerStyleMap;
27 |
28 | public WorkbookContext(Workbook book) {
29 | this.book = book;
30 | this.cellStyleMap = new HashMap<>();
31 | this.headerStyleMap = new HashMap<>();
32 | }
33 |
34 | public CellStyle getCellStyle(Field field) {
35 | CellStyle style = cellStyleMap.get(field);
36 | if (style == null) {
37 | style = book.createCellStyle();
38 | Font font = book.createFont();
39 | FieldCellStyle fieldCellStyle = field.getFieldCellStyle();
40 |
41 |
42 | font.setFontHeightInPoints(fieldCellStyle.getFontSize());
43 | font.setBold(fieldCellStyle.isBold());
44 | ColorUtils.setColor(book, font, fieldCellStyle.getColor());
45 | style.setFont(font);
46 | ColorUtils.setForegroundColor(book, style, fieldCellStyle.getForegroundColor());
47 |
48 | setStyleBorder(style, fieldCellStyle.getBorder());
49 | ColorUtils.setBorderColor(book, style, fieldCellStyle.getBorderColor());
50 |
51 | style.setAlignment(HorizontalAlignment.CENTER);
52 | style.setVerticalAlignment(VerticalAlignment.CENTER);
53 | cellStyleMap.put(field, style);
54 | return style;
55 | }
56 | return style;
57 | }
58 |
59 | public CellStyle getHeaderStyle(Field field) {
60 | CellStyle style = headerStyleMap.get(field);
61 | if (style == null) {
62 | style = book.createCellStyle();
63 | Font font = book.createFont();
64 | FieldCellStyle fieldCellStyle = field.getHeaderFieldCellStyle();
65 |
66 |
67 | font.setFontHeightInPoints(fieldCellStyle.getFontSize());
68 | font.setBold(fieldCellStyle.isBold());
69 | ColorUtils.setColor(book, font, fieldCellStyle.getColor());
70 | style.setFont(font);
71 | style.setAlignment(HorizontalAlignment.CENTER);
72 | style.setVerticalAlignment(VerticalAlignment.CENTER);
73 | ColorUtils.setForegroundColor(book, style, fieldCellStyle.getForegroundColor());
74 |
75 | setStyleBorder(style, fieldCellStyle.getBorder());
76 | ColorUtils.setBorderColor(book, style, fieldCellStyle.getBorderColor());
77 | cellStyleMap.put(field, style);
78 | return style;
79 | }
80 | return style;
81 | }
82 |
83 | private void setStyleBorder(CellStyle style, BorderStyle[] border) {
84 | if (border != null) {
85 | if (border[0] != null) {
86 | style.setBorderTop(border[0]);
87 | }
88 | if (border[1] != null) {
89 | style.setBorderRight(border[1]);
90 | }
91 | if (border[2] != null) {
92 | style.setBorderBottom(border[2]);
93 | }
94 | if (border[3] != null) {
95 | style.setBorderLeft(border[3]);
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/reader/AbstractSheetReader.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.reader;
2 |
3 | import cn.chenhuanming.octopus.config.Config;
4 | import cn.chenhuanming.octopus.config.Field;
5 | import cn.chenhuanming.octopus.exception.ParseException;
6 | import cn.chenhuanming.octopus.formatter.Formatter;
7 | import cn.chenhuanming.octopus.model.CellPosition;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.apache.poi.ss.usermodel.Sheet;
10 |
11 | import java.lang.reflect.Method;
12 | import java.util.Iterator;
13 |
14 | /**
15 | * @author chenhuanming
16 | * Created at 2018/12/20
17 | */
18 | @Slf4j
19 | public abstract class AbstractSheetReader implements SheetReader {
20 |
21 | protected Sheet sheet;
22 | protected Config config;
23 | protected CellPosition startPoint;
24 |
25 | public AbstractSheetReader(Sheet sheet, Config config, CellPosition startPoint) {
26 | if (sheet == null || config == null || startPoint == null) {
27 | throw new NullPointerException();
28 | }
29 | this.sheet = sheet;
30 | this.config = config;
31 | this.startPoint = startPoint;
32 | }
33 |
34 | @Override
35 | public T get(int i) {
36 | T t = newInstance(config.getClassType());
37 |
38 | int col = startPoint.getCol();
39 | for (Field field : config.getFields()) {
40 | col = read(startPoint.getRow() + i, col, field, t);
41 | }
42 | return t;
43 | }
44 |
45 | /**
46 | * set value into object
47 | */
48 | protected void setValue(String str, Field field, Object o) throws ParseException {
49 | Method pusher = field.getPusher();
50 |
51 | Object value = null;
52 |
53 | if (field.getFormatter() != null) {
54 | value = field.getFormatter().parse(str);
55 | } else {
56 | Formatter globalFormatter = config.getFormatterContainer().get(pusher.getParameterTypes()[0]);
57 | if (globalFormatter != null) {
58 | value = globalFormatter.parse(str);
59 | } else {
60 | value = str;
61 | }
62 | }
63 |
64 | try {
65 | if (value != null) {
66 | pusher.invoke(o, value);
67 | }
68 | } catch (Exception e) {
69 | log.error("can not set value:" + field.getName(), e);
70 | throw new ParseException("invoke method failed", e);
71 | }
72 | }
73 |
74 | protected T newInstance(Class classType) {
75 | try {
76 | return (T) config.getClassType().newInstance();
77 | } catch (Exception e) {
78 | throw new IllegalArgumentException("wrong type or no default constructor", e);
79 | }
80 | }
81 |
82 | @Override
83 | public int size() {
84 | return sheet.getLastRowNum() - startPoint.getRow() + 1;
85 | }
86 |
87 | abstract int read(int row, int col, Field field, Object o);
88 |
89 | @Override
90 | public Iterator iterator() {
91 | return new RowIterator(sheet.getLastRowNum() - startPoint.getRow(), 0);
92 | }
93 |
94 | private class RowIterator implements Iterator {
95 | private int last;
96 | private int cursor;
97 |
98 | public RowIterator(int last, int cursor) {
99 | this.last = last;
100 | this.cursor = cursor;
101 | }
102 |
103 | @Override
104 | public boolean hasNext() {
105 | return cursor <= last;
106 | }
107 |
108 | @Override
109 | public T next() {
110 | return (T) get(cursor++);
111 | }
112 |
113 | @Override
114 | public void remove() {
115 | throw new UnsupportedOperationException("remove");
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/reader/CheckedSheetReader.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.reader;
2 |
3 | import cn.chenhuanming.octopus.config.Config;
4 | import cn.chenhuanming.octopus.config.Field;
5 | import cn.chenhuanming.octopus.config.ImportValidation;
6 | import cn.chenhuanming.octopus.exception.CanNotBeBlankException;
7 | import cn.chenhuanming.octopus.exception.NotAllowValueException;
8 | import cn.chenhuanming.octopus.exception.ParseException;
9 | import cn.chenhuanming.octopus.exception.PatternNotMatchException;
10 | import cn.chenhuanming.octopus.model.CellPosition;
11 | import cn.chenhuanming.octopus.model.CheckedData;
12 | import cn.chenhuanming.octopus.model.DefaultCellPosition;
13 | import cn.chenhuanming.octopus.util.StringUtils;
14 | import org.apache.poi.ss.usermodel.Sheet;
15 |
16 | /**
17 | * not thread-safe
18 | *
19 | * @author chenhuanming
20 | * Created at 2019-01-09
21 | */
22 | public class CheckedSheetReader extends DefaultSheetReader> {
23 | /**
24 | * not thread-safe
25 | */
26 | private CheckedData checkedData;
27 |
28 | public CheckedSheetReader(Sheet sheet, Config config, CellPosition startPoint) {
29 | super(sheet, config, startPoint);
30 | }
31 |
32 | @Override
33 | int read(int row, int col, Field field, Object o) {
34 | if (o instanceof CheckedData) {
35 | return super.read(row, col, field, ((CheckedData) o).getData());
36 | }
37 | return super.read(row, col, field, o);
38 | }
39 |
40 | @Override
41 | protected void setValue(String str, Field field, Object o) throws ParseException {
42 | ImportValidation validation = field.getImportValidation();
43 | if (!validation.isBlankable() && StringUtils.isEmpty(str)) {
44 | throw new CanNotBeBlankException();
45 | }
46 |
47 | if (validation.getOptions() != null && validation.getOptions().size() > 0 && !validation.getOptions().contains(str)) {
48 | throw new NotAllowValueException(validation.getOptions());
49 | }
50 |
51 | if (validation.getRegex() != null && !validation.getRegex().matcher(str).matches()) {
52 | throw new PatternNotMatchException(validation.getRegex());
53 | }
54 |
55 | super.setValue(str, field, o);
56 | }
57 |
58 | @Override
59 | protected void failWhenParse(int row, int col, Field field, ParseException e) {
60 | e.setField(field);
61 | e.setCellPosition(new DefaultCellPosition(row, col));
62 | checkedData.getExceptions().add(e);
63 | }
64 |
65 | @Override
66 | protected CheckedData newInstance(Class classType) {
67 | try {
68 | T data = (T) config.getClassType().newInstance();
69 | checkedData = new CheckedData<>();
70 | checkedData.setData(data);
71 | return checkedData;
72 | } catch (Exception e) {
73 | throw new IllegalArgumentException("wrong type or no default constructor", e);
74 | }
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/reader/DefaultSheetReader.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.reader;
2 |
3 | import cn.chenhuanming.octopus.config.Config;
4 | import cn.chenhuanming.octopus.config.Field;
5 | import cn.chenhuanming.octopus.exception.ParseException;
6 | import cn.chenhuanming.octopus.formatter.Formatter;
7 | import cn.chenhuanming.octopus.model.CellPosition;
8 | import cn.chenhuanming.octopus.util.CellUtils;
9 | import cn.chenhuanming.octopus.util.ReflectionUtils;
10 | import lombok.extern.slf4j.Slf4j;
11 | import org.apache.poi.ss.usermodel.Cell;
12 | import org.apache.poi.ss.usermodel.DateUtil;
13 | import org.apache.poi.ss.usermodel.Sheet;
14 |
15 | import java.util.Date;
16 |
17 | /**
18 | * @author chenhuanming
19 | * Created at 2019-01-06
20 | */
21 | @Slf4j
22 | public class DefaultSheetReader extends AbstractSheetReader {
23 |
24 | public DefaultSheetReader(Sheet sheet, Config config, CellPosition startPoint) {
25 | super(sheet, config, startPoint);
26 | }
27 |
28 | @Override
29 | int read(int row, int col, Field field, Object o) {
30 | if (field.isLeaf()) {
31 |
32 | try {
33 | Cell cell = sheet.getRow(row).getCell(col);
34 | String str;
35 | if (CellUtils.isDate(cell)) {
36 | Formatter dateFormatter = config.getFormatterContainer().get(Date.class);
37 | str = dateFormatter.format(DateUtil.getJavaDate(cell.getNumericCellValue()));
38 | } else {
39 | str = CellUtils.getCellValue(sheet, row, col, field.getDefaultValue());
40 | }
41 |
42 | setValue(str, field, o);
43 | } catch (ParseException e) {
44 | failWhenParse(row, col, field, e);
45 | }
46 |
47 | return col + 1;
48 | }
49 |
50 | Object instance = ReflectionUtils.newInstance(field.getPusher().getParameterTypes()[0]);
51 | for (Field child : field.getChildren()) {
52 | if (instance != null) {
53 | col = read(row, col, child, instance);
54 | try {
55 | field.getPusher().invoke(o, instance);
56 | } catch (Exception e) {
57 | log.error("failed to set " + instance + " into " + o, e);
58 | }
59 | }
60 | }
61 | return col;
62 | }
63 |
64 | protected void failWhenParse(int row, int col, final Field field, ParseException e) {
65 | log.error("failed to read value from " + field.getName() + " in excel(" + (row + 1) + "," + col + ")", e);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/reader/SheetReader.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.reader;
2 |
3 | /**
4 | * Sheet Reader
5 | * Created by Administrator on 2017-06-10.
6 | */
7 | public interface SheetReader extends Iterable {
8 | /**
9 | * Get data from data index i.
10 | * i is data index,not the sheet row index
11 | *
12 | * @param i data index
13 | * @return data
14 | */
15 | T get(int i);
16 |
17 | /**
18 | * Data count.
19 | *
20 | * @return data count
21 | */
22 | int size();
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/util/CellUtils.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.util;
2 |
3 | import org.apache.poi.ss.usermodel.Cell;
4 | import org.apache.poi.ss.usermodel.CellStyle;
5 | import org.apache.poi.ss.usermodel.DateUtil;
6 | import org.apache.poi.ss.usermodel.Row;
7 | import org.apache.poi.ss.usermodel.Sheet;
8 | import org.apache.poi.ss.util.CellRangeAddress;
9 | import org.apache.poi.ss.util.RegionUtil;
10 | import org.apache.poi.xssf.streaming.SXSSFSheet;
11 | import org.apache.poi.xssf.usermodel.XSSFSheet;
12 |
13 | /**
14 | * @author chenhuanming
15 | * Created at 2018/12/16
16 | */
17 | public class CellUtils {
18 |
19 | public static void setCellValue(Sheet sheet, int row, int col, String value, CellStyle cellStyle) {
20 | Row sheetRow = sheet.getRow(row);
21 | if (sheetRow == null) {
22 | sheetRow = sheet.createRow(row);
23 | }
24 | Cell cell = sheetRow.getCell(col);
25 | if (cell == null) {
26 | cell = sheetRow.createCell(col);
27 | }
28 | cell.setCellValue(value);
29 | cell.setCellStyle(cellStyle);
30 | }
31 |
32 | public static void setMergeRegionValue(Sheet sheet, int row, int lastRow, int col, int lastCol, String value, CellStyle cellStyle) {
33 | if (row == lastRow && col == lastCol) {
34 | setCellValue(sheet, row, col, value, cellStyle);
35 | } else {
36 | setCellValue(sheet, lastRow, col, value, cellStyle);
37 | setMergeRegion(sheet, row, lastRow, col, lastCol, cellStyle);
38 | }
39 | }
40 |
41 | public static void setMergeRegion(Sheet sheet, int row, int lastRow, int col, int lastCol, CellStyle cellStyle) {
42 |
43 | int i = sheet.addMergedRegion(new CellRangeAddress(row, lastRow, col, lastCol));
44 |
45 | /**
46 | * seems like a bug
47 | */
48 | CellRangeAddress region = sheet.getMergedRegion(sheet instanceof XSSFSheet || sheet instanceof SXSSFSheet ? i - 1 : i);
49 |
50 | RegionUtil.setBorderTop(cellStyle.getBorderTopEnum(), region, sheet);
51 | RegionUtil.setBorderLeft(cellStyle.getBorderLeftEnum(), region, sheet);
52 | RegionUtil.setBorderBottom(cellStyle.getBorderBottomEnum(), region, sheet);
53 | RegionUtil.setBorderRight(cellStyle.getBorderRightEnum(), region, sheet);
54 | }
55 |
56 | public static String getCellValue(Sheet sheet, int row, int col, String defaultValue) {
57 | Cell cell = sheet.getRow(row).getCell(col);
58 |
59 | if (cell == null) {
60 | return defaultValue;
61 | }
62 | switch (cell.getCellTypeEnum()) {
63 | case STRING:
64 | return cell.getStringCellValue();
65 | case FORMULA:
66 | case NUMERIC:
67 | return String.valueOf(cell.getNumericCellValue());
68 | case BOOLEAN:
69 | return String.valueOf(cell.getBooleanCellValue());
70 | default:
71 | return defaultValue;
72 | }
73 | }
74 |
75 | public static boolean isDate(Cell cell) {
76 | try {
77 | return DateUtil.isCellDateFormatted(cell);
78 | } catch (Exception e) {
79 | return false;
80 | }
81 | }
82 |
83 |
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/util/ColorUtils.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.util;
2 |
3 |
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.apache.poi.hssf.usermodel.HSSFCellStyle;
6 | import org.apache.poi.hssf.usermodel.HSSFFont;
7 | import org.apache.poi.hssf.usermodel.HSSFPalette;
8 | import org.apache.poi.hssf.usermodel.HSSFWorkbook;
9 | import org.apache.poi.hssf.util.HSSFColor;
10 | import org.apache.poi.ss.usermodel.CellStyle;
11 | import org.apache.poi.ss.usermodel.FillPatternType;
12 | import org.apache.poi.ss.usermodel.Font;
13 | import org.apache.poi.ss.usermodel.Workbook;
14 | import org.apache.poi.xssf.usermodel.XSSFCellStyle;
15 | import org.apache.poi.xssf.usermodel.XSSFColor;
16 | import org.apache.poi.xssf.usermodel.XSSFFont;
17 |
18 | import java.awt.*;
19 |
20 | /**
21 | * @author chenhuanming
22 | * Created at 2018/12/13
23 | */
24 | @Slf4j
25 | public class ColorUtils {
26 |
27 | /**
28 | * hex color to {@link java.awt.Color}
29 | *
30 | * @param colorStr e.g. "#FFFFFF"
31 | * @return null if colorStr is null
32 | */
33 | public static Color hex2Rgb(String colorStr) {
34 | if (colorStr == null) {
35 | return null;
36 | }
37 | return new Color(
38 | Integer.valueOf(colorStr.substring(1, 3), 16),
39 | Integer.valueOf(colorStr.substring(3, 5), 16),
40 | Integer.valueOf(colorStr.substring(5, 7), 16));
41 | }
42 |
43 | public static void setColor(Workbook workbook, Font font, Color color) {
44 | if (color == null) {
45 | return;
46 | }
47 | if (font instanceof XSSFFont) {
48 | ((XSSFFont) font).setColor(new XSSFColor(color));
49 | } else if (font instanceof HSSFFont && workbook instanceof HSSFWorkbook) {
50 | font.setColor(getSimilarColor((HSSFWorkbook) workbook, color).getIndex());
51 | } else {
52 | log.error("unknown font type");
53 | }
54 | }
55 |
56 | public static void setForegroundColor(Workbook workbook, CellStyle cellStyle, Color color) {
57 | if (color == null) {
58 | return;
59 | }
60 | cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
61 | if (cellStyle instanceof XSSFCellStyle) {
62 | ((XSSFCellStyle) cellStyle).setFillForegroundColor(new XSSFColor(color));
63 | } else if (cellStyle instanceof HSSFCellStyle && workbook instanceof HSSFWorkbook) {
64 | cellStyle.setFillForegroundColor(getSimilarColor((HSSFWorkbook) workbook, color).getIndex());
65 | } else {
66 | log.error("unknown font type");
67 | }
68 | }
69 |
70 | public static void setBorderColor(Workbook workbook, CellStyle cellStyle, Color[] color) {
71 | if (color == null) {
72 | return;
73 | }
74 | if (cellStyle instanceof XSSFCellStyle) {
75 | ((XSSFCellStyle) cellStyle).setTopBorderColor(new XSSFColor(color[0]));
76 | ((XSSFCellStyle) cellStyle).setRightBorderColor(new XSSFColor(color[1]));
77 | ((XSSFCellStyle) cellStyle).setBottomBorderColor(new XSSFColor(color[2]));
78 | ((XSSFCellStyle) cellStyle).setLeftBorderColor(new XSSFColor(color[3]));
79 | } else if (cellStyle instanceof HSSFCellStyle && workbook instanceof HSSFWorkbook) {
80 | cellStyle.setTopBorderColor(getSimilarColor((HSSFWorkbook) workbook, color[0]).getIndex());
81 | cellStyle.setRightBorderColor(getSimilarColor((HSSFWorkbook) workbook, color[1]).getIndex());
82 | cellStyle.setBottomBorderColor(getSimilarColor((HSSFWorkbook) workbook, color[2]).getIndex());
83 | cellStyle.setLeftBorderColor(getSimilarColor((HSSFWorkbook) workbook, color[3]).getIndex());
84 | } else {
85 | log.error("unknown font type");
86 | }
87 | }
88 |
89 | private static HSSFColor getSimilarColor(HSSFWorkbook workbook, Color color) {
90 | HSSFPalette palette = workbook.getCustomPalette();
91 | HSSFColor result = palette.findSimilarColor(color.getRed(), color.getGreen(), color.getBlue());
92 | return result == null ? HSSFColor.HSSFColorPredefined.AUTOMATIC.getColor() : result;
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/util/MethodHandleUtil.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.util;
2 |
3 | import java.lang.invoke.MethodHandle;
4 | import java.lang.invoke.MethodType;
5 | import java.lang.reflect.Field;
6 |
7 | import static java.lang.invoke.MethodHandles.lookup;
8 |
9 | /**
10 | * Created by chenhuanming on 2017-07-01.
11 | *
12 | * @author chenhuanming
13 | */
14 | public class MethodHandleUtil {
15 | /**
16 | * 获取Setter方法句柄
17 | */
18 | public static MethodHandle setterMethodHandle(Field field) {
19 | StringBuilder setter = new StringBuilder("set");
20 | String name = field.getName();
21 | setter.append(Character.toUpperCase(name.charAt(0))).append(name.substring(1));
22 |
23 | MethodType mt = MethodType.methodType(void.class, field.getType());
24 | try {
25 | return lookup().findVirtual(field.getDeclaringClass(), setter.toString(), mt);
26 | } catch (NoSuchMethodException e) {
27 | e.printStackTrace();
28 | throw new IllegalArgumentException("property " + name + " must has setter method");
29 | } catch (IllegalAccessException e) {
30 | e.printStackTrace();
31 | }
32 | throw new IllegalArgumentException("property " + name + " must has setter method");
33 | }
34 |
35 | /**
36 | * 获取Getter方法句柄
37 | */
38 | public static MethodHandle getterMethodHandle(Field field) {
39 | StringBuilder getter = new StringBuilder("get");
40 | String name = field.getName();
41 | getter.append(Character.toUpperCase(name.charAt(0))).append(name.substring(1));
42 |
43 | MethodType mt = MethodType.methodType(void.class, field.getType());
44 | try {
45 | return lookup().findVirtual(field.getDeclaringClass(), getter.toString(), mt);
46 | } catch (NoSuchMethodException e) {
47 | e.printStackTrace();
48 | throw new IllegalArgumentException("property " + name + " must has getter method");
49 | } catch (IllegalAccessException e) {
50 | e.printStackTrace();
51 | }
52 | throw new IllegalArgumentException("property " + name + " must has getter method");
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/util/ReflectionUtils.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.util;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 |
5 | import java.beans.PropertyDescriptor;
6 | import java.lang.reflect.Method;
7 |
8 | /**
9 | * @author chenhuanming
10 | * Created at 2018/12/13
11 | */
12 | @Slf4j
13 | public class ReflectionUtils {
14 |
15 | /**
16 | * call method and get the return string value,or return defaultValue
17 | *
18 | * @param method method
19 | * @param o which call method from
20 | * @param defaultValue default value
21 | * @return return of method,or default value when is null
22 | */
23 | public static String invokeReadMethod(Method method, Object o, String defaultValue) {
24 | if (method == null || o == null) {
25 | return defaultValue;
26 | }
27 | String value = defaultValue;
28 | Object o1 = invokeReadMethod(method, o);
29 | if (o1 != null) {
30 | value = String.valueOf(o1);
31 | }
32 |
33 | return value;
34 | }
35 |
36 | /**
37 | * call getter method and return value,or return null
38 | *
39 | * @param method method
40 | * @param o which call method from
41 | * @return return value fo read method
42 | * return null if failed or method is null
43 | */
44 | public static Object invokeReadMethod(Method method, Object o) {
45 | if (method == null) {
46 | return null;
47 | }
48 | try {
49 | return method.invoke(o);
50 | } catch (Exception e) {
51 | log.debug("failed to invoke " + method.getClass() + "#" + method.getName(), e);
52 | return null;
53 | }
54 | }
55 |
56 | public static Method readMethod(Class clazz, String fieldName) {
57 | if (clazz == null) {
58 | return null;
59 | }
60 | try {
61 | return new PropertyDescriptor(fieldName, clazz).getReadMethod();
62 | } catch (Exception e) {
63 | log.debug("failed to fetch getter method for field " + fieldName, e);
64 | return null;
65 | }
66 | }
67 |
68 | public static Method writeMethod(Class clazz, String fieldName) {
69 | if (clazz == null) {
70 | return null;
71 | }
72 | try {
73 | return new PropertyDescriptor(fieldName, clazz).getWriteMethod();
74 | } catch (Exception e) {
75 | log.debug("failed to fetch setter method for field " + fieldName, e);
76 | return null;
77 | }
78 | }
79 |
80 | public static T newInstance(Class clazz) {
81 | try {
82 | return clazz.newInstance();
83 | } catch (Exception e) {
84 | log.error("can not new instance through default constructor:" + clazz.getName());
85 | return null;
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/util/StringUtils.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.util;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2019-01-09
6 | */
7 | public class StringUtils {
8 | public static final String OPTION_SPLITTER_VERTICAL = "\\|";
9 | public static boolean isEmpty(String s) {
10 | return s == null || s.length() == 0;
11 | }
12 |
13 | public static String defaultIfEmpty(String s, String defaultValue) {
14 | return isEmpty(s) ? defaultValue : s;
15 | }
16 |
17 | public static boolean isNotEmpty(String s) {
18 | return !isEmpty(s);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/util/ValidationUtils.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.util;
2 |
3 | /**
4 | * @author guangdao
5 | * Created at 2019-05-06
6 | */
7 | public class ValidationUtils {
8 | public static void notEmpty(String s, String name) {
9 | if (StringUtils.isEmpty(s)) {
10 | throw new IllegalArgumentException(String.format("%s can not be empty", name));
11 | }
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/writer/AbstractSheetWriter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.writer;
2 |
3 | import cn.chenhuanming.octopus.config.Config;
4 | import cn.chenhuanming.octopus.config.Field;
5 | import cn.chenhuanming.octopus.formatter.Formatter;
6 | import cn.chenhuanming.octopus.model.CellPosition;
7 | import cn.chenhuanming.octopus.model.CellPositions;
8 | import cn.chenhuanming.octopus.model.WorkbookContext;
9 | import cn.chenhuanming.octopus.util.CellUtils;
10 | import cn.chenhuanming.octopus.util.ReflectionUtils;
11 | import cn.chenhuanming.octopus.util.StringUtils;
12 | import lombok.extern.slf4j.Slf4j;
13 | import org.apache.poi.ss.usermodel.Sheet;
14 |
15 | import java.util.Collection;
16 |
17 | /**
18 | * Created by chenhuanming on 2017-07-01.
19 | *
20 | * @author chenhuanming
21 | */
22 | @Slf4j
23 | public abstract class AbstractSheetWriter implements SheetWriter {
24 | protected Config config;
25 | protected HeaderWriter headerWriter;
26 | protected CellPosition startPosition;
27 |
28 | public AbstractSheetWriter(Config config, HeaderWriter headerWriter, CellPosition startPosition) {
29 | this.config = config;
30 | this.headerWriter = headerWriter;
31 | this.startPosition = startPosition;
32 | }
33 |
34 | /**
35 | * Writes the content after writing the header.
36 | *
37 | * @param sheet sheet
38 | * @param data data
39 | * @param startPosition start position to write
40 | * @param workbookContext workbook context
41 | * @return content last position
42 | */
43 | abstract CellPosition writeContent(Sheet sheet, Collection data, CellPosition startPosition, WorkbookContext workbookContext);
44 |
45 | @Override
46 | public CellPosition write(Sheet sheet, Collection data) {
47 | if (data == null || data.size() == 0) {
48 | return CellPositions.POSITION_ZERO_ZERO;
49 | }
50 |
51 | Class dataType = data.iterator().next().getClass();
52 | if (config.getClassType() != dataType) {
53 | throw new IllegalArgumentException("class of config is " + config.getClassType().getName() + " but type of data is " + dataType.getName());
54 | }
55 |
56 | WorkbookContext workbookContext = new WorkbookContext(sheet.getWorkbook());
57 |
58 | if (headerWriter != null) {
59 | CellPosition headerEndPos = headerWriter.drawHeader(sheet, startPosition, config.getFields());
60 | return writeContent(sheet, data, CellPositions.of(headerEndPos.getRow() + 1, startPosition.getCol()), workbookContext);
61 | } else {
62 | return writeContent(sheet, data, startPosition, workbookContext);
63 | }
64 |
65 | }
66 |
67 | /**
68 | * Writes field recursively in the DFS(Depth First Search)
69 | *
70 | * @param sheet sheet
71 | * @param row row index,starts from 0
72 | * @param col column index,starts from 0
73 | * @param field field config
74 | * @param o data object
75 | * @param workbookContext workbook context
76 | * @return next column index,aka col+1
77 | */
78 | protected int writeField(Sheet sheet, final int row, final int col, Field field, Object o, WorkbookContext workbookContext) {
79 | if (field.isLeaf()) {
80 | writeCell(sheet, row, col, field, o, workbookContext);
81 | return col + 1;
82 | }
83 |
84 | Object value = null;
85 | if (o != null) {
86 | value = ReflectionUtils.invokeReadMethod(field.getPicker(), o);
87 | }
88 | int c = col;
89 | for (Field child : field.getChildren()) {
90 | c = writeField(sheet, row, c, child, value, workbookContext);
91 | }
92 | return c;
93 | }
94 |
95 | /**
96 | * Writes one cell with formatter of field config.
97 | * It is convenient when you customize sheet content with field config.
98 | *
99 | * @param sheet sheet
100 | * @param row row index,starts from 0
101 | * @param col column index,starts from 0
102 | * @param field field config
103 | * @param data data value
104 | * @param workbookContext workbook context
105 | */
106 | protected void writeCell(Sheet sheet, final int row, final int col, Field field, Object data, WorkbookContext workbookContext) {
107 | if (data == null) {
108 | return;
109 | }
110 |
111 | //Apply special formatter if not null
112 | if (field.getFormatter() != null) {
113 | String value = field.getFormatter().format(ReflectionUtils.invokeReadMethod(field.getPicker(), data));
114 | CellUtils.setCellValue(sheet, row, col, value, workbookContext.getCellStyle(field));
115 | return;
116 | }
117 |
118 | //Try Applying global formatter of container
119 | Formatter formatter = config.getFormatterContainer().get(field.getPicker().getReturnType());
120 |
121 | //Set data.toString() if no formatter
122 | if (formatter == null) {
123 | String value = ReflectionUtils.invokeReadMethod(field.getPicker(), data, field.getDefaultValue());
124 | CellUtils.setCellValue(sheet, row, col, value, workbookContext.getCellStyle(field));
125 | return;
126 | }
127 |
128 | //Apply formatter and set value
129 | String value = formatter.format(ReflectionUtils.invokeReadMethod(field.getPicker(), data));
130 | CellUtils.setCellValue(sheet, row, col, StringUtils.isEmpty(value) ? field.getDefaultValue() : value, workbookContext.getCellStyle(field));
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/writer/AutoSizeSheetWriter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.writer;
2 |
3 | import cn.chenhuanming.octopus.model.CellPosition;
4 | import org.apache.poi.ss.usermodel.Sheet;
5 |
6 | import java.util.Collection;
7 |
8 | /**
9 | * Auto size all content,but width of config will be ignored.
10 | * This process can be relatively slow on large sheets.
11 | *
12 | * @author guangdao
13 | * Created at 2021-08-15
14 | * @since 1.1.5
15 | */
16 | public class AutoSizeSheetWriter implements SheetWriter {
17 | private final SheetWriter sheetWriter;
18 |
19 | public AutoSizeSheetWriter(SheetWriter sheetWriter) {
20 | this.sheetWriter = sheetWriter;
21 | }
22 |
23 | @Override
24 | public CellPosition write(Sheet sheet, Collection data) {
25 | CellPosition end = sheetWriter.write(sheet, data);
26 | for (int i = 0; i < end.getCol(); i++) {
27 | sheet.autoSizeColumn(i, true);
28 | }
29 | return end;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/writer/DefaultExcelWriter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.writer;
2 |
3 | import org.apache.poi.ss.usermodel.Sheet;
4 | import org.apache.poi.ss.usermodel.Workbook;
5 | import org.apache.poi.xssf.usermodel.XSSFWorkbook;
6 |
7 | import java.io.IOException;
8 | import java.io.OutputStream;
9 | import java.util.Collection;
10 |
11 | /**
12 | * @author chenhuanming
13 | * Created at 2018/12/19
14 | */
15 | public class DefaultExcelWriter implements ExcelWriter {
16 | private final Workbook workbook;
17 | private final OutputStream os;
18 |
19 | public DefaultExcelWriter(OutputStream os) {
20 | this(new XSSFWorkbook(), os);
21 | }
22 |
23 | public DefaultExcelWriter(Workbook workbook, OutputStream os) {
24 | this.workbook = workbook;
25 | this.os = os;
26 | }
27 |
28 | @Override
29 | public ExcelWriter write(String sheetName, SheetWriter sheetWriter, Collection data) {
30 | Sheet sheet = workbook.createSheet(sheetName);
31 | sheetWriter.write(sheet, data);
32 | return this;
33 | }
34 |
35 | @Override
36 | public void close() throws IOException {
37 | workbook.write(os);
38 | workbook.close();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/writer/DefaultHeaderWriter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.writer;
2 |
3 | import cn.chenhuanming.octopus.config.Field;
4 | import cn.chenhuanming.octopus.model.CellPosition;
5 | import cn.chenhuanming.octopus.model.DefaultCellPosition;
6 | import cn.chenhuanming.octopus.model.WorkbookContext;
7 | import cn.chenhuanming.octopus.util.CellUtils;
8 | import lombok.AllArgsConstructor;
9 | import lombok.Data;
10 | import org.apache.poi.ss.usermodel.CellStyle;
11 | import org.apache.poi.ss.usermodel.Sheet;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | /**
17 | * @author chenhuanming
18 | * Created at 2018/12/15
19 | */
20 | public class DefaultHeaderWriter implements HeaderWriter {
21 | @Override
22 | public CellPosition drawHeader(Sheet sheet, CellPosition startPoint, List fields) {
23 |
24 | SupportField supportField = new SupportField(fields);
25 |
26 | int row = startPoint.getRow() - 1;
27 | int col = startPoint.getCol();
28 |
29 | int lastRow = row + supportField.getHeight() - 1;
30 | int lastCol = col + supportField.getWidth() - 1;
31 |
32 | WorkbookContext bookResource = new WorkbookContext(sheet.getWorkbook());
33 |
34 | drawHeaderImpl(sheet, row, lastRow, col, lastCol, supportField, bookResource);
35 |
36 | return new DefaultCellPosition(lastRow, lastCol);
37 | }
38 |
39 | private Cost drawHeaderImpl(Sheet sheet, int row, int lastRow, int col, int lastCol, SupportField header, WorkbookContext bookResource) {
40 | Field field = header.getField();
41 | if (header.isLeaf()) {
42 | CellStyle style = bookResource.getHeaderStyle(field);
43 | //set value into the bottom left cell
44 | if (header.getHeight() == 1) {
45 | CellUtils.setCellValue(sheet, lastRow, col, field.getDescription(), style);
46 | } else {
47 | //cost its need
48 | CellUtils.setCellValue(sheet, lastRow - header.getHeight() + 1, col, field.getDescription(), style);
49 | CellUtils.setMergeRegion(sheet, lastRow - header.getHeight() + 1, lastRow, col, col, style);
50 | }
51 | return new Cost(header.getHeight(), 1);
52 | }
53 |
54 | int costRow = 0;
55 | int c = col;
56 | for (SupportField headerChildren : header.getHeaderChildren()) {
57 | Cost cost = drawHeaderImpl(sheet, row + 1, lastRow, c, col + header.getWidth() - 1, headerChildren, bookResource);
58 | c += cost.getColNum();
59 | costRow = cost.getRowNum();
60 | }
61 |
62 | if (field != null) {
63 | CellStyle style = bookResource.getHeaderStyle(field);
64 | CellUtils.setMergeRegionValue(sheet, row, lastRow - costRow, col, col + header.getWidth() - 1,
65 | field.getDescription(), style);
66 | }
67 |
68 | return new Cost(header.getHeight(), header.getWidth());
69 | }
70 |
71 | @Data
72 | @AllArgsConstructor
73 | private class Cost {
74 | private int rowNum;
75 | private int colNum;
76 | }
77 |
78 | /**
79 | * help to calculate height and width
80 | *
81 | * @author chenhuanming
82 | * Created at 2018/12/14
83 | */
84 | @Data
85 | private static class SupportField {
86 | private Field field;
87 | private int height;
88 | private int width;
89 | private List headerChildren;
90 |
91 | private SupportField(List fields) {
92 | if (fields == null || fields.size() == 0) {
93 | this.height = 1;
94 | this.width = 1;
95 | return;
96 | }
97 | this.headerChildren = new ArrayList<>(fields.size());
98 | int h = 1;
99 | int w = 0;
100 | for (Field child : fields) {
101 | SupportField header = new SupportField(child);
102 | h = Math.max(h, header.getHeight());
103 | w += header.width;
104 | headerChildren.add(header);
105 | }
106 |
107 | //height of all children is the max value of them
108 | for (SupportField child : headerChildren) {
109 | child.setHeight(h);
110 | }
111 | this.height = h + 1;
112 | this.width = w;
113 | }
114 |
115 | private SupportField(Field field) {
116 | this(field.getChildren());
117 | this.field = field;
118 | }
119 |
120 | public boolean isLeaf() {
121 | return getHeaderChildren() == null || getHeaderChildren().size() == 0;
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/writer/DefaultSheetWriter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.writer;
2 |
3 | import cn.chenhuanming.octopus.config.Config;
4 | import cn.chenhuanming.octopus.config.Field;
5 | import cn.chenhuanming.octopus.model.CellPosition;
6 | import cn.chenhuanming.octopus.model.CellPositions;
7 | import cn.chenhuanming.octopus.model.DefaultCellPosition;
8 | import cn.chenhuanming.octopus.model.WorkbookContext;
9 | import org.apache.poi.ss.usermodel.Sheet;
10 |
11 | import java.util.Collection;
12 |
13 | /**
14 | * Default sheet writer,writes sheet header and content starting on start point
15 | *
16 | * @author chenhuanming
17 | * Created at 2018/12/16
18 | */
19 | public class DefaultSheetWriter extends AbstractSheetWriter {
20 |
21 | public DefaultSheetWriter(Config config, HeaderWriter headerWriter, CellPosition startPoint) {
22 | super(config, headerWriter, startPoint);
23 | }
24 |
25 | public DefaultSheetWriter(Config config, CellPosition startPoint) {
26 | super(config, new DefaultHeaderWriter(), startPoint);
27 | }
28 |
29 | public DefaultSheetWriter(Config config) {
30 | this(config, new DefaultHeaderWriter(), CellPositions.POSITION_ZERO_ZERO);
31 | }
32 |
33 | @Override
34 | CellPosition writeContent(Sheet sheet, Collection data, CellPosition startPosition, WorkbookContext workbookContext) {
35 | int row = startPosition.getRow();
36 | int col = startPosition.getCol();
37 |
38 | for (T t : data) {
39 | col = startPosition.getCol();
40 | for (Field field : config.getFields()) {
41 | col = writeField(sheet, row, col, field, t, workbookContext);
42 | }
43 | row++;
44 | }
45 | return new DefaultCellPosition(row, col);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/writer/ExcelWriter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.writer;
2 |
3 | import java.io.Closeable;
4 | import java.util.Collection;
5 |
6 | /**
7 | * @author chenhuanming
8 | * Created at 2018/12/19
9 | */
10 | public interface ExcelWriter extends Closeable {
11 | /**
12 | * Create new sheet and write data into it
13 | *
14 | * @param sheetName new sheet name
15 | * @param sheetWriter sheet writer
16 | * @param data data
17 | * @param data type
18 | * @return this
19 | */
20 | ExcelWriter write(String sheetName, SheetWriter sheetWriter, Collection data);
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/writer/HeaderWriter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.writer;
2 |
3 | import cn.chenhuanming.octopus.config.Field;
4 | import cn.chenhuanming.octopus.model.CellPosition;
5 | import org.apache.poi.ss.usermodel.Sheet;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * @author chenhuanming
11 | * Created at 2018/12/15
12 | */
13 | public interface HeaderWriter {
14 | /**
15 | * Draw header into sheet according to fields config
16 | *
17 | * @param sheet sheet
18 | * @param startPoint where to start to draw header
19 | * @param fields field config
20 | * @return draw end position
21 | */
22 | CellPosition drawHeader(Sheet sheet, CellPosition startPoint, List fields);
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/writer/NoHeaderSheetWriter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.writer;
2 |
3 | import cn.chenhuanming.octopus.config.Config;
4 | import cn.chenhuanming.octopus.model.CellPosition;
5 | import cn.chenhuanming.octopus.model.CellPositions;
6 |
7 | /**
8 | * @author guangdao
9 | * Created at 2021-08-20
10 | */
11 | public class NoHeaderSheetWriter extends DefaultSheetWriter {
12 | public NoHeaderSheetWriter(Config config, CellPosition startPoint) {
13 | super(config, null, startPoint);
14 | }
15 |
16 | public NoHeaderSheetWriter(Config config) {
17 | super(config, null, CellPositions.POSITION_ZERO_ZERO);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/cn/chenhuanming/octopus/writer/SheetWriter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.writer;
2 |
3 | import cn.chenhuanming.octopus.model.CellPosition;
4 | import org.apache.poi.ss.usermodel.Sheet;
5 |
6 | import java.util.Collection;
7 |
8 | /**
9 | * writes data into a sheet
10 | * Created by chenhuanming on 2017-07-01.
11 | *
12 | * @author chenhuanming
13 | */
14 | public interface SheetWriter {
15 |
16 | CellPosition write(Sheet sheet, Collection data);
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/resources/octopus.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/config/ConfigFactoryTest.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.config;
2 |
3 | import cn.chenhuanming.octopus.config.annotation.AnnotationConfigFactory;
4 | import cn.chenhuanming.octopus.entity.Applicants;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | import java.io.InputStream;
9 |
10 | /**
11 | * @author guangdao
12 | * Created at 2019-05-07
13 | */
14 | public class ConfigFactoryTest {
15 |
16 | @Test
17 | public void assertSameConfig() {
18 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("applicants.xml");
19 | Config xmlConfig = new XmlConfigFactory(is).getConfig();
20 | Config javaConfig = new AnnotationConfigFactory(Applicants.class).getConfig();
21 |
22 | Assert.assertEquals(xmlConfig.getClassType(), javaConfig.getClassType());
23 |
24 | Assert.assertEquals(xmlConfig.getFields(), javaConfig.getFields());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/entity/Address.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.entity;
2 |
3 | import cn.chenhuanming.octopus.config.annotation.Field;
4 | import cn.chenhuanming.octopus.config.annotation.Sheet;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | /**
10 | * @author chenhuanming
11 | * Created at 2018/12/18
12 | */
13 | @Data
14 | @AllArgsConstructor
15 | @NoArgsConstructor
16 | @Sheet
17 | public class Address {
18 | @Field(description = "City")
19 | private String city;
20 | @Field(description = "Detail")
21 | private String detail;
22 | }
23 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/entity/Applicants.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.entity;
2 |
3 | import cn.chenhuanming.octopus.config.annotation.Field;
4 | import cn.chenhuanming.octopus.config.annotation.Formatter;
5 | import cn.chenhuanming.octopus.config.annotation.Header;
6 | import cn.chenhuanming.octopus.config.annotation.Sheet;
7 | import cn.chenhuanming.octopus.formatter.BigDecimalFormatter;
8 | import lombok.Data;
9 | import lombok.NoArgsConstructor;
10 |
11 | import java.math.BigDecimal;
12 | import java.util.Date;
13 |
14 | /**
15 | * @author chenhuanming
16 | * Created at 2018/12/18
17 | */
18 | @Data
19 | @NoArgsConstructor
20 | @Sheet(formatters = {
21 | @Formatter(target = BigDecimal.class, format = BigDecimalFormatter.class),
22 | })
23 | public class Applicants {
24 | @Field(description = "Value", color = "#74f441", width = 50)
25 | private int id;
26 | @Field(description = "Name", fontSize = 20, border = "0,2,0,2", borderColor = ",#4242f4,,#4242f4")
27 | private String name;
28 | @Header(description = "Job", headerColor = "#4286f4")
29 | private Job job;
30 | @Field(description = "Entry Date", dateFormat = "yyyy-MM-dd HH:mm::ss")
31 | private Date entryDate;
32 | @Field(description = "Working/Leaved", options = "Working|Leaved",
33 | formatter = cn.chenhuanming.octopus.formatter.WorkingFormatter.class, color = "#42f4b9")
34 | private boolean working = true;
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/entity/Company.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.entity;
2 |
3 | import cn.chenhuanming.octopus.config.annotation.Field;
4 | import cn.chenhuanming.octopus.config.annotation.Header;
5 | import cn.chenhuanming.octopus.config.annotation.Sheet;
6 | import lombok.AllArgsConstructor;
7 | import lombok.Data;
8 | import lombok.NoArgsConstructor;
9 |
10 | /**
11 | * @author chenhuanming
12 | * Created at 2019-01-11
13 | */
14 | @AllArgsConstructor
15 | @NoArgsConstructor
16 | @Data
17 | @Sheet
18 | public class Company {
19 | @Field(description = "Company Name")
20 | private String name;
21 | @Header(description = "Address")
22 | private Address address;
23 | //@Field(description = "Status") //因为导入样例表没有这列 所以注释: 嗯好像是体现了 xml 的灵活性
24 | private String status;
25 |
26 | public Company(String name, Address address) {
27 | this.name = name;
28 | this.address = address;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/entity/Job.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.entity;
2 |
3 | import cn.chenhuanming.octopus.config.annotation.Field;
4 | import cn.chenhuanming.octopus.config.annotation.Header;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | /**
10 | * @author chenhuanming
11 | * Created at 2018/12/18
12 | */
13 | @Data
14 | @AllArgsConstructor
15 | @NoArgsConstructor
16 | public class Job {
17 | @Header(description = "Company Name")
18 | private Company company;
19 | @Field(description = "JD")
20 | private String JobDescription;
21 | @Field(description = "Rank", color = "#f44271", foregroundColor = "#eaeae5")
22 | private String rank;
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/example/AddressExample.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.example;
2 |
3 | import cn.chenhuanming.octopus.Octopus;
4 | import cn.chenhuanming.octopus.config.Config;
5 | import cn.chenhuanming.octopus.config.XmlConfigFactory;
6 | import cn.chenhuanming.octopus.config.annotation.AnnotationConfigFactory;
7 | import cn.chenhuanming.octopus.entity.Address;
8 | import org.fluttercode.datafactory.impl.DataFactory;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 |
12 | import java.io.FileOutputStream;
13 | import java.io.InputStream;
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * @author chenhuanming
19 | * Created at 2019-01-11
20 | */
21 | public class AddressExample {
22 | List addresses;
23 |
24 | /**
25 | * preparing testing data
26 | */
27 | @Before
28 | public void prepare() {
29 | addresses = new ArrayList<>();
30 | DataFactory df = new DataFactory();
31 | for (int i = 0; i < df.getNumberBetween(5, 10); i++) {
32 | addresses.add(new Address(df.getCity(), df.getAddress()));
33 | }
34 | }
35 |
36 | @Test
37 | public void export() throws Exception {
38 |
39 | //where to export
40 | String rootPath = this.getClass().getClassLoader().getResource("").getPath();
41 | FileOutputStream os = new FileOutputStream(rootPath + "/address.xlsx");
42 |
43 | //get config from xml file.Singleton pattern is recommending
44 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("address.xml");
45 | Config config = new XmlConfigFactory(is).getConfig();
46 |
47 | //just one line of code with Octopus facade
48 | Octopus.writeOneSheet(os, config, "address", addresses);
49 | }
50 |
51 | @Test
52 | public void exportWithAnnotation() throws Exception {
53 | //where to export
54 | String rootPath = this.getClass().getClassLoader().getResource("").getPath();
55 | FileOutputStream os = new FileOutputStream(rootPath + "/address1.xlsx");
56 |
57 | Config config = new AnnotationConfigFactory(Address.class).getConfig();
58 |
59 | Octopus.writeOneSheet(os, config, "address", addresses);
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/example/AdvancedExample.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.example;
2 |
3 | import cn.chenhuanming.octopus.config.Config;
4 | import cn.chenhuanming.octopus.config.XmlConfigFactory;
5 | import cn.chenhuanming.octopus.entity.Address;
6 | import cn.chenhuanming.octopus.entity.Applicants;
7 | import cn.chenhuanming.octopus.entity.Company;
8 | import cn.chenhuanming.octopus.model.CellPosition;
9 | import cn.chenhuanming.octopus.model.CellPositions;
10 | import cn.chenhuanming.octopus.test.AbstractTest;
11 | import cn.chenhuanming.octopus.writer.AutoSizeSheetWriter;
12 | import cn.chenhuanming.octopus.writer.DefaultExcelWriter;
13 | import cn.chenhuanming.octopus.writer.DefaultSheetWriter;
14 | import cn.chenhuanming.octopus.writer.ExcelWriter;
15 | import cn.chenhuanming.octopus.writer.NoHeaderSheetWriter;
16 | import org.apache.poi.ss.usermodel.Sheet;
17 | import org.apache.poi.ss.usermodel.Workbook;
18 | import org.apache.poi.xssf.usermodel.XSSFWorkbook;
19 | import org.fluttercode.datafactory.impl.DataFactory;
20 | import org.junit.Test;
21 |
22 | import java.io.FileOutputStream;
23 | import java.io.IOException;
24 | import java.util.ArrayList;
25 | import java.util.List;
26 |
27 | /**
28 | * Advanced example for customized
29 | *
30 | * @author guangdao
31 | * Created at 2021-08-20
32 | */
33 | public class AdvancedExample extends AbstractTest {
34 |
35 | Config config = new XmlConfigFactory(this.getClass().getClassLoader().getResourceAsStream("applicants.xml")).getConfig();
36 |
37 | /**
38 | * Auto size content
39 | */
40 | @Test
41 | public void autoSize() throws IOException {
42 | try (
43 | FileOutputStream os = new FileOutputStream(this.getClass().getClassLoader().getResource("").getPath() + "/autoSize.xlsx");
44 | ExcelWriter excelWriter = new DefaultExcelWriter(os)
45 | ) {
46 | excelWriter.write("autoSize", new AutoSizeSheetWriter(new DefaultSheetWriter(config)), applicantsList);
47 | }
48 |
49 | }
50 |
51 | /**
52 | * Write several sheets in one workbook
53 | */
54 | @Test
55 | public void multiSheet() throws IOException {
56 | try (
57 | FileOutputStream os = new FileOutputStream(this.getClass().getClassLoader().getResource("").getPath() + "/multiSheet.xlsx");
58 | ExcelWriter excelWriter = new DefaultExcelWriter(os)
59 | ) {
60 | excelWriter.write("autoSize", new AutoSizeSheetWriter(new DefaultSheetWriter(config)), applicantsList)
61 | .write("default", new DefaultSheetWriter(config), applicantsList)
62 | .write("noHeader", new NoHeaderSheetWriter(config), applicantsList);
63 | }
64 | }
65 |
66 | /**
67 | * Append data in one sheet
68 | */
69 | @Test
70 | public void append() throws IOException {
71 | //Prepare company data
72 | List companies = new ArrayList<>();
73 | DataFactory df = new DataFactory();
74 | for (int i = 0; i < df.getNumberBetween(5, 10); i++) {
75 | companies.add(new Company(df.getBusinessName(), new Address(df.getCity(), df.getAddress())));
76 | }
77 |
78 | try (
79 | FileOutputStream os = new FileOutputStream(this.getClass().getClassLoader().getResource("").getPath() + "/append.xlsx");
80 | Workbook workbook = new XSSFWorkbook();
81 | ) {
82 | //Create one sheet
83 | Sheet sheet = workbook.createSheet("append");
84 | CellPosition startPosition = CellPositions.of(0, 0);
85 |
86 | //Write applicants
87 | CellPosition endPosition = new DefaultSheetWriter(config).write(sheet, applicantsList);
88 |
89 | //Append company data to next row
90 | Config companyConfig = new XmlConfigFactory(this.getClass().getClassLoader().getResourceAsStream("company.xml")).getConfig();
91 | new DefaultSheetWriter(companyConfig, CellPositions.of(endPosition.getRow() + 1, startPosition.getCol())).write(sheet, companies);
92 |
93 | //Must call write method,otherwise sheet is empty
94 | workbook.write(os);
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/example/ApplicantExample.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.example;
2 |
3 | import cn.chenhuanming.octopus.Octopus;
4 | import cn.chenhuanming.octopus.config.Config;
5 | import cn.chenhuanming.octopus.config.XmlConfigFactory;
6 | import cn.chenhuanming.octopus.config.annotation.AnnotationConfigFactory;
7 | import cn.chenhuanming.octopus.entity.Applicants;
8 | import cn.chenhuanming.octopus.test.AbstractTest;
9 | import org.junit.Test;
10 |
11 | import java.io.FileOutputStream;
12 | import java.io.IOException;
13 |
14 | /**
15 | * @author chenhuanming
16 | * Created at 2019-01-12
17 | */
18 | public class ApplicantExample extends AbstractTest {
19 | @Test
20 | public void export() throws IOException {
21 | String rootPath = this.getClass().getClassLoader().getResource("").getPath();
22 | FileOutputStream os = new FileOutputStream(rootPath + "/applicator.xlsx");
23 |
24 | Config config = new XmlConfigFactory(this.getClass().getClassLoader().getResourceAsStream("applicants.xml")).getConfig();
25 |
26 | Octopus.writeOneSheet(os, config, "test", applicantsList);
27 | }
28 |
29 | @Test
30 | public void exportWithAnnotation() throws IOException {
31 | String rootPath = this.getClass().getClassLoader().getResource("").getPath();
32 | FileOutputStream os = new FileOutputStream(rootPath + "/applicator1.xlsx");
33 |
34 | Config config = new AnnotationConfigFactory(Applicants.class).getConfig();
35 |
36 | Octopus.writeOneSheet(os, config, "test", applicantsList);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/example/CompanyExample.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.example;
2 |
3 | import cn.chenhuanming.octopus.Octopus;
4 | import cn.chenhuanming.octopus.config.Config;
5 | import cn.chenhuanming.octopus.config.XmlConfigFactory;
6 | import cn.chenhuanming.octopus.config.annotation.AnnotationConfigFactory;
7 | import cn.chenhuanming.octopus.entity.Address;
8 | import cn.chenhuanming.octopus.entity.Company;
9 | import cn.chenhuanming.octopus.model.CellPositions;
10 | import cn.chenhuanming.octopus.model.CheckedData;
11 | import cn.chenhuanming.octopus.model.DefaultCellPosition;
12 | import cn.chenhuanming.octopus.reader.SheetReader;
13 | import org.fluttercode.datafactory.impl.DataFactory;
14 | import org.junit.Before;
15 | import org.junit.Test;
16 |
17 | import java.io.FileInputStream;
18 | import java.io.FileOutputStream;
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.util.ArrayList;
22 | import java.util.List;
23 |
24 | /**
25 | * @author chenhuanming
26 | * Created at 2019-01-12
27 | */
28 | public class CompanyExample {
29 | List companies;
30 |
31 | /**
32 | * preparing testing data
33 | */
34 | @Before
35 | public void prepare() {
36 | companies = new ArrayList<>();
37 | DataFactory df = new DataFactory();
38 | for (int i = 0; i < df.getNumberBetween(5, 10); i++) {
39 | companies.add(new Company(df.getBusinessName(), new Address(df.getCity(), df.getAddress())));
40 | }
41 | }
42 |
43 | @Test
44 | public void export() throws Exception {
45 |
46 | //where to export
47 | String rootPath = this.getClass().getClassLoader().getResource("").getPath();
48 | FileOutputStream os = new FileOutputStream(rootPath + "/company.xlsx");
49 |
50 | //get config from xml file.Singleton pattern is recommending
51 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("company.xml");
52 | Config config = new XmlConfigFactory(is).getConfig();
53 |
54 | Octopus.writeOneSheet(os, config, "company", companies);
55 | }
56 |
57 | @Test
58 | public void exportWithAnnotation() throws Exception {
59 | //where to export
60 | String rootPath = this.getClass().getClassLoader().getResource("").getPath();
61 | FileOutputStream os = new FileOutputStream(rootPath + "/company1.xlsx");
62 |
63 | Config config = new AnnotationConfigFactory(Company.class).getConfig();
64 |
65 | Octopus.writeOneSheet(os, config, "company", companies);
66 | }
67 |
68 | @Test
69 | public void exportAndImport() throws Exception {
70 |
71 | //where to export
72 | String rootPath = this.getClass().getClassLoader().getResource("").getPath();
73 | FileOutputStream os = new FileOutputStream(rootPath + "/company2.xlsx");
74 |
75 | //read config from company.xml
76 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("company2.xml");
77 | Config config = new XmlConfigFactory(is).getConfig();
78 |
79 | try {
80 | Octopus.writeOneSheet(os, config, "company2", companies);
81 | } catch (IOException e) {
82 | System.out.println("export failed");
83 | }
84 |
85 | //Import Excel
86 |
87 | //First get the excel file
88 | FileInputStream fis = new FileInputStream(rootPath + "/company2.xlsx");
89 |
90 | SheetReader importData = Octopus.readFirstSheet(fis, config, CellPositions.getContentStartPosition(CellPositions.POSITION_ZERO_ZERO, config));
91 |
92 | for (Company company : importData) {
93 | System.out.println(company);
94 | }
95 | }
96 |
97 | @Test
98 | public void importCheckedData() throws Exception {
99 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("wrongCompany.xlsx");
100 |
101 | Config config = new XmlConfigFactory(
102 | this.getClass().getClassLoader().getResourceAsStream("company3.xml")).getConfig();
103 |
104 | final SheetReader> sheetReader = Octopus
105 | .readFirstSheetWithValidation(is, config, new DefaultCellPosition(1, 0));
106 |
107 | for (CheckedData checkedData : sheetReader) {
108 | System.out.println(checkedData);
109 | }
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/formatter/AddressFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | import cn.chenhuanming.octopus.entity.Address;
4 | import cn.chenhuanming.octopus.exception.ParseException;
5 | import lombok.EqualsAndHashCode;
6 |
7 | /**
8 | * @author chenhuanming
9 | * Created at 2018/12/19
10 | */
11 | @EqualsAndHashCode
12 | public class AddressFormatter implements Formatter {
13 | @Override
14 | public String format(Address address) {
15 | return address.getCity() + "," + address.getDetail();
16 | }
17 |
18 | @Override
19 | public Address parse(String str) throws ParseException {
20 | String[] split = str.split(",");
21 | if (split.length != 2) {
22 | return null;
23 | }
24 | return new Address(split[0], split[1]);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/formatter/BigDecimalFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | import lombok.EqualsAndHashCode;
4 |
5 | import java.math.BigDecimal;
6 |
7 | /**
8 | * @author : youthlin.chen @ 2019-04-26 17:23
9 | */
10 | @EqualsAndHashCode
11 | public class BigDecimalFormatter extends AbstractFormatter {
12 | @Override
13 | public String format(BigDecimal bigDecimal) {
14 | return String.valueOf(bigDecimal);
15 | }
16 |
17 | @Override
18 | public BigDecimal parseImpl(String str) throws Exception {
19 | return new BigDecimal(str);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/formatter/WorkingFormatter.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.formatter;
2 |
3 | import lombok.EqualsAndHashCode;
4 |
5 | /**
6 | * @author chenhuanming
7 | * Created at 2019-01-09
8 | */
9 | @EqualsAndHashCode
10 | public class WorkingFormatter extends AbstractFormatter {
11 | @Override
12 | public Boolean parseImpl(String str) throws Exception {
13 | return "Working".equals(str);
14 | }
15 |
16 | @Override
17 | public String format(Boolean aBoolean) {
18 | return aBoolean ? "Working" : "Leaved";
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/model/CellPositionsTest.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.model;
2 |
3 | import cn.chenhuanming.octopus.config.Config;
4 | import cn.chenhuanming.octopus.config.annotation.AnnotationConfigFactory;
5 | import cn.chenhuanming.octopus.entity.Applicants;
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | /**
10 | * @author guangdao
11 | * Created at 2021-08-19
12 | */
13 | public class CellPositionsTest {
14 |
15 | @Test
16 | public void getContentStartPosition() {
17 | Config config = new AnnotationConfigFactory(Applicants.class).getConfig();
18 |
19 | CellPosition contentStartPos = CellPositions.getContentStartPosition(CellPositions.POSITION_ZERO_ZERO, config);
20 |
21 | Assert.assertEquals(CellPositions.of(4, 0), contentStartPos);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/test/AbstractTest.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.test;
2 |
3 | import cn.chenhuanming.octopus.entity.Address;
4 | import cn.chenhuanming.octopus.entity.Applicants;
5 | import cn.chenhuanming.octopus.entity.Company;
6 | import cn.chenhuanming.octopus.entity.Job;
7 | import org.fluttercode.datafactory.impl.DataFactory;
8 | import org.junit.Before;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * @author chenhuanming
15 | * Created at 2019-01-09
16 | */
17 | public class AbstractTest {
18 | protected List applicantsList;
19 |
20 | protected Integer number = 5;
21 |
22 | @Before
23 | public void before() {
24 | applicantsList = new ArrayList<>();
25 | DataFactory df = new DataFactory();
26 | number = number();
27 | for (int i = 0; i < number; i++) {
28 | Applicants applicants = new Applicants();
29 | applicants.setId(df.getNumberBetween(0, 100));
30 | applicants.setName(df.getName());
31 | Address address = new Address(df.getCity(), df.getAddress());
32 | Company company = new Company(df.getBusinessName(), address);
33 | applicants.setJob(new Job(company, df.getRandomWord(), df.getRandomWord()));
34 | applicants.setEntryDate(df.getBirthDate());
35 | applicants.setWorking((i & 1) == 0);
36 | applicantsList.add(applicants);
37 |
38 | }
39 | }
40 |
41 | protected int number() {
42 | return 5;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/test/AutoTest.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.test;
2 |
3 | import cn.chenhuanming.octopus.Octopus;
4 | import cn.chenhuanming.octopus.config.Config;
5 | import cn.chenhuanming.octopus.config.XmlConfigFactory;
6 | import cn.chenhuanming.octopus.config.annotation.AnnotationConfigFactory;
7 | import cn.chenhuanming.octopus.entity.Applicants;
8 | import cn.chenhuanming.octopus.exception.SheetNotFoundException;
9 | import cn.chenhuanming.octopus.model.CellPositions;
10 | import cn.chenhuanming.octopus.reader.SheetReader;
11 | import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
12 | import org.junit.Assert;
13 | import org.junit.Test;
14 |
15 | import java.io.FileOutputStream;
16 | import java.io.IOException;
17 | import java.io.InputStream;
18 |
19 | /**
20 | * Automated Testing.
21 | *
22 | * @author chenhuanming
23 | * Created at 2018/12/13
24 | */
25 | public class AutoTest extends AbstractTest {
26 |
27 | @Test
28 | public void testConfigFactory() {
29 | Config annotationConfig = new AnnotationConfigFactory(Applicants.class).getConfig();
30 |
31 | Config xmlConfig = new XmlConfigFactory(this.getClass().getClassLoader().getResourceAsStream("applicants.xml")).getConfig();
32 |
33 | Assert.assertEquals(annotationConfig, xmlConfig);
34 |
35 | }
36 |
37 | /**
38 | * Write data to excel file,then read from it,make sure that data is equal
39 | */
40 | @Test
41 | public void testExportAndImport() throws IOException, InvalidFormatException, SheetNotFoundException {
42 | final String fileName = "testExportAndImport.xlsx";
43 | final String sheetName = "sheet1";
44 | Config config = new AnnotationConfigFactory(Applicants.class).getConfig();
45 |
46 | //Write to file
47 | FileOutputStream os = new FileOutputStream(this.getClass().getClassLoader().getResource("").getPath() + "/" + fileName);
48 | Octopus.writeOneSheet(os, config, sheetName, applicantsList);
49 |
50 | //Then read from file
51 | InputStream is = this.getClass().getClassLoader().getResourceAsStream(fileName);
52 | SheetReader readApplicants = Octopus.readBySheetName(is, sheetName, config, CellPositions.getContentStartPosition(CellPositions.POSITION_ZERO_ZERO, config));
53 |
54 | //Assert data is equal
55 | Assert.assertEquals(applicantsList.size(), readApplicants.size());
56 | for (int i = 0; i < readApplicants.size() - 4; i++) {
57 | Assert.assertEquals(applicantsList.get(i), readApplicants.get(i));
58 | }
59 |
60 | }
61 |
62 | }
--------------------------------------------------------------------------------
/src/test/java/cn/chenhuanming/octopus/util/Timer.java:
--------------------------------------------------------------------------------
1 | package cn.chenhuanming.octopus.util;
2 |
3 | /**
4 | * @author chenhuanming
5 | * Created at 2019-01-09
6 | */
7 | public class Timer {
8 | public static long wastedSeconds(Runnable event) {
9 | long start = System.currentTimeMillis();
10 | event.run();
11 | long end = System.currentTimeMillis();
12 | return (end - start) / 1000;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/resources/address.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/test/resources/applicants.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
16 |
17 |
32 |
33 |
36 |
37 |
42 |
43 |
--------------------------------------------------------------------------------
/src/test/resources/company.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/src/test/resources/company2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/src/test/resources/company3.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
10 |
11 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/test/resources/import.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerouwar/Octopus/d07183a8515e205c36c92c6832c2d884f7a902ec/src/test/resources/import.xlsx
--------------------------------------------------------------------------------
/src/test/resources/wrongCompany.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerouwar/Octopus/d07183a8515e205c36c92c6832c2d884f7a902ec/src/test/resources/wrongCompany.xlsx
--------------------------------------------------------------------------------