1 /***
2 * Copyright (C) 2008 rweber <quietgenie@users.sourceforge.net>
3 *
4 * This file is part of CsvObjectMapper.
5 *
6 * CsvObjectMapper is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CsvObjectMapper is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with CsvObjectMapper. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /***
21 *
22 */
23 package com.projectnine.csvmapper;
24
25 import java.io.FileWriter;
26 import java.io.IOException;
27 import java.io.Writer;
28 import java.util.Collections;
29 import java.util.Comparator;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Vector;
33
34 import net.sf.csv4j.CSVWriter;
35
36 import org.apache.commons.jexl.Expression;
37 import org.apache.commons.jexl.ExpressionFactory;
38 import org.apache.commons.jexl.JexlContext;
39 import org.apache.commons.jexl.JexlHelper;
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42
43 /***
44 * @author robweber
45 *
46 */
47 public class ObjectToCsvMapper {
48 private static final Log log = LogFactory.getLog(ObjectToCsvMapper.class);
49
50 private String mappingDefinition;
51 private Writer output;
52 private CSVWriter csvWriter;
53
54 public ObjectToCsvMapper(String mappingDefinition, String outputFilename) {
55 this.mappingDefinition = mappingDefinition;
56 try {
57 this.output = new FileWriter(outputFilename);
58 csvWriter = new CSVWriter(output);
59 } catch (IOException e) {
60 log.error("Unable to open the specified file, " + outputFilename
61 + ", for writing.", e);
62 throw new RuntimeException(e);
63 }
64 }
65
66 public void writeObjectsToCsv(List<? extends Object> list) throws Exception {
67 writeObjectsToCsv(list, false);
68 }
69
70 public void writeObjectsToCsv(List<? extends Object> list,
71 boolean writeHeader) throws Exception {
72 if (writeHeader) {
73 List<String> csvHeader = createHeader(mappingDefinition);
74 if (csvHeader != null) {
75 csvWriter.writeLine(csvHeader);
76 }
77 }
78
79 for (int i = 0; i < list.size(); i++) {
80 if (list.get(i) != null) {
81 List<String> stringList = convertObjectToCsv(list.get(i),
82 mappingDefinition);
83 csvWriter.writeLine(stringList);
84 }
85 }
86
87 try {
88 output.flush();
89 output.close();
90 } catch (Exception e) {
91 log.warn("Unable to close the CSV output.");
92 }
93 }
94
95 public static List<String> createHeader(String mappingDefinition) {
96 List<String> csvHeader = new Vector<String>();
97 CsvMappingDefinition csvMappingDefinition = CsvToObjectMapper.csvMappingDefinitions
98 .get(mappingDefinition);
99
100 for (int i = 0; i < csvMappingDefinition.getFieldMappings().size(); i++) {
101 String fieldHeader = csvMappingDefinition.getFieldMappings().get(i)
102 .getCsvFieldHeader();
103 if (fieldHeader == null) {
104 fieldHeader = "";
105 }
106
107 csvHeader.add(fieldHeader);
108 }
109
110 return csvHeader;
111 }
112
113 @SuppressWarnings("unchecked")
114 public static List<String> convertObjectToCsv(Object object,
115 String mappingDefinition) throws Exception {
116 log.debug("Converting object of class, " + object.getClass().getName()
117 + ", using mapping definition, " + mappingDefinition);
118 CsvMappingDefinition csvMappingDefinition = CsvToObjectMapper.csvMappingDefinitions
119 .get(mappingDefinition);
120
121 if (csvMappingDefinition == null) {
122 throw new Exception("The mapping definition specified, "
123 + mappingDefinition + ", is not valid.");
124 }
125
126 List<CsvFieldMapping> fieldMappingList = getFieldMappingListForMappingDefinition(mappingDefinition);
127 log.debug("Retrieved " + fieldMappingList.size() + " "
128 + (fieldMappingList.size() != 1 ? "fields" : "field") + ".");
129 List<String> csvFieldList = new Vector<String>();
130 Collections.sort(fieldMappingList, new Comparator<CsvFieldMapping>() {
131
132 public int compare(CsvFieldMapping o1, CsvFieldMapping o2) {
133 if (o1.getColumnIndex() < o2.getColumnIndex()) {
134 return -1;
135 }
136
137 if (o1.getColumnIndex() == o2.getColumnIndex()) {
138 return 0;
139 }
140
141 return 1;
142 }
143
144 });
145
146 if (log.isDebugEnabled()) {
147 for (int i = 0; i < fieldMappingList.size(); i++) {
148 log.debug("FieldMapping "
149 + fieldMappingList.get(i).getColumnIndex());
150 }
151 }
152
153 int lastCsvIndex;
154 int currentFieldMappingIndex = 0;
155 if (csvMappingDefinition.getExpectedNumberOfFields() > 0) {
156 log
157 .debug("Getting expected number of fields from explicit definition.");
158 lastCsvIndex = csvMappingDefinition.getExpectedNumberOfFields();
159 } else {
160 log.debug("Getting expected number of fields by inferrence.");
161 lastCsvIndex = fieldMappingList.get(fieldMappingList.size() - 1)
162 .getColumnIndex() + 1;
163 }
164
165 log.debug("Expect the CSV to have " + lastCsvIndex + " "
166 + (lastCsvIndex != 1 ? "fields" : "field") + ".");
167
168 JexlContext jexlContext = JexlHelper.createContext();
169 jexlContext.getVars().put(csvMappingDefinition.getBeanVariableName(),
170 object);
171 if (csvMappingDefinition.getExtendedContext() != null) {
172 Iterator<String> it = csvMappingDefinition.getExtendedContext()
173 .keySet().iterator();
174 while (it.hasNext()) {
175 String variableName = it.next();
176 String valueExpression = csvMappingDefinition
177 .getExtendedContext().get(variableName);
178 Expression preProcessing = ExpressionFactory
179 .createExpression(valueExpression);
180 preProcessing.addPreResolver(new PoundDefineJexlResolver());
181 jexlContext.getVars().put(variableName,
182 preProcessing.evaluate(jexlContext));
183 }
184 }
185
186 for (int i = 0; i < lastCsvIndex; i++) {
187 if (fieldMappingList.get(currentFieldMappingIndex).getColumnIndex() != i) {
188 csvFieldList.add("");
189 } else {
190 String expressionString = fieldMappingList.get(
191 currentFieldMappingIndex).getObjectToCsvExpression();
192 Expression expression = ExpressionFactory
193 .createExpression(expressionString);
194 csvFieldList.add(fieldMappingList.get(currentFieldMappingIndex)
195 .getCsvFieldValueFromObject(
196 expression.evaluate(jexlContext)));
197 currentFieldMappingIndex++;
198
199 if (currentFieldMappingIndex < fieldMappingList.size()) {
200 while (fieldMappingList.get(currentFieldMappingIndex)
201 .getColumnIndex() == i) {
202 currentFieldMappingIndex++;
203 }
204 }
205 }
206 }
207
208 return csvFieldList;
209 }
210
211 private static List<CsvFieldMapping> getFieldMappingListForMappingDefinition(
212 String mappingDefinition) {
213 CsvMappingDefinition csvMappingDefinition = CsvToObjectMapper.csvMappingDefinitions
214 .get(mappingDefinition);
215 List<CsvFieldMapping> rawList = csvMappingDefinition.getFieldMappings();
216 log.debug("The raw list of field mappings has " + rawList.size() + " "
217 + (rawList.size() != 1 ? "elements" : "element") + ".");
218 List<CsvFieldMapping> finishedList = new Vector<CsvFieldMapping>();
219
220 for (int i = 0; i < rawList.size(); i++) {
221 if (rawList.get(i).getBeanName() == null) {
222 finishedList.add(rawList.get(i));
223 } else {
224 log.debug("Complex bean definition. Getting fields for "
225 + rawList.get(i).getBeanName());
226 List<CsvFieldMapping> deeperRawList = getFieldMappingListForMappingDefinition(
227 rawList.get(i).getBeanName());
228 log.debug("The next bean, " + rawList.get(i).getBeanName()
229 + ", gave us " + deeperRawList.size()
230 + " more field mappings.");
231 for (int j = 0; j < deeperRawList.size(); j++) {
232 CsvFieldMapping temp = (CsvFieldMapping) deeperRawList.get(
233 j).clone();
234 String newExpression = new StringBuffer(temp
235 .getObjectToCsvExpression()).toString();
236 temp.setObjectToCsvExpression(newExpression.replace(
237 csvMappingDefinition.getBeanVariableName(), rawList
238 .get(i).getObjectToCsvExpression()));
239 finishedList.add(temp);
240 }
241 }
242 }
243
244 return finishedList;
245 }
246 }