001: /*
002: * CSVParser.java
003: *
004: * Copyright (C) 2005 Anupam Sengupta (anupamsg@users.sourceforge.net)
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",
102: rootBeanMapping).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
125: Iterator<Object> {
126:
127: /**
128: * The actual line iterator to use.
129: */
130: private Iterator<List<String>> csvLineIter;
131:
132: /**
133: * The iterator constructor.
134: *
135: * @param csvLineIter The actual line iterator to use
136: */
137: MappedObjectIterator(final Iterator<List<String>> csvLineIter) {
138: super ();
139: this .csvLineIter = csvLineIter;
140: }
141:
142: /**
143: * Finalizes this iterator and nullifies all instance variables.
144: *
145: * @throws Throwable if the finalization fails
146: * @see Object#finalize()
147: */
148: @Override
149: protected final void finalize() throws Throwable {
150: super .finalize();
151: csvLineIter = null;
152: }
153:
154: /**
155: * Indicates whether more parsed POJO beans exist.
156: *
157: * @return indicates whether there are any more parsed beans
158: * @see java.util.Iterator#hasNext()
159: */
160: public final boolean hasNext() {
161: return csvLineIter.hasNext();
162: }
163:
164: /**
165: * Returns the parsed and mapped POJO bean corresponding to the current
166: * CSV line. Each subsequent invocation will parse and return the next
167: * parsed POJO, until end of the CSV stream is reached.
168: *
169: * @return the parsed bean
170: * @see java.util.Iterator#next()
171: */
172: public Object next() {
173: final List<String> csvLine = csvLineIter.next();
174: return getMappedBean(csvLine, getRootBeanMapping());
175: }
176:
177: /**
178: * This operation is not supported.
179: *
180: * @see java.util.Iterator#remove()
181: */
182: public final void remove() {
183: csvLineIter.remove();
184: }
185:
186: /**
187: * Applies the field formatters if present.
188: *
189: * @param csvFieldValue the field to format
190: * @param fieldMapping the field mapping from which the formatter should be used
191: * @return the formatted value
192: */
193: private String formatValue(final String csvFieldValue,
194: final CSVFieldMapping fieldMapping) {
195: final CSVFieldFormatter formatter = fieldMapping
196: .getFormatter();
197: if (formatter == null) {
198: return csvFieldValue;
199: }
200:
201: return formatter.format(csvFieldValue);
202: }
203:
204: /**
205: * Returns the mapped bean from the specified list of CSV values.
206: *
207: * @param csvLine the CSV line to parse
208: * @param beanMap the bean mapping to use
209: * @return the mapped bean
210: */
211: private Object getMappedBean(final List<String> csvLine,
212: final CSVBeanMapping beanMap) {
213:
214: try {
215: final Object bean = Class.forName(
216: beanMap.getBeanClass()).newInstance();
217:
218: for (CSVFieldMapping fieldMapping : beanMap) {
219: final Object formattedFieldValue;
220:
221: if (fieldMapping.getBeanReferenceName().equals(
222: "none")) {
223: formattedFieldValue = getMappedField(csvLine,
224: fieldMapping);
225:
226: } else {
227: // Recurse and get the value.
228: formattedFieldValue = getMappedBean(csvLine,
229: fieldMapping.getBeanReference());
230: }
231:
232: try {
233: BeanUtils.setProperty(bean, fieldMapping
234: .getAttributeName(),
235: formattedFieldValue);
236: } catch (final IllegalAccessException e) {
237: LOG.warn(e);
238: } catch (final InvocationTargetException e) {
239: LOG.warn(e);
240: }
241: }
242: return bean;
243:
244: } catch (final ClassNotFoundException e) {
245: LOG.warn("The Bean for class: " + beanMap.getClass()
246: + " could not be instantiated", e);
247: return null;
248:
249: } catch (final IllegalAccessException e) {
250: LOG.warn("The Bean for class: " + beanMap.getClass()
251: + " could not be instantiated", e);
252: return null;
253: } catch (final InstantiationException e) {
254: LOG.warn("The Bean for class: " + beanMap.getClass()
255: + " could not be instantiated", e);
256: return null;
257: }
258: }
259:
260: /**
261: * Returns the parsed field value.
262: *
263: * @param csvLine the CSV line to parse
264: * @param fieldMapping the field mapping to use
265: * @return the mapped field value
266: */
267: private Object getMappedField(final List<String> csvLine,
268: final CSVFieldMapping fieldMapping) {
269:
270: final String csvFieldValue = csvLine.get(fieldMapping
271: .getFieldPosition());
272: return formatValue(csvFieldValue, fieldMapping);
273:
274: }
275:
276: }
277:
278: /**
279: * Returns the iterator for retrieving the parsed POJO beans.
280: *
281: * @return the iterator over the parsed beans
282: * @see Iterable#iterator()
283: */
284: public Iterator<Object> iterator() {
285:
286: return new MappedObjectIterator(reader.iterator());
287: }
288:
289: /**
290: * Returns the root bean mapping. The root bean mapping is the bean mapping
291: * with which the Parser is configured. "Child" bean mappings (which are not
292: * directly accessible) are the bean mapping configurations which may be
293: * present as references from the root mapping.
294: *
295: * @return Returns the root bean mapping.
296: */
297: private CSVBeanMapping getRootBeanMapping() {
298: return this.rootBeanMapping;
299: }
300:
301: }
|