View Javadoc

1   package de.keepondreaming.xml;
2   
3   import java.lang.reflect.Method;
4   import java.util.HashMap;
5   import java.util.HashSet;
6   import java.util.Map;
7   import java.util.Properties;
8   import java.util.Set;
9   
10  import de.keepondreaming.xml.util.Util;
11  
12  /***
13   * Resolves interfaces and attributes via a {@link java.util.Properties} mapping.
14   * <p>
15   * If the mapping does not resolve the strategy can be configured via the constructor
16   * {@link #ClassNameMappingObjectStrategy(Properties, boolean)} to use a backup
17   * strategy, usually {@link de.keepondreaming.xml.ProxyObjectStrategy}
18   * <p>
19   * Property mappings are as follows:
20   * <ul>
21   * <li>Interface to class mapping: &lt;Interface-name&gt;=&lt;Class-name&gt;, <p>i.e. <code>de.keepondreaming.xml.example.Root=de.keepondreaming.xml.example.impl.RootImpl</code></li>
22   * <li>Map XML-tagname to getter method:&lt;interface-name&gt;.&lt;tag-name&gt;=&lt;getter-method&gt;, <p>i.e. <code>de.package.MyClass.UGLY_XML_TAG_NAME=getNiceNamedMethod</code></li>
23   * <li>Map XML-tagname to setter method:<b>&lt;class-name&gt;</b>.&lt;tag-name&gt;=&lt;setter-method&gt;, <p>i.e. <code>de.package.MyClassImpl.UGLY_XML_TAG_NAME=setNiceNamedMethodWithOtherName</code></li>
24   * </ul> 
25   * Note that the mappings of XML tagnames to setter/getter methods is only needed if the methods can not get obtained via reflection
26   * 
27   * <p>
28   * $Author: wintermond $
29   * $Date: 2005/07/10 18:38:43 $
30   * $Log: ClassNameMappingObjectStrategy.java,v $
31   * Revision 1.1  2005/07/10 18:38:43  wintermond
32   * Renamed ImplObjectStrategy to ClassNameMappingStrategy
33   * Performance improvements
34   *
35   * Revision 1.2  2005/07/09 09:56:38  wintermond
36   * Javadoc and implemented ProxyClassCreator. Backupstrategy now more generic
37   *
38   */
39  public class ClassNameMappingObjectStrategy implements ObjectStrategy
40  {
41  	/***
42  	 * Backup strategy if mapping information is insufficient.
43  	 */
44  	private ObjectStrategy backupStrategyM = null;
45  	
46  	/***
47  	 * lookup cache, stores mappings of interfaces to classes
48  	 */
49  	private Map<Class, Class> classCacheM = new HashMap<Class, Class>();
50  	
51  	/***
52  	 * reverse cache, caches the implementation classes to the interfaces
53  	 */
54  	private Map<Class, Class> interfaceCacheM = new HashMap<Class, Class>();
55  	
56  	/***
57  	 *	Stores all classes already instanciated by this instance 
58  	 */
59  	private Set<Class> cachedClassesM = new HashSet<Class>();
60  	
61  	/***
62  	 *	To prevent spamming of messages unresolved attributes get logged here 
63  	 */
64  	private Set<String> unresolvedAttributes = new HashSet<String>();
65  	
66  	/***
67  	 * Mapping information, tells the strategy how to map
68  	 * interfaces to classes and the name of setter methods.
69  	 */
70  	private Properties mappingsM = null;
71  	
72  	/***
73  	 *	Performance optimisation: method lookup for {@link #setAttribute(Object, String, Object))} 
74  	 */
75  	private Map<String, Method> methodCacheM = new HashMap<String, Method>();
76  	
77  	/***
78  	 * Default constructor
79  	 * 
80  	 * @param mappings Must not be null;
81  	 */
82  	public ClassNameMappingObjectStrategy(Properties mappings)
83  	{
84  		this(mappings, true);
85  	}
86  
87  	/***
88  	 * Constructor, specifying if in case of insufficient mapping information
89  	 * the call should get passed to the backup strategy
90  	 * 
91  	 * @param mappings
92  	 * @param useProxyMapping
93  	 */
94  	public ClassNameMappingObjectStrategy(Properties mappings, boolean useProxyMapping)
95  	{
96  		if(useProxyMapping)
97  		{
98  			backupStrategyM = new ProxyObjectStrategy();
99  		}
100 		if(mappings == null)
101 		{
102 			throw new NullPointerException("maping is null");
103 		}
104 		mappingsM = mappings;
105 	}
106 
107 	/* (non-Javadoc)
108 	 * @see de.keepondreaming.xml.ObjectStrategy#createInstance(java.lang.Class)
109 	 */
110 	public Object createInstance(Class clazz)
111 	{
112 		Object result = null;
113 		
114 		//
115 		//	Lookup parameter in class cache
116 		//
117 		Class creatorClass = classCacheM.get(clazz); 
118 		
119 		//
120 		//	Not cached, so try to resolve the class
121 		//
122 		if(creatorClass == null)
123 		{
124 			String className = mappingsM.getProperty(clazz.getName());
125 			if(className != null)
126 			{
127 				try
128 				{
129 					creatorClass = Class.forName(className);
130 					classCacheM.put(clazz, creatorClass);		
131 					cachedClassesM.add(creatorClass);
132 					interfaceCacheM.put(creatorClass, clazz);
133 				}
134 				catch (ClassNotFoundException e)
135 				{
136 					throw new IllegalArgumentException("Unkown class [" + className + "] specified in mapping!");
137 				}							
138 			}
139 			//
140 			//	Information not stored in mapping, see if a backupStrategy should be used 
141 			//
142 			else if(backupStrategyM == null)
143 			{
144 				throw new IllegalArgumentException("No mapping specified for class [" + className + "]!");
145 			}
146 			else
147 			{
148 				result = backupStrategyM.createInstance(clazz);				
149 			}
150 		}
151 		
152 		//
153 		//	Class information could be obtained, no backup strategy was used
154 		//
155 		if(result == null)
156 		{
157 			try
158 			{
159 				result = creatorClass.newInstance();
160 			}
161 			catch (Throwable e)
162 			{
163 				throw new IllegalArgumentException("Error while instanciating class [" + creatorClass.getName() + "]", e);
164 			}			
165 		}
166 		
167 		return result;
168 	}
169 
170 	/* (non-Javadoc)
171 	 * @see de.keepondreaming.xml.ObjectStrategy#setAttribute(java.lang.Object, java.lang.String, java.lang.Object)
172 	 */
173 	/* (non-Javadoc)
174 	 * @see de.keepondreaming.xml.ObjectStrategy#setAttribute(java.lang.Object, java.lang.String, java.lang.Object)
175 	 */
176 	public void setAttribute(Object target, String attribute, Object value)
177 	{
178 		Method method = getMethod(target, attribute, value);
179 		
180 		if(method != null)
181 		{
182 			try
183 			{
184 				method.invoke(target, new Object[]{value});	
185 			}
186 			catch (Throwable e)
187 			{
188 				throw new IllegalArgumentException("Could not set value [" + value + "] of attribute [" + attribute + "] in object [" + target + "] using method [" + method + "]. Value class is [" + value.getClass() + "]", e);		
189 			}
190 		}
191 		else if(backupStrategyM != null && !cachedClassesM.contains(target.getClass()))
192 		{
193 			backupStrategyM.setAttribute(target, attribute, value);
194 		}
195 		else
196 		{
197 			String key = target.getClass().getName() 
198 							+ "."
199 							+ attribute;
200 
201 			if(!unresolvedAttributes.contains(key))
202 			{
203 				unresolvedAttributes.add(key);
204 				System.err.println("Could not resolve setter for attribute " + attribute + " of class " + target.getClass());	
205 			}							
206 		}			
207 
208 	}
209 
210 	private Method getMethod(Object target, String attribute, Object value)
211 	{
212 		//
213 		//	Try to lookup setter method via standard naming patterns and 
214 		//	mapping entries
215 		//
216 		String key = target.getClass().getName() 
217 						+ "."
218 						+ attribute;
219 		
220 		Method result = methodCacheM.get(key);
221 		
222 		if(result == null)
223 		{
224 			String backupKey = "set"
225 								+ Util.capitalize(attribute);
226 			String methodName = mappingsM.getProperty(key, backupKey);
227 			
228 			try
229 			{
230 				Class valueClass = value.getClass();
231 				
232 				//  if the object was created by a backup strategy and is a dynamic proxy
233 				//	the method lookup needs to obtain the original class
234 				if (value instanceof ProxyClassCreator)
235 				{
236 					valueClass = ((ProxyClassCreator) value).getWrappedClass(value);				
237 				}
238 				result = target.getClass().getMethod(methodName, new Class[]{valueClass});			
239 			}
240 			catch (NoSuchMethodException checkForPrimitive)
241 			{
242 				//
243 				//	Maybe its not 
244 				//		public void setFoo(Integer a) 
245 				//	but 
246 				//		public void setFoo(int a)
247 				//
248 				Class primitive = Util.computePrimitiveClass(value);
249 				if(primitive != null)
250 				{
251 					try
252 					{
253 						result = target.getClass().getMethod(methodName, new Class[]{primitive});			
254 					}
255 					catch (NoSuchMethodException ignore)
256 					{
257 						// left blank
258 					}		
259 				}
260 			}
261 			
262 			if(result != null)
263 			{
264 				methodCacheM.put(key, result);
265 			}
266 		}
267 		return result;
268 	}
269 
270 	/* (non-Javadoc)
271 	 * @see de.keepondreaming.xml.ObjectStrategy#init()
272 	 */
273 	public void init()
274 	{
275 		classCacheM.clear();
276 		cachedClassesM.clear();
277 		interfaceCacheM.clear();
278 		unresolvedAttributes.clear();
279 		methodCacheM.clear();
280 	}
281 
282 	/* (non-Javadoc)
283 	 * @see de.keepondreaming.xml.ObjectStrategy#resolveInterface(java.lang.Object)
284 	 */
285 	public Class resolveInterface(Object object)
286 	{
287 		Class result = interfaceCacheM.get(object.getClass());
288 		if(result == null)
289 		{
290 			result = backupStrategyM.resolveInterface(object);
291 		}
292 		return result;
293 	}
294 
295 	/* (non-Javadoc)
296 	 * @see de.keepondreaming.xml.ObjectStrategy#getMethodName(java.lang.Class, java.lang.String, boolean)
297 	 */
298 	public String getMethodName(Class clazz, String attribute, boolean set)
299 	{
300 		String key = clazz.getName() + "." + attribute;
301 		String result = mappingsM.getProperty(key);
302 		return result;
303 	}
304 	
305 	
306 
307 
308 }