View Javadoc

1   /*
2    * Copyright 2002-2006 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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;	// TODO
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) {	// First line, read keys
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 {					// Other lines, read values
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 }