001    /*
002     * CSVParserFactory.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.formatters.CSVFormatterFactory;
026    import net.sf.anupam.csv.mapping.CSVBeanMapping;
027    import net.sf.anupam.csv.mapping.CSVFieldMapping;
028    import net.sf.anupam.csv.mapping.CSVMappingParser;
029    import net.sf.anupam.csv.exceptions.CSVOException;
030    import org.apache.commons.lang.StringUtils;
031    import org.apache.commons.logging.Log;
032    import org.apache.commons.logging.LogFactory;
033    
034    import java.io.FileNotFoundException;
035    import java.io.FileReader;
036    import java.io.InputStream;
037    import java.io.InputStreamReader;
038    import java.io.Reader;
039    import java.util.HashMap;
040    import java.util.Map;
041    
042    /**
043     * Singleton factory for creating the {@link CSVParser CSVParser} parser objects
044     * for clients' of the framework. This factory uses the
045     * <code>csv-mapping.xml</code> mapping configuration to create CSV parsers
046     * customized for the POJO bean to parse. This is the first interface for
047     * clients of the framework.
048     *
049     * @author Anupam Sengupta
050     * @version $Revision: 1.3 $
051     * @see CSVParser
052     * @since 1.5
053     */
054    public final class CSVParserFactory {
055    
056        /**
057         * The Mapping file name.
058         */
059        private static final String MAPPING_FILE_NAME = "csv-mapping.xml";
060    
061        /**
062         * The logger to use.
063         */
064        private static final Log LOG = LogFactory.getLog(CSVParserFactory.class);
065    
066        /**
067         * The singleton factory instance.
068         */
069        private static CSVParserFactory singleton;
070    
071        private static final CSVFormatterFactory FORMATTER_FACTORY = CSVFormatterFactory
072                .getSingleton();
073    
074        /**
075         * The CSV to POJO mapping repository.
076         */
077        private Map<String, CSVBeanMapping> beanMappings;
078    
079        static {
080    
081        }
082    
083        /**
084         * Constructor for CSVParserFactory. Private as this is a singleton.
085         */
086        private CSVParserFactory() {
087            super();
088            beanMappings = new HashMap<String, CSVBeanMapping>();
089        }
090    
091        /**
092         * Returns the singleton instance of this factory.
093         *
094         * @return the singleton parser factory
095         * @throws CSVOException thrown if the singleton cannot be created
096         */
097        public synchronized static CSVParserFactory getSingleton()
098                throws CSVOException {
099            if (singleton == null) {
100                // Create the singleton at startup.
101                singleton = new CSVParserFactory();
102                singleton.loadMappings();
103                LOG.info("Created the Singleton for: " + CSVParserFactory.class);
104            }
105            return singleton;
106        }
107    
108        /**
109         * Loads the bean mapping configuration from the XML mapping file.
110         *
111         * @throws CSVOException thrown if the mapping cannot be loaded
112         */
113        private void loadMappings()
114                throws CSVOException {
115            final CSVMappingParser parser = new CSVMappingParser();
116            beanMappings.putAll(parser.getMappings(MAPPING_FILE_NAME, true));
117    
118            for (String beanNames : beanMappings.keySet()) {
119                final CSVBeanMapping currentBeanMapping = beanMappings
120                        .get(beanNames);
121                for (CSVFieldMapping currentFieldMapping : currentBeanMapping) {
122                    createFormattersFor(currentFieldMapping);
123                    resolveBeanReferencesFor(currentFieldMapping);
124                }
125            }
126            LOG.debug("Loaded the CSV Mapping configuration from "
127                    + MAPPING_FILE_NAME);
128        }
129    
130        /**
131         * Creates any necessary field formatters for the specified field mapping.
132         *
133         * @param fieldMapping the field for which formatters should be created
134         * @throws net.sf.anupam.csv.exceptions.CSVOException
135         *          thrown if the specified formatters cannot be created
136         */
137        private void createFormattersFor(final CSVFieldMapping fieldMapping)
138                throws CSVOException {
139    
140            final CSVFieldFormatter formatter = FORMATTER_FACTORY
141                    .createFormatterFor(fieldMapping.getReformatterName());
142            fieldMapping.setFormatter(formatter);
143    
144        }
145    
146        /**
147         * Resolves bean references for the specified field, and sets the bean
148         * mapping hierarchy accordingly.
149         *
150         * @param fieldMapping the field for which references need to be resolved
151         */
152        private void resolveBeanReferencesFor(final CSVFieldMapping fieldMapping) {
153    
154            final String beanRefName = fieldMapping.getBeanReferenceName();
155            if (!beanRefName.equalsIgnoreCase("none")) {
156                final CSVBeanMapping referencedBean = getBeanMapping(beanRefName);
157    
158                if (referencedBean != null) {
159                    fieldMapping.setBeanReference(referencedBean);
160                } else {
161                    LOG.warn("For field " + fieldMapping
162                            + " the referenced bean does not exist");
163                    fieldMapping.setBeanReferenceName("none");
164                }
165            }
166    
167        }
168    
169        /**
170         * Returns the requested bean mapping configuration.
171         *
172         * @param beanName the POJO bean for which the mapping is to be returned
173         * @return the CSV bean mapping, or <code>null</code> if not found
174         */
175        public CSVBeanMapping getBeanMapping(final String beanName) {
176            return beanMappings.get(beanName);
177        }
178    
179        /**
180         * Returns a new CSV file parser for the specified mapping, and the
181         * specified CSV file.
182         *
183         * @param mappingName the CSV mapping to for which the parser should be created
184         * @param csvFileName the CSV file to be parsed
185         * @param inClassPath indicates whether the CSV file is in the classpath
186         * @return the CSV Parser, or <code>null</code> if not found
187         * @throws FileNotFoundException thrown if the specified CSV file cannot be found
188         * @see #getCSVParser(String,java.io.Reader)
189         */
190        public CSVParser getCSVParser(final String mappingName,
191                                      final String csvFileName, final boolean inClassPath
192        ) throws FileNotFoundException {
193    
194            if (StringUtils.isEmpty(csvFileName)) {
195                LOG.warn("The specified CSV Filename is empty");
196                throw new IllegalArgumentException("File Name is empty");
197            }
198    
199            final Reader reader;
200    
201            try {
202                if (inClassPath) {
203                    final InputStream is = ClassLoader
204                            .getSystemResourceAsStream(csvFileName);
205                    if (is == null) {
206                        throw new FileNotFoundException("The CSV File: "
207                                + csvFileName + " was not found in the classpath");
208                    }
209                    reader = new InputStreamReader(is);
210                } else {
211                    reader = new FileReader(csvFileName);
212    
213                }
214                LOG.debug("Successfully read the CSV file");
215            } catch (final FileNotFoundException e) {
216                LOG.warn("The specified CSV File: " + csvFileName
217                        + " was not found", e);
218                throw e;
219            }
220    
221            return getCSVParser(mappingName, reader);
222        }
223    
224        /**
225         * Returns a new CSV file parser for the specified mapping and the specified
226         * CSV reader stream.
227         *
228         * @param mappingName the CSV mapping for which the parser should be returned
229         * @param csvReader   the CSV stream to parse
230         * @return the CSV Parser, or <code>null</code> if not found
231         * @see #getCSVParser(String,String,boolean)
232         */
233        public CSVParser getCSVParser(final String mappingName,
234                                      final Reader csvReader) {
235    
236            final CSVBeanMapping beanMapping = getBeanMapping(mappingName);
237    
238            if (beanMapping == null) {
239                LOG.warn("Specified bean mapping was not found");
240                throw new IllegalArgumentException(
241                        "Specified bean mapping was not found");
242            }
243    
244            if (csvReader == null) {
245                LOG.warn("Specified CSV IO Reader was null");
246                throw new IllegalArgumentException(
247                        "Specified CSV IO Reader was null");
248            }
249    
250            final CSVReader reader = new CSVReader(csvReader, beanMapping
251                    .isCsvHeaderPresent());
252    
253            return new CSVParser(beanMapping, reader);
254        }
255    }