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    }