001 /*
002 * CSVParser.java
003 *
004 * Copyright (C) 2005 Anupam Sengupta ([email protected])
005 *
006 * This program is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU General Public License
008 * as published by the Free Software Foundation; either version 2
009 * of the License, or (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
019 *
020 * Version: $Revision: 1.3 $
021 */
022 package net.sf.anupam.csv;
023
024 import net.sf.anupam.csv.formatters.CSVFieldFormatter;
025 import net.sf.anupam.csv.mapping.CSVBeanMapping;
026 import net.sf.anupam.csv.mapping.CSVFieldMapping;
027 import org.apache.commons.beanutils.BeanUtils;
028 import org.apache.commons.lang.builder.ToStringBuilder;
029 import org.apache.commons.logging.Log;
030 import org.apache.commons.logging.LogFactory;
031
032 import java.lang.reflect.InvocationTargetException;
033 import java.util.Iterator;
034 import java.util.List;
035
036 /**
037 * Parses CSV files and creates the mapped POJO objects. This is the primary
038 * interface into the CSV parsing framework.
039 * <p/>
040 * The class implements {@link Iterable Iterable} interface and can be
041 * used in the new <em>Tiger</em> for loops to iterate over all the CSV
042 * records in the file.
043 * </p>
044 * <p/>
045 * Configuration of the parser is performed via the <code>csv-mapping.xml</code>
046 * file. See the package description for more details.
047 * </p>
048 * <p/>
049 * Note that the class is not meant to be instantiated directly. Instead, the
050 * {@link CSVParserFactory CSVParserFactory} factory should be
051 * used for creation of instances.
052 * </p>
053 *
054 * @author Anupam Sengupta
055 * @version $Revision: 1.3 $
056 * @see CSVParserFactory
057 * @since 1.5
058 */
059 public class CSVParser implements Iterable<Object> {
060
061 /**
062 * The logger to use.
063 */
064 private static final Log LOG = LogFactory.getLog(CSVParser.class);
065
066 /**
067 * The CSV Reader to use for this parser.
068 */
069 private CSVReader reader;
070
071 /**
072 * The root bean mapping configuration for this parser.
073 */
074 private CSVBeanMapping rootBeanMapping;
075
076 /**
077 * Constructor for CSVParser. The constructor accepts the bean mapping to
078 * use as the starting CSV mapping configuration
079 * <em>(a.k.a the root bean mapping)</em> and the CSV reader/parser engine
080 * to use for actual parsing.
081 *
082 * @param rootBeanMapping the bean mapping to use as the starting configuration
083 * @param reader the CSV Reader object which will actually parse the CSV file
084 */
085 public CSVParser(final CSVBeanMapping rootBeanMapping,
086 final CSVReader reader) {
087 super();
088 this.rootBeanMapping = rootBeanMapping;
089 this.reader = reader;
090 }
091
092 /**
093 * Dumps the root bean mapping configuration for this parser. This is meant
094 * for <strong>debugging</strong> only.
095 *
096 * @return the string representation of this parser
097 * @see Object#toString()
098 */
099 @Override
100 public String toString() {
101 return new ToStringBuilder(this).append("beanMapping", rootBeanMapping)
102 .toString();
103 }
104
105 /**
106 * Finalizes this parser and closes the reader.
107 *
108 * @throws Throwable thrown if the finalization fails
109 * @see Object#finalize()
110 */
111 @Override
112 protected void finalize() throws Throwable {
113 super.finalize();
114 if (reader != null) {
115 reader.close();
116 reader = null;
117 }
118 rootBeanMapping = null;
119 }
120
121 /**
122 * The iterator to provide the Iterable interface to the parser.
123 */
124 private final class MappedObjectIterator implements Iterator<Object> {
125
126 /**
127 * The actual line iterator to use.
128 */
129 private Iterator<List<String>> csvLineIter;
130
131 /**
132 * The iterator constructor.
133 *
134 * @param csvLineIter The actual line iterator to use
135 */
136 MappedObjectIterator(final Iterator<List<String>> csvLineIter) {
137 super();
138 this.csvLineIter = csvLineIter;
139 }
140
141 /**
142 * Finalizes this iterator and nullifies all instance variables.
143 *
144 * @throws Throwable if the finalization fails
145 * @see Object#finalize()
146 */
147 @Override
148 protected final void finalize() throws Throwable {
149 super.finalize();
150 csvLineIter = null;
151 }
152
153 /**
154 * Indicates whether more parsed POJO beans exist.
155 *
156 * @return indicates whether there are any more parsed beans
157 * @see java.util.Iterator#hasNext()
158 */
159 public final boolean hasNext() {
160 return csvLineIter.hasNext();
161 }
162
163 /**
164 * Returns the parsed and mapped POJO bean corresponding to the current
165 * CSV line. Each subsequent invocation will parse and return the next
166 * parsed POJO, until end of the CSV stream is reached.
167 *
168 * @return the parsed bean
169 * @see java.util.Iterator#next()
170 */
171 public Object next() {
172 final List<String> csvLine = csvLineIter.next();
173 return getMappedBean(csvLine, getRootBeanMapping());
174 }
175
176 /**
177 * This operation is not supported.
178 *
179 * @see java.util.Iterator#remove()
180 */
181 public final void remove() {
182 csvLineIter.remove();
183 }
184
185 /**
186 * Applies the field formatters if present.
187 *
188 * @param csvFieldValue the field to format
189 * @param fieldMapping the field mapping from which the formatter should be used
190 * @return the formatted value
191 */
192 private String formatValue(final String csvFieldValue,
193 final CSVFieldMapping fieldMapping) {
194 final CSVFieldFormatter formatter = fieldMapping.getFormatter();
195 if (formatter == null) {
196 return csvFieldValue;
197 }
198
199 return formatter.format(csvFieldValue);
200 }
201
202 /**
203 * Returns the mapped bean from the specified list of CSV values.
204 *
205 * @param csvLine the CSV line to parse
206 * @param beanMap the bean mapping to use
207 * @return the mapped bean
208 */
209 private Object getMappedBean(final List<String> csvLine,
210 final CSVBeanMapping beanMap) {
211
212 try {
213 final Object bean = Class.forName(beanMap.getBeanClass())
214 .newInstance();
215
216 for (CSVFieldMapping fieldMapping : beanMap) {
217 final Object formattedFieldValue;
218
219 if (fieldMapping.getBeanReferenceName().equals("none")) {
220 formattedFieldValue = getMappedField(csvLine,
221 fieldMapping);
222
223 } else {
224 // Recurse and get the value.
225 formattedFieldValue = getMappedBean(csvLine,
226 fieldMapping.getBeanReference());
227 }
228
229 try {
230 BeanUtils.setProperty(bean, fieldMapping
231 .getAttributeName(), formattedFieldValue);
232 } catch (final IllegalAccessException e) {
233 LOG.warn(e);
234 } catch (final InvocationTargetException e) {
235 LOG.warn(e);
236 }
237 }
238 return bean;
239
240 } catch (final ClassNotFoundException e) {
241 LOG.warn("The Bean for class: " + beanMap.getClass()
242 + " could not be instantiated", e);
243 return null;
244
245 } catch (final IllegalAccessException e) {
246 LOG.warn("The Bean for class: " + beanMap.getClass()
247 + " could not be instantiated", e);
248 return null;
249 } catch (final InstantiationException e) {
250 LOG.warn("The Bean for class: " + beanMap.getClass()
251 + " could not be instantiated", e);
252 return null;
253 }
254 }
255
256 /**
257 * Returns the parsed field value.
258 *
259 * @param csvLine the CSV line to parse
260 * @param fieldMapping the field mapping to use
261 * @return the mapped field value
262 */
263 private Object getMappedField(final List<String> csvLine,
264 final CSVFieldMapping fieldMapping) {
265
266 final String csvFieldValue = csvLine.get(fieldMapping
267 .getFieldPosition());
268 return formatValue(csvFieldValue, fieldMapping);
269
270 }
271
272 }
273
274 /**
275 * Returns the iterator for retrieving the parsed POJO beans.
276 *
277 * @return the iterator over the parsed beans
278 * @see Iterable#iterator()
279 */
280 public Iterator<Object> iterator() {
281
282 return new MappedObjectIterator(reader.iterator());
283 }
284
285 /**
286 * Returns the root bean mapping. The root bean mapping is the bean mapping
287 * with which the Parser is configured. "Child" bean mappings (which are not
288 * directly accessible) are the bean mapping configurations which may be
289 * present as references from the root mapping.
290 *
291 * @return Returns the root bean mapping.
292 */
293 private CSVBeanMapping getRootBeanMapping() {
294 return this.rootBeanMapping;
295 }
296
297 }