View Javadoc

1   /*
2    * Copyright (c) 2007, James Leigh All rights reserved.
3    * 
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    * 
7    * - Redistributions of source code must retain the above copyright notice, this
8    *   list of conditions and the following disclaimer.
9    * - Redistributions in binary form must reproduce the above copyright notice,
10   *   this list of conditions and the following disclaimer in the documentation
11   *   and/or other materials provided with the distribution. 
12   * - Neither the name of the openrdf.org nor the names of its contributors may
13   *   be used to endorse or promote products derived from this software without
14   *   specific prior written permission.
15   * 
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26   * POSSIBILITY OF SUCH DAMAGE.
27   * 
28   */
29  package org.openrdf.elmo.impl;
30  
31  import static java.lang.reflect.Modifier.isTransient;
32  
33  import java.lang.reflect.Constructor;
34  import java.lang.reflect.Method;
35  import java.util.ArrayList;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.regex.Pattern;
39  
40  import org.openrdf.elmo.annotations.intercepts;
41  import org.openrdf.elmo.dynacode.ClassTemplate;
42  import org.openrdf.elmo.dynacode.CodeBuilder;
43  
44  class Behaviour {
45  	private static final String FACTORY_SUFFIX = "Factory";
46  
47  	private Class<?> javaClass;
48  
49  	private ClassTemplate declaring;
50  
51  	private String getterName;
52  
53  	private Map<Class<?>, MethodFactory> factories;
54  
55  	public void setDeclaring(ClassTemplate declaring) {
56  		this.declaring = declaring;
57  	}
58  
59  	public void setFactories(Map<Class<?>, MethodFactory> factories) {
60  		this.factories = factories;
61  	}
62  
63  	public void setJavaClass(Class<?> javaClass) {
64  		this.javaClass = javaClass;
65  	}
66  
67  	public List<Method> getAroundInvoke(Method method, Class<?> face,
68  			ClassTemplate cc) throws Exception {
69  		List<Method> list = new ArrayList<Method>();
70  		Method jm = findInterfaceMethod(method, face);
71  		for (Method im : javaClass.getMethods()) {
72  			if (im.isAnnotationPresent(intercepts.class)) {
73  				intercepts it = im.getAnnotation(intercepts.class);
74  				if (!nameMatches(method.getName(), it))
75  					continue;
76  				if (!argcMatch(it.argc(), jm.getParameterTypes().length))
77  					continue;
78  				if (!argsMatch(it.parameters(), jm.getParameterTypes()))
79  					continue;
80  				if (!returnTypeMatches(jm, it))
81  					continue;
82  				if (!declaredInMatches(jm, it))
83  					continue;
84  				if (isEnabled(jm, im.getDeclaringClass(), it))
85  					list.add(im);
86  			}
87  		}
88  		return list;
89  	}
90  
91  	public String getGetterName() {
92  		return getterName;
93  	}
94  
95  	public Class<?> getJavaClass() {
96  		return javaClass;
97  	}
98  
99  	public boolean isMethodPresent(Method method) throws Exception {
100 		try {
101 			Class<?>[] types = method.getParameterTypes();
102 			Method m = javaClass.getMethod(method.getName(), types);
103 			if (isTransient(m.getModifiers()))
104 				return false;
105 			return !isObjectMethod(m);
106 		} catch (NoSuchMethodException e) {
107 			return false;
108 		}
109 	}
110 
111 	public boolean invokeCondition(Method jm, Class<?> face)
112 			throws Exception {
113 		for (Method im : javaClass.getMethods()) {
114 			if (im.isAnnotationPresent(intercepts.class)) {
115 				intercepts it = im.getAnnotation(intercepts.class);
116 				if (!nameMatches(jm.getName(), it))
117 					continue;
118 				if (!argcMatch(it.argc(), jm.getParameterTypes().length))
119 					continue;
120 				if (!argsMatch(it.parameters(), jm.getParameterTypes()))
121 					continue;
122 				if (!returnTypeMatches(jm, it))
123 					continue;
124 				if (!declaredInMatches(jm, it))
125 					continue;
126 				if (isEnabled(jm, im.getDeclaringClass(), it))
127 					return true;
128 			}
129 		}
130 		return false;
131 	}
132 
133 	public void init() throws Exception {
134 		getterName = "_$get" + javaClass.getSimpleName()
135 				+ Integer.toHexString(javaClass.hashCode());
136 		String fieldName = "_$" + getterName.substring(5);
137 		declaring.createField(javaClass, fieldName);
138 		CodeBuilder code = declaring.createMethod(javaClass, getterName);
139 		code.code("if (").code(fieldName).code(" != null){\n");
140 		code.code("return ").code(fieldName).code(";\n} else {\n");
141 		code.code("return ").code(fieldName).code(" = ($r) ");
142 		appendNewInstance(code);
143 		code.code(";\n}").end();
144 	}
145 
146 	private void appendNewInstance(CodeBuilder code) throws Exception {
147 		MethodFactory mf = factories.get(javaClass);
148 		if (mf == null) {
149 			code.code("new ").code(javaClass.getName());
150 			if (getConstructorParameterType() == null) {
151 				javaClass.getDeclaredConstructor();
152 				code.code("()");
153 			} else {
154 				code.code("($0)");
155 			}
156 		} else {
157 			Class<?> factory = mf.getFactoryClass();
158 			String name = "_$" + factory.getSimpleName()
159 					+ Integer.toHexString(factory.getName().hashCode())
160 					+ FACTORY_SUFFIX;
161 			CodeBuilder field = declaring.assignStaticField(factory, name);
162 			Method instanceMethod = mf.getInstanceMethod();
163 			if (instanceMethod == null) {
164 				field.construct(factory).end();
165 			} else {
166 				field.staticInvoke(instanceMethod).end();
167 			}
168 			code.code(name).code(".").code(mf.getMethod().getName());
169 			if (mf.getMethod().getParameterTypes().length == 0) {
170 				code.code("()");
171 			} else {
172 				code.code("($0)");
173 			}
174 		}
175 	}
176 
177 	private String getConstructorParameterType() throws Exception {
178 		for (Constructor<?> c : javaClass.getConstructors()) {
179 			Class<?>[] param = c.getParameterTypes();
180 			if (param.length == 1 && param[0].isInterface()) {
181 				for (Class<?> f : declaring.getInterfaces()) {
182 					if (param[0].isAssignableFrom(f)) {
183 						return param[0].getName();
184 					}
185 				}
186 			}
187 		}
188 		return null;
189 	}
190 
191 	private boolean nameMatches(String method, intercepts it) {
192 		if (it.method().length() == 0)
193 			return true;
194 		return Pattern.matches(it.method(), method);
195 	}
196 
197 	private boolean argcMatch(int argc1, int argc2) {
198 		return argc1 < 0 || argc2 < 0 || argc1 == argc2;
199 	}
200 
201 	private boolean argsMatch(Class<?>[] pattern, Class<?>[] type) {
202 		if (pattern.length == 1 && pattern[0] == intercepts.class)
203 			return true;
204 		if (pattern.length != type.length)
205 			return false;
206 		for (int i = 0; i < pattern.length; i++) {
207 			if (!isAssignableFrom(pattern[i], type[i]))
208 				return false;
209 			if (!pattern[i].equals(type[i]))
210 				return false;
211 		}
212 		return true;
213 	}
214 
215 	private boolean returnTypeMatches(Method jm, intercepts it) {
216 		if (it.returns() == intercepts.class)
217 			return true;
218 		return isAssignableFrom(jm.getReturnType(), it.returns());
219 	}
220 
221 	private boolean declaredInMatches(Method jm, intercepts it) {
222 		if (it.declaring() == intercepts.class)
223 			return true;
224 		try {
225 			it.declaring().getMethod(jm.getName(), jm.getParameterTypes());
226 			return true;
227 		} catch (NoSuchMethodException e) {
228 			return false;
229 		}
230 	}
231 
232 	private boolean isEnabled(Method jm, Class<?> interceptor, intercepts it)
233 			throws Exception {
234 		if (it.conditional().length() == 0)
235 			return true;
236 		Method cm = interceptor.getDeclaredMethod(it.conditional(),
237 				new Class[] { Method.class });
238 		assert cm != null : it.conditional();
239 		return Boolean.TRUE.equals(cm.invoke(null, new Object[] { jm }));
240 	}
241 
242 	private boolean isAssignableFrom(Class<?> t1, Class<?> t2) {
243 		if (t1.isAssignableFrom(t2))
244 			return true;
245 		if (t2.isPrimitive())
246 			return getWrapperClass(t2).equals(t1);
247 		if (t1.isPrimitive())
248 			return getWrapperClass(t1).equals(t2);
249 		return false;
250 	}
251 
252 	private Class<?> getWrapperClass(Class<?> primitiveClass) {
253 		if (primitiveClass.equals(Boolean.TYPE))
254 			return Boolean.class;
255 		if (primitiveClass.equals(Byte.TYPE))
256 			return Byte.class;
257 		if (primitiveClass.equals(Character.TYPE))
258 			return Character.class;
259 		if (primitiveClass.equals(Short.TYPE))
260 			return Short.class;
261 		if (primitiveClass.equals(Integer.TYPE))
262 			return Integer.class;
263 		if (primitiveClass.equals(Long.TYPE))
264 			return Long.class;
265 		if (primitiveClass.equals(Float.TYPE))
266 			return Float.class;
267 		if (primitiveClass.equals(Double.TYPE))
268 			return Double.class;
269 		if (primitiveClass.equals(Void.TYPE))
270 			return Void.class;
271 		return primitiveClass;
272 	}
273 
274 	private boolean isObjectMethod(Method m) {
275 		return m.getDeclaringClass().getName().equals(Object.class.getName());
276 	}
277 
278 	private Method findInterfaceMethod(Method method, Class<?> face)
279 			throws Exception {
280 		String name = method.getName();
281 		Class<?>[] types = method.getParameterTypes();
282 		Class<?> clazz = face;
283 		return clazz.getDeclaredMethod(name, types);
284 	}
285 }