View Javadoc

1   package net.sourceforge.jparam.conversion;
2   
3   import java.lang.reflect.Method;
4   import java.util.HashMap;
5   import java.util.Iterator;
6   import java.util.LinkedList;
7   import java.util.List;
8   import java.util.Map;
9   
10  import net.sourceforge.jparam.JParamException;
11  import net.sourceforge.jparam.conversion.converters.AmbigousConverter;
12  import net.sourceforge.jparam.conversion.converters.IConverter;
13  import net.sourceforge.jparam.conversion.converters.MethodConverter;
14  import net.sourceforge.jparam.conversion.converters.PathConverter;
15  import net.sourceforge.jparam.conversion.creators.ObjectCreator;
16  import net.sourceforge.jparam.conversion.weights.ScalarConversionWeight;
17  import net.sourceforge.jparam.typename.TypeNameRegistry;
18  
19  /***
20   * This registry maintains a list of all possible conversions that are currently
21   * supported by JParam, if new conversions are added than the list is updated
22   * accordingly
23   *  
24   * @author ron_sidi
25   * 
26   */
27  public class ConverterRegistry {
28  	private static ConverterRegistry registry = null;
29  
30  	private Map converters = new HashMap();
31  
32  	private ConverterRegistry() {
33  	}
34  
35  	public static ConverterRegistry getInstance() {
36  		if (registry == null) {
37  			registry = new ConverterRegistry();
38  		}
39  		return registry;
40  	}
41  
42  	/***
43  	 * Register all conversion methods in the given helper class, a method is
44  	 * considered a conversion if:
45  	 * <p>
46  	 * <li>The method name is:
47  	 * <ul>
48  	 * <li>convert/user_convert for performing user conversions</li>
49  	 * <li>promote_convert for promotion conversion</li>
50  	 * <li>std_convert for standard conversion</li>
51  	 * </ul>
52  	 * <li>The method is <code>public</code></li>
53  	 * <li>The method is <code>static</code></li>
54  	 * <li>The method has exactly one argument</li>
55  	 * <li>The method has a typed return value and not void</li>
56  	 * 
57  	 * @param helperClass
58  	 */
59  	public void registerHelperClass(Class helperClass) {
60  		Method[] creatorClassMethods = helperClass.getMethods();
61  		for (int i = 0; i < creatorClassMethods.length; i++) {
62  			Method m = creatorClassMethods[i];
63  
64  			if (MethodConverter.isMethodValid(m)) {
65  				MethodConverter mc = new MethodConverter(m);
66  
67  				registerConverter(mc);
68  			}
69  		}
70  	}
71  
72  	/***
73  	 * This method is used to register a new converter, it shouldn't be used
74  	 * externally, but is defined public to support special cases that might
75  	 * arise
76  	 * 
77  	 * It will perform all needed changes to the registry that are introduced by
78  	 * the new converter
79  	 * 
80  	 * @param c
81  	 */
82  	public void registerConverter(IConverter c) {
83  		ConverterKey key = new ConverterKey(c.getSourceClass(), c
84  				.getTargetClass());
85  
86  		// don't register converters from a type to itself...
87  		if (key.sourceType.equals(key.targetType)) {
88  			return;
89  		}
90  
91  		// Try to find an existing converter for the given conversion
92  		IConverter curConverter = (IConverter) converters.get(key);
93  		if (curConverter != null) {
94  			// if one exists, and is worse then the new one, update the current
95  			// converter
96  			if (curConverter.getWeight().compareTo(c.getWeight()) > 0) {
97  				updateConverter(c);
98  			} else
99  			// if the converters are equal in weight, make them ambigous
100 			if (curConverter.getWeight().compareTo(c.getWeight()) == 0) {
101 				// there are scenarios when the same converter could be
102 				// registered twice, for instance:
103 				// we have a->b and c->d, now register b->c
104 				// this will register a->c and b->d, when these converters are
105 				// registered, they will trigger a->d using the same path...
106 				if (!curConverter.equals(c)) {
107 					// this means there is an ambiguity
108 					updateConverter(AmbigousConverter.createConverter(
109 							curConverter, c));
110 				}
111 			}
112 			// if the new converter is worse then the current one simply
113 			// ignore it
114 		} else {
115 			List convertersToRegister = new LinkedList();
116 			boolean userConversion = c.getWeight().compareTo(
117 					ScalarConversionWeight.CONV_USER) >= 0;
118 			// create a converter for all possible concatenations of the new
119 			// converter with existing converters
120 			for (Iterator iter = converters.entrySet().iterator(); iter
121 					.hasNext();) {
122 				Map.Entry entry = (Map.Entry) iter.next();
123 
124 				IConverter existingConverter = (IConverter) entry.getValue();
125 				if (!userConversion
126 						|| existingConverter.getWeight().compareTo(
127 								ScalarConversionWeight.CONV_USER) < 0) {
128 					if (existingConverter.getSourceClass().equals(
129 							c.getTargetClass())) {
130 						// exis(a->b), new(c->a) -> create (c->b) {make sure
131 						// a->b + b->a won't create b->b}
132 						if (!existingConverter.getTargetClass().equals(
133 								c.getSourceClass())) {
134 							convertersToRegister
135 									.add(PathConverter.createConversionPath(c,
136 											existingConverter));
137 						}
138 					} else if (existingConverter.getTargetClass().equals(
139 							c.getSourceClass())) {
140 						// exis(a->b), new(b->c) -> create (a->c) {make sure
141 						// a->b + b->a won't create a->a}
142 						if (!existingConverter.getSourceClass().equals(
143 								c.getTargetClass())) {
144 							convertersToRegister
145 									.add(PathConverter.createConversionPath(
146 											existingConverter, c));
147 						}
148 					}
149 				}
150 			}
151 
152 			// there is no need to do an update here since we branch here only
153 			// if there is no converter for key...
154 			converters.put(key, c);
155 			for (Iterator iter = convertersToRegister.iterator(); iter
156 					.hasNext();) {
157 				IConverter converter = (IConverter) iter.next();
158 				registerConverter(converter);
159 			}
160 		}
161 	}
162 
163 	private void updateConverter(IConverter converter) {
164 		List updatedConverters = new LinkedList();
165 		ConverterKey key = new ConverterKey(converter.getSourceClass(),
166 				converter.getTargetClass());
167 
168 		converters.remove(key);
169 		for (Iterator iter = converters.values().iterator(); iter.hasNext();) {
170 			IConverter curConv = (IConverter) iter.next();
171 			IConverter newConv = curConv.update(converter);
172 
173 			if (newConv != curConv) {
174 				updatedConverters.add(newConv);
175 			}
176 		}
177 		converters.put(key, converter);
178 
179 		for (Iterator iter = updatedConverters.iterator(); iter.hasNext();) {
180 			IConverter conv = (IConverter) iter.next();
181 			updateConverter(conv);
182 		}
183 	}
184 
185 	/***
186 	 * Convert the source object to the given target class, an exception is
187 	 * thrown either when there is no exception/exception is ambigous or when
188 	 * the conversion process itself failed
189 	 * 
190 	 * @param targetType
191 	 * @param sourceObject
192 	 * @return the converted object
193 	 * @throws JParamException
194 	 */
195 	public Object convert(Class targetType, Object sourceObject)
196 			throws JParamException {
197 		if (sourceObject == null) {
198 			if (targetType.isPrimitive()) {
199 				throw new JParamException(
200 						"The value \"null\" cannot be converted into a primitive: "
201 								+ targetType);
202 			}
203 			return null;
204 		}
205 
206 		targetType = ObjectCreator.getInstance().getNonPrimitiveClass(
207 				targetType);
208 		if (sourceObject.getClass().equals(targetType)) {
209 			return sourceObject;
210 		}
211 
212 		IConverter converter = (IConverter) converters.get(new ConverterKey(
213 				sourceObject.getClass(), targetType));
214 		if (converter == null) {
215 			throw new JParamException("no conversion path found from "
216 					+ TypeNameRegistry.getInstance().getJParamTypeName(
217 							sourceObject.getClass())
218 					+ " to "
219 					+ TypeNameRegistry.getInstance().getJParamTypeName(
220 							targetType));
221 		}
222 		return converter.convert(sourceObject);
223 	}
224 
225 	public static void clearSingleton() {
226 		registry = null;
227 	}
228 
229 	/***
230 	 * Return the weight of the conversion between the given source and target
231 	 * type
232 	 * 
233 	 * @param sourceType
234 	 * @param targetType
235 	 * @return
236 	 */
237 	public ScalarConversionWeight getConversionWeight(Class sourceType,
238 			Class targetType) {
239 		if (sourceType.equals(targetType)) {
240 			return ScalarConversionWeight.CONV_EXACT;
241 		}
242 
243 		IConverter converter = (IConverter) converters.get(new ConverterKey(
244 				sourceType, targetType));
245 		if (converter != null) {
246 			return converter.getWeight();
247 		}
248 
249 		return ScalarConversionWeight.CONV_IMPOSSIBLE;
250 	}
251 }