View Javadoc

1   /*
2    * Copyright 2004-2005, 2007-2008 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * 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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package net.sf.morph.transform.copiers;
17  
18  import java.util.Arrays;
19  import java.util.Enumeration;
20  import java.util.Iterator;
21  import java.util.Locale;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import net.sf.composite.util.ObjectUtils;
26  import net.sf.morph.reflect.GrowableContainerReflector;
27  import net.sf.morph.reflect.IndexedContainerReflector;
28  import net.sf.morph.reflect.MutableIndexedContainerReflector;
29  import net.sf.morph.transform.DecoratedConverter;
30  import net.sf.morph.transform.DecoratedCopier;
31  import net.sf.morph.transform.NodeCopier;
32  import net.sf.morph.transform.TransformationException;
33  import net.sf.morph.transform.Transformer;
34  import net.sf.morph.transform.support.ResetableIteratorWrapper;
35  import net.sf.morph.transform.transformers.BaseReflectorTransformer;
36  import net.sf.morph.util.ContainerUtils;
37  import net.sf.morph.util.IteratorEnumeration;
38  import net.sf.morph.util.ReflectorUtils;
39  import net.sf.morph.util.TransformerUtils;
40  import net.sf.morph.util.TypeMap;
41  
42  /**
43   * <p>
44   * Copies information from any container object to any object that has either a
45   * {@link GrowableContainerReflector}or a
46   * {@link MutableIndexedContainerReflector}. If the source object has a
47   * growable container reflector, information is added to the end of the
48   * destination when a copy is performed. If the source object does not have a
49   * growable container reflector (i.e. - it has a mutable indexed reflector),
50   * information copied from the source to the destination will overwrite the
51   * information in the destination.
52   * </p>
53   * 
54   * <p>
55   * By default, this means:
56   * </p>
57   * <table border="1">
58   * <tr>
59   * <th align="left">Destinations</th>
60   * <th align="left">Sources</th>
61   * </tr>
62   * <tr>
63   * <td>
64   * java.util.List <br>
65   * java.util.Set <br>
66   * java.lang.Object[], int[], etc. (arrays)</td>
67   * <td>
68   * java.util.Iterator <br>
69   * java.util.Enumeration <br>
70   * java.util.Collection <br>
71   * java.lang.Object[], int[], etc. (arrays) <br>
72   * java.util.Map (values are extracted) <br>
73   * java.lang.Object (added to the end of the destination) <br>
74   * </td>
75   * </tr>
76   * </table>
77   * 
78   * @author Matt Sgarlata
79   * @since Nov 27, 2004
80   */
81  public class ContainerCopier extends BaseReflectorTransformer implements DecoratedCopier,
82  		DecoratedConverter, NodeCopier {
83  
84  	// map of Class to Class
85  	private Map containedSourceToDestinationTypeMap;
86  	private boolean preferGrow = true;
87  
88  	/**
89  	 * Create a new ContainerCopier.
90  	 */
91  	public ContainerCopier() {
92  		super();
93  	}
94  
95  	/**
96  	 * Determine the container element destination type.  By default delegates to {@link #determineDestinationContainedType(Object, Class)}.
97  	 * @param destination container
98  	 * @param sourceValue source value
99  	 * @param sourceValueClass source type, or type source value would be if not null
100 	 * @return destination element type
101 	 */
102 	protected Class determineDestinationContainedType(Object destination,
103 			Object sourceValue, Class sourceValueClass, Locale locale) {
104 		return determineDestinationContainedType(destination, sourceValueClass);
105 	}
106 
107 	/**
108 	 * Determine the container element destination type.
109 	 * @param destination container
110 	 * @param sourceValueClass source type
111 	 * @return destination element type
112 	 * @deprecated in favor of fully-specified method
113 	 */
114 	protected Class determineDestinationContainedType(Object destination,
115 			Class sourceValueClass) {
116 		// determine the destinationType
117 		Class destinationType = null;
118 
119 		// first, see if there is an explicitly registered mapping of contained
120 		// value classes to destination classes, and if so, use that.
121 		destinationType = TransformerUtils.getMappedDestinationType(
122 				containedSourceToDestinationTypeMap, sourceValueClass);
123 
124 		// if no such mapping is found
125 		if (destinationType == null) {
126 			Class candidateDestinationType = getContainerReflector().getContainedType(
127 					destination.getClass());
128 			// if the destination has a defined contained type than simply
129 			// Object.class (which basically just means untyped)
130 			if (candidateDestinationType != Object.class) {
131 				// use that contained type as the destination type
132 				destinationType = candidateDestinationType;
133 			}
134 		}
135 
136 		if (destinationType == null) {
137 			//check whether nestedTransformer has only one possible destination for the given source:
138 			Class[] availableDestinationTypes = TransformerUtils.getDestinationClasses(getNestedTransformer(), sourceValueClass);
139 			if (availableDestinationTypes.length == 1 && availableDestinationTypes[0] != Object.class) {
140 				destinationType = availableDestinationTypes[0];
141 			}
142 		}
143 		// if no mapping was found and the destination is untyped
144 		if (destinationType == null) {
145 			// choose the class of the source as the destination class			
146 			destinationType = sourceValueClass;
147 		}
148 		return destinationType;
149 	}
150 
151 	/**
152 	 * Adds an element to the destination object that is from the given index of
153 	 * the source object.
154 	 * 
155 	 * @param index
156 	 *            the current index into the container that is being transformed
157 	 * @param destination
158 	 *            the destination container to which values are being copied
159 	 *            from the source container
160 	 * @param sourceValue
161 	 *            the value the source object has at the current index
162 	 * @param sourceValueClass
163 	 *            the type of sourceValue, or the type the sourceValue would be
164 	 *            were it not <code>null</code>
165 	 * @param locale
166 	 *            the locale in which the current transformation is taking place
167 	 * @param preferredTransformationType
168 	 *            the preferred transformation type to perform when transforming
169 	 *            the sourceValue for addition into the destination
170 	 */
171 	protected void put(int index, Object destination, Object sourceValue, Class sourceValueClass, Locale locale, Integer preferredTransformationType) {
172 		Class destinationContainedType =
173 			determineDestinationContainedType(destination, sourceValue, sourceValueClass, locale);
174 
175 		boolean canGrow = ReflectorUtils.isReflectable(getReflector(),
176 				destination.getClass(), GrowableContainerReflector.class);
177 		boolean canMutate = ReflectorUtils.isReflectable(getReflector(),
178 				destination.getClass(), MutableIndexedContainerReflector.class)
179 				&& getMutableIndexedContainerReflector().getSize(destination) > index;
180 		Integer xform = null;
181 
182 		if (isPreferGrow() || TRANSFORMATION_TYPE_CONVERT.equals(preferredTransformationType)) {
183 			xform = canGrow ? TRANSFORMATION_TYPE_CONVERT
184 					: canMutate ? TRANSFORMATION_TYPE_COPY : null;
185 		}
186 		else if (TRANSFORMATION_TYPE_COPY.equals(preferredTransformationType)) {
187 			xform = canMutate ? TRANSFORMATION_TYPE_COPY
188 					: canGrow ? TRANSFORMATION_TYPE_CONVERT : null;
189 		}
190 		// if we can just add items to the end of the existing container
191 		if (TRANSFORMATION_TYPE_CONVERT.equals(xform)) {
192 
193 			// do a nested conversion so that we have a new instance called
194 			// convertedValue that we can ...
195 			Object convertedValue = nestedTransform(destinationContainedType, null,
196 					sourceValue, locale, TRANSFORMATION_TYPE_CONVERT);
197 			// ... add to the end of the existing container
198 			getGrowableContainerReflector().add(destination, convertedValue);
199 		}
200 		// else we are overwriting a value at a given index of the destination
201 		// container
202 		else if (TRANSFORMATION_TYPE_COPY.equals(xform)) {
203 			// we may want to do a copy or a convert, depending on the
204 			// capabilities of our graph transformer and whether a copy
205 			// operation is preferred. this logic is implemented in the
206 			// TransformerUtils.transform method method
207 			Object destinationValue = getMutableIndexedContainerReflector().get(
208 				destination, index);
209 			Object transformedValue = nestedTransform(destinationContainedType,
210 					destinationValue, sourceValue, locale, preferredTransformationType);
211 			getMutableIndexedContainerReflector().set(destination, index,
212 				transformedValue);
213 		}
214 		else {
215 			// this shouldn't happen
216 			throw new TransformationException("Unable to copy value at index "
217 				+ index + " to the destination because "
218 				+ ObjectUtils.getObjectDescription(getReflector())
219 				+ ", the reflector specified for "
220 				+ ObjectUtils.getObjectDescription(this)
221 				+ ", cannot reflect destination "
222 				+ ObjectUtils.getObjectDescription(destination)
223 				+ " with a reflector that implements "
224 				+ GrowableContainerReflector.class.getName() + " or "
225 				+ MutableIndexedContainerReflector.class.getName());
226 		}
227 	}
228 
229 	/**
230 	 * Do a nested transformation.
231 	 * @param destinationContainedType
232 	 * @param destinationValue
233 	 * @param sourceValue
234 	 * @param locale
235 	 * @param preferredTransformationType
236 	 * @return result
237 	 */
238 	protected Object nestedTransform(Class destinationContainedType,
239 			Object destinationValue, Object sourceValue, Locale locale,
240 			Integer preferredTransformationType) {
241 		return TransformerUtils.transform(getNestedTransformer(),
242 				destinationContainedType, destinationValue, sourceValue, locale,
243 				preferredTransformationType);
244 	}
245 
246 	/**
247 	 * {@inheritDoc}
248 	 */
249 	protected Object convertImpl(Class destinationClass, Object source, Locale locale) throws Exception {
250 		// The code here for Iterators and Enumerations is not quite 
251 		// as rigorous as it could be.  Being as rigorous as possible, we would
252 		// take into account the possibility of converting from one type of
253 		// Iterator to another type of Iterator or one type of Enumeration to
254 		// another type of Enumeration.  That's kind of silly though because
255 		// there aren't any stand-alone Iterator or Enumeration implementations
256 		// that come with the JDK.  Thus, if any Iterator or Iterator subclass
257 		// or Enumeration or Enumeration subclass is requested, we just return
258 		// whatever type is most readily available.
259 		boolean iter = Iterator.class.isAssignableFrom(destinationClass);
260 		if (iter || Enumeration.class.isAssignableFrom(destinationClass)) {
261 			// a newInstance call doesn't really make sense... just return the
262 			// final Iterator that will be returned to the user of the
263 			// ContainerCopier
264 			Iterator iterator = getContainerReflector().getIterator(source);
265 			return iter ? (Object) iterator : new IteratorEnumeration(iterator);
266 		}
267 		return super.convertImpl(destinationClass, source, locale);
268 	}
269 
270 	/**
271 	 * {@inheritDoc}
272 	 */
273 	protected void copyImpl(Object destination, Object source, Locale locale, Integer preferredTransformationType)
274 		throws TransformationException {
275 		// if the destination is an Iterator or Enumeration, we actually already
276 		// did all the required work in the createNewInstance method, so just
277 		// return
278 		if (destination instanceof Iterator ||
279 			destination instanceof Enumeration) {
280 			return;
281 		}
282 		int i = 0;
283 		Iterator sourceIterator = getContainerReflector().getIterator(source);
284 		while (sourceIterator.hasNext()) {
285 			Object sourceValue = sourceIterator.next();
286 			// determine the 
287 			Class sourceValueClass;
288 			if (sourceValue == null) {
289 				sourceValueClass = getContainerReflector().getContainedType(
290 					source.getClass());				
291 			}
292 			else {
293 				sourceValueClass = sourceValue.getClass();
294 			}
295 			put(i++, destination, sourceValue, sourceValueClass, locale,
296 				preferredTransformationType);
297 		}
298 	}
299 
300 	/**
301 	 * {@inheritDoc}
302 	 */
303 	public Object createReusableSource(Class destinationClass, Object source) {
304 		// if to array, get a resetable iterator over the source object:
305 		return destinationClass.isArray() ? new ResetableIteratorWrapper(
306 				getContainerReflector().getIterator(source))
307 				: super.createReusableSource(destinationClass, source);
308 	}
309 
310 	/**
311 	 * {@inheritDoc}
312 	 */
313 	protected Class[] getDestinationClassesImpl() throws Exception {
314 		Set set = ContainerUtils.createOrderedSet();
315 		if (hasReflector(GrowableContainerReflector.class)) {
316 			set.addAll(Arrays.asList(getGrowableContainerReflector().getReflectableClasses()));
317 		}
318 		if (hasReflector(IndexedContainerReflector.class)) {
319 			set.addAll(Arrays.asList(getIndexedContainerReflector().getReflectableClasses()));
320 		}
321 		set.add(Iterator.class);
322 		set.add(Enumeration.class);
323 		return (Class[]) set.toArray(new Class[set.size()]);
324 	}
325 
326 	/**
327 	 * {@inheritDoc}
328 	 */
329 	protected Class[] getSourceClassesImpl() throws Exception {
330 		return getContainerReflector().getReflectableClasses();
331 	}
332 
333 	/**
334 	 * {@inheritDoc}
335 	 */
336 	protected boolean isWrappingRuntimeExceptions() {
337 		// this transformer can recursively call other transformers, so we don't
338 		// want to eat user defined exceptions
339 	    return false;
340 	}
341 
342 	/**
343 	 * {@inheritDoc}
344 	 */
345 	public Transformer getNestedTransformer() {
346 		return super.getNestedTransformer();
347 	}
348 
349 	/**
350 	 * {@inheritDoc}
351 	 */
352 	public void setNestedTransformer(Transformer transformer) {
353 		super.setNestedTransformer(transformer);
354 	}
355 
356 	/**
357 	 * Get the mapping of source to destination container element types.
358 	 * @return Map
359 	 * @see {@link net.sf.morph.util.TypeMap}
360 	 */
361 	public Map getContainedSourceToDestinationTypeMap() {
362 		return containedSourceToDestinationTypeMap;
363 	}
364 
365 	/**
366 	 * Set the mapping of source to destination container element types.
367 	 * @param containedSourceToDestinationMapping Map
368 	 * @see {@link net.sf.morph.util.TypeMap}
369 	 */
370 	public void setContainedSourceToDestinationTypeMap(
371 		Map containedSourceToDestinationMapping) {
372 		this.containedSourceToDestinationTypeMap = new TypeMap(containedSourceToDestinationMapping);
373 	}
374 
375 	/**
376 	 * Learn whether this ContainerCopier prefers to grow the destination when possible.
377 	 * @return boolean
378 	 * @since Morph 1.1
379 	 */
380 	public boolean isPreferGrow() {
381 		return preferGrow;
382 	}
383 
384 	/**
385 	 * Set whether this ContainerCopier prefers to grow the destination when possible.
386 	 * Backward-compatible default setting is <code>true</code>; when <code>false</code>
387 	 * grow vs. mutate will be determined at runtime by the capabilities of the configured
388 	 * reflector with regard to the copy destination; preferredTransformationType will also
389 	 * be observed.
390 	 * @see #copyImpl(Object, Object, Locale, Integer)
391 	 * @since Morph 1.1
392 	 * @param preferGrow the boolean to set
393 	 */
394 	public void setPreferGrow(boolean preferGrow) {
395 		this.preferGrow = preferGrow;
396 	}
397 }