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.transformers;
17  
18  import java.util.ArrayList;
19  import java.util.Iterator;
20  import java.util.List;
21  import java.util.Locale;
22  
23  import net.sf.composite.util.ObjectUtils;
24  import net.sf.morph.transform.Converter;
25  import net.sf.morph.transform.Copier;
26  import net.sf.morph.transform.DecoratedConverter;
27  import net.sf.morph.transform.DecoratedCopier;
28  import net.sf.morph.transform.ExplicitTransformer;
29  import net.sf.morph.transform.ImpreciseTransformer;
30  import net.sf.morph.transform.TransformationException;
31  import net.sf.morph.transform.Transformer;
32  import net.sf.morph.transform.copiers.CopierDecorator;
33  import net.sf.morph.util.Assert;
34  import net.sf.morph.util.ClassUtils;
35  import net.sf.morph.util.TransformerUtils;
36  
37  /**
38   * Runs one or more transformers in a chain.
39   *
40   * @author Matt Sgarlata
41   * @author Matt Benson
42   * @since Nov 24, 2004
43   */
44  public class ChainedTransformer extends BaseCompositeTransformer implements
45  		DecoratedConverter, DecoratedCopier, ExplicitTransformer, ImpreciseTransformer {
46  
47  	private Converter copyConverter;
48  
49  	/**
50  	 * Create a new ChainedTransformer.
51  	 */
52  	public ChainedTransformer() {
53  	}
54  
55  	/**
56  	 * Create a new ChainedTransformer.
57  	 * @param chain
58  	 */
59  	public ChainedTransformer(Transformer[] chain) {
60  		setComponents(chain);
61  	}
62  
63  	/**
64  	 * {@inheritDoc}
65  	 * @see net.sf.morph.transform.transformers.BaseTransformer#isTransformableImpl(java.lang.Class, java.lang.Class)
66  	 */
67  	protected boolean isTransformableImpl(Class destinationType, Class sourceType) throws Exception {
68  		return getConversionPath(destinationType, sourceType) != null;
69  	}
70  
71  	/**
72  	 * {@inheritDoc}
73  	 */
74  	protected boolean isImpreciseTransformationImpl(Class destinationClass, Class sourceClass) {
75  		List conversionPath = getConversionPath(destinationClass, sourceClass);
76  		return !isPrecise(conversionPath, sourceClass, 0);
77  	}
78  
79  	/**
80  	 * Get the converter used when using a ChainedTransformer as a Copier.
81  	 * @return
82  	 */
83  	protected synchronized Converter getCopyConverter() {
84  		if (copyConverter == null) {
85  			Transformer[] chain = getChain();
86  			Assert.notNull(chain, "components");
87  			if (chain.length == 2) {
88  				copyConverter = getConverter(chain[0]);
89  			} else {
90  				Transformer[] newChain = new Transformer[chain.length - 1];
91  				System.arraycopy(chain, 0, newChain, 0, newChain.length);
92  				copyConverter = new ChainedTransformer(newChain);
93  			}
94  		}
95  		return copyConverter;
96  	}
97  
98  	//TODO is this overstepping our bounds?  Should we just throw an exception if we need a converter and don't have one?
99  	private Converter getConverter(Transformer t) {
100 		if (t instanceof Converter) {
101 			return (Converter) t;
102 		}
103 		if (t instanceof Copier) {
104 			return new CopierDecorator((Copier) t);
105 		}
106 		throw new IllegalArgumentException("Don't know how to use " + t + " as a Converter");
107 	}
108 
109 	/**
110 	 * {@inheritDoc}
111 	 * @see net.sf.morph.transform.transformers.BaseTransformer#convertImpl(java.lang.Class, java.lang.Object, java.util.Locale)
112 	 */
113 	protected Object convertImpl(Class destinationClass, Object source, Locale locale)
114 			throws Exception {
115 		if (log.isTraceEnabled()) {
116 			log.trace("Using chain to convert "
117 				+ ObjectUtils.getObjectDescription(source) + " to "
118 				+ ObjectUtils.getObjectDescription(destinationClass));
119 		}
120 		Transformer[] chain = getChain();
121 		Class sourceType = ClassUtils.getClass(source);
122 		List conversionPath = getConversionPath(destinationClass, sourceType);
123 		if (conversionPath == null) {
124 			throw new TransformationException(destinationClass, sourceType, null,
125 					"Chained conversion path could not be determined");
126 		}
127 		if (log.isDebugEnabled()) {
128 			log.debug("Using chained conversion path " + conversionPath);
129 		}
130 		Object o = source;
131 		for (int i = 0; i < conversionPath.size(); i++) {
132 			o = getConverter(chain[i]).convert((Class) conversionPath.get(i), o, locale);
133 			logConversion(i + 1, source, o);
134 		}
135 		return o;
136 	}
137 
138 	/**
139 	 * {@inheritDoc}
140 	 * @see net.sf.morph.transform.transformers.BaseTransformer#copyImpl(java.lang.Object, java.lang.Object, java.util.Locale, java.lang.Integer)
141 	 */
142 	protected void copyImpl(Object destination, Object source, Locale locale, Integer preferredTransformationType) throws Exception {
143 		if (log.isTraceEnabled()) {
144 			log.trace("Using chain to copy "
145 				+ ObjectUtils.getObjectDescription(source) + " to "
146 				+ ObjectUtils.getObjectDescription(destination));
147 		}
148 		Class destinationClass = ClassUtils.getClass(destination);
149 		Transformer[] chain = getChain();
150 		Transformer copier = chain[chain.length - 1];
151 		if (!(copier instanceof Copier)) {
152 			throw new TransformationException(destinationClass, source, null,
153 					"Last chain component must be a Copier");
154 		}
155 		Class sourceType = ClassUtils.getClass(source);
156 		List conversionPath = getConversionPath(destinationClass, sourceType);
157 		if (conversionPath == null) {
158 			throw new TransformationException(destinationClass, source, null,
159 					"Chained conversion path could not be determined");
160 		}
161 		if (log.isDebugEnabled()) {
162 			log.debug("Using chained conversion path " + conversionPath);
163 		}
164 		Object last = getCopyConverter().convert((Class) conversionPath.get(chain.length - 2), source, locale);
165 		((Copier) copier).copy(destination, last, locale);
166 	}
167 
168 	/**
169 	 * Log one conversion in the chain.
170 	 * @param conversionNumber
171 	 * @param source
172 	 * @param destination
173 	 */
174 	protected void logConversion(int conversionNumber, Object source, Object destination) {
175 		if (log.isTraceEnabled()) {
176 			log.trace("Conversion "
177 				+ conversionNumber
178 				+ " of "
179 				+ getComponents().length
180 				+ " was from "
181 				+ ObjectUtils.getObjectDescription(source)
182 				+ " to "
183 				+ ObjectUtils.getObjectDescription(destination)
184 				+ " and was performed by "
185 				+ ObjectUtils.getObjectDescription(getComponents()[conversionNumber - 1]));
186 		}
187 	}
188 
189 	/**
190 	 * {@inheritDoc}
191 	 * @see net.sf.morph.transform.transformers.BaseTransformer#getDestinationClassesImpl()
192 	 */
193 	protected Class[] getDestinationClassesImpl() throws Exception {
194 		return getChain()[getChain().length - 1].getDestinationClasses();
195 	}
196 
197 	/**
198 	 * {@inheritDoc}
199 	 * @see net.sf.morph.transform.transformers.BaseTransformer#getSourceClassesImpl()
200 	 */
201 	protected Class[] getSourceClassesImpl() throws Exception {
202 		return getChain()[0].getSourceClasses();
203 	}
204 
205 	/**
206 	 * Get the List of destination classes on the conversion path.
207 	 * @param destinationType
208 	 * @param sourceType
209 	 * @return List
210 	 */
211 	protected List getConversionPath(Class destinationType, Class sourceType) {
212 		return getConversionPath(destinationType, sourceType, 0);
213 	}
214 
215 	/**
216 	 * Get a conversion path by investigating possibilities recursively.
217 	 * @param destinationType
218 	 * @param sourceType should be non-null if !allowNull
219 	 * @param chain
220 	 * @param index
221 	 * @param allowNull
222 	 * @return List
223 	 */
224 	private List getConversionPath(Class destinationType, Class sourceType, int index) {
225 		Transformer[] chain = getChain();
226 		Transformer c = chain[index];
227 		if (index + 1 == chain.length) {
228 			if (TransformerUtils.isTransformable(c, destinationType, sourceType)) {
229 				List result = new ArrayList();
230 				result.add(destinationType);
231 				return result;
232 			}
233 			return null;
234 		}
235 		List possibleResult = null;
236 		Class[] available = c.getDestinationClasses();
237 		for (int i = 0; i < available.length; i++) {
238 			if (TransformerUtils.isTransformable(c, available[i], sourceType)) {
239 				List tail = getConversionPath(destinationType, available[i], index + 1);
240 				if (tail != null) {
241 					tail.add(0, available[i]);
242 					if (isPrecise(tail, sourceType, index)) {
243 						return tail;
244 					}
245 					possibleResult = tail;
246 				}
247 			}
248 		}
249 		return possibleResult;
250 	}
251 
252 	private boolean isPrecise(List conversionPath, Class sourceType, int index) {
253 		Transformer[] chain = getChain();
254 		Class currentSource = sourceType;
255 		int i = 0;
256 		for (Iterator iter = conversionPath.iterator(); iter.hasNext(); i++) {
257 			Class currentDest = (Class) iter.next();
258 			if (TransformerUtils.isImpreciseTransformation(chain[index + i], currentDest,
259 					currentSource)) {
260 				return false;
261 			}
262 			currentSource = currentDest;
263 		}
264 		return true;
265 	}
266 
267 	/**
268 	 * Get the components array narrowed to a Transformer[].
269 	 * @return Transformer[]
270 	 */
271 	protected synchronized Transformer[] getChain() {
272 		return (Transformer[]) getComponents();
273 	}
274 
275 }