├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── core
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── creditdatamw
│ │ └── zerocell
│ │ ├── Reader.java
│ │ ├── ReaderUtil.java
│ │ ├── SheetNotFoundException.java
│ │ ├── ZeroCellException.java
│ │ ├── ZeroCellReader.java
│ │ ├── annotation
│ │ ├── Column.java
│ │ ├── RowNumber.java
│ │ └── ZerocellReaderBuilder.java
│ │ ├── column
│ │ ├── ColumnInfo.java
│ │ ├── ColumnMapping.java
│ │ └── RowNumberInfo.java
│ │ ├── converter
│ │ ├── BooleanConverter.java
│ │ ├── Converter.java
│ │ ├── ConverterUtils.java
│ │ ├── Converters.java
│ │ ├── DefaultConverter.java
│ │ ├── DefaultConverters.java
│ │ ├── DoubleConverter.java
│ │ ├── FallbackStrategy.java
│ │ ├── FloatConverter.java
│ │ ├── IntegerConverter.java
│ │ ├── LocalDateConverter.java
│ │ ├── LocalDateTimeConverter.java
│ │ ├── LongConverter.java
│ │ ├── NoopConverter.java
│ │ ├── SqlDateConverter.java
│ │ └── SqlTimestampConverter.java
│ │ ├── handler
│ │ ├── EntityExcelSheetHandler.java
│ │ └── EntityHandler.java
│ │ └── internal
│ │ ├── IgnoreInvalidValueException.java
│ │ └── package-info.java
│ └── test
│ ├── java
│ └── com
│ │ └── creditdatamw
│ │ └── zerocell
│ │ ├── Person.java
│ │ ├── TestHandler.java
│ │ ├── TestProgrammaticApi.java
│ │ ├── TestReader.java
│ │ ├── TestReaderUtil.java
│ │ ├── TestSkipEmptyRows.java
│ │ ├── column
│ │ ├── TestColumnInfo.java
│ │ ├── TestColumnMapping.java
│ │ └── TestRowNumberInfo.java
│ │ ├── converter
│ │ ├── TestBooleanConverter.java
│ │ ├── TestConverterUtils.java
│ │ ├── TestDoubleConverter.java
│ │ └── TestLongConverter.java
│ │ └── handler
│ │ ├── TestColumnFieldWriter.java
│ │ ├── TestEntityExcelSheetHandler.java
│ │ └── TestEntityHandler.java
│ └── resources
│ ├── test_people.xlsx
│ ├── test_people_with_empty_rows.xlsx
│ ├── test_people_with_offset_header.xlsx
│ └── test_people_with_offset_sheet.xlsx
├── example
├── pom.xml
└── src
│ ├── generated
│ └── java
│ │ └── com
│ │ └── creditdatamw
│ │ └── zerocell
│ │ └── example
│ │ └── PersonExcelReader.java
│ └── main
│ └── java
│ └── com
│ └── creditdatamw
│ └── zerocell
│ └── example
│ ├── AnnotationProcessorExample.java
│ ├── BasicUsageExample.java
│ ├── IdPrefixingConverter.java
│ ├── ManualColumnMappingExample.java
│ ├── Patient.java
│ └── Person.java
├── findbugs-exclude.xml
├── pom.xml
└── processor
├── pom.xml
└── src
└── main
└── java
└── com
└── creditdatamw
└── zerocell
└── processor
├── ZeroCellAnnotationProcessor.java
└── spec
├── CellMethodSpec.java
├── ColumnInfoType.java
└── ReaderTypeSpec.java
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .classpath
3 | .idea
4 | .project
5 | .settings
6 | *.iml
7 | *.ipr
8 | *.iws
9 | *.log
10 | *.pyc
11 | *.swp
12 | *~
13 | logs
14 | node_modules
15 | target
16 | apidoc
17 | template_schemas
18 | dependency-reduced-pom.xml
19 | notes
20 | backups/
21 | pom.xml.versionsBackup
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - openjdk8
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to get contributions from you!
4 |
5 | If you have any questions or run into any problems, please create
6 | an issue here or email us: it [at] creditdatamw.com
7 |
8 | ## Workflow
9 |
10 | The supported workflow for contributing is as follows:
11 |
12 | 1. Fork the repo
13 | 1. Make a feature or bugfix branch (use `git checkout -b "your-feature-or-fix"`)
14 | 1. Make your cool new feature or bugfix on your branch
15 | 1. Include tests for your change
16 | 1. From your branch, make a pull request
17 | 1. The maintainers will review your change
18 | 1. Once your change has been reviewed and accepted, wait for your change to be merged into `master`
19 | 1. Delete your feature branch
20 |
21 | ## Style
22 |
23 | We use [SpotBugs][spotbugs], which uses static analysis to look for bugs
24 | in Java code, to catch common bugs and we generally follow a style guide
25 | similar to the [Google Java Style Guide][style] with exception on
26 | indentation (we use 4 spaces)
27 |
28 | ## Issues & Pull Requests
29 |
30 | When creating an issue please explain the steps to reproduce
31 | the issue or include a code snippet to illustrate the issue in more
32 | detail. The maintainers will review all issues created and we'll do
33 | our best to give feedback if necessary. However, we recommend that you send
34 | a Pull Request with a fix if you can manage - contributions are always welcome
35 |
36 | As outlined above, you must create a feature or bugfix branch and make a
37 | pull request from the branch which the maintainers will review.
38 | You may be asked to improve some things in the pull request and once the
39 | pull request is satisfactory it will be merged into the mainline.
40 |
41 | Please see the following post on writing [good commit messages](https://chris.beams.io/posts/git-commit/)
42 |
43 | ## Code Review
44 |
45 | The repository on GitHub is kept in sync with an internal repository at
46 | Credit Data CRB Ltd. For the most part this process should be transparent
47 | but it may have some implications on how pull requests are merged in.
48 |
49 | When you submit a pull request on GitHub, it will be reviewed
50 | inside (and possibly outside of) Credit Data CRB Ltd, and once the changes are
51 | approved, your commits will be brought into the internal system for additional
52 | testing. Once the changes are merged internally, they will be pushed back to
53 | GitHub.
54 |
55 | [spotbugs]: https://spotbugs.github.io/
56 | [style]: https://google.github.io/styleguide/javaguide.html
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2017 Credit Data CRB Ltd
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](./LICENSE)
2 | [](http://mvnrepository.com/artifact/com.creditdatamw.labs/zerocell-core)
3 |
4 | ZeroCell
5 | ========
6 |
7 | ZeroCell provides a simple API for loading data from Excel sheets into
8 | Plain Old Java Objects (POJOs) using annotations to map columns from an Excel sheet
9 | to fields in Java classes.
10 |
11 | In case you don't fancy annotations or don't want to have to change your existing classes,
12 | you can map the columns to the fields without the annotations.
13 |
14 | ## Why should I use this?
15 |
16 | The library doesn't use the same approach that Apache POIs usermodel API and
17 | other POI based libraries use to process/store data loaded from the Excel file
18 | as a result it uses less resources as it doesn't process things such as Cell styles that take up memory.
19 | You also don't have to spend time setting data from cells to your Java objects, just
20 | define the mappings and let ZeroCell handle the rest.
21 |
22 | ## What ZeroCell _cannot_ do for you
23 |
24 | * Read or process excel workbook styles and other visual effects
25 | * Load data into complex object hierarchies
26 | * Write to excel files: The Apache POI library (which we use underneath) has a good API for writing to Excel files and provides the `SXSSFWorkbook` for writing large files in an efficient manner.
27 |
28 | ## Usage
29 |
30 | There are three ways to use zerocell: via annotations, the programmatic api and using the annotation processor.
31 |
32 | First things first, add the following dependency to your `pom.xml`
33 |
34 | ```xml
35 |
36 | com.creditdatamw.labs
37 | zerocell-core
38 | 0.3.2
39 |
40 | ```
41 |
42 | ### Using Annotations
43 |
44 | You create a class with `@Column` (and optionally `@RowNumber`)
45 | annotations to represent a row in an Excel sheet and
46 | then use the static methods on the `Reader` class to read the
47 | list of data from the file.
48 |
49 | For example:
50 |
51 | ```java
52 | public class Person {
53 | @RowNumber
54 | private int rowNumber;
55 |
56 | @Column(index=0, name="FIRST_NAME")
57 | private String firstName;
58 |
59 | @Column(index=1, name="LAST_NAME")
60 | private String lastName;
61 |
62 | @Column(index=2, name="DATE_OF_BIRTH")
63 | private LocalDate dateOfBirth;
64 |
65 | // Getters and setters here ...
66 |
67 | public static void main(String... args) {
68 | // Then using the `Reader` class you can load
69 | // a list from the excel file as follows:
70 | List people = Reader.of(Person.class)
71 | .from(new File("people.xlsx"))
72 | .sheet("Sheet 1")
73 | .list();
74 |
75 | // You can also inspect the column names of
76 | // the class using the static `columnsOf` method:
77 | String[] columns = Reader.columnsOf(Person.class);
78 | }
79 | }
80 | ```
81 |
82 | ### Using the Programmatic API
83 |
84 | If you don't want to use annotations you can still use ZeroCell to load from Excel sheet
85 | to your Java objects without too much work. The only difference with the annotation approach
86 | is that you have to define the column mappings via the `Reader.using` method.
87 |
88 | For example:
89 |
90 | ```java
91 | public class Person {
92 | private int rowNo;
93 |
94 | private String id;
95 |
96 | private String firstName;
97 |
98 | private String middleName;
99 |
100 | private String lastName;
101 |
102 | private LocalDate dateOfBirth;
103 |
104 | private LocalDate dateOfRegistration;
105 |
106 | // Getters and setters here ...
107 |
108 | public static void main(String... args) {
109 | // Map the columns using, Reader.using method here
110 | List people = Reader.of(Person.class)
111 | .from(new File("people.xlsx"))
112 | .using(
113 | new RowNumberInfo("rowNo", Integer.class),
114 | new ColumnInfo("ID", "id", 0, String.class),
115 | new ColumnInfo("FIRST_NAME", "firstName", 1, String.class),
116 | new ColumnInfo("MIDDLE_NAME", "middleName", 2, String.class),
117 | new ColumnInfo("LAST_NAME", "lastName", 3, String.class),
118 | new ColumnInfo("DATE_OF_BIRTH", "dateOfBirth", 4, LocalDate.class),
119 | new ColumnInfo("DATE_REGISTERED", "dateOfRegistration", 5, Date.class)
120 | )
121 | .sheet("Sheet 1")
122 | .list();
123 |
124 | people.forEach(person -> {
125 | // Do something with person here
126 | });
127 | }
128 | }
129 | ```
130 |
131 | ### Using the Annotation Processor
132 |
133 | ZeroCell provides an annotation processor to generate Reader
134 | classes to read records from Excel without Runtime reflection
135 | which makes the code amenable to better auditing and customization.
136 |
137 | In order to use the functionality you will _need_ to add
138 | the `zerocell-processor` dependency to your POM. This adds a compile-time
139 | annotation processor which generates the classes:
140 |
141 | ```xml
142 |
143 | com.creditdatamw.labs
144 | zerocell-processor
145 | 0.3.2
146 | provided
147 |
148 | ```
149 |
150 | Then, in your code use the `@ZerocellReaderBuilder` annotation on a class
151 | that contains ZeroCell `@Column` annotations.
152 |
153 | Using a class defined as in the example shown below:
154 |
155 | ```java
156 | package com.example;
157 |
158 | @ZerocellReaderBuilder
159 | public class Person {
160 | @RowNumber
161 | private int rowNumber;
162 |
163 | @Column(index=0, name="FIRST_NAME")
164 | private String firstName;
165 |
166 | @Column(index=1, name="LAST_NAME")
167 | private String lastName;
168 |
169 | @Column(index=2, name="DATE_OF_BIRTH")
170 | private LocalDate dateOfBirth;
171 |
172 | public static void main(String... args) {
173 | File file = new File("people.xlsx");
174 | String sheet = "Sheet 1";
175 | ZeroCellReader reader = new com.example.PersonReader();
176 | List people = reader.read(file, sheet);
177 | people.forEach(person -> {
178 | // do something with person
179 | });
180 | }
181 | }
182 | ```
183 |
184 | Generates a class in the com.example package
185 |
186 | ```java
187 | package com.example;
188 |
189 | public class PersonReader implements ZeroCellReader {
190 | // generated code here
191 | }
192 | ```
193 |
194 |
195 | ## Using Converters
196 |
197 | Converters allow you to process the value loaded from an Excel Cell for a
198 | particular field, primarily converters enable you to transform String values to
199 | another data type. This allows you to load data into fields that have types
200 | other than the default supported types.
201 |
202 | An example converter is shown below:
203 |
204 | ```java
205 | public class SimpleISOCurrency {
206 | public final String isoCurrency;
207 | public final double amount;
208 |
209 | public SimpleISOCurrency(String iso, double amount) {
210 | assert amount > 0.0;
211 | this.isoCurrency = iso;
212 | this.amount = amount;
213 | }
214 | }
215 |
216 | public class MalawiKwachaConverter implements Converter {
217 | @Override
218 | public SimpleISOCurrency convert(String value, String columnName, int row) {
219 | return new SimpleISOCurrency("MWK", Double.parseDouble(value));
220 | }
221 | }
222 |
223 | // Usage looks like this:
224 |
225 | // ...
226 | @Column(index=1, name="Balance (MWK)", converter=MalawiKwachaConverter.class)
227 | private SimpleISOCurrency balance;
228 | // ...
229 | ```
230 |
231 | ### Using converters for pre-processing
232 |
233 | You can also use converters as sort of pre-processing step where you operate on
234 | the String from the file before it's set on the field.
235 |
236 | Below is a simple example:
237 |
238 | ```java
239 | /**
240 | * Simple Converter that prefixes values with ID-
241 | */
242 | public class IdPrefixingConverter implements Converter {
243 | @Override
244 | public String convert(String value, String columnName, int row) {
245 | return String.format("ID-%s", value);
246 | }
247 | }
248 |
249 | // Usage looks like this:
250 |
251 | // ...
252 | @Column(index=3, name="ID No.", converter=IdPrefixingConverter.class)
253 | private String idNo;
254 | // ...
255 | ```
256 |
257 | ### Basic ISO LocalDate Converter
258 |
259 | Below is a simple implementation of an converter for `java.time.LocalDate` that
260 | you can use.
261 |
262 | > Please note: that if you need to parse date times considering timezones
263 | > you should implement your own converter and use a type like
264 | > [OffsetDateTime](https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html)
265 |
266 | ```java
267 |
268 | public class BasicISOLocalDateConverter implements Converter {
269 | /**
270 | * Basic ISO converter - attempts to parse a string that is formatted as an
271 | * ISO8601 date and convert it to a java.time.LocalDate instance.
272 | *
273 | * @param value the value to convert
274 | * @param column the name of the current column
275 | * @param row the current row index
276 | *
277 | * @throws ZeroCellException if the value cannot be parsed as an ISO date
278 | */
279 | @Override
280 | public LocalDate convert(String value, String column, int row) {
281 | if (value == null) return null;
282 | DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
283 | try {
284 | return LocalDate.parse(value, formatter);
285 | } catch (DateTimeParseException e) {
286 | throw new ZeroCellException(e);
287 | }
288 | }
289 | }
290 | ```
291 |
292 | ## Loading the Correct Sheet
293 |
294 | If you do not specify the name of the sheet to load from, zerocell attempts to
295 | load data from the first Sheet in the Workbook. If the Sheet doesn't have matching
296 | columns as the defined model an exception will be thrown. In order to ensure
297 | the correct sheet is loaded it is recommended to always specify the sheet name.
298 |
299 | ## Exception Handling
300 |
301 | The API throws `ZeroCellException` if something goes wrong, e.g. sheet not found.
302 | It is an unchecked exception and may cause your code to stop executing if not
303 | handled. Typically `ZeroCellException` will wrap another exception, so it's worth
304 | peeking at the cause using `Exception#getCause`.
305 |
306 | ## CONTRIBUTING
307 |
308 | See the [`CONTRIBUTING.md`](CONTRIBUTING.md) file for more information.
309 |
310 | ---
311 |
312 | Copyright (c) Credit Data CRB Ltd
313 |
--------------------------------------------------------------------------------
/core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | zerocell-parent
5 | com.creditdatamw.labs
6 |
7 | 0.5.2-SNAPSHOT
8 |
9 |
10 | 4.0.0
11 |
12 | zerocell-core
13 |
14 |
15 |
16 | org.apache.commons
17 | commons-lang3
18 | 3.4
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/Reader.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell;
2 |
3 | import com.creditdatamw.zerocell.column.ColumnInfo;
4 | import com.creditdatamw.zerocell.column.ColumnMapping;
5 | import com.creditdatamw.zerocell.column.RowNumberInfo;
6 | import com.creditdatamw.zerocell.handler.EntityHandler;
7 |
8 | import java.io.File;
9 | import java.io.InputStream;
10 | import java.util.List;
11 | import java.util.Objects;
12 |
13 | /**
14 | * Main API for ZeroCell
15 | */
16 | public class Reader {
17 |
18 | public static String[] columnsOf(Class clazz) {
19 | return ColumnInfo.columnsOf(clazz);
20 | }
21 |
22 | public static ReaderBuilder of(Class clazz) {
23 | return new ReaderBuilder<>(clazz);
24 | }
25 |
26 | public static final class ReaderBuilder {
27 | private final Class clazz;
28 | private File file;
29 | private String sheetName;
30 | private ColumnMapping columnMapping;
31 | private boolean skipHeaderRow = false;
32 | private boolean skipEmptyRows = true;
33 | private int skipFirstNRows = 0;
34 | private int maxRowNumber = 0;
35 | private InputStream inputStream;
36 |
37 | public ReaderBuilder(Class clazz) {
38 | this.clazz = clazz;
39 | }
40 |
41 | public ReaderBuilder from(File file) {
42 | Objects.requireNonNull(file);
43 | this.file = file;
44 | return this;
45 | }
46 |
47 | public ReaderBuilder from(InputStream inputStream) {
48 | Objects.requireNonNull(inputStream);
49 | this.inputStream = inputStream;
50 | return this;
51 | }
52 |
53 | public ReaderBuilder using(RowNumberInfo rowNumberInfo, ColumnInfo... columns) {
54 | this.columnMapping = new ColumnMapping(rowNumberInfo, columns);
55 | return this;
56 | }
57 |
58 | public ReaderBuilder using(ColumnInfo... columns) {
59 | this.columnMapping = new ColumnMapping(null, columns);
60 | return this;
61 | }
62 |
63 | public ReaderBuilder sheet(String sheetName) {
64 | Objects.requireNonNull(sheetName);
65 | this.sheetName = sheetName;
66 | return this;
67 | }
68 |
69 | public ReaderBuilder skipHeaderRow(boolean value) {
70 | this.skipHeaderRow = value;
71 | return this;
72 | }
73 |
74 | public ReaderBuilder skipEmptyRows(boolean value) {
75 | this.skipEmptyRows = value;
76 | return this;
77 | }
78 |
79 | /**
80 | * Set the number of rows to skip before the header
81 | *
82 | * @param value is the number of rows to skip
83 | * @return ReaderBuilder
84 | */
85 | public ReaderBuilder skipFirstNRows(int value) {
86 | assert (value > 0);
87 | this.skipFirstNRows = value;
88 | this.maxRowNumber += value;
89 | return this;
90 | }
91 |
92 | /**
93 | * Set the maximum number of rows to parse.
94 | *
95 | * @param rowNumber the number of rows to parse.
96 | * @return ReaderBuilder
97 | */
98 | public ReaderBuilder setRowNumber(int rowNumber) {
99 | assert (rowNumber > 0);
100 | this.maxRowNumber += rowNumber;
101 | return this;
102 | }
103 |
104 | @SuppressWarnings("unchecked")
105 | public List list() {
106 | EntityHandler entityHandler;
107 | if (!Objects.isNull(sheetName) && !Objects.isNull(columnMapping)) {
108 | entityHandler = new EntityHandler(clazz, sheetName, columnMapping, skipHeaderRow, skipFirstNRows, maxRowNumber);
109 | } else if (Objects.isNull(sheetName) && !Objects.isNull(columnMapping)) {
110 | entityHandler = new EntityHandler(clazz, columnMapping, skipHeaderRow, skipFirstNRows, maxRowNumber);
111 | } else if (!Objects.isNull(sheetName) && Objects.isNull(columnMapping)) {
112 | entityHandler = new EntityHandler(clazz, sheetName, skipHeaderRow, skipFirstNRows, maxRowNumber);
113 | } else {
114 | entityHandler = new EntityHandler(clazz, skipHeaderRow, skipFirstNRows, maxRowNumber);
115 | }
116 | entityHandler.setSkipEmptyRows(this.skipEmptyRows);
117 |
118 | if (Objects.nonNull(file)) {
119 | entityHandler.process(file);
120 | } else {
121 | entityHandler.process(inputStream);
122 | }
123 |
124 | return entityHandler.readAsList();
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/ReaderUtil.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell;
2 |
3 | import com.creditdatamw.zerocell.handler.EntityHandler;
4 | import org.apache.poi.EmptyFileException;
5 | import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
6 | import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
7 | import org.apache.poi.openxml4j.opc.OPCPackage;
8 | import org.apache.poi.openxml4j.opc.PackageAccess;
9 | import org.apache.poi.ss.usermodel.DataFormatter;
10 | import org.apache.poi.util.XMLHelper;
11 | import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
12 | import org.apache.poi.xssf.eventusermodel.XSSFReader;
13 | import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
14 | import org.apache.poi.xssf.model.StylesTable;
15 | import org.xml.sax.InputSource;
16 | import org.xml.sax.XMLReader;
17 |
18 | import java.io.*;
19 | import java.util.Objects;
20 |
21 | /**
22 | * Utility class for Reader implementations
23 | *
24 | */
25 | public final class ReaderUtil {
26 | public static final String ERROR_NOT_OPENXML = "Cannot load file. The file must be an Excel 2007+ Workbook (.xlsx)";
27 | /**
28 | * Reads a list of POJOs from the given excel file.
29 | *
30 | * @param path Excel file to read from
31 | * @param sheetName The sheet to extract from in the workbook
32 | * @param reader The reader class to use to load the file from the sheet
33 | */
34 | public static void process(String path, String sheetName, ZeroCellReader reader) {
35 | if (path == null || path.trim().isEmpty()) {
36 | throw new IllegalArgumentException("'path' must be given");
37 | }
38 |
39 | File file = new File(path);
40 | if (file.exists() && file.isDirectory()) {
41 | throw new IllegalArgumentException("path must not be a directory");
42 | }
43 | try (OPCPackage opcPackage = OPCPackage.open(file.getAbsolutePath(), PackageAccess.READ)) {
44 | process(opcPackage, sheetName, reader);
45 | } catch(InvalidFormatException | EmptyFileException | NotOfficeXmlFileException ife) {
46 | throw new ZeroCellException(ERROR_NOT_OPENXML);
47 | } catch (IOException ioe) {
48 | throw new ZeroCellException("Failed to process file", ioe);
49 | }
50 | }
51 |
52 | /**
53 | * Reads a list of POJOs from the given excel file.
54 | *
55 | * @param file Excel file to read from
56 | * @param sheetName The sheet to extract from in the workbook
57 | * @param reader The reader class to use to load the file from the sheet
58 | */
59 | public static void process(File file, String sheetName, ZeroCellReader reader) {
60 | try (OPCPackage opcPackage = OPCPackage.open(file, PackageAccess.READ)) {
61 | process(opcPackage, sheetName, reader);
62 | } catch(InvalidFormatException | EmptyFileException | NotOfficeXmlFileException ife) {
63 | throw new ZeroCellException(ERROR_NOT_OPENXML);
64 | } catch (IOException ioe) {
65 | throw new ZeroCellException("Failed to process file", ioe);
66 | }
67 | }
68 |
69 | /**
70 | * Reads a list of POJOs from the given input stream.
71 | *
72 | * @param is InputStream to read Excel file from
73 | * @param sheetName The sheet to extract from in the workbook
74 | * @param reader The reader class to use to load the file from the sheet
75 | */
76 | public static void process(InputStream is, String sheetName, ZeroCellReader reader) {
77 | try (PushbackInputStream p = new PushbackInputStream(is, 16);
78 | OPCPackage opcPackage = OPCPackage.open(p)) {
79 | process(opcPackage, sheetName, reader);
80 | } catch (Exception e) {
81 | throw new ZeroCellException("Failed to process file", e);
82 | }
83 | }
84 |
85 | /**
86 | * Processes data from an Excel file contained in the OPCPackage using the
87 | * reader implementation
88 | *
89 | * Please note that the process will read data from the first sheet in the
90 | * File when if sheet name is not specified
91 | * (i.e. the sheet name defaults to the {@link EntityHandler.DEFAULT_SHEET})
92 | *
93 | * @param opcPackage the OpenXML OPC Package
94 | * @param sheetName The sheet name
95 | * @param reader the reader implementation that handles the entity mapping
96 | */
97 | private static void process(OPCPackage opcPackage, String sheetName, ZeroCellReader reader) {
98 | try {
99 | DataFormatter dataFormatter = new DataFormatter();
100 | ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(opcPackage);
101 | XSSFReader xssfReader = new XSSFReader(opcPackage);
102 | StylesTable stylesTable = xssfReader.getStylesTable();
103 | InputStream sheetInputStream = null;
104 | XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
105 | while (sheets.hasNext()) {
106 | sheetInputStream = sheets.next();
107 |
108 | if (EntityHandler.DEFAULT_SHEET.equalsIgnoreCase(sheetName)) {
109 | break;
110 | }
111 | if (sheets.getSheetName().equalsIgnoreCase(sheetName)) {
112 | break;
113 | } else {
114 | sheetInputStream = null;
115 | }
116 | }
117 |
118 | if (Objects.isNull(sheetInputStream)) {
119 | throw new SheetNotFoundException(sheetName);
120 | }
121 |
122 | XMLReader xmlReader = XMLHelper.newXMLReader();
123 | xmlReader.setContentHandler(new XSSFSheetXMLHandler(stylesTable, strings, reader, dataFormatter, false));
124 | xmlReader.parse(new InputSource(sheetInputStream));
125 | sheetInputStream.close();
126 | xmlReader = null;
127 | sheetInputStream = null;
128 | stylesTable = null;
129 | strings = null;
130 | xssfReader = null;
131 | } catch(InvalidFormatException | EmptyFileException | NotOfficeXmlFileException ife) {
132 | throw new ZeroCellException(ERROR_NOT_OPENXML);
133 | } catch(SheetNotFoundException ex) {
134 | throw new ZeroCellException(ex.getMessage());
135 | } catch (ZeroCellException ze) {
136 | throw ze; // Rethrow the Exception
137 | } catch (Exception e) {
138 | throw new ZeroCellException("Failed to process file", e);
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/SheetNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell;
2 |
3 | public class SheetNotFoundException extends Exception {
4 | public SheetNotFoundException(String message) {
5 | super(String.format("Could not find sheet %s", message));
6 | }
7 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/ZeroCellException.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell;
2 |
3 | /**
4 | * RuntimeException occurred
5 | */
6 | public class ZeroCellException extends RuntimeException {
7 | public ZeroCellException(String message) {
8 | super(message);
9 | }
10 |
11 | public ZeroCellException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 |
15 | public ZeroCellException(Throwable cause) {
16 | super(cause);
17 | }
18 |
19 | public ZeroCellException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
20 | super(message, cause, enableSuppression, writableStackTrace);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/ZeroCellReader.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell;
2 |
3 | import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
4 |
5 | import java.io.File;
6 | import java.util.List;
7 |
8 | /**
9 | * Proxy for ZeroCellReader Implementations
10 | */
11 | public interface ZeroCellReader extends XSSFSheetXMLHandler.SheetContentsHandler {
12 | List read(final File file, final String sheet);
13 | }
14 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/annotation/Column.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.annotation;
2 |
3 | import com.creditdatamw.zerocell.converter.FallbackStrategy;
4 | import com.creditdatamw.zerocell.converter.NoopConverter;
5 |
6 | import java.lang.annotation.ElementType;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.RetentionPolicy;
9 | import java.lang.annotation.Target;
10 |
11 | /**
12 | * A Column in the excel workbook
13 | */
14 | @Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
15 | @Retention(RetentionPolicy.RUNTIME)
16 | public @interface Column {
17 | /**
18 | *
19 | * @return Name of the column in the excelWorkbook - this must be the value in that column
20 | */
21 | String name();
22 |
23 | /**
24 | *
25 | * @return Position of the column. Starts with the 0 index.
26 | */
27 | int index();
28 |
29 | /**
30 | *
31 | * @return Format for the value of the column, iff applicable
32 | */
33 | String dataFormat() default "";
34 |
35 | /**
36 | *
37 | * @return Format for the value of the column, iff applicable
38 | */
39 | FallbackStrategy fallback() default FallbackStrategy.DEFAULT;
40 |
41 | /**
42 | *
43 | * @return Converter to use to convert the string value to the field type's value
44 | */
45 | Class> converterClass() default NoopConverter.class;
46 | }
47 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/annotation/RowNumber.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.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 | import static java.lang.annotation.ElementType.FIELD;
9 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
10 |
11 | /**
12 | * Keep track of the source row number in the excel using this annotation.
13 | * Apply it to an integer type to store the value there
14 | */
15 | @Target({ElementType.FIELD})
16 | @Retention(RetentionPolicy.RUNTIME)
17 | public @interface RowNumber {
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/annotation/ZerocellReaderBuilder.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.annotation;
2 |
3 | import java.lang.annotation.*;
4 |
5 | /**
6 | * Marker annotation for a class that Generates a ZeroCell Reader
7 | *
8 | */
9 | @Retention(RetentionPolicy.SOURCE)
10 | @Target(ElementType.TYPE)
11 | @Documented
12 | public @interface ZerocellReaderBuilder {
13 | /**
14 | * Gets the name of the generated Reader class, which
15 | * defaults to the name of the class with suffix of "Reader" e.g. PersonReader
16 | *
17 | * @return The name of the generated Reader class.
18 | */
19 | String value() default "__none__";
20 | }
21 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/column/ColumnInfo.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.column;
2 |
3 | import com.creditdatamw.zerocell.converter.FallbackStrategy;
4 | import com.creditdatamw.zerocell.annotation.Column;
5 | import com.creditdatamw.zerocell.converter.NoopConverter;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import java.lang.reflect.Field;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 | import java.util.Objects;
12 |
13 | /**
14 | * Information about a Column in a Java Bean
15 | */
16 | public class ColumnInfo {
17 | private String name;
18 |
19 | private String fieldName;
20 |
21 | private int index;
22 |
23 | private String dataFormat;
24 |
25 | private Class> type;
26 |
27 | private Class> converterClass;
28 |
29 | private FallbackStrategy fallbackStrategy;
30 |
31 | public ColumnInfo(String name, String fieldName, int index, Class> dataType) {
32 | this(name, fieldName, index, "", dataType, NoopConverter.class);
33 | }
34 |
35 | public ColumnInfo(String name, String fieldName, int index, Class> dataType, FallbackStrategy fallbackStrategy) {
36 | this(name, fieldName, index, "", dataType, NoopConverter.class, fallbackStrategy);
37 | }
38 |
39 | public ColumnInfo(String name, String fieldName, int index, String dataFormat, Class> type, Class> converterClass) {
40 | this(name, fieldName, index, dataFormat, type, converterClass, FallbackStrategy.DEFAULT);
41 | }
42 |
43 | public ColumnInfo(String name, String fieldName, int index, String dataFormat, Class> type, Class> converterClass, FallbackStrategy fallbackStrategy) {
44 | this.name = name;
45 | this.fieldName = fieldName;
46 | this.index = index;
47 | this.dataFormat = dataFormat;
48 | this.type = type;
49 | this.converterClass = converterClass;
50 | this.fallbackStrategy = fallbackStrategy;
51 | }
52 |
53 | /**
54 | * Name of the column in the Excel file
55 | * @return name of the column
56 | */
57 | public String getName() {
58 | return name.toUpperCase().trim();
59 | }
60 |
61 | /**
62 | * Name of the field/attribute in the class
63 | * @return name of the field in the class
64 | */
65 | public String getFieldName() {
66 | return fieldName;
67 | }
68 |
69 | /**
70 | * Index of the column in the Excel file
71 | * @return column index
72 | */
73 | public int getIndex() {
74 | return index;
75 | }
76 |
77 | /**
78 | * Data format specification.
79 | * @return data format specification
80 | */
81 | public String getDataFormat() {
82 | return dataFormat;
83 | }
84 |
85 | /**
86 | * Class of the type of the data expected at that column in the Excel file
87 | * @return Class instance
88 | */
89 | public Class> getType() {
90 | return type;
91 | }
92 |
93 | public Class> getConverterClass() {
94 | return converterClass;
95 | }
96 |
97 | public FallbackStrategy getFallbackStrategy() {
98 | return fallbackStrategy;
99 | }
100 |
101 | /**
102 | * Finds and extacts {@link Column} annotations from the provided class
103 | *
104 | * @param clazz the class to extraction annotations frlom
105 | * @return list of {@link Column} annotations from the clas
106 | */
107 | public static final List annotationsOf(Class> clazz) {
108 | Field[] fieldArray = clazz.getDeclaredFields();
109 | List columns = new ArrayList<>(fieldArray.length);
110 | for (Field field : fieldArray) {
111 | Column annotation = field.getAnnotation(Column.class);
112 | if (!Objects.isNull(annotation)) {
113 | columns.add(annotation);
114 | }
115 | }
116 | return columns;
117 | }
118 |
119 | /**
120 | * Finds the column names for all fields annotated with {@link Column}
121 | * annotation.
122 | *
123 | * @param clazz the class to extract annotations from
124 | * @return array of column names
125 | */
126 | public static final String[] columnsOf(Class> clazz) {
127 | List columns = annotationsOf(clazz);
128 | String[] columnNames = new String[columns.size()];
129 |
130 | LoggerFactory.getLogger(ColumnInfo.class).debug(String.format("Found %s columns in class %s", columnNames.length, clazz.getName()));
131 |
132 | for(Column annotation: columns) {
133 | columnNames[annotation.index()] = annotation.name().trim();
134 | }
135 | columns = null;
136 | return columnNames;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/column/ColumnMapping.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.column;
2 |
3 | import com.creditdatamw.zerocell.ZeroCellException;
4 | import com.creditdatamw.zerocell.annotation.Column;
5 | import com.creditdatamw.zerocell.annotation.RowNumber;
6 |
7 | import java.lang.reflect.Field;
8 | import java.util.*;
9 |
10 | public final class ColumnMapping {
11 | private final RowNumberInfo rowNumberInfo;
12 | private final List columns;
13 |
14 | public ColumnMapping(RowNumberInfo rowNumberInfo, List columns) {
15 | this.rowNumberInfo = rowNumberInfo;
16 | this.columns = columns;
17 | }
18 |
19 | public ColumnMapping(RowNumberInfo rowNumberInfo,ColumnInfo... columns) {
20 | this.rowNumberInfo = rowNumberInfo;
21 | this.columns = Arrays.asList(columns);
22 | }
23 |
24 | /**
25 | * RowNumberInfo column if the {@link RowNumber} annotation is defined
26 | * on the class otherwise returns null
27 | * @return rown number info
28 | */
29 | public RowNumberInfo getRowNumberInfo() {
30 | return rowNumberInfo;
31 | }
32 |
33 | /**
34 | * Columns defined in this mapping. The returned list is immutable
35 | * @return immutable list of ColumnInfo
36 | */
37 | public List getColumns() {
38 | return Collections.unmodifiableList(columns);
39 | }
40 |
41 | /**
42 | * Creates a ColumnMapping from Zerocell {@link Column}
43 | * and {@link RowNumber} annotations applied on a Class
44 | *
45 | * @param clazz the class to extract column mapping from
46 | * @return column mapping of the class
47 | */
48 | public static ColumnMapping parseColumnMappingFromAnnotations(Class> clazz) {
49 | Field[] fieldArray = clazz.getDeclaredFields();
50 | ArrayList list = new ArrayList<>(fieldArray.length);
51 | RowNumberInfo rowNumberColumn = null;
52 | for (Field field: fieldArray) {
53 |
54 | RowNumber rowNumberAnnotation = field.getAnnotation(RowNumber.class);
55 |
56 | if (! Objects.isNull(rowNumberAnnotation)) {
57 | rowNumberColumn = new RowNumberInfo(field.getName(), Integer.class);
58 | continue;
59 | }
60 |
61 | Column annotation = field.getAnnotation(Column.class);
62 | if (! Objects.isNull(annotation)) {
63 | Class> converter = annotation.converterClass();
64 | list.add(new ColumnInfo(annotation.name().trim(),
65 | field.getName(),
66 | annotation.index(),
67 | annotation.dataFormat(),
68 | field.getType(),
69 | converter,
70 | annotation.fallback()));
71 | }
72 | }
73 |
74 | if (list.isEmpty()) {
75 | throw new ZeroCellException(String.format("Class %s does not have @Column annotations", clazz.getName()));
76 | }
77 | list.trimToSize();
78 | return new ColumnMapping(rowNumberColumn, list);
79 | }
80 |
81 | /**
82 | * Create th map, where key is the index from {@link Column} annotation,
83 | * and value is the ColumnInfo.
84 | * The method also checks the indexes duplicates and throws a
85 | * ZeroCellException in this case
86 | *
87 | * @return map of column info
88 | * @throws ZeroCellException in the case of a duplicate index
89 | */
90 | public Map getColumnsMap() {
91 | Map map = new HashMap<>();
92 | this.columns.forEach(info -> {
93 | int index = info.getIndex();
94 | if (Objects.isNull(map.get(index))) {
95 | map.put(index, info);
96 | } else {
97 | throw new ZeroCellException("Cannot map two columns to the same index: " + index);
98 | }
99 | });
100 | return map;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/column/RowNumberInfo.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.column;
2 |
3 | public final class RowNumberInfo extends ColumnInfo {
4 | public RowNumberInfo(String fieldName, Class> dataType) {
5 | super("__rowNUmber__", fieldName, -1, dataType);
6 | }
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/converter/BooleanConverter.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.converter;
2 |
3 | import com.creditdatamw.zerocell.ZeroCellException;
4 |
5 | public class BooleanConverter extends DefaultConverter {
6 | @Override
7 | public Boolean convert(String value, String columnName, int row) {
8 | try {
9 | return Boolean.valueOf(value);
10 | } catch(Exception e) {
11 | switch (this.getFallbackStrategy()) {
12 | case DEFAULT_TO_TRUE:
13 | return Boolean.TRUE;
14 | case LEGACY:
15 | case DEFAULT_TO_FALSE:
16 | return Boolean.FALSE;
17 | // This is a special case for DO_NOT_SET
18 | case IGNORE: // TODO(zikani03): Review whether this case is appropriate
19 | default:
20 | throw new ZeroCellException(String.format(
21 | "Failed to parse '%s' as Boolean at column='%s' row='%s'", value, columnName, row));
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/converter/Converter.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.converter;
2 |
3 | /**
4 | * Converts a value from a String type to another
5 | */
6 | @FunctionalInterface
7 | public interface Converter {
8 | T convert(String value, String columnName, int row);
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/converter/ConverterUtils.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.converter;
2 |
3 | import java.sql.Timestamp;
4 | import java.time.LocalDate;
5 | import java.time.LocalDateTime;
6 |
7 | /**
8 | * Utility class for Converters
9 | */
10 | public final class ConverterUtils {
11 | ConverterUtils() {}
12 |
13 | static final DefaultConverters defaultConverters = Converters.newDefaultConverters();
14 |
15 | /**
16 | * Converts a value from a string to an object of the specified type
17 | *
18 | * @param fieldType Type of class to convert value to
19 | * @param formattedValue the value from Excel as a string
20 | * @param columnName The name of the column
21 | * @param rowNum The row number
22 | * @return a value converted from string to the corresponding type
23 | */
24 | @Deprecated
25 | public static Object convertValueToType(Class> fieldType,
26 | String formattedValue,
27 | String columnName,
28 | int rowNum) {
29 |
30 | Object value = null;
31 | if (fieldType == String.class) {
32 | value = String.valueOf(formattedValue);
33 | } else if (fieldType == LocalDateTime.class) {
34 | return Converters.toLocalDateTime
35 | .convert(formattedValue, columnName, rowNum);
36 |
37 | } else if (fieldType == LocalDate.class) {
38 | return Converters.toLocalDate
39 | .convert(formattedValue, columnName, rowNum);
40 |
41 | } else if (fieldType == java.sql.Date.class) {
42 | return Converters.toSqlDate
43 | .convert(formattedValue, columnName, rowNum);
44 |
45 | } else if (fieldType == Timestamp.class) {
46 | return defaultConverters.toSqlTimestamp
47 | .convert(formattedValue, columnName, rowNum);
48 |
49 | } else if (fieldType == Integer.class || fieldType == int.class) {
50 | return Converters.toInteger
51 | .convert(formattedValue, columnName, rowNum);
52 |
53 | } else if (fieldType == Long.class || fieldType == long.class) {
54 | return Converters.toLong
55 | .convert(formattedValue, columnName, rowNum);
56 |
57 | } else if (fieldType == Double.class || fieldType == double.class) {
58 | return Converters.toDouble
59 | .convert(formattedValue, columnName, rowNum);
60 |
61 | } else if (fieldType == Float.class || fieldType == float.class) {
62 | return defaultConverters.toFloat
63 | .convert(formattedValue, columnName, rowNum);
64 |
65 | } else if (fieldType == Boolean.class) {
66 | return defaultConverters.toBoolean
67 | .convert(formattedValue, columnName, rowNum);
68 | }
69 |
70 | return value;
71 | }
72 |
73 | /**
74 | * Converts a value from a string to an object of the specified type
75 | *
76 | * @param fieldType Type of class to convert value to
77 | * @param formattedValue the value from Excel as a string
78 | * @param columnName The name of the column
79 | * @param rowNum The row number
80 | * @return a value converted from string to the corresponding type
81 | */
82 | public static Object convertValueToType(Class> fieldType,
83 | String formattedValue,
84 | String columnName,
85 | int rowNum,
86 | FallbackStrategy fallbackStrategy) {
87 |
88 |
89 | Object value = null;
90 | if (fieldType == String.class) {
91 | value = String.valueOf(formattedValue);
92 | } else if (fieldType == LocalDateTime.class) {
93 | return defaultConverters.toLocalDateTime
94 | .withFallbackStrategy(fallbackStrategy)
95 | .convert(formattedValue, columnName, rowNum);
96 |
97 | } else if (fieldType == LocalDate.class) {
98 | return defaultConverters.toLocalDate
99 | .withFallbackStrategy(fallbackStrategy)
100 | .convert(formattedValue, columnName, rowNum);
101 |
102 | } else if (fieldType == java.sql.Date.class) {
103 | return defaultConverters.toSqlDate
104 | .withFallbackStrategy(fallbackStrategy)
105 | .convert(formattedValue, columnName, rowNum);
106 |
107 | } else if (fieldType == Timestamp.class) {
108 | return defaultConverters.toSqlTimestamp
109 | .withFallbackStrategy(fallbackStrategy)
110 | .convert(formattedValue, columnName, rowNum);
111 |
112 | } else if (fieldType == Integer.class || fieldType == int.class) {
113 | return defaultConverters.toInteger
114 | .withFallbackStrategy(fallbackStrategy)
115 | .convert(formattedValue, columnName, rowNum);
116 |
117 | } else if (fieldType == Long.class || fieldType == long.class) {
118 | return defaultConverters.toLong
119 | .withFallbackStrategy(fallbackStrategy)
120 | .convert(formattedValue, columnName, rowNum);
121 |
122 | } else if (fieldType == Double.class || fieldType == double.class) {
123 | return defaultConverters.toDouble
124 | .withFallbackStrategy(fallbackStrategy)
125 | .convert(formattedValue, columnName, rowNum);
126 |
127 | } else if (fieldType == Float.class || fieldType == float.class) {
128 | return defaultConverters.toFloat
129 | .withFallbackStrategy(fallbackStrategy)
130 | .convert(formattedValue, columnName, rowNum);
131 |
132 | } else if (fieldType == Boolean.class) {
133 | return defaultConverters.toBoolean
134 | .withFallbackStrategy(fallbackStrategy)
135 | .convert(formattedValue, columnName, rowNum);
136 | }
137 |
138 | return value;
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/converter/Converters.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.converter;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.sql.Date;
7 | import java.sql.Timestamp;
8 | import java.time.LocalDate;
9 | import java.time.LocalDateTime;
10 | import java.util.Objects;
11 |
12 | /**
13 | * Common Converters
14 | *
15 | * The converters will throw a ZeroCellException in case of failure to parse the
16 | * provided.
17 | */
18 | public class Converters {
19 | private static final Logger LOGGER = LoggerFactory.getLogger(Converters.class);
20 |
21 | public static DefaultConverters newDefaultConverters() {
22 | return new DefaultConverters();
23 | }
24 |
25 | public static final Converter toFloat = ((formattedValue, columnName, row) -> {
26 | try {
27 | return Float.valueOf(formattedValue);
28 | } catch (Exception e) {
29 | LOGGER.error("Failed to parse {} as Float. Using default of null at column={} row={} ", formattedValue, columnName, row);
30 | return null;
31 | }
32 | });
33 |
34 | public static final Converter noop = ((formattedValue, columnName, row) -> {
35 | return formattedValue;
36 | });
37 |
38 | public static final Converter toLocalDateTime = ((formattedValue, columnName, rowNum) -> {
39 | try {
40 | return LocalDateTime.parse(formattedValue);
41 | } catch(Exception e) {
42 | LOGGER.error("Failed to parse {} as Date. Using default of null at column={} row={} ", formattedValue, columnName, rowNum);
43 | return null;
44 | }
45 | });
46 |
47 |
48 | public static final Converter toLocalDate = ((formattedValue, columnName, rowNum) -> {
49 | try {
50 | return LocalDate.parse(formattedValue);
51 | } catch(Exception e) {
52 | LOGGER.error("Failed to parse {} as Date. Using default of null at column={} row={} ", formattedValue, columnName, rowNum);
53 | return null;
54 | }
55 | });
56 |
57 | public static final Converter toSqlDate = ((formattedValue, columnName, rowNum) -> {
58 | try {
59 | return Objects.isNull(formattedValue) ? null : Date.valueOf(formattedValue);
60 | } catch (Exception e) {
61 | LOGGER.error("Failed to parse {} as Date. Using default of null at column={} row={} ", formattedValue, columnName, rowNum);
62 | return null;
63 | }
64 | });
65 |
66 | public static final Converter toSqlTimestamp = ((formattedValue, columnName, rowNum) -> {
67 | try {
68 | return Timestamp.valueOf(formattedValue);
69 | } catch (Exception e) {
70 | LOGGER.error("Failed to parse {} as Timestamp. Using default of null at column={} row={} ", formattedValue, columnName, rowNum);
71 | return null;
72 | }
73 | });
74 |
75 | public static final Converter toInteger = ((formattedValue, columnName, rowNum) -> {
76 | try {
77 | return Integer.valueOf(formattedValue == null ? "0.0" : formattedValue);
78 | } catch (Exception e) {
79 | LOGGER.error("Failed to parse {} as Integer. Using default of null at column={} row={} ", formattedValue, columnName, rowNum);
80 | return null;
81 | }
82 | });
83 |
84 | public static final Converter toLong = ((formattedValue, columnName, rowNum) -> {
85 | //} else if (fieldType == Long.class || fieldType == long.class) {
86 | try {
87 | return Long.valueOf(formattedValue);
88 | } catch (Exception e) {
89 | LOGGER.error("Failed to parse {} as Long. Using default of null at column={} row={} ", formattedValue, columnName, rowNum);
90 | return null;
91 | }
92 | });
93 |
94 | public static final Converter toDouble = ((formattedValue, columnName, rowNum) -> {
95 | // } else if (fieldType == Double.class || fieldType == double.class) {
96 | try {
97 | return Double.valueOf(formattedValue);
98 | } catch (Exception e) {
99 | LOGGER.error("Failed to parse {} as Double. Using default of null at column={} row={} ", formattedValue, columnName, rowNum);
100 | return null;
101 | }
102 | });
103 |
104 | public static final Converter toBoolean = ((formattedValue, columnName, rowNum) -> {
105 | try {
106 | return Boolean.valueOf(formattedValue == null ? "FALSE" : "TRUE");
107 | } catch(Exception e) {
108 | LOGGER.error("Failed to parse {} as Boolean. Using default of false at column={} row={} ", formattedValue, columnName, rowNum);
109 | return Boolean.FALSE;
110 | }
111 | });
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/converter/DefaultConverter.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.converter;
2 |
3 |
4 | abstract class DefaultConverter implements Converter {
5 | private FallbackStrategy fallbackStrategy;
6 |
7 | public DefaultConverter() {
8 | this.fallbackStrategy = FallbackStrategy.DEFAULT;
9 | }
10 |
11 | public DefaultConverter(FallbackStrategy fallbackStrategy) {
12 | this.fallbackStrategy = fallbackStrategy;
13 | }
14 |
15 | public DefaultConverter withFallbackStrategy(FallbackStrategy fallbackStrategy) {
16 | this.fallbackStrategy = fallbackStrategy;
17 | return this;
18 | }
19 |
20 | public FallbackStrategy getFallbackStrategy() {
21 | return fallbackStrategy;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/converter/DefaultConverters.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.converter;
2 |
3 | import java.sql.Date;
4 | import java.sql.Timestamp;
5 | import java.time.LocalDate;
6 | import java.time.LocalDateTime;
7 |
8 | /**
9 | * Common Converters
10 | *
11 | * The converters will throw a ZeroCellException in case of failure to parse the
12 | * provided.
13 | */
14 | public class DefaultConverters {
15 | DefaultConverters() {}
16 |
17 | public final DefaultConverter noop = new DefaultConverter(){
18 | @Override
19 | public String convert(String value, String columnName, int row) {
20 | FallbackStrategy s = this.getFallbackStrategy();
21 |
22 | if (value == null &&
23 | (s == FallbackStrategy.DEFAULT_TO_EMPTY_STRING ||
24 | s == FallbackStrategy.DEFAULT)) {
25 |
26 | return "";
27 | }
28 | return value;
29 | }
30 | };
31 |
32 | public final DefaultConverter toFloat = new FloatConverter()
33 | .withFallbackStrategy(FallbackStrategy.DEFAULT);
34 | public final DefaultConverter toLocalDateTime = new LocalDateTimeConverter()
35 | .withFallbackStrategy(FallbackStrategy.DEFAULT);
36 | public final DefaultConverter toLocalDate = new LocalDateConverter()
37 | .withFallbackStrategy(FallbackStrategy.DEFAULT);
38 | public final DefaultConverter toSqlDate = new SqlDateConverter()
39 | .withFallbackStrategy(FallbackStrategy.DEFAULT);
40 | public final DefaultConverter toSqlTimestamp = new SqlTimestampConverter()
41 | .withFallbackStrategy(FallbackStrategy.DEFAULT);
42 | public final DefaultConverter toInteger = new IntegerConverter()
43 | .withFallbackStrategy(FallbackStrategy.DEFAULT);
44 | public final DefaultConverter toLong = new LongConverter()
45 | .withFallbackStrategy(FallbackStrategy.DEFAULT);
46 | public final DefaultConverter toDouble = new DoubleConverter()
47 | .withFallbackStrategy(FallbackStrategy.DEFAULT);
48 | public final DefaultConverter toBoolean = new BooleanConverter()
49 | .withFallbackStrategy(FallbackStrategy.DEFAULT);
50 | }
51 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/converter/DoubleConverter.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.converter;
2 |
3 | import com.creditdatamw.zerocell.internal.IgnoreInvalidValueException;
4 | import com.creditdatamw.zerocell.ZeroCellException;
5 |
6 | public class DoubleConverter extends DefaultConverter {
7 | private static final String message = "Failed to parse '%s' as Double at column='%s' row='%s'";
8 |
9 | @Override
10 | public Double convert(String value, String columnName, int row) {
11 | try {
12 | return Double.parseDouble(value);
13 | } catch (Exception e) {
14 | switch (this.getFallbackStrategy()) {
15 | case LEGACY:
16 | case DEFAULT_TO_NULL:
17 | return null;
18 | case DEFAULT_TO_ZERO:
19 | return 0.0D;
20 | case THROW_EXCEPTION:
21 | throw new ZeroCellException(String.format(message, value, columnName, row));
22 | case IGNORE:
23 | throw new IgnoreInvalidValueException();
24 | case DEFAULT_TO_MIN_VALUE:
25 | case DEFAULT:
26 | default:
27 | return Double.MIN_VALUE;
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/core/src/main/java/com/creditdatamw/zerocell/converter/FallbackStrategy.java:
--------------------------------------------------------------------------------
1 | package com.creditdatamw.zerocell.converter;
2 |
3 | public enum FallbackStrategy {
4 | /**
5 | * Uses the LEGACY approach for handling with failures, which is basically
6 | * setting the field to null for reference types and
7 | * 0 for numeric types.
8 | *
9 | * @deprecated this should be considered deprecated
10 | */
11 | LEGACY,
12 | /**
13 | * Applies the default Fallback Strategy for the field.
14 | */
15 | DEFAULT,
16 | /**
17 | * Ignores the invalid value from the Excel Cell -
18 | * does not set anything on the field. Basically skips the field.
19 | */
20 | IGNORE,
21 | /**
22 | * Sets the field on the field to null. Regardless of type.
23 | */
24 | DEFAULT_TO_NULL,
25 | /**
26 | * Sets the field to 0 if the value from the Excel cell
27 | * cannot be parsed as a numeric value.
28 | * Applies only to numeric types.
29 | */
30 | DEFAULT_TO_ZERO,
31 | /**
32 | * Sets the field to true if the value from the Excel cell
33 | * cannot be parsed as a boolean value.
34 | * Applies only to Boolean types.
35 | */
36 | DEFAULT_TO_TRUE,
37 | /**
38 | * Sets the field to false if the value from the Excel cell
39 | * cannot be parsed as a boolean value.
40 | * Applies only to Boolean types.
41 | */
42 | DEFAULT_TO_FALSE,
43 | /**
44 | * Sets the field to an empty String ("") if the value from
45 | * the Excel cell cannot be parsed. Applies only to String fields.
46 | */
47 | DEFAULT_TO_EMPTY_STRING,
48 | /**
49 | * Sets the field to the MIN_VALUE of the Field type.
50 | *
51 | *
Long.MIN_VALUE for long, Long fields e.g. private long id;
52 | *
Integer.MIN_VALUE for int, Integer fields e.g. private int id;
53 | *
Double.MIN_VALUE for double, Double fields e.g. private double amount;
54 | *
Float.MIN_VALUE for float, Float fields e.g. private float id;
55 | *
56 | *
57 | */
58 | DEFAULT_TO_MIN_VALUE,
59 | /**
60 | * Throws a {@link com.creditdatamw.zerocell.ZeroCellException} if the cell
61 | * value cannot be parsed and converted properly.
62 | *
NOTE:The library will NOT process the rest of the
63 | * row (and File, actually) when this occurs.