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    }