001 // Copyright 2006, 2007, 2008 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.tapestry5.services;
016
017 import javassist.CtBehavior;
018 import org.apache.tapestry5.ioc.AnnotationProvider;
019 import org.slf4j.Logger;
020
021 import java.lang.annotation.Annotation;
022 import java.util.List;
023
024 /**
025 * Contains class-specific information used when transforming a raw component class into an executable component class.
026 * An executable class is one that has been transformed to work within Tapestry. This includes adding interfaces
027 * ({@link org.apache.tapestry5.runtime.Component}) but also transforming access to fields, based on annotations and
028 * naming conventions. Most of the changes are provided by different implementations of {@link
029 * ComponentClassTransformWorker}.
030 * <p/>
031 * Much of this information is somewhat like ordinary reflection, but applies to a class that has not yet been loaded.
032 * <p/>
033 * Transformation is primarily about identifying annotations on fields and on methods and changing the class, adding new
034 * interfaces, fields and methods, and deleting some existing fields.
035 * <p/>
036 * A ClassTransformation contains all the state data specific to a particular class being transformed. A number of
037 * <em>workers</em> will operate upon the ClassTransformation to effect the desired changes before the true class is
038 * loaded into memory.
039 * <p/>
040 * Instances of this class are not designed to be thread safe, access to an instance should be restricted to a single
041 * thread. In fact, the design of this type is to allow stateless singletons in multiple threads to work on
042 * thread-specific data (within the ClassTransformation).
043 * <p/>
044 * The majority of methods concern the <em>declared</em> members (field and methods) of a specific class, rather than
045 * any fields or methods inherited from a base class.
046 *
047 * @see org.apache.tapestry5.services.TapestryModule#contributeComponentClassTransformWorker(org.apache.tapestry5.ioc.OrderedConfiguration,
048 * org.apache.tapestry5.ioc.ObjectLocator, InjectionProvider, ComponentClassResolver)
049 */
050 public interface ClassTransformation extends AnnotationProvider
051 {
052 /**
053 * Returns the fully qualified class name of the class being transformed.
054 */
055 String getClassName();
056
057 /**
058 * Returns the name of a new member (field or method). Ensures that the resulting name does not conflict with any
059 * existing member (declared by the underlying class, or inherited from a base class).
060 *
061 * @param suggested the suggested value for the member
062 * @return a unique name for the member
063 */
064 String newMemberName(String suggested);
065
066 /**
067 * As with {@link #newMemberName(String)}, but the suggested name is constructed from the prefix and base name. An
068 * underscore will seperate the prefix from the base name.
069 *
070 * @param prefix for the generated name
071 * @param baseName an name, often of an existing field or method
072 * @return a unique name
073 */
074 String newMemberName(String prefix, String baseName);
075
076 /**
077 * Generates a list of the names of declared instance fields that have the indicated annotation. Non-private and
078 * static fields are ignored. Only the names of private instance fields are returned.
079 */
080 List<String> findFieldsWithAnnotation(Class<? extends Annotation> annotationClass);
081
082 /**
083 * Finds all methods defined in the class that are marked with the provided annotation.
084 *
085 * @param annotationClass
086 * @return a list of method signature (which may be empty) in ascending order
087 * @see #findMethods(MethodFilter)
088 */
089 List<TransformMethodSignature> findMethodsWithAnnotation(Class<? extends Annotation> annotationClass);
090
091 /**
092 * Finds all methods matched by the provided filter.
093 *
094 * @param filter Passed each method signature, it may include or exclude each potential
095 * @return a list of matching method signatures (which may be empty) in ascending order (by method name), but
096 * descending order (by parameter count) within overrides of a single method name.
097 */
098 List<TransformMethodSignature> findMethods(MethodFilter filter);
099
100 /**
101 * Finds all unclaimed fields matched by the provided filter. Only considers private instance fields.
102 *
103 * @param filter passed each field name and field type
104 * @return the names of all matched fields, in ascending order
105 */
106 List<String> findFields(FieldFilter filter);
107
108 /**
109 * Finds an annotation on a declared instance field.
110 *
111 * @param <T> constrains parameter and return value to Annotation types
112 * @param fieldName the name of the field, which must exist
113 * @param annotationClass the type of annotation to access
114 * @return the annotation if present, or null otherwise
115 * @throws IllegalArgumentException if the fieldName does not correspond to a declared field
116 */
117 <T extends Annotation> T getFieldAnnotation(String fieldName, Class<T> annotationClass);
118
119 /**
120 * Finds an annotation on a declared method.
121 *
122 * @param <T> constrains parameter and return value to Annotation types
123 * @param method the method signature to search
124 * @param annotationClass the type of annotation to access
125 * @return the annotation if present, or null otherwise
126 * @throws IllegalArgumentException if the method signature does not correspond to a declared method
127 */
128 <T extends Annotation> T getMethodAnnotation(TransformMethodSignature method, Class<T> annotationClass);
129
130 /**
131 * Claims a field so as to ensure that only a single annotation is applied to any single field. When a
132 * transformation occurs (driven by a field annotation), the field is claimed (using the annotation object as the
133 * tag). If a field has multiple conflicting annotations, this will be discovered when the code attempts to claim
134 * the field a second time.
135 *
136 * @param fieldName the name of the field that is being claimed
137 * @param tag a non-null object that represents why the field is being tagged (this is typically a specific
138 * annotation on the field)
139 * @throws IllegalArgumentException if the fieldName does not correspond to a declared instance field
140 * @throws IllegalStateException if the field is already claimed for some other tag
141 */
142 void claimField(String fieldName, Object tag);
143
144 /**
145 * Changes the field to be read only. Any existing code that changes the field will cause a runtime exception.
146 *
147 * @param fieldName name of field to so change
148 */
149 void makeReadOnly(String fieldName);
150
151 /**
152 * Finds any declared <em>instance</em> fields that have not been claimed (via {@link #claimField(String, Object)})
153 * and returns the names of those fields. May return an empty array.
154 */
155 List<String> findUnclaimedFields();
156
157 /**
158 * Obtains the type of a declared instance field.
159 *
160 * @param fieldName
161 * @return the type of the field, as a string
162 * @throws IllegalArgumentException if the fieldName does not correspond to a declared instance field
163 */
164 String getFieldType(String fieldName);
165
166 /**
167 * Returns true if the indicated name is a private instance field.
168 *
169 * @param fieldName
170 * @return true if field exists
171 */
172 boolean isField(String fieldName);
173
174 /**
175 * Defines a new declared field for the class. The suggestedName may be modified to ensure uniqueness.
176 *
177 * @param modifiers modifiers for the field (typically, {@link java.lang.reflect.Modifier#PRIVATE})
178 * @param type the type for the field, as a string
179 * @param suggestedName the desired name for the field, which may be modified (for uniqueness) when returned
180 * @return the (uniqued) name for the field
181 */
182 String addField(int modifiers, String type, String suggestedName);
183
184 /**
185 * Defines a new <strong>protected</strong> instance variable whose initial value is provided statically, via a
186 * constructor parameter. The transformation caches the result, so calling this method repeatedly with the same type
187 * and value will return the same field name. Caching extends to the parent transformation, so that a value injected
188 * into a parent class will be available (via the protected instance variable) to subclasses.
189 *
190 * @param type the type of object to inject
191 * @param suggestedName the suggested name for the new field
192 * @param value to be injected. This value is retained.
193 * @return the actual name of the injected field
194 */
195 String addInjectedField(Class type, String suggestedName, Object value);
196
197 /**
198 * Converts the field into a read only field whose value is the provided value. This is used when converting an
199 * existing field into a read-only injected value.
200 *
201 * @param fieldName name of field to convert
202 * @param value the value provided by the field
203 */
204 void injectField(String fieldName, Object value);
205
206 /**
207 * Transforms the class to implement the indicated interface. If the class (or its super class) does not already
208 * implement the interface, then the interface is added, and default implementations of any methods of the interface
209 * are added.
210 * <p/>
211 * TODO: Checking that the names of methods in the interface do not conflict with the names of methods present in
212 * the (unmodified) class.
213 *
214 * @param interfaceClass the interface to be implemented by the class
215 * @throws IllegalArgumentException if the interfaceClass argument does not represent an interface
216 */
217 void addImplementedInterface(Class interfaceClass);
218
219 /**
220 * Extends an existing method. The provided method body is inserted at the end of the existing method (i.e. {@link
221 * javassist.CtBehavior#insertAfter(java.lang.String)}). To access or change the return value, use the
222 * <code>$_</code> pseudo variable.
223 * <p/>
224 * The method may be declared in the class, or may be inherited from a super-class. For inherited methods, a method
225 * is added that first invokes the super implementation. Use {@link #addMethod(TransformMethodSignature, String)}
226 * when it is necessary to control when the super-class method is invoked.
227 * <p/>
228 * The extended method is considered <em>new</em>. New methods <em>are not</em> scanned for {@linkplain
229 * #removeField(String)} removed}, {@linkplain #replaceReadAccess(String, String)} read replaced}, or {@linkplain
230 * #replaceWriteAccess(String, String) write replaced} fields. Generally that's what you want!
231 *
232 * @param methodSignature the signature of the method to extend
233 * @param methodBody the body of code
234 * @throws org.apache.tapestry5.internal.services.MethodCompileException
235 * if the provided Javassist method body can not be compiled
236 * @see #extendExistingMethod(TransformMethodSignature, String)
237 */
238 void extendMethod(TransformMethodSignature methodSignature, String methodBody);
239
240 /**
241 * Like {@link #extendMethod(TransformMethodSignature, String)}, but the extension does not mark the method as new,
242 * and field changes <em>will</em> be processed.
243 *
244 * @param methodSignature signature of the method to extend
245 * @param methodBody the body of code
246 * @throws org.apache.tapestry5.internal.services.MethodCompileException
247 * if the provided method body can not be compiled
248 * @see #prefixMethod(TransformMethodSignature, String)
249 */
250 void extendExistingMethod(TransformMethodSignature methodSignature, String methodBody);
251
252 /**
253 * Inserts code at the beginning of a method body (i.e. {@link CtBehavior#insertBefore(String)}.
254 * <p/>
255 * The method may be declared in the class, or may be inherited from a super-class. For inherited methods, a method
256 * is added that first invokes the super implementation. Use {@link #addMethod(TransformMethodSignature, String)}
257 * when it is necessary to control when the super-class method is invoked.
258 * <p/>
259 * <p/>
260 * Like {@link #extendExistingMethod(TransformMethodSignature, String)}, this method is generally used to "wrap" an
261 * existing method adding additional functionality such as caching or transaction support.
262 *
263 * @param methodSignature
264 * @param methodBody
265 * @throws org.apache.tapestry5.internal.services.MethodCompileException
266 * if the provided method body can not be compiled
267 */
268 void prefixMethod(TransformMethodSignature methodSignature, String methodBody);
269
270 /**
271 * Returns the name of a field that provides the {@link org.apache.tapestry5.ComponentResources} for the transformed
272 * component. This will be a protected field, accessible to the class and subclasses.
273 *
274 * @return name of field
275 */
276 String getResourcesFieldName();
277
278 /**
279 * Adds a new method to the transformed class. Replaces any existing method declared for the class. When overriding
280 * a super-class method, you should use {@link #extendMethod(TransformMethodSignature, String)}, or you should
281 * remember to invoke the super class implemetation explicitly. Use this method to control when the super-class
282 * implementation is invoked.
283 */
284 void addMethod(TransformMethodSignature signature, String methodBody);
285
286 /**
287 * As with {@link #addMethod(TransformMethodSignature, String)}, but field references inside the method
288 * <em>will</em> be transformed, and the method <em>must not already exist</em>.
289 */
290 void addTransformedMethod(TransformMethodSignature methodSignature, String methodBody);
291
292 /**
293 * Adds a statement to the constructor. The statement is added as is, though a newline is added.
294 *
295 * @param statement the statement to add, which should end with a semicolon
296 */
297 void extendConstructor(String statement);
298
299 /**
300 * Replaces all read-references to the specified field with invocations of the specified method name. Replacements
301 * do not occur in methods added via {@link #addMethod(TransformMethodSignature, String)} or {@link
302 * #extendMethod(TransformMethodSignature, String)}.
303 */
304 void replaceReadAccess(String fieldName, String methodName);
305
306 /**
307 * Replaces all write accesses to the specified field with invocations of the specified method name. The method
308 * should take a single parameter of the same type as the field. Replacements do not occur in methods added via
309 * {@link #addMethod(TransformMethodSignature, String)} or {@link #extendMethod(TransformMethodSignature, String)}.
310 */
311 void replaceWriteAccess(String fieldName, String methodName);
312
313 /**
314 * Removes a field entirely; this is useful for fields that are replaced entirely by computed values.
315 *
316 * @param fieldName the name of the field to remove
317 * @see #replaceReadAccess(String, String)
318 * @see #replaceWriteAccess(String, String)
319 */
320 void removeField(String fieldName);
321
322 /**
323 * Converts a type name into a corresponding class (possibly, a transformed class). Primitive type names are
324 * returned as wrapper types.
325 */
326
327 Class toClass(String type);
328
329 /**
330 * Returns a logger, based on the class name being transformed, to which warnings or errors concerning the class
331 * being transformed may be logged.
332 */
333 Logger getLogger();
334
335 /**
336 * Returns the modifiers for the named field.
337 */
338 int getFieldModifiers(String fieldName);
339
340 /**
341 * Converts a signature to a string used to identify the method; this consists of the {@link
342 * TransformMethodSignature#getMediumDescription()} appended with source file information and line number
343 * information (when available).
344 *
345 * @param signature
346 * @return a string that identifies the class, method name, types of parameters, source file and source line number
347 */
348 String getMethodIdentifier(TransformMethodSignature signature);
349
350 /**
351 * Returns true if this transformation represents a root class (one that extends directly from Object), or false if
352 * this transformation is an extension of another transformed class.
353 *
354 * @return true if root class, false if sub-class
355 */
356 boolean isRootTransformation();
357
358
359 /**
360 * Adds a catch block to the method. The body should end with a return or a throw. The special Javassist variable
361 * $e is the exception instance.
362 *
363 * @param methodSignature method to be extended.
364 * @param exceptionType fully qualified class name of exception
365 * @param body code to execute
366 */
367 void addCatch(TransformMethodSignature methodSignature, String exceptionType, String body);
368
369 /**
370 * Adds method advice for the indicated method.
371 */
372 void advise(TransformMethodSignature methodSignature, ComponentMethodAdvice advice);
373
374 /**
375 * Returns true if the method is an override of a method from the parent class.
376 *
377 * @param methodSignature signature of method to check
378 * @return true if the parent class contains a method with the name signature
379 */
380 boolean isMethodOverride(TransformMethodSignature methodSignature);
381 }