1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.gageot.excel.core;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.lang.reflect.Array;
23 import java.util.ArrayList;
24 import java.util.List;
25
26 import org.apache.poi.hssf.usermodel.HSSFRow;
27 import org.apache.poi.hssf.usermodel.HSSFSheet;
28 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
29 import org.springframework.beans.factory.InitializingBean;
30 import org.springframework.core.io.ClassPathResource;
31 import org.springframework.core.io.FileSystemResource;
32 import org.springframework.core.io.Resource;
33 import org.springframework.dao.CleanupFailureDataAccessException;
34 import org.springframework.dao.DataAccessException;
35 import org.springframework.dao.DataAccessResourceFailureException;
36 import org.springframework.util.Assert;
37
38 /***
39 * <b>This is the central class in the Excel core package.</b>
40 * It simplifies the use of Excel and helps to avoid common errors.
41 * It executes core Excel parsing workflow, leaving application code extract results.
42 *
43 * <p>Code using this class need only implement callback interfaces, giving
44 * them a clearly defined contract. The RowCallbackHandler
45 * interface extracts values from each row of a HSSFSheet.
46 *
47 * <p>Can be used within a service implementation via direct instantiation
48 * with a Resource reference, or get prepared in an application context
49 * and given to services as bean reference.
50 *
51 * <p>The motivation and design of this class is inspired from JdbcTemplate.
52 *
53 * <p>Because this class is parameterizable by the callback interfaces,
54 * it isn't necessary to subclass it.
55 *
56 * @author David Gageot
57 * @see SheetExtractor
58 * @see RowCallbackHandler
59 * @see RowMapper
60 */
61 public class ExcelTemplate implements InitializingBean {
62 /*** Used to obtain Excel data throughout the lifecycle of this object */
63 private Resource resource;
64
65 /***
66 * Construct a new ExcelTemplate for bean usage.
67 * Note: The Resource has to be set before using the instance.
68 * This constructor can be used to prepare a ExcelTemplate via a BeanFactory,
69 * typically setting the Resource via setResource.
70 * @see #setResource
71 */
72 public ExcelTemplate() {
73 }
74
75 /***
76 * Construct a new ExcelTemplate, given a Resource to obtain the Excel stream from.
77 * @param aResource Resource to obtain the Excel stream from
78 */
79 public ExcelTemplate (Resource aResource) {
80 setResource (aResource);
81 afterPropertiesSet();
82 }
83
84 /***
85 * Construct a new ExcelTemplate, given an Excel File.
86 * @param aFile Excel file
87 */
88 public ExcelTemplate (File aFile) {
89 setResource (new FileSystemResource (aFile));
90 afterPropertiesSet();
91 }
92
93 /***
94 * Construct a new ExcelTemplate, given a path to an Excel file.
95 * The path can be relative to the given class,
96 * or absolute within the classpath via a leading slash.
97 * @param aPath relative or absolute path within the class path
98 * @param aClass the class to load resources with
99 * @see java.lang.Class#getResourceAsStream
100 */
101 public ExcelTemplate (String aPath, Class aClass) {
102 setResource (new ClassPathResource (aPath, aClass));
103 afterPropertiesSet();
104 }
105
106 /***
107 * Read the content of an Excel file for a given sheet name.
108 * The content of the sheet is extracted using SheetExtractor.
109 * @param sheetName name of the excel sheet
110 * @param sheetExtractor object that will extract results
111 * @return an arbitrary result object, as returned by the ResultSetExtractor
112 * @throws DataAccessException if there is any problem
113 */
114 public Object read (String sheetName, SheetExtractor sheetExtractor) throws DataAccessException {
115 Assert.notNull (sheetExtractor, "SheetExtractor must not be null");
116 Assert.notNull (sheetName, "sheetName must not be null");
117 Assert.notNull (getResource(), "resource is required");
118
119 InputStream in = null;
120 try {
121 in = getResource().getInputStream();
122
123 HSSFWorkbook wb = new HSSFWorkbook (in);
124 HSSFSheet sheet = wb.getSheet (sheetName);
125
126 return sheetExtractor.extractData (sheet);
127 } catch (IOException e) {
128 throw new DataAccessResourceFailureException ("Problem reading file", e);
129 }
130 finally {
131 if (null != in) {
132 try {
133 in.close();
134 } catch (IOException e) {
135 throw new CleanupFailureDataAccessException ("Problem closing file", e);
136 }
137 }
138 }
139 }
140
141 public String[][] read (String sheetName) throws DataAccessException {
142 return (String[][]) read (sheetName, new StringCellMapper(), String.class);
143 }
144
145 public Object[][] read (String sheetName, CellMapper cellMapper) throws DataAccessException {
146 return (Object[][]) read (sheetName, cellMapper, Object.class);
147 }
148
149 public Object[][] read (String sheetName, CellMapper cellMapper, Class clazz) throws DataAccessException {
150 return (Object[][]) read (sheetName, new ObjectArraySheetExtractor (cellMapper, clazz));
151 }
152
153 public List readList (String sheetName, RowMapper rowMapper) throws DataAccessException {
154 return (List) read (sheetName, new RowMapperSheetExtractor (rowMapper));
155 }
156
157 public List readList (String sheetName) throws DataAccessException {
158 return readList (sheetName, new StringCellMapper());
159 }
160
161 public List readList (String sheetName, CellMapper cellMapper) throws DataAccessException {
162 MapListRowCallbackHandler rowHandler = new MapListRowCallbackHandler (cellMapper);
163
164 read (sheetName, rowHandler);
165
166 return rowHandler.getValues();
167 }
168
169 public void read (String sheetName, RowCallbackHandler rowCallbackHandler) throws DataAccessException {
170 read (sheetName, new RowCallbackHandlerSheetExtractor (rowCallbackHandler));
171 }
172
173 public void read (String sheetName, CellCallbackHandler cellCallbackHandler) throws DataAccessException {
174 read (sheetName, new CellCallbackHandlerSheetExtractor (cellCallbackHandler));
175 }
176
177 public List readBeans (String sheetName, Class clazz) throws DataAccessException {
178 BeanCellCallbackHandler handler = new BeanCellCallbackHandler (clazz);
179
180 read (sheetName, handler);
181
182 return handler.getBeans();
183 }
184
185 /***
186 * Set the Resource to obtain the Excel stream from.
187 */
188 public Resource getResource() {
189 return resource;
190 }
191
192 /***
193 * Return the Resource used by this template.
194 */
195 public void setResource(Resource aResource) {
196 this.resource = aResource;
197 }
198
199 /***
200 */
201 public void afterPropertiesSet() {
202 if (null == getResource()) {
203 throw new IllegalArgumentException("resource is required");
204 }
205 }
206
207 /***
208 * RowMapper implementation that creates a <code>java.lang.Object</code> array
209 * for each row.
210 */
211 private static class ObjectArrayRowMapper implements RowMapper {
212 private final CellMapper cellMapper;
213 private final Class cellClass;
214
215 public ObjectArrayRowMapper (CellMapper aCellMapper, Class aCellClass) {
216 cellMapper = aCellMapper;
217 cellClass = aCellClass;
218 }
219
220 public Object mapRow (HSSFRow row, int rowNum) throws IOException {
221 short lastColumnNum = row.getLastCellNum();
222
223 if (lastColumnNum <= 0) {
224 lastColumnNum = 0;
225 }
226
227 Object[] rowValues = (Object[]) Array.newInstance(cellClass, lastColumnNum);
228
229 for (short columnNum = 0; columnNum < lastColumnNum; columnNum++) {
230 rowValues[columnNum] = cellMapper.mapCell (row.getCell(columnNum), rowNum, columnNum);
231 }
232
233 return rowValues;
234 }
235 }
236
237 /***
238 * Adapter to enable use of a ObjectArrayRowMapper inside a SheetExtractor.
239 */
240 private static class ObjectArraySheetExtractor implements SheetExtractor {
241 private final CellMapper cellMapper;
242 private final Class cellClass;
243
244 public ObjectArraySheetExtractor (CellMapper aCellMapper, Class aCellClass) {
245 cellMapper = aCellMapper;
246 cellClass = aCellClass;
247 }
248
249 public Object extractData (HSSFSheet sheet) throws IOException {
250 List rowValues = new ArrayList();
251
252 ObjectArrayRowMapper rowMapper = new ObjectArrayRowMapper (cellMapper, cellClass);
253
254 int maxRowSize = 0;
255 int firstRowIndex = sheet.getFirstRowNum();
256 int lastRowIndex = sheet.getLastRowNum();
257
258 for (int i = firstRowIndex; i <= lastRowIndex; i++) {
259 HSSFRow row = sheet.getRow (i);
260
261 if (null != row) {
262 Object[] currentRowValues = (Object[]) rowMapper.mapRow (row, i);
263 maxRowSize = Math.max (maxRowSize, currentRowValues.length);
264 rowValues.add (currentRowValues);
265 }
266 }
267
268 return rowValues.toArray ((Object[][]) Array.newInstance (cellClass,
269 new int[] {rowValues.size(), maxRowSize}));
270 }
271 }
272
273 /***
274 * RowCallbackHandler implementation that creates a <code>java.util.Map</code>
275 * for each row and put all maps in a list.
276 * The first line is used as keys for the maps.
277 */
278 private static class MapListRowCallbackHandler implements RowCallbackHandler {
279 private final List values = new ArrayList();
280 private final CellMapper cellMapper;
281 private ColumnMapRowMapper rowMapper;
282
283 public MapListRowCallbackHandler (CellMapper aCellMapper) {
284 cellMapper = aCellMapper;
285 }
286
287 public void processRow (HSSFRow row, int rowIndex) throws IOException {
288 if (null == rowMapper) {
289 RowMapper firstRowMapper = new ObjectArrayRowMapper (
290 new StringCellMapper(), String.class);
291
292 String[] keys = (String[]) firstRowMapper.mapRow (row, rowIndex);
293
294 rowMapper = new ColumnMapRowMapper (keys) {
295 protected CellMapper createCellMapper (int rowNum) {
296 return cellMapper;
297 }
298 };
299 } else {
300 values.add (rowMapper.mapRow (row, rowIndex));
301 }
302 }
303
304 public List getValues() {
305 return values;
306 }
307 }
308
309 /***
310 * Adapter to enable use of a RowCallbackHandler inside a SheetExtractor.
311 */
312 private static class RowCallbackHandlerSheetExtractor implements SheetExtractor {
313 private final RowCallbackHandler rowCallbackHandler;
314
315 public RowCallbackHandlerSheetExtractor (RowCallbackHandler aRowCallbackHandler) {
316 rowCallbackHandler = aRowCallbackHandler;
317 }
318
319 public Object extractData (HSSFSheet sheet) throws IOException {
320 int firstRowIndex = sheet.getFirstRowNum();
321 int lastRowIndex = sheet.getLastRowNum();
322
323 for (int i = firstRowIndex; i <= lastRowIndex; i++) {
324 rowCallbackHandler.processRow (sheet.getRow (i), i);
325 }
326
327 return null ;
328 }
329 }
330
331 /***
332 * Adapter to enable use of a CellCallbackHandler inside a SheetExtractor.
333 */
334 private static class CellCallbackHandlerSheetExtractor implements SheetExtractor {
335 private final CellCallbackHandler cellCallbackHandler;
336
337 public CellCallbackHandlerSheetExtractor (CellCallbackHandler aCellCallbackHandler) {
338 cellCallbackHandler = aCellCallbackHandler;
339 }
340
341 public Object extractData (HSSFSheet sheet) throws IOException {
342 int firstRowIndex = sheet.getFirstRowNum();
343 int lastRowIndex = sheet.getLastRowNum();
344
345 for (int i = firstRowIndex; i <= lastRowIndex; i++) {
346 HSSFRow row = sheet.getRow (i);
347 if (null != row) {
348 short firstColIndex = row.getFirstCellNum();
349 short lastColIndex = row.getLastCellNum();
350
351 for (short j = firstColIndex; j < lastColIndex; j++) {
352 cellCallbackHandler.processCell (row.getCell(j), i, j);
353 }
354 }
355 }
356
357 return null ;
358 }
359 }
360 }