001: // Copyright 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.internal.services;
016:
017: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newConcurrentMap;
018: import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
019: import static org.apache.tapestry.ioc.internal.util.Defense.notNull;
020:
021: import java.lang.annotation.Annotation;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Modifier;
024: import java.util.Map;
025:
026: import org.apache.tapestry.PropertyConduit;
027: import org.apache.tapestry.internal.events.InvalidationListener;
028: import org.apache.tapestry.internal.util.MultiKey;
029: import org.apache.tapestry.ioc.AnnotationProvider;
030: import org.apache.tapestry.ioc.services.ClassFab;
031: import org.apache.tapestry.ioc.services.ClassFabUtils;
032: import org.apache.tapestry.ioc.services.ClassFactory;
033: import org.apache.tapestry.ioc.services.MethodSignature;
034: import org.apache.tapestry.ioc.services.PropertyAccess;
035: import org.apache.tapestry.ioc.services.PropertyAdapter;
036: import org.apache.tapestry.ioc.util.BodyBuilder;
037: import org.apache.tapestry.services.PropertyConduitSource;
038:
039: public class PropertyConduitSourceImpl implements
040: PropertyConduitSource, InvalidationListener {
041: private static final String PARENS = "()";
042:
043: private final PropertyAccess _access;
044:
045: private final ClassFactory _classFactory;
046:
047: private final Map<Class, Class> _classToEffectiveClass = newConcurrentMap();
048:
049: /** Keyed on combination of root class and expression. */
050: private final Map<MultiKey, PropertyConduit> _cache = newConcurrentMap();
051:
052: private static final MethodSignature GET_SIGNATURE = new MethodSignature(
053: Object.class, "get", new Class[] { Object.class }, null);
054:
055: private static final MethodSignature SET_SIGNATURE = new MethodSignature(
056: void.class, "set",
057: new Class[] { Object.class, Object.class }, null);
058:
059: public PropertyConduitSourceImpl(final PropertyAccess access,
060: final ClassFactory classFactory) {
061: _access = access;
062: _classFactory = classFactory;
063: }
064:
065: public PropertyConduit create(Class rootClass, String expression) {
066: notNull(rootClass, "rootClass");
067: notBlank(expression, "expression");
068:
069: Class effectiveClass = toEffectiveClass(rootClass);
070:
071: MultiKey key = new MultiKey(effectiveClass, expression);
072:
073: PropertyConduit result = _cache.get(key);
074:
075: if (result == null) {
076: result = build(effectiveClass, expression);
077: _cache.put(key, result);
078:
079: }
080:
081: return result;
082: }
083:
084: private Class toEffectiveClass(Class rootClass) {
085: Class result = _classToEffectiveClass.get(rootClass);
086:
087: if (result == null) {
088: result = _classFactory.importClass(rootClass);
089:
090: _classToEffectiveClass.put(rootClass, result);
091: }
092:
093: return result;
094: }
095:
096: /**
097: * Clears its cache when the component class loader is invalidated; this is because it will be
098: * common to generated conduits rooted in a component class (which will no longer be valid and
099: * must be released to the garbage collector).
100: */
101: public void objectWasInvalidated() {
102: _cache.clear();
103: _classToEffectiveClass.clear();
104: }
105:
106: /**
107: * Builds a subclass of {@link BasePropertyConduit} that implements the get() and set() methods
108: * and overrides the constructor. In a worst-case race condition, we may build two (or more)
109: * conduits for the same rootClass/expression, and it will get sorted out when the conduit is
110: * stored into the cache.
111: *
112: * @param rootClass
113: * @param expression
114: * @return the conduit
115: */
116: private PropertyConduit build(Class rootClass, String expression) {
117: String name = ClassFabUtils
118: .generateClassName("PropertyConduit");
119:
120: ClassFab classFab = _classFactory.newClass(name,
121: BasePropertyConduit.class);
122:
123: classFab.addConstructor(new Class[] { Class.class,
124: AnnotationProvider.class, String.class }, null,
125: "super($$);");
126:
127: String[] terms = expression.split("\\.");
128:
129: final Method readMethod = buildGetter(rootClass, classFab,
130: expression, terms);
131: final Method writeMethod = buildSetter(rootClass, classFab,
132: expression, terms);
133:
134: // A conduit is either readable or writeable, otherwise there will already have been
135: // an error about unknown method name or property name.
136:
137: Class propertyType = readMethod != null ? readMethod
138: .getReturnType() : writeMethod.getParameterTypes()[0];
139:
140: String description = String.format("PropertyConduit[%s %s]",
141: rootClass.getName(), expression);
142:
143: Class conduitClass = classFab.createClass();
144:
145: AnnotationProvider provider = new AnnotationProvider() {
146:
147: public <T extends Annotation> T getAnnotation(
148: Class<T> annotationClass) {
149: T result = readMethod == null ? null : readMethod
150: .getAnnotation(annotationClass);
151:
152: if (result == null && writeMethod != null)
153: result = writeMethod.getAnnotation(annotationClass);
154:
155: return result;
156: }
157:
158: };
159:
160: try {
161: return (PropertyConduit) conduitClass.getConstructors()[0]
162: .newInstance(propertyType, provider, description);
163: } catch (Exception ex) {
164: throw new RuntimeException(ex);
165: }
166:
167: }
168:
169: private Method buildGetter(Class rootClass, ClassFab classFab,
170: String expression, String[] terms) {
171: BodyBuilder builder = new BodyBuilder();
172: builder.begin();
173:
174: builder.addln("%s root = (%<s) $1;", ClassFabUtils
175: .toJavaClassName(rootClass));
176: String previousStep = "root";
177:
178: Class activeType = rootClass;
179: Method result = null;
180: boolean writeOnly = false;
181:
182: for (int i = 0; i < terms.length; i++) {
183: String this Step = "step" + (i + 1);
184: String term = terms[i];
185:
186: boolean nullable = term.endsWith("?");
187: if (nullable)
188: term = term.substring(0, term.length() - 1);
189:
190: Method readMethod = readMethodForTerm(activeType,
191: expression, term, (i < terms.length - 1));
192:
193: if (readMethod == null) {
194: writeOnly = true;
195: break;
196: }
197:
198: // If a primitive type, convert to wrapper type
199:
200: Class termType = ClassFabUtils.getWrapperType(readMethod
201: .getReturnType());
202:
203: // $w is harmless for non-wrapper types.
204:
205: builder.addln("%s %s = ($w) %s.%s();", ClassFabUtils
206: .toJavaClassName(termType), this Step, previousStep,
207: readMethod.getName());
208:
209: if (nullable)
210: builder.addln("if (%s == null) return null;", this Step);
211:
212: activeType = termType;
213: result = readMethod;
214: previousStep = this Step;
215: }
216:
217: builder.addln("return %s;", previousStep);
218:
219: builder.end();
220:
221: if (writeOnly) {
222: builder.clear();
223: builder
224: .addln(
225: "throw new java.lang.RuntimeException(\"Expression %s for class %s is write-only.\");",
226: expression, rootClass.getName());
227: }
228:
229: classFab.addMethod(Modifier.PUBLIC, GET_SIGNATURE, builder
230: .toString());
231:
232: return result;
233: }
234:
235: private Method buildSetter(Class rootClass, ClassFab classFab,
236: String expression, String[] terms) {
237: BodyBuilder builder = new BodyBuilder();
238: builder.begin();
239:
240: builder.addln("%s root = (%<s) $1;", ClassFabUtils
241: .toJavaClassName(rootClass));
242: String previousStep = "root";
243:
244: Class activeType = rootClass;
245:
246: for (int i = 0; i < terms.length - 1; i++) {
247: String this Step = "step" + (i + 1);
248: String term = terms[i];
249:
250: boolean nullable = term.endsWith("?");
251: if (nullable)
252: term = term.substring(0, term.length() - 1);
253:
254: Method readMethod = readMethodForTerm(activeType,
255: expression, term, true);
256:
257: // If a primitive type, convert to wrapper type
258:
259: Class termType = ClassFabUtils.getWrapperType(readMethod
260: .getReturnType());
261:
262: // $w is harmless for non-wrapper types.
263:
264: builder.addln("%s %s = ($w) %s.%s();", ClassFabUtils
265: .toJavaClassName(termType), this Step, previousStep,
266: readMethod.getName());
267:
268: if (nullable)
269: builder.addln("if (%s == null) return;", this Step);
270:
271: activeType = termType;
272: previousStep = this Step;
273: }
274:
275: // When writing, the last step is different.
276:
277: Method writeMethod = writeMethodForTerm(activeType, expression,
278: terms[terms.length - 1]);
279:
280: if (writeMethod == null) {
281: builder.clear();
282: builder
283: .addln(
284: "throw new java.lang.RuntimeException(\"Expression %s for class %s is read-only.\");",
285: expression, rootClass.getName());
286: classFab.addMethod(Modifier.PUBLIC, SET_SIGNATURE, builder
287: .toString());
288:
289: return null;
290: }
291:
292: Class parameterType = writeMethod.getParameterTypes()[0];
293:
294: Class wrapperType = ClassFabUtils.getWrapperType(parameterType);
295:
296: builder.addln("%s value = (%<s) $2;", ClassFabUtils
297: .toJavaClassName(wrapperType));
298:
299: builder.add("%s.%s(value", previousStep, writeMethod.getName());
300:
301: if (parameterType != wrapperType)
302: builder.add(".%s()", ClassFabUtils
303: .getUnwrapMethodName(parameterType.getName()));
304:
305: builder.addln(");");
306:
307: builder.end();
308:
309: classFab.addMethod(Modifier.PUBLIC, SET_SIGNATURE, builder
310: .toString());
311:
312: return writeMethod;
313: }
314:
315: private Method writeMethodForTerm(Class activeType,
316: String expression, String term) {
317: if (term.endsWith(PARENS))
318: return null;
319:
320: PropertyAdapter adapter = _access.getAdapter(activeType)
321: .getPropertyAdapter(term);
322:
323: if (adapter == null)
324: throw new RuntimeException(ServicesMessages.noSuchProperty(
325: activeType, term, expression));
326:
327: return adapter.getWriteMethod();
328: }
329:
330: private Method readMethodForTerm(Class activeType,
331: String expression, String term, boolean mustExist) {
332: if (term.endsWith(PARENS)) {
333: Method method = null;
334: String methodName = term.substring(0, term.length()
335: - PARENS.length());
336:
337: try {
338: method = activeType.getMethod(methodName);
339: } catch (NoSuchMethodException ex) {
340: throw new RuntimeException(ServicesMessages
341: .methodNotFound(term, activeType, expression),
342: ex);
343: }
344:
345: if (method.getReturnType().equals(void.class))
346: throw new RuntimeException(ServicesMessages
347: .methodIsVoid(term, activeType, expression));
348:
349: return method;
350: }
351:
352: PropertyAdapter adapter = _access.getAdapter(activeType)
353: .getPropertyAdapter(term);
354:
355: if (adapter == null)
356: throw new RuntimeException(ServicesMessages.noSuchProperty(
357: activeType, term, expression));
358:
359: Method m = adapter.getReadMethod();
360:
361: if (m == null && mustExist)
362: throw new RuntimeException(ServicesMessages
363: .writeOnlyProperty(term, activeType, expression));
364:
365: return m;
366: }
367: }
|