View Javadoc
1   /*
2    * Copyright 2007 Kasper B. Graversen
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.supercsv.io.dozer;
17  
18  import static org.dozer.loader.api.TypeMappingOptions.oneWay;
19  import static org.dozer.loader.api.TypeMappingOptions.wildcard;
20  
21  import java.io.IOException;
22  import java.io.Writer;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import org.dozer.DozerBeanMapper;
27  import org.dozer.loader.api.BeanMappingBuilder;
28  import org.dozer.loader.api.FieldsMappingOptions;
29  import org.dozer.loader.api.TypeMappingBuilder;
30  import org.supercsv.cellprocessor.ift.CellProcessor;
31  import org.supercsv.io.AbstractCsvWriter;
32  import org.supercsv.io.CsvBeanWriter;
33  import org.supercsv.prefs.CsvPreference;
34  import org.supercsv.util.Util;
35  
36  /**
37   * CsvDozerBeanWriter is a powerful replacement for {@link CsvBeanWriter} that uses Dozer to map from a bean to CSV.
38   * 
39   * @author James Bassett
40   * @since 2.0.0
41   */
42  public class CsvDozerBeanWriter extends AbstractCsvWriter implements ICsvDozerBeanWriter {
43  	
44  	private final DozerBeanMapper dozerBeanMapper;
45  	
46  	// target of dozer bean mapping
47  	private final CsvDozerBeanData beanData = new CsvDozerBeanData();
48  	
49  	// temporary storage of processed columns to be written
50  	private final List<Object> processedColumns = new ArrayList<Object>();
51  	
52  	/**
53  	 * Constructs a new <tt>CsvDozerBeanWriter</tt> with the supplied Writer and CSV preferences and and creates it's
54  	 * own DozerBeanMapper. Note that the <tt>writer</tt> will be wrapped in a <tt>BufferedWriter</tt> before accessed.
55  	 * 
56  	 * @param writer
57  	 *            the writer
58  	 * @param preference
59  	 *            the CSV preferences
60  	 * @throws NullPointerException
61  	 *             if writer or preference are null
62  	 */
63  	public CsvDozerBeanWriter(final Writer writer, final CsvPreference preference) {
64  		super(writer, preference);
65  		this.dozerBeanMapper = new DozerBeanMapper();
66  	}
67  	
68  	/**
69  	 * Constructs a new <tt>CsvDozerBeanWriter</tt> with the supplied Writer, CSV preferences and DozerBeanMapper. Note
70  	 * that the <tt>writer</tt> will be wrapped in a <tt>BufferedWriter</tt> before accessed.
71  	 * 
72  	 * @param writer
73  	 *            the writer
74  	 * @param preference
75  	 *            the CSV preferences
76  	 * @param dozerBeanMapper
77  	 *            the pre-configured DozerBeanMapper
78  	 * @throws NullPointerException
79  	 *             if writer, preference or dozerBeanMapper are null
80  	 */
81  	public CsvDozerBeanWriter(final Writer writer, final CsvPreference preference, final DozerBeanMapper dozerBeanMapper) {
82  		super(writer, preference);
83  		if( dozerBeanMapper == null ) {
84  			throw new NullPointerException("dozerBeanMapper should not be null");
85  		}
86  		this.dozerBeanMapper = dozerBeanMapper;
87  	}
88  	
89  	/**
90  	 * {@inheritDoc}
91  	 */
92  	public void configureBeanMapping(final Class<?> clazz, final String[] fieldMapping) {
93  		dozerBeanMapper.addMapping(new MappingBuilder(clazz, fieldMapping));
94  	}
95  	
96  	/**
97  	 * {@inheritDoc}
98  	 */
99  	public void write(final Object source) throws IOException {
100 		
101 		if( source == null ) {
102 			throw new NullPointerException("object to write should not be null");
103 		}
104 		
105 		// update the current row/line numbers
106 		super.incrementRowAndLineNo();
107 		
108 		// extract the bean values into the List using dozer
109 		beanData.getColumns().clear();
110 		dozerBeanMapper.map(source, beanData);
111 		
112 		// write the list
113 		super.writeRow(beanData.getColumns());
114 	}
115 	
116 	/**
117 	 * {@inheritDoc}
118 	 */
119 	public void write(final Object source, final CellProcessor[] processors) throws IOException {
120 		
121 		if( source == null ) {
122 			throw new NullPointerException("object to write should not be null");
123 		} else if( processors == null ) {
124 			throw new NullPointerException("processors should not be null");
125 		}
126 		
127 		// update the current row/line numbers
128 		super.incrementRowAndLineNo();
129 		
130 		// extract the bean values into the List using dozer
131 		beanData.getColumns().clear();
132 		dozerBeanMapper.map(source, beanData);
133 		
134 		// execute the cell processors
135 		Util.executeCellProcessors(processedColumns, beanData.getColumns(), processors, getLineNumber(), getRowNumber());
136 		
137 		// write the list
138 		super.writeRow(processedColumns);
139 	}
140 	
141 	/**
142 	 * Assembles the dozer bean mappings required by CsvDozerBeanWriter programatically using the Dozer API.
143 	 */
144 	private static class MappingBuilder extends BeanMappingBuilder {
145 		
146 		private final Class<?> clazz;
147 		private final String[] fieldMapping;
148 		
149 		/**
150 		 * Constructs a new MappingBuilder.
151 		 * 
152 		 * @param clazz
153 		 *            the class to add mapping configuration for (same as the type passed into write methods)
154 		 * @param fieldMapping
155 		 *            the field mapping for for each column (cannot contain <tt>null</tt> elements)
156 		 * @throws NullPointerException
157 		 *             if clazz or fieldMapping (or one of its elements) is null
158 		 */
159 		public MappingBuilder(final Class<?> clazz, final String[] fieldMapping) {
160 			if( clazz == null ) {
161 				throw new NullPointerException("clazz should not be null");
162 			} else if( fieldMapping == null ) {
163 				throw new NullPointerException("fieldMapping should not be null");
164 			}
165 			this.clazz = clazz;
166 			this.fieldMapping = fieldMapping;
167 		}
168 		
169 		@Override
170 		protected void configure() {
171 			
172 			/*
173 			 * Add the required dozer mappings to map from each field in the supplied class to its associated column (in
174 			 * the CsvDozerBeanData List). mapNull is enabled so that null field values are added to the List (otherwise
175 			 * the List would be too short!). oneWay is enabled just in case a custom DozerBeanMapper is supplied (so
176 			 * the same DozerBeanMapper can be used by CsvDozerBeanReader). wildcard is disabled to prevent Dozer from
177 			 * trying to map every field in the bean automatically. copyByReference is enabled on the field mapping to
178 			 * ensure no conversions are performed (bean values are just copied to the List).
179 			 */
180 			final TypeMappingBuilder mappingBuilder = mapping(clazz, type(CsvDozerBeanData.class).mapNull(true),
181 				oneWay(), wildcard(false));
182 			
183 			for( int i = 0; i < fieldMapping.length; i++ ) {
184 				
185 				final String mapping = fieldMapping[i];
186 				
187 				if( mapping == null ) {
188 					// a null field mapping at end of array results in the List being too short, so don't allow
189 					throw new NullPointerException(String.format("fieldMapping at index %d should not be null", i));
190 				}
191 				
192 				// add a field mapping from the field to the appropriate column in the beanData List
193 				mappingBuilder.fields(mapping, "columns[" + i + "]", FieldsMappingOptions.copyByReference());
194 			}
195 		}
196 	}
197 }