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 				code.code("()");
152 			} else {
153 				code.code("($0)");
154 			}
155 		} else {
156 			Class<?> factory = mf.getFactoryClass();
157 			String name = "_$" + factory.getSimpleName()
158 					+ Integer.toHexString(factory.getName().hashCode())
159 					+ FACTORY_SUFFIX;
160 			CodeBuilder field = declaring.assignStaticField(factory, name);
161 			Method instanceMethod = mf.getInstanceMethod();
162 			if (instanceMethod == null) {
163 				field.construct(factory).end();
164 			} else {
165 				field.staticInvoke(instanceMethod).end();
166 			}
167 			code.code(name).code(".").code(mf.getMethod().getName());
168 			if (mf.getMethod().getParameterTypes().length == 0) {
169 				code.code("()");
170 			} else {
171 				code.code("($0)");
172 			}
173 		}
174 	}
175 
176 	private String getConstructorParameterType() throws Exception {
177 		for (Constructor<?> c : javaClass.getConstructors()) {
178 			Class<?>[] param = c.getParameterTypes();
179 			if (param.length == 1 && param[0].isInterface()) {
180 				for (Class<?> f : declaring.getInterfaces()) {
181 					if (param[0].isAssignableFrom(f)) {
182 						return param[0].getName();
183 					}
184 				}
185 			}
186 		}
187 		return null;
188 	}
189 
190 	private boolean nameMatches(String method, intercepts it) {
191 		if (it.method().length() == 0)
192 			return true;
193 		return Pattern.matches(it.method(), method);
194 	}
195 
196 	private boolean argcMatch(int argc1, int argc2) {
197 		return argc1 < 0 || argc2 < 0 || argc1 == argc2;
198 	}
199 
200 	private boolean argsMatch(Class<?>[] pattern, Class<?>[] type) {
201 		if (pattern.length == 1 && pattern[0] == intercepts.class)
202 			return true;
203 		if (pattern.length != type.length)
204 			return false;
205 		for (int i = 0; i < pattern.length; i++) {
206 			if (!isAssignableFrom(pattern[i], type[i]))
207 				return false;
208 			if (!pattern[i].equals(type[i]))
209 				return false;
210 		}
211 		return true;
212 	}
213 
214 	private boolean returnTypeMatches(Method jm, intercepts it) {
215 		if (it.returns() == intercepts.class)
216 			return true;
217 		return isAssignableFrom(jm.getReturnType(), it.returns());
218 	}
219 
220 	private boolean declaredInMatches(Method jm, intercepts it) {
221 		if (it.declaring() == intercepts.class)
222 			return true;
223 		try {
224 			it.declaring().getMethod(jm.getName(), jm.getParameterTypes());
225 			return true;
226 		} catch (NoSuchMethodException e) {
227 			return false;
228 		}
229 	}
230 
231 	private boolean isEnabled(Method jm, Class<?> interceptor, intercepts it)
232 			throws Exception {
233 		if (it.conditional().length() == 0)
234 			return true;
235 		Method cm = interceptor.getDeclaredMethod(it.conditional(),
236 				new Class[] { Method.class });
237 		assert cm != null : it.conditional();
238 		return Boolean.TRUE.equals(cm.invoke(null, new Object[] { jm }));
239 	}
240 
241 	private boolean isAssignableFrom(Class<?> t1, Class<?> t2) {
242 		if (t1.isAssignableFrom(t2))
243 			return true;
244 		if (t2.isPrimitive())
245 			return getWrapperClass(t2).equals(t1);
246 		if (t1.isPrimitive())
247 			return getWrapperClass(t1).equals(t2);
248 		return false;
249 	}
250 
251 	private Class<?> getWrapperClass(Class<?> primitiveClass) {
252 		if (primitiveClass.equals(Boolean.TYPE))
253 			return Boolean.class;
254 		if (primitiveClass.equals(Byte.TYPE))
255 			return Byte.class;
256 		if (primitiveClass.equals(Character.TYPE))
257 			return Character.class;
258 		if (primitiveClass.equals(Short.TYPE))
259 			return Short.class;
260 		if (primitiveClass.equals(Integer.TYPE))
261 			return Integer.class;
262 		if (primitiveClass.equals(Long.TYPE))
263 			return Long.class;
264 		if (primitiveClass.equals(Float.TYPE))
265 			return Float.class;
266 		if (primitiveClass.equals(Double.TYPE))
267 			return Double.class;
268 		if (primitiveClass.equals(Void.TYPE))
269 			return Void.class;
270 		return primitiveClass;
271 	}
272 
273 	private boolean isObjectMethod(Method m) {
274 		return m.getDeclaringClass().getName().equals(Object.class.getName());
275 	}
276 
277 	private Method findInterfaceMethod(Method method, Class<?> face)
278 			throws Exception {
279 		String name = method.getName();
280 		Class<?>[] types = method.getParameterTypes();
281 		Class<?> clazz = face;
282 		return clazz.getDeclaredMethod(name, types);
283 	}
284 }