├── 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 | [![MIT License](http://img.shields.io/badge/license-MIT-green.svg) ](https://github.com/mockito/mockito/blob/master/LICENSE) 21 | [![Maven Central](https://img.shields.io/badge/maven-octopus-blue.svg)](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 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/applicant_example.png) 29 | 30 | ## 从Maven导入 31 | ``` 32 | 33 | cn.chenhuanming 34 | octopus 35 | 1.1.4 36 | 37 | ``` 38 | 39 | ## 导出Excel 40 | 41 | ### 从最简单的例子开始 42 | 我们从最简单的例子开始,导出一些地址数据 43 | 44 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/simplest_example.png) 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 |
141 | 142 | 143 |
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 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/auto_drawing_header.png) 187 | 188 | Octopus可以处理更复杂的数据,你可以在`cn.chenhuanming.octopus.example.ApplicantExample`查看这个更复杂的例子 189 | 190 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/applicant_example.png) 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 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/convering_data.png) 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 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/wrong_data.png) 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 | [![MIT License](http://img.shields.io/badge/license-MIT-green.svg) ](https://github.com/mockito/mockito/blob/master/LICENSE) 22 | [![Maven Central](https://img.shields.io/badge/maven-octopus-blue.svg)](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 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/applicant_example.png) 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 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/simplest_example.png) 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 |
140 | 141 | 142 |
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 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/auto_drawing_header.png) 187 | 188 | Octopus can handle more complicated data,you can check this from `cn.chenhuanming.octopus.example.ApplicantExample` in test classpath 189 | 190 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/applicant_example.png) 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 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/convering_data.png) 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 | ![](https://raw.githubusercontent.com/zerouwar/Octopus/master/pictures/wrong_data.png) 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 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 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 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 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 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 |
18 |
20 | 21 |
22 | 23 | 24 |
25 |
26 | 27 | 31 |
32 | 33 | 36 | 37 | 42 | 43 |
-------------------------------------------------------------------------------- /src/test/resources/company.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 |
11 | 12 | 13 |
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 --------------------------------------------------------------------------------