001: // Copyright 2006, 2007 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 static java.lang.String.format;
018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newConcurrentMap;
019:
020: import java.lang.reflect.Constructor;
021: import java.lang.reflect.Modifier;
022: import java.util.List;
023: import java.util.Map;
024:
025: import org.apache.tapestry.ioc.annotations.InjectService;
026: import org.apache.tapestry.ioc.services.ChainBuilder;
027: import org.apache.tapestry.ioc.services.ClassFab;
028: import org.apache.tapestry.ioc.services.ClassFabUtils;
029: import org.apache.tapestry.ioc.services.ClassFactory;
030: import org.apache.tapestry.ioc.services.MethodIterator;
031: import org.apache.tapestry.ioc.services.MethodSignature;
032: import org.apache.tapestry.ioc.util.BodyBuilder;
033:
034: public class ChainBuilderImpl implements ChainBuilder {
035: private final ClassFactory _classFactory;
036:
037: /**
038: * Map, keyed on service interface, of implementation Class.
039: */
040:
041: private Map<Class, Class> _cache = newConcurrentMap();
042:
043: public ChainBuilderImpl(@InjectService("ClassFactory")
044: ClassFactory classFactory) {
045: _classFactory = classFactory;
046: }
047:
048: @SuppressWarnings("unchecked")
049: public <T> T build(Class<T> commandInterface, List<T> commands) {
050: Class<T> chainClass = findImplementationClass(commandInterface);
051:
052: return createInstance(chainClass, commands);
053: }
054:
055: private Class findImplementationClass(Class commandInterface) {
056: Class result = _cache.get(commandInterface);
057:
058: if (result == null) {
059: result = constructImplementationClass(commandInterface);
060: _cache.put(commandInterface, result);
061: }
062:
063: return result;
064: }
065:
066: private Class constructImplementationClass(Class commandInterface) {
067: // In rare, rare cases, a race condition to create an implementation class
068: // for the same interface may occur. We just let that happen, and there'll
069: // be two different classes corresponding to the same interface.
070:
071: String name = ClassFabUtils.generateClassName(commandInterface);
072:
073: ClassFab cf = _classFactory.newClass(name, Object.class);
074:
075: addInfrastructure(cf, commandInterface);
076:
077: addMethods(cf, commandInterface);
078:
079: return cf.createClass();
080: }
081:
082: private void addInfrastructure(ClassFab cf, Class commandInterface) {
083: // Array types are very, very tricky to deal with.
084: // Also, generics don't help (<T> new T[]) is still java.lang.Object[].
085:
086: String arrayClassName = commandInterface.getCanonicalName()
087: + "[]";
088: String jvmName = ClassFabUtils.toJVMBinaryName(arrayClassName);
089:
090: Class array = null;
091:
092: try {
093: ClassLoader loader = commandInterface.getClass()
094: .getClassLoader();
095: if (loader == null)
096: loader = Thread.currentThread().getContextClassLoader();
097:
098: array = Class.forName(jvmName, true, loader);
099: } catch (Exception ex) {
100: throw new RuntimeException(ex);
101: }
102:
103: cf.addInterface(commandInterface);
104: cf.addField("_commands", Modifier.PRIVATE | Modifier.FINAL,
105: array);
106:
107: BodyBuilder builder = new BodyBuilder();
108: builder.addln("_commands = (%s[]) $1.toArray(new %<s[0]);",
109: commandInterface.getName());
110:
111: cf.addConstructor(new Class[] { List.class }, null, builder
112: .toString());
113: }
114:
115: @SuppressWarnings("unchecked")
116: private <T> T createInstance(Class<T> instanceClass,
117: List<T> commands) {
118: try {
119: Constructor<T> ctor = instanceClass.getConstructors()[0];
120:
121: return instanceClass.cast(ctor.newInstance(commands));
122: } catch (Exception ex) {
123: // This should not be reachable!
124: throw new RuntimeException(ex);
125: }
126:
127: }
128:
129: private void addMethods(ClassFab cf, Class commandInterface) {
130: MethodIterator mi = new MethodIterator(commandInterface);
131:
132: while (mi.hasNext()) {
133: MethodSignature sig = mi.next();
134:
135: addMethod(cf, commandInterface, sig);
136: }
137:
138: if (!mi.getToString())
139: cf.addToString(format("<Command chain of %s>",
140: commandInterface.getName()));
141: }
142:
143: private void addMethod(ClassFab cf, Class commandInterface,
144: MethodSignature sig) {
145: Class returnType = sig.getReturnType();
146:
147: if (returnType.equals(void.class)) {
148: addVoidMethod(cf, commandInterface, sig);
149: return;
150: }
151:
152: String defaultValue = defaultForReturnType(returnType);
153:
154: BodyBuilder builder = new BodyBuilder();
155: builder.begin();
156:
157: builder.addln("%s result = %s;", ClassFabUtils
158: .toJavaClassName(returnType), defaultValue);
159: builder.addln("for (int i = 0; i < _commands.length; i++)");
160:
161: builder.begin();
162: builder.addln("result = _commands[i].%s($$);", sig.getName());
163:
164: builder.addln("if (result != %s) break;", defaultValue);
165:
166: builder.end();
167:
168: builder.addln("return result;");
169: builder.end();
170:
171: cf.addMethod(Modifier.PUBLIC, sig, builder.toString());
172: }
173:
174: private String defaultForReturnType(Class returnType) {
175: // For all object and array types.
176:
177: if (!returnType.isPrimitive())
178: return "null";
179:
180: if (returnType.equals(boolean.class))
181: return "false";
182:
183: // Assume, then, that it is a numeric type (this method
184: // isn't called for type void). Javassist seems to be
185: // able to handle 0 for all numeric types.
186:
187: return "0";
188: }
189:
190: private void addVoidMethod(ClassFab cf, Class commandInterface,
191: MethodSignature sig) {
192: BodyBuilder builder = new BodyBuilder();
193:
194: builder.begin();
195:
196: builder.addln("for (int i = 0; i < _commands.length; i++)");
197: builder.addln(" _commands[i].%s($$);", sig.getName());
198:
199: builder.end();
200:
201: cf.addMethod(Modifier.PUBLIC, sig, builder.toString());
202: }
203:
204: }
|