View Javadoc

1   /*
2    * This file is a part of CAST project.
3    * (c) Copyright 2007, AGH University of Science & Technology
4    * https://caribou.iisg.agh.edu.pl/trac/cast
5    *
6    * Licensed under the Eclipse Public License, Version 1.0 (the "License").
7    * You may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    * http://www.eclipse.org/legal/epl-v10.html
10   */
11  /*
12   * File: Helper.java
13   * Created: 2007-12-15
14   * Author: apohllo
15   * $Id: Helper.java 2335 2009-01-13 16:40:16Z entrop $
16   */
17  
18  package pl.edu.agh.cast.model.mapper.internal;
19  
20  import java.lang.reflect.Constructor;
21  import java.lang.reflect.Method;
22  import java.util.HashMap;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.eclipse.osgi.util.NLS;
28  
29  import pl.edu.agh.cast.model.mapper.Mappable;
30  import pl.edu.agh.cast.model.mapper.annotation.MapAttribute;
31  import pl.edu.agh.cast.model.mapper.annotation.MapLink;
32  import pl.edu.agh.cast.model.mapper.annotation.Mapping;
33  
34  /**
35   * Helper class for the mapper - contains methods processing reflection data.
36   *
37   * @author AGH CAST Team
38   *
39   */
40  public class Helper {
41  	private static Map<Class, Boolean> superMapped = new HashMap<Class, Boolean>();
42  
43  	private static Map<Class<Mappable>, LinkEntry[]> linkMap = new HashMap<Class<Mappable>, LinkEntry[]>();
44  
45  	private static HashMap<Class<Mappable>, AttributeEntry[]> attributeMap = new HashMap<Class<Mappable>, AttributeEntry[]>();
46  
47  	private static Map<Class, Map<String, String>> accessors = new HashMap<Class, Map<String, String>>();
48  
49  	private static Map<Class, Map<String, Method>> methods = new HashMap<Class, Map<String, Method>>();
50  
51  	private static Map<Class, String> mappingNames = new HashMap<Class, String>();
52  
53  	private static Map<Class, Constructor<Mappable>> constructors = new HashMap<Class, Constructor<Mappable>>();
54  
55  	// private static Map<String, Class> classes = new HashMap<String, Class>();
56  
57  	/**
58  	 * Return default constructor for given class.
59  	 *
60  	 * @param klass
61  	 *            The class
62  	 * @return the default constructor
63  	 */
64  	public static Constructor<Mappable> getConstructor(Class<Mappable> klass) throws SecurityException,
65  	        NoSuchMethodException {
66  		Constructor<Mappable> result = constructors.get(klass);
67  		if (result == null) {
68  			result = klass.getConstructor(new Class[] {});
69  			constructors.put(klass, result);
70  		}
71  		return result;
72  	}
73  
74  	/**
75  	 * Returns the name of the accessor (i.e. getter/setter name without "get/set") for the link with given name
76  	 *
77  	 * @param klass
78  	 *            The klass for which the accessor is to be returned.
79  	 * @param linkName
80  	 *            The name of the link.
81  	 * @return Accessor (getter/setter name without "get/set").
82  	 */
83  	public static String objectAccessor(Class klass, String linkName) {
84  		// cut the "get" part of the name
85  		String accessor = null;
86  		Map<String, String> linkToAccessor = null;
87  		if (accessors.containsKey(klass)) {
88  			linkToAccessor = accessors.get(klass);
89  			if (linkToAccessor.containsKey(linkName)) {
90  				accessor = linkToAccessor.get(linkName);
91  			} else {
92  				accessor = getAnnotatedMethod(klass, linkName).getName().substring(3);
93  				linkToAccessor.put(linkName, accessor);
94  			}
95  		} else {
96  			accessor = getAnnotatedMethod(klass, linkName).getName().substring(3);
97  			linkToAccessor = new HashMap<String, String>(5);
98  			linkToAccessor.put(linkName, accessor);
99  			accessors.put(klass, linkToAccessor);
100 		}
101 		return accessor;
102 	}
103 
104 	/**
105 	 * Get mapped type name for given class. By default the type name is the same as FQ class name, but it might be
106 	 * different to allow polimorphic mapping
107 	 *
108 	 * @param klass
109 	 *            The klass of the mapped object
110 	 * @return Mapped type name
111 	 */
112 	public static String mappedTypeName(Class klass) {
113 		if (mappingNames.containsKey(klass)) {
114 			return mappingNames.get(klass);
115 		}
116 		String typeName = ((Mapping)klass.getAnnotation(Mapping.class)).value();
117 		if (typeName == null || typeName.equals("")) {
118 			typeName = klass.getCanonicalName();
119 		}
120 		mappingNames.put(klass, typeName);
121 		return typeName;
122 	}
123 
124 	/**
125 	 * Return link data (MapLink annotation) for the given mappable and link with given name.
126 	 *
127 	 * @param klass
128 	 *            The class where the link should be looked for.
129 	 * @param linkName
130 	 *            The name of the link
131 	 * @return The MapLinkAnnotation
132 	 */
133 	public static MapLink getLinkData(Class klass, String linkName) {
134 		return getAnnotatedMethod(klass, linkName).getAnnotation(MapLink.class);
135 	}
136 
137 	/**
138 	 * Return an annotated method whose annotation name is the given parameter.
139 	 *
140 	 * @param klass
141 	 *            The class for which the method is returned.
142 	 * @param linkName
143 	 *            The name of the link.
144 	 * @return The method which is annotaded as a link with given name.
145 	 */
146 	public static Method getAnnotatedMethod(Class klass, String linkName) {
147 		Method method = null;
148 		Map<String, Method> linkToAccessor = null;
149 		if (methods.containsKey(klass)) {
150 			linkToAccessor = methods.get(klass);
151 			if (linkToAccessor.containsKey(linkName)) {
152 				method = linkToAccessor.get(linkName);
153 			} else {
154 				method = getAnnotatedMethodInternal(klass, linkName);
155 				linkToAccessor.put(linkName, method);
156 			}
157 		} else {
158 			method = getAnnotatedMethodInternal(klass, linkName);
159 			linkToAccessor = new HashMap<String, Method>(5);
160 			linkToAccessor.put(linkName, method);
161 			methods.put(klass, linkToAccessor);
162 		}
163 		return method;
164 	}
165 
166 	/**
167 	 * Internal version doesn't use cache
168 	 *
169 	 * @param klass
170 	 * @param linkName
171 	 * @return
172 	 */
173 	private static Method getAnnotatedMethodInternal(Class klass, String linkName) {
174 		for (Method method : klass.getMethods()) {
175 			if (method.isAnnotationPresent(MapLink.class)) {
176 				if ((method.getAnnotation(MapLink.class)).name().equals(linkName)) {
177 					return method;
178 				}
179 			}
180 		}
181 		throw new RuntimeException(NLS.bind("No data for link {0} for {1}", //$NON-NLS-1$
182 		        linkName, klass));
183 	}
184 
185 	/**
186 	 * Find all methods which have the MapAttribute annotation present.
187 	 *
188 	 * @param klass
189 	 *            Class which is searched for the methods
190 	 * @return Methods which have the MapAttribute annotation present.
191 	 */
192 	public static Method[] mappedAttributeMethods(Class klass) {
193 		Method[] methods1 = klass.getDeclaredMethods();
194 		List<Method> result = new LinkedList<Method>();
195 		for (Method method : methods1) {
196 			if (method.isAnnotationPresent(MapAttribute.class)) {
197 				result.add(method);
198 			}
199 		}
200 		return result.toArray(new Method[0]);
201 	}
202 
203 	/**
204 	 * Find all methods which have the MapAttribute annotation present.
205 	 *
206 	 * @param klass
207 	 *            Class which is searched for the methods
208 	 * @return Methods which have the MapLink annotation present.
209 	 */
210 	public static Method[] mappedLinkMethods(Class klass) {
211 		Method[] methods1 = klass.getDeclaredMethods();
212 		List<Method> result = new LinkedList<Method>();
213 		for (Method method : methods1) {
214 			if (method.isAnnotationPresent(MapLink.class)) {
215 				result.add(method);
216 			}
217 		}
218 		return result.toArray(new Method[0]);
219 	}
220 
221 	/**
222 	 * Checks if given class is mapped to metamodel.
223 	 *
224 	 * @param klass
225 	 *            Checked class
226 	 * @return True if the class is mapped.
227 	 */
228 	public static boolean isSuperMapped(Class klass) {
229 		if (superMapped.containsKey(klass)) {
230 			return superMapped.get(klass);
231 		}
232 		Class superClass = klass.getSuperclass();
233 		if (superClass.equals(Object.class)) {
234 			superMapped.put(klass, false);
235 			return false;
236 		}
237 		Mapping mapping = (Mapping)superClass.getAnnotation(Mapping.class);
238 		if (mapping != null) {
239 			superMapped.put(klass, true);
240 			return true;
241 		} else {
242 			superMapped.put(klass, false);
243 			return false;
244 		}
245 
246 	}
247 
248 	/**
249 	 * Returns the setter methods. Walks through argument's class hierarchy (including implemented directly implemented
250 	 * interfaces) trying to find the proper setter. If not found throws RuntimeException.
251 	 *
252 	 * @param object
253 	 *            The object whose class is looked for the setter.
254 	 * @param linkedElement
255 	 *            The object whose class is used to determine the setters argument
256 	 * @param setterName
257 	 *            The name of the setter method (with set in it.)
258 	 * @return Setter in class of object, which accepts linkedElement as argument.
259 	 */
260 	public static Method getSetter(Mappable object, Object linkedElement, String setterName) {
261 		Class linkedElementClass = linkedElement.getClass();
262 		Method setter = null;
263 		while (!linkedElementClass.equals(Object.class)) {
264 			try {
265 				setter = object.getClass().getMethod(setterName, linkedElementClass);
266 				return setter;
267 			} catch (NoSuchMethodException ex) {
268 				// do nothing
269 			}
270 			for (Class interf : linkedElementClass.getInterfaces()) {
271 				try {
272 					setter = object.getClass().getMethod(setterName, interf);
273 					return setter;
274 				} catch (NoSuchMethodException ex) {
275 					// do nothing
276 				}
277 			}
278 			linkedElementClass = linkedElementClass.getSuperclass();
279 		}
280 		throw new RuntimeException(NLS
281 		        .bind("Setter not found: {0}.{1}({2})", //$NON-NLS-1$
282 		                new Object[] { object.getClass().getSimpleName(), setterName,
283 		                    linkedElement.getClass().getSimpleName() }));
284 	}
285 
286 	/**
287 	 * Get methods with MapLink annotations for the given class.
288 	 *
289 	 * @param klass
290 	 *            The class for which methods with annotations are returned.
291 	 * @return Array of pairs: method -- annotation
292 	 */
293 	public static LinkEntry[] getMappedLinks(Class<Mappable> klass) {
294 		if (linkMap.containsKey(klass)) {
295 			return linkMap.get(klass);
296 		}
297 		Method[] methods1 = Helper.mappedLinkMethods(klass);
298 		LinkEntry[] entries = new LinkEntry[methods1.length];
299 		for (int i = 0; i < entries.length; i++) {
300 			entries[i] = new LinkEntry(methods1[i], methods1[i].getAnnotation(MapLink.class));
301 		}
302 		linkMap.put(klass, entries);
303 		return entries;
304 	}
305 
306 	/**
307 	 * Get methods with MapAttribute annotations for the given class.
308 	 *
309 	 * @param klass
310 	 *            The class for which methods with annotations are returned.
311 	 * @return Array of pairs: method -- annotation
312 	 */
313 	public static AttributeEntry[] getMappedAttributes(Class<Mappable> klass) {
314 		if (attributeMap.containsKey(klass)) {
315 			return attributeMap.get(klass);
316 		}
317 		Method[] methods1 = Helper.mappedAttributeMethods(klass);
318 		AttributeEntry[] entries = new AttributeEntry[methods1.length];
319 		for (int i = 0; i < entries.length; i++) {
320 			entries[i] = new AttributeEntry(methods1[i], methods1[i].getAnnotation(MapAttribute.class));
321 		}
322 		attributeMap.put(klass, entries);
323 		return entries;
324 	}
325 
326 	/**
327 	 * Returns the class for given type name and type map.
328 	 *
329 	 * @param typeName
330 	 *            The name of the type in base model.
331 	 * @param typeMap
332 	 *            The type map.
333 	 * @return The class for given type name.
334 	 * @throws ClassNotFoundException
335 	 */
336 	public static Class<Mappable> mappedClass(String typeName, Map<String, Class> typeMap)
337 	        throws ClassNotFoundException {
338 		Class<Mappable> klass = typeMap.get(typeName);
339 		if (klass == null) {
340 			klass = (Class<Mappable>)Class.forName(typeName);
341 			typeMap.put(typeName, klass);
342 		}
343 		return klass;
344 	}
345 
346 }