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.util;
17  
18  import java.util.Arrays;
19  import java.util.Iterator;
20  import java.util.Locale;
21  import java.util.Map;
22  import java.util.Set;
23  
24  import net.sf.composite.util.ObjectUtils;
25  import net.sf.morph.transform.Converter;
26  import net.sf.morph.transform.Copier;
27  import net.sf.morph.transform.ExplicitTransformer;
28  import net.sf.morph.transform.ImpreciseTransformer;
29  import net.sf.morph.transform.TransformationException;
30  import net.sf.morph.transform.Transformer;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  
35  /**
36   * Utility functions for implementing and using Transformers.
37   *
38   * @author Matt Sgarlata
39   * @since Nov 26, 2004
40   */
41  public abstract class TransformerUtils {
42  
43  	private static abstract class ClassStrategy {
44  		abstract Class[] get(Transformer t);
45  	}
46  
47  	private static final ClassStrategy SOURCE = new ClassStrategy() {
48  		Class[] get(Transformer t) {
49  			return t.getSourceClasses();
50  		}
51  	};
52  
53  	private static final ClassStrategy DEST = new ClassStrategy() {
54  		Class[] get(Transformer t) {
55  			return t.getDestinationClasses();
56  		}
57  	};
58  
59  	private static final Log log = LogFactory.getLog(TransformerUtils.class);
60  	private static final Class[] CLASS_NONE = new Class[0];
61  
62  	/**
63  	 * Learn whether <code>sourceClass</code> is transformable to <code>destinationClass</code>
64  	 * by <code>transformer</code> considering only source and destination types.
65  	 * @param transformer
66  	 * @param destinationClass
67  	 * @param sourceClass
68  	 * @return boolean
69  	 */
70  	public static boolean isImplicitlyTransformable(Transformer transformer, Class destinationClass, Class sourceClass) {
71  		return ClassUtils.inheritanceContains(transformer.getDestinationClasses(), destinationClass)
72  			&& ClassUtils.inheritanceContains(transformer.getSourceClasses(), sourceClass);
73  	}
74  
75  	/**
76  	 * Learn whether <code>sourceClass</code> is transformable to <code>destinationClass</code>
77  	 * by <code>transformer</code> by implicit or explicit rules.
78  	 * @param transformer
79  	 * @param destinationClass
80  	 * @param sourceClass
81  	 * @return boolean
82  	 * @see #isImplicitlyTransformable(Transformer, Class, Class)
83  	 * @see ExplicitTransformer
84  	 */
85  	public static boolean isTransformable(Transformer transformer, Class destinationClass, Class sourceClass) {
86  		if (transformer instanceof ExplicitTransformer) {
87  			return ((ExplicitTransformer) transformer).isTransformable(destinationClass, sourceClass);
88  		}
89  		return isImplicitlyTransformable(transformer, destinationClass, sourceClass);
90  	}
91  
92  	/**
93  	 * Learn whether <code>transformer</code>'s transformation
94  	 * of <code>sourceClass</code> to <code>destinationClass</code> might yield an imprecise result. 
95  	 * @param transformer
96  	 * @param destinationClass
97  	 * @param sourceClass
98  	 * @return boolean
99  	 * @see ImpreciseTransformer
100 	 * @since Morph 1.1
101 	 */
102 	public static boolean isImpreciseTransformation(Transformer transformer,
103 			Class destinationClass, Class sourceClass) {
104 		if (transformer instanceof ImpreciseTransformer) {
105 			return ((ImpreciseTransformer) transformer).isImpreciseTransformation(
106 					destinationClass, sourceClass);
107 		}
108 		return destinationClass == null && sourceClass != null;
109 	}
110 
111 	/**
112 	 * Performs a transformation of one object graph into another object graph.
113 	 *
114 	 * @param destinationType
115 	 *            the type of the root node of the destination object graph
116 	 * @param the
117 	 *            optional root node of the destination object graph. If this
118 	 *            parameter is specified, it will be possible to copy
119 	 *            information to an existing object graph rather than requiring
120 	 *            a new object graph be created
121 	 * @param source
122 	 *            the root node of the source object graph
123 	 * @param locale
124 	 *            the locale in which any needed transformations should take
125 	 *            place
126 	 * @param preferredTransformationType
127 	 *            the preferred type of transformation to be performed
128 	 * @return the transformed object graph
129 	 * @throws TransformationException
130 	 *             if the graph could not be transformed for some reason
131 	 * @see Converter#TRANSFORMATION_TYPE_CONVERT
132 	 * @see Copier#TRANSFORMATION_TYPE_COPY
133 	 */
134 	public static Object transform(Transformer transformer, Class destinationType, Object destination,
135 		Object source, Locale locale, Integer preferredTransformationType)
136 		throws TransformationException {
137 
138 		//default to preferredTransformationType if specified, else by Transformer type
139 		Integer xform = preferredTransformationType != null ? preferredTransformationType
140 				: transformer instanceof Copier ? Transformer.TRANSFORMATION_TYPE_COPY
141 						: Transformer.TRANSFORMATION_TYPE_CONVERT;
142 
143 		boolean mutableDest = !ClassUtils.isImmutableObject(destination);
144 
145 		// next, override impossible operations with possible ones
146 		// (this block is somewhat more verbose than necessary but
147 		// should be proof against possible additional Transformer types):
148 		if (Transformer.TRANSFORMATION_TYPE_COPY.equals(xform)) {
149 			if (transformer instanceof Converter && !mutableDest) {
150 				xform = Transformer.TRANSFORMATION_TYPE_CONVERT;
151 			}
152 		}
153 		else if (Transformer.TRANSFORMATION_TYPE_CONVERT.equals(xform)) {
154 			if (!(transformer instanceof Converter) && transformer instanceof Copier
155 					&& mutableDest) {
156 				xform = Transformer.TRANSFORMATION_TYPE_COPY;
157 			}
158 		}
159 
160 		if (Transformer.TRANSFORMATION_TYPE_CONVERT.equals(xform)) {
161 			if (log.isTraceEnabled()) {
162 				log.trace("Performing nested conversion of "
163 					+ ObjectUtils.getObjectDescription(source)
164 					+ " to destination type "
165 					+ ObjectUtils.getObjectDescription(destinationType));
166 			}
167 			try {
168 				return ((Converter) transformer).convert(destinationType, source, locale);
169 			}
170 			catch (TransformationException e) {
171 				throw e;
172 			}
173 			catch (Exception e) {
174 				throw new TransformationException("Unable to perform transformation", e);
175 			}
176 		}
177 		if (Transformer.TRANSFORMATION_TYPE_COPY.equals(xform)) {
178 			if (log.isTraceEnabled()) {
179 				log.trace("Performing nested copy of "
180 					+ ObjectUtils.getObjectDescription(source)
181 					+ " to destination "
182 					+ ObjectUtils.getObjectDescription(destination));
183 			}
184 			try {
185 				((Copier) transformer).copy(destination, source, locale);
186 			}
187 			catch (TransformationException e) {
188 				throw e;
189 			}
190 			catch (Exception e) {
191 				throw new TransformationException("Unable to perform graph transformation", e);
192 			}
193 			return destination;
194 		}
195 		// shouldn't happen unless a new transformer type is introduced
196 		// and this class has not yet been updated to handle it
197 		throw new TransformationException(
198 			"Unable to perform transformation using transformer "
199 				+ ObjectUtils.getObjectDescription(transformer));
200 	}
201 
202 	/**
203 	 * Get the mapped destination type from the specified typemap.
204 	 * @param typeMapping Map
205 	 * @param requestedType Class
206 	 * @return Class
207 	 */
208 	public static Class getMappedDestinationType(Map typeMapping, Class requestedType) {
209 		if (typeMapping == null) {
210 			return null;
211 		}
212 		// see if the requested type has been directly mapped to some other type
213 		Class mappedDestinationType = (Class) typeMapping.get(requestedType);
214 		// see if the requested type has been indirectly mapped to some other type
215 		if (mappedDestinationType == null) {
216 			Set keys = typeMapping.keySet();
217 			for (Iterator i = keys.iterator(); i.hasNext();) {
218 				Class type = (Class) i.next();
219 				if (type.isAssignableFrom(requestedType)) {
220 					mappedDestinationType = (Class) typeMapping.get(type);
221 					break;
222 				}
223 			}
224 		}
225 		return mappedDestinationType;
226 	}
227 
228 	/**
229 	 * Get the set of source classes available from the specified Transformer for the specified destination type.
230 	 * @param transformer
231 	 * @param destinationType
232 	 * @return Class[]
233 	 */
234 	public static Class[] getSourceClasses(Transformer transformer, Class destinationType) {
235 		if (!ClassUtils.inheritanceContains(transformer.getDestinationClasses(), destinationType)) {
236 			return CLASS_NONE;
237 		}
238 		Class[] sourceTypes = transformer.getSourceClasses();
239 		if (transformer instanceof ExplicitTransformer) {
240 			Set result = ContainerUtils.createOrderedSet();
241 			for (int i = 0; i < sourceTypes.length; i++) {
242 				if (((ExplicitTransformer) transformer).isTransformable(destinationType, sourceTypes[i])) {
243 					result.add(sourceTypes[i]);
244 				}
245 			}
246 			return result.isEmpty() ? CLASS_NONE : (Class[]) result.toArray(new Class[result.size()]);
247 		}
248 		return sourceTypes;
249 	}
250 
251 	/**
252 	 * Get the set of destination classes available from the specified Transformer for the specified source type.
253 	 * @param transformer
254 	 * @param sourceType
255 	 * @return Class[]
256 	 * @since Morph 1.1
257 	 */
258 	public static Class[] getDestinationClasses(Transformer transformer, Class sourceType) {
259 		if (!ClassUtils.inheritanceContains(transformer.getSourceClasses(), sourceType)) {
260 			return CLASS_NONE;
261 		}
262 		Class[] destinationTypes = transformer.getDestinationClasses();
263 		if (transformer instanceof ExplicitTransformer) {
264 			Set result = ContainerUtils.createOrderedSet();
265 			for (int i = 0; i < destinationTypes.length; i++) {
266 				if (((ExplicitTransformer) transformer).isTransformable(destinationTypes[i], sourceType)) {
267 					result.add(destinationTypes[i]);
268 				}
269 			}
270 			return result.isEmpty() ? CLASS_NONE : (Class[]) result.toArray(new Class[result.size()]);
271 		}
272 		return destinationTypes;
273 	}
274 
275 	/**
276 	 * Get the set of source classes common to all specified Transformers.
277 	 * @param transformers
278 	 * @return Class[]
279 	 */
280 	public static Class[] getSourceClassIntersection(Transformer[] transformers) {
281 		return getClassIntersection(transformers, SOURCE);
282 	}
283 
284 	/**
285 	 * Get the set of destination classes common to all specified Transformers.
286 	 * @param transformers
287 	 * @return Class[]
288 	 */
289 	public static Class[] getDestinationClassIntersection(Transformer[] transformers) {
290 		return getClassIntersection(transformers, DEST);
291 		
292 	}
293 
294 	private static Class[] getClassIntersection(Transformer[] transformers, ClassStrategy strategy) {
295 		Set s = ContainerUtils.createOrderedSet();
296 		s.addAll(Arrays.asList(strategy.get(transformers[0])));
297 
298 		for (int i = 1; i < transformers.length; i++) {
299 			Set survivors = ContainerUtils.createOrderedSet();
300 			Class[] c = strategy.get(transformers[i]);
301 			for (int j = 0; j < c.length; j++) {
302 				if (s.contains(c[j])) {
303 					survivors.add(c[j]);
304 					break;
305 				}
306 				if (c[j] == null) {
307 					break;
308 				}
309 				for (Iterator it = s.iterator(); it.hasNext();) {
310 					Class next = (Class) it.next();
311 					if (next != null && next.isAssignableFrom(c[j])) {
312 						survivors.add(c[j]);
313 						break;
314 					}
315 				}
316 			}
317 			if (!survivors.containsAll(s)) {
318 				for (Iterator it = s.iterator(); it.hasNext();) {
319 					Class next = (Class) it.next();
320 					if (survivors.contains(next) || next == null) {
321 						break;
322 					}
323 					for (int j = 0; j < c.length; j++) {
324 						if (c[j] != null && c[j].isAssignableFrom(next)) {
325 							survivors.add(next);
326 							break;
327 						}
328 					}
329 				}
330 			}
331 			s = survivors;
332 		}
333 		return s.isEmpty() ? CLASS_NONE : (Class[]) s.toArray(new Class[s.size()]);
334 	}
335 
336 }