View Javadoc
1   /*
2    * Copyright 2007 Kasper B. Graversen
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  package org.supercsv.io;
17  
18  import java.io.IOException;
19  import java.io.Reader;
20  import java.lang.reflect.Method;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.supercsv.cellprocessor.ift.CellProcessor;
25  import org.supercsv.exception.SuperCsvConstraintViolationException;
26  import org.supercsv.exception.SuperCsvException;
27  import org.supercsv.exception.SuperCsvReflectionException;
28  import org.supercsv.prefs.CsvPreference;
29  import org.supercsv.util.BeanInterfaceProxy;
30  import org.supercsv.util.MethodCache;
31  
32  /**
33   * CsvBeanReader reads a CSV file by instantiating a bean for every row and mapping each column to a field on the bean
34   * (using the supplied name mapping). The bean to populate can be either a class or interface. If a class is used, it
35   * must be a valid Javabean, i.e. it must have a default no-argument constructor and getter/setter methods. An interface
36   * may also be used if it defines getters/setters - a proxy object will be created that implements the interface.
37   * 
38   * @author Kasper B. Graversen
39   * @author James Bassett
40   */
41  public class CsvBeanReader extends AbstractCsvReader implements ICsvBeanReader {
42  	
43  	// temporary storage of processed columns to be mapped to the bean
44  	private final List<Object> processedColumns = new ArrayList<Object>();
45  	
46  	// cache of methods for mapping from columns to fields
47  	private final MethodCache cache = new MethodCache();
48  	
49  	/**
50  	 * Constructs a new <tt>CsvBeanReader</tt> with the supplied Reader and CSV preferences. Note that the
51  	 * <tt>reader</tt> will be wrapped in a <tt>BufferedReader</tt> before accessed.
52  	 * 
53  	 * @param reader
54  	 *            the reader
55  	 * @param preferences
56  	 *            the CSV preferences
57  	 * @throws NullPointerException
58  	 *             if reader or preferences are null
59  	 */
60  	public CsvBeanReader(final Reader reader, final CsvPreference preferences) {
61  		super(reader, preferences);
62  	}
63  	
64  	/**
65  	 * Constructs a new <tt>CsvBeanReader</tt> with the supplied (custom) Tokenizer and CSV preferences. The tokenizer
66  	 * should be set up with the Reader (CSV input) and CsvPreference beforehand.
67  	 * 
68  	 * @param tokenizer
69  	 *            the tokenizer
70  	 * @param preferences
71  	 *            the CSV preferences
72  	 * @throws NullPointerException
73  	 *             if tokenizer or preferences are null
74  	 */
75  	public CsvBeanReader(final ITokenizer tokenizer, final CsvPreference preferences) {
76  		super(tokenizer, preferences);
77  	}
78  	
79  	/**
80  	 * Instantiates the bean (or creates a proxy if it's an interface).
81  	 * 
82  	 * @param clazz
83  	 *            the bean class to instantiate (a proxy will be created if an interface is supplied), using the default
84  	 *            (no argument) constructor
85  	 * @return the instantiated bean
86  	 * @throws SuperCsvReflectionException
87  	 *             if there was a reflection exception when instantiating the bean
88  	 */
89  	private static <T> T instantiateBean(final Class<T> clazz) {
90  		final T bean;
91  		if( clazz.isInterface() ) {
92  			bean = BeanInterfaceProxy.createProxy(clazz);
93  		} else {
94  			try {
95  				bean = clazz.newInstance();
96  			}
97  			catch(InstantiationException e) {
98  				throw new SuperCsvReflectionException(String.format(
99  					"error instantiating bean, check that %s has a default no-args constructor", clazz.getName()), e);
100 			}
101 			catch(IllegalAccessException e) {
102 				throw new SuperCsvReflectionException("error instantiating bean", e);
103 			}
104 		}
105 		
106 		return bean;
107 	}
108 	
109 	/**
110 	 * Invokes the setter on the bean with the supplied value.
111 	 * 
112 	 * @param bean
113 	 *            the bean
114 	 * @param setMethod
115 	 *            the setter method for the field
116 	 * @param fieldValue
117 	 *            the field value to set
118 	 * @throws SuperCsvException
119 	 *             if there was an exception invoking the setter
120 	 */
121 	private static void invokeSetter(final Object bean, final Method setMethod, final Object fieldValue) {
122 		try {
123 			setMethod.invoke(bean, fieldValue);
124 		}
125 		catch(final Exception e) {
126 			throw new SuperCsvReflectionException(String.format("error invoking method %s()", setMethod.getName()), e);
127 		}
128 	}
129 	
130 	/**
131 	 * Populates the bean by mapping the processed columns to the fields of the bean.
132 	 * 
133 	 * @param resultBean
134 	 *            the bean to populate
135 	 * @param nameMapping
136 	 *            the name mappings
137 	 * @return the populated bean
138 	 * @throws SuperCsvReflectionException
139 	 *             if there was a reflection exception while populating the bean
140 	 */
141 	private <T> T populateBean(final T resultBean, final String[] nameMapping) {
142 		
143 		// map each column to its associated field on the bean
144 		for( int i = 0; i < nameMapping.length; i++ ) {
145 			
146 			final Object fieldValue = processedColumns.get(i);
147 			
148 			// don't call a set-method in the bean if there is no name mapping for the column or no result to store
149 			if( nameMapping[i] == null || fieldValue == null ) {
150 				continue;
151 			}
152 			
153 			// invoke the setter on the bean
154 			Method setMethod = cache.getSetMethod(resultBean, nameMapping[i], fieldValue.getClass());
155 			invokeSetter(resultBean, setMethod, fieldValue);
156 			
157 		}
158 		
159 		return resultBean;
160 	}
161 	
162 	/**
163 	 * {@inheritDoc}
164 	 */
165 	public <T> T read(final Class<T> clazz, final String... nameMapping) throws IOException {
166 		
167 		if( clazz == null ) {
168 			throw new NullPointerException("clazz should not be null");
169 		} else if( nameMapping == null ) {
170 			throw new NullPointerException("nameMapping should not be null");
171 		}
172 		
173 		return readIntoBean(instantiateBean(clazz), nameMapping, null);
174 	}
175 	
176 	/**
177 	 * {@inheritDoc}
178 	 */
179 	public <T> T read(final Class<T> clazz, final String[] nameMapping, final CellProcessor... processors)
180 		throws IOException {
181 		
182 		if( clazz == null ) {
183 			throw new NullPointerException("clazz should not be null");
184 		} else if( nameMapping == null ) {
185 			throw new NullPointerException("nameMapping should not be null");
186 		} else if( processors == null ) {
187 			throw new NullPointerException("processors should not be null");
188 		}
189 		
190 		return readIntoBean(instantiateBean(clazz), nameMapping, processors);
191 	}
192 	
193 	/**
194 	 * {@inheritDoc}
195 	 */
196 	public <T> T read(final T bean, final String... nameMapping) throws IOException {
197 		
198 		if( bean == null ) {
199 			throw new NullPointerException("bean should not be null");
200 		} else if( nameMapping == null ) {
201 			throw new NullPointerException("nameMapping should not be null");
202 		}
203 		
204 		return readIntoBean(bean, nameMapping, null);
205 	}
206 	
207 	/**
208 	 * {@inheritDoc}
209 	 */
210 	public <T> T read(final T bean, final String[] nameMapping, final CellProcessor... processors) throws IOException {
211 		if( bean == null ) {
212 			throw new NullPointerException("bean should not be null");
213 		} else if( nameMapping == null ) {
214 			throw new NullPointerException("nameMapping should not be null");
215 		} else if( processors == null ) {
216 			throw new NullPointerException("processors should not be null");
217 		}
218 		
219 		return readIntoBean(bean, nameMapping, processors);
220 	}
221 	
222 	/**
223 	 * Reads a row of a CSV file and populates the bean, using the supplied name mapping to map column values to the
224 	 * appropriate fields. If processors are supplied then they are used, otherwise the raw String values will be used.
225 	 * 
226 	 * @param bean
227 	 *            the bean to populate
228 	 * @param nameMapping
229 	 *            the name mapping array
230 	 * @param processors
231 	 *            the (optional) cell processors
232 	 * @return the populated bean, or null if EOF was reached
233 	 * @throws IllegalArgumentException
234 	 *             if nameMapping.length != number of CSV columns read
235 	 * @throws IOException
236 	 *             if an I/O error occurred
237 	 * @throws NullPointerException
238 	 *             if bean or nameMapping are null
239 	 * @throws SuperCsvConstraintViolationException
240 	 *             if a CellProcessor constraint failed
241 	 * @throws SuperCsvException
242 	 *             if there was a general exception while reading/processing
243 	 * @throws SuperCsvReflectionException
244 	 *             if there was an reflection exception while mapping the values to the bean
245 	 */
246 	private <T> T readIntoBean(final T bean, final String[] nameMapping, final CellProcessor[] processors)
247 		throws IOException {
248 		
249 		if( readRow() ) {
250 			if( nameMapping.length != length() ) {
251 				throw new IllegalArgumentException(String.format(
252 					"the nameMapping array and the number of columns read "
253 						+ "should be the same size (nameMapping length = %d, columns = %d)", nameMapping.length,
254 					length()));
255 			}
256 			
257 			if( processors == null ) {
258 				processedColumns.clear();
259 				processedColumns.addAll(getColumns());
260 			} else {
261 				executeProcessors(processedColumns, processors);
262 			}
263 			
264 			return populateBean(bean, nameMapping);
265 		}
266 		
267 		return null; // EOF
268 	}
269 	
270 }