001: // Copyright 2006 The Apache Software Foundation
002: //
003: // Licensed under the Apache License, Version 2.0 (the "License");
004: // you may not use this file except in compliance with the License.
005: // You may obtain a copy of the License at
006: //
007: // http://www.apache.org/licenses/LICENSE-2.0
008: //
009: // Unless required by applicable law or agreed to in writing, software
010: // distributed under the License is distributed on an "AS IS" BASIS,
011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: // See the License for the specific language governing permissions and
013: // limitations under the License.
014:
015: package org.apache.tapestry.ioc.internal.services;
016:
017: import java.io.Serializable;
018: import java.lang.reflect.Constructor;
019: import java.lang.reflect.Modifier;
020: import java.util.List;
021: import java.util.Map;
022: import java.util.zip.DataFormatException;
023:
024: import javassist.CtClass;
025:
026: import org.apache.commons.logging.LogFactory;
027: import org.apache.tapestry.ioc.BaseLocatable;
028: import org.apache.tapestry.ioc.internal.services.LoggingDecoratorImplTest.ToStringService;
029: import org.apache.tapestry.ioc.services.ClassFab;
030: import org.apache.tapestry.ioc.services.MethodSignature;
031: import org.apache.tapestry.ioc.services.PropertyAccess;
032: import org.apache.tapestry.ioc.test.IOCTestCase;
033: import org.testng.annotations.Test;
034:
035: public class ClassFabImplTest extends IOCTestCase {
036: private final CtClassSource _source;
037:
038: private final PropertyAccess _access = new PropertyAccessImpl();
039:
040: public interface SampleService {
041: int primitiveMethod(int primitiveValue);
042:
043: void voidMethod(String input);
044:
045: String objectMethod(String input);
046: }
047:
048: public interface SampleToStringService {
049: String toString();
050: }
051:
052: public ClassFabImplTest() {
053: ClassLoader threadLoader = Thread.currentThread()
054: .getContextClassLoader();
055:
056: ClassFactoryClassPool pool = new ClassFactoryClassPool(
057: threadLoader);
058:
059: pool.addClassLoaderIfNeeded(threadLoader);
060:
061: _source = new CtClassSource(pool, threadLoader);
062: }
063:
064: private ClassFab newClassFab(String className, Class super Class) {
065: CtClass ctClass = _source.newClass(className, super Class);
066:
067: return new ClassFabImpl(_source, ctClass, LogFactory
068: .getLog("ClassFab"));
069: }
070:
071: @Test
072: public void create_simple_bean() throws Exception {
073: ClassFab cf = newClassFab("TargetBean", Object.class);
074:
075: cf.addField("_stringValue", String.class);
076:
077: MethodSignature setStringValue = new MethodSignature(
078: void.class, "setStringValue",
079: new Class[] { String.class }, null);
080:
081: cf.addMethod(Modifier.PUBLIC, setStringValue,
082: "_stringValue = $1;");
083:
084: MethodSignature getStringValue = new MethodSignature(
085: String.class, "getStringValue", null, null);
086:
087: cf.addMethod(Modifier.PUBLIC, getStringValue,
088: "return _stringValue;");
089:
090: Class targetClass = cf.createClass();
091:
092: Object targetBean = targetClass.newInstance();
093:
094: _access.set(targetBean, "stringValue", "Fred");
095:
096: // May keep a test-time dependency on HiveMind, just for PropertyUtils.
097:
098: String actual = (String) _access.get(targetBean, "stringValue");
099:
100: assertEquals(actual, "Fred");
101: }
102:
103: @Test
104: public void add_to_string() throws Exception {
105: ClassFab cf = newClassFab("ToString", Object.class);
106:
107: cf.addToString("ToString Description");
108:
109: Class clazz = cf.createClass();
110:
111: Object instance = clazz.newInstance();
112:
113: assertEquals(instance.toString(), "ToString Description");
114: }
115:
116: @Test
117: public void proxy_methods_to_delegate() throws Exception {
118: ClassFab cf = newClassFab("Delegator", Object.class);
119:
120: cf.addField("_delegate", SampleService.class);
121: cf.addConstructor(new Class[] { SampleService.class }, null,
122: "_delegate = $1;");
123:
124: cf.proxyMethodsToDelegate(SampleService.class, "_delegate",
125: "<Delegator>");
126:
127: SampleService delegate = newMock(SampleService.class);
128:
129: Class clazz = cf.createClass();
130:
131: SampleService proxy = (SampleService) clazz.getConstructors()[0]
132: .newInstance(delegate);
133:
134: expect(delegate.primitiveMethod(5)).andReturn(10);
135:
136: delegate.voidMethod("fred");
137:
138: expect(delegate.objectMethod("barney")).andReturn("rubble");
139:
140: replay();
141:
142: assertEquals(proxy.primitiveMethod(5), 10);
143:
144: proxy.voidMethod("fred");
145:
146: assertEquals(proxy.objectMethod("barney"), "rubble");
147: assertEquals(proxy.toString(), "<Delegator>");
148:
149: verify();
150: }
151:
152: @Test
153: public void proxy_methods_to_delegate_with_to_string()
154: throws Exception {
155: ClassFab cf = newClassFab("ToStringDelegator", Object.class);
156:
157: cf.addField("_delegate", ToStringService.class);
158: cf.addConstructor(new Class[] { ToStringService.class }, null,
159: "_delegate = $1;");
160:
161: cf.proxyMethodsToDelegate(ToStringService.class, "_delegate",
162: "<ToStringDelegator>");
163:
164: ToStringService delegate = new ToStringService() {
165: @Override
166: public String toString() {
167: return "ACTUAL TO-STRING";
168: }
169: };
170:
171: Class clazz = cf.createClass();
172:
173: ToStringService proxy = (ToStringService) clazz
174: .getConstructors()[0].newInstance(delegate);
175:
176: assertEquals(proxy.toString(), "ACTUAL TO-STRING");
177: }
178:
179: @Test
180: public void add_constructor() throws Exception {
181: ClassFab cf = newClassFab("ConstructableBean", Object.class);
182:
183: cf.addField("_stringValue", String.class);
184: cf.addConstructor(new Class[] { String.class }, null,
185: "{ _stringValue = $1; }");
186:
187: MethodSignature getStringValue = new MethodSignature(
188: String.class, "getStringValue", null, null);
189:
190: cf.addMethod(Modifier.PUBLIC, getStringValue,
191: "return _stringValue;");
192:
193: Class targetClass = cf.createClass();
194:
195: try {
196: targetClass.newInstance();
197: unreachable();
198: } catch (InstantiationException ex) {
199: }
200:
201: Constructor c = targetClass.getConstructors()[0];
202:
203: Object targetBean = c.newInstance(new Object[] { "Buffy" });
204:
205: String actual = (String) _access.get(targetBean, "stringValue");
206:
207: assertEquals("Buffy", actual);
208: }
209:
210: @Test
211: public void add_constructor_from_base_class() throws Exception {
212: ClassFab cf = newClassFab("MyIntHolder",
213: AbstractIntWrapper.class);
214:
215: cf.addField("_intValue", int.class);
216: cf.addConstructor(new Class[] { int.class }, null,
217: "{ _intValue = $1; }");
218:
219: cf.addMethod(Modifier.PUBLIC, new MethodSignature(int.class,
220: "getIntValue", null, null), "return _intValue;");
221:
222: Class targetClass = cf.createClass();
223: Constructor c = targetClass.getConstructors()[0];
224:
225: AbstractIntWrapper targetBean = (AbstractIntWrapper) c
226: .newInstance(new Object[] { new Integer(137) });
227:
228: assertEquals(targetBean.getIntValue(), 137);
229: }
230:
231: @Test
232: public void invalid_super _class() throws Exception {
233: ClassFab cf = newClassFab("InvalidSuperClass", List.class);
234:
235: try {
236: cf.createClass();
237: unreachable();
238: } catch (RuntimeException ex) {
239: assertExceptionSubstring(ex,
240: "Unable to create class InvalidSuperClass");
241: }
242: }
243:
244: private void assertExceptionSubstring(Throwable t,
245: String partialMessage) {
246: assertTrue(t.getMessage().contains(partialMessage));
247: }
248:
249: @Test
250: public void add_interface() throws Exception {
251: ClassFab cf = newClassFab("SimpleService", Object.class);
252:
253: cf.addInterface(SimpleService.class);
254:
255: cf.addMethod(Modifier.PUBLIC, new MethodSignature(int.class,
256: "add", new Class[] { int.class, int.class }, null),
257: "return $1 + $2;");
258:
259: Class targetClass = cf.createClass();
260:
261: SimpleService s = (SimpleService) targetClass.newInstance();
262:
263: assertEquals(207, s.add(99, 108));
264: }
265:
266: @Test
267: public void attempt_to_subclass_from_final_class() throws Exception {
268: ClassFab cf = newClassFab("StringSubclass", String.class);
269:
270: try {
271: cf.createClass();
272: } catch (RuntimeException ex) {
273: assertExceptionRegexp(ex,
274: "Unable to create class StringSubclass\\:.*Cannot inherit from final class");
275: }
276: }
277:
278: private void assertExceptionRegexp(Throwable ex, String pattern) {
279: assertTrue(ex.getMessage().matches(pattern));
280: }
281:
282: @Test
283: public void create_class_within_non_default_package()
284: throws Exception {
285: ClassFab cf = newClassFab("org.apache.hivemind.InPackage",
286: Object.class);
287:
288: Class c = cf.createClass();
289:
290: Object o = c.newInstance();
291:
292: assertEquals("org.apache.hivemind.InPackage", o.getClass()
293: .getName());
294: }
295:
296: @Test
297: public void invalid_method_body() throws Exception {
298: ClassFab cf = newClassFab("BadMethodBody", Object.class);
299:
300: cf.addInterface(Runnable.class);
301:
302: try {
303: cf.addMethod(Modifier.PUBLIC, new MethodSignature(
304: void.class, "run", null, null), "fail;");
305: } catch (RuntimeException ex) {
306: assertExceptionSubstring(ex,
307: "Unable to add method void run() to class BadMethodBody:");
308: }
309: }
310:
311: @Test
312: public void add_duplicate_method_signature() throws Exception {
313: ClassFab cf = newClassFab("DupeMethodAdd", Object.class);
314:
315: cf.addMethod(Modifier.PUBLIC, new MethodSignature(void.class,
316: "foo", null, null), "{}");
317:
318: try {
319: cf.addMethod(Modifier.PUBLIC, new MethodSignature(
320: void.class, "foo", null, null), "{}");
321: unreachable();
322: } catch (RuntimeException ex) {
323: assertEquals(
324: "Attempt to redefine method void foo() of class DupeMethodAdd.",
325: ex.getMessage());
326: }
327: }
328:
329: @Test
330: public void invalid_constructor_body() throws Exception {
331: ClassFab cf = newClassFab("BadConstructor", Object.class);
332:
333: try {
334: cf.addConstructor(null, null, " woops!");
335: } catch (RuntimeException ex) {
336: assertExceptionSubstring(ex,
337: "Unable to add constructor to class BadConstructor");
338: }
339:
340: }
341:
342: @Test
343: public void invalid_field() throws Exception {
344: ClassFab cf = newClassFab("InvalidField", Object.class);
345:
346: // You'd think some of these would fail, but the ultimate failure
347: // occurs when we create the class.
348:
349: cf.addField("a%b", String.class);
350: cf.addField("", int.class);
351:
352: // Aha! Adding a duplicate fails!
353:
354: cf.addField("buffy", int.class);
355:
356: try {
357: cf.addField("buffy", String.class);
358: unreachable();
359: } catch (RuntimeException ex) {
360: assertEquals(ex.getMessage(),
361: "Unable to add field buffy to class InvalidField: duplicate field: buffy");
362: }
363:
364: }
365:
366: @Test
367: public void to_string() throws Exception {
368: ClassFab cf = newClassFab("FredRunnable", BaseLocatable.class);
369:
370: cf.addInterface(Runnable.class);
371: cf.addInterface(Serializable.class);
372:
373: cf.addField("_map", Map.class);
374:
375: cf.addConstructor(new Class[] { Map.class, Runnable.class },
376: new Class[] { IllegalArgumentException.class,
377: DataFormatException.class }, "{ _map = $1; }");
378:
379: MethodSignature sig = new MethodSignature(Map.class,
380: "doTheNasty", new Class[] { int.class, String.class },
381: new Class[] { InstantiationException.class,
382: IllegalAccessException.class });
383:
384: cf.addMethod(Modifier.PUBLIC + Modifier.FINAL
385: + Modifier.SYNCHRONIZED, sig, "{ return _map; }");
386:
387: String toString = cf.toString();
388:
389: assertContains(
390: toString,
391: "public class FredRunnable extends "
392: + BaseLocatable.class.getName()
393: + "\n"
394: + " implements java.lang.Runnable, java.io.Serializable");
395:
396: assertContains(toString, "private java.util.Map _map;");
397:
398: assertContains(
399: toString,
400: "public FredRunnable(java.util.Map $1, java.lang.Runnable $2)\n"
401: + " throws java.lang.IllegalArgumentException, java.util.zip.DataFormatException\n"
402: + "{ _map = $1; }");
403:
404: assertContains(
405: toString,
406: "public final synchronized java.util.Map doTheNasty(int $1, java.lang.String $2)\n"
407: + " throws java.lang.InstantiationException, java.lang.IllegalAccessException\n"
408: + "{ return _map; }");
409:
410: }
411:
412: @Test
413: public void add_noop_method() throws Exception {
414: ClassFab cf = newClassFab("NoOp", Object.class);
415: cf.addInterface(Runnable.class);
416:
417: cf.addNoOpMethod(new MethodSignature(void.class, "run", null,
418: null));
419: cf.addNoOpMethod(new MethodSignature(int.class, "getInt", null,
420: null));
421: cf.addNoOpMethod(new MethodSignature(double.class, "getDouble",
422: null, null));
423:
424: Class clazz = cf.createClass();
425:
426: Runnable instance = (Runnable) clazz.newInstance();
427:
428: instance.run();
429:
430: assertEquals(_access.get(instance, "int"), 0);
431: assertEquals(_access.get(instance, "double"), 0.0d);
432: }
433:
434: private void assertContains(String actual, String expectedSubstring) {
435: assertTrue(actual.contains(expectedSubstring),
436: "Missing substring: " + expectedSubstring);
437: }
438: }
|