001    /*
002      Copyright (C) 2001-2003 Renaud Pawlak <renaud@aopsys.com>, 
003                              Laurent Martelli <laurent@aopsys.com>
004      
005      This program is free software; you can redistribute it and/or modify
006      it under the terms of the GNU Lesser General Public License as
007      published by the Free Software Foundation; either version 2 of the
008      License, or (at your option) any later version.
009    
010      This program is distributed in the hope that it will be useful,
011      but WITHOUT ANY WARRANTY; without even the implied warranty of
012      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013      GNU Lesser General Public License for more details.
014    
015      You should have received a copy of the GNU Lesser General Public License
016      along with this program; if not, write to the Free Software
017      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
018    
019    package org.objectweb.jac.core.rtti;
020    
021    import java.lang.NoSuchMethodException;
022    import java.lang.reflect.InvocationTargetException;
023    import java.lang.reflect.Method;
024    import java.util.Arrays;
025    import java.util.Vector;
026    import org.apache.log4j.Logger;
027    import org.objectweb.jac.core.Wrappee;
028    import org.objectweb.jac.util.Strings;
029    import org.objectweb.jac.util.WrappedThrowableException;
030    
031    /**
032     * This class defines a meta item that corresponds to the
033     * <code>java.lang.reflect.Method</code> meta element.
034     *
035     * <p>In addition to the <code>java.lang.reflect</code> classical
036     * features, this RTTI method element is able to tell if a method is a
037     * setter, a getter, or more generally speaking, a state modifier for
038     * a given field of the object it belongs to. And, if this field is a
039     * collection (an array, a list or a map), then this meta element is
040     * able to tell if the method it represents adds or removes elements
041     * to or from this collection.
042     *
043     * <p>It also provides to introspection features for references on Jac
044     * objects.
045     *
046     * <p>For the moment, default meta informations are setted by the
047     * <code>ClassRepository</code> class using some naming
048     * conventions. In a close future, these informations will be deduced
049     * from the class bytecodes analysis at load-time.
050     *
051     * @see java.lang.reflect.Method
052     * @see #getWrittenFields()
053     * @see #getAccessedFields()
054     * @see #getAddedCollections()
055     * @see #getRemovedCollections()
056     * @see #isModifier()
057     * @see #hasAccessedReferences()
058     *
059     * @author Renaud Pawlak
060     * @author Laurent Martelli
061     */
062    
063    public class MethodItem extends AbstractMethodItem {
064        static Logger logger = Logger.getLogger("rtti.method");
065    
066        /**
067         * Transforms a method items array into a methods array containing
068         * the <code>java.lang.reflect</code> methods wrapped by the method
069         * items.<p>
070         *
071         * @param methodItems the method items
072         * @return the actual methods in <code>java.lang.reflect</code>
073         */
074    
075        public static Method[] toMethods( MethodItem[] methodItems ) {
076            Method[] res = new Method[methodItems.length];
077            for ( int i = 0; i < methodItems.length; i++ ) {
078                if ( methodItems[i] == null ) {
079                    res[i] = null;
080                } else {
081                    res[i] = methodItems[i].getActualMethod();
082                }
083            }
084            return res;
085        }
086    
087        /**
088         * Default contructor to create a new method item object.<p>
089         *
090         * @param delegate the <code>java.lang.reflect.Method</code> actual
091         * meta item */
092    
093        public MethodItem(Method delegate, ClassItem parent) 
094            throws InvalidDelegateException 
095        {
096            super(delegate,parent);
097            Class cl = delegate.getDeclaringClass();
098            if (Wrappee.class.isAssignableFrom(cl)) {
099                String orgName = "_org_"+delegate.getName()+
100                    (isStatic?"":"_"+Strings.getShortClassName(cl));
101                try {
102                    orgMethod = 
103                        cl.getDeclaredMethod(orgName,delegate.getParameterTypes());
104                    orgMethod.setAccessible(true);
105                } catch(NoSuchMethodException e) {
106                    //Log.warning("No _org_ method "+orgName+" found for "+delegate);
107                }
108            }
109        }
110    
111        Method orgMethod;
112    
113        public final Method getOrgMethod() {
114            return orgMethod;
115        }
116    
117        /**
118         * Tells if this method accesses any references of the class it
119         * belongs to.
120         *
121         * @return true if one or more references are accessed */
122       
123        public final boolean hasAccessedReferences() {
124            ((ClassItem)parent).buildFieldInfo();
125            return numAccessedReferences>0;
126        }
127    
128        FieldItem returnedField;
129        /**
130         * Returns the field item corresponding to the field value returned
131         * by this method, if any.
132         * @see #setReturnedField(FieldItem)
133         */
134        public final FieldItem getReturnedField() {
135            return returnedField;
136        }
137        /**
138         * Sets the field returned by the method.
139         * @see #getReturnedField()
140         */
141        public void setReturnedField(FieldItem returnedField) {
142            if (this.returnedField!=null &&
143                !(returnedField.isCalculated() && this.returnedField.isCalculated())) {
144                logger.warn("overriding returned field "+this.returnedField.getName()+
145                            " for "+getParent().getName()+"."+getName()+
146                            " with "+returnedField.getName());
147            }
148            this.returnedField = returnedField;
149        }
150    
151        FieldItem setField;
152        /**
153         * Returns <em>the</em> field set by this method, if any. In other
154         * words, the field for which the method is <em>the</em> setter. A
155         * method is <em>the</em>setter of a field if it assigns this field
156         * directly with one of its argument.
157         * @see #setReturnedField(FieldItem) */
158        public final FieldItem getSetField() {
159            return setField;
160        }
161        /**
162         * Sets <em>the</em> field set by the method. This method should
163         * not be called more than once for a gievn field.
164         * @see #getSetField() */
165        public void setSetField(FieldItem setField) {
166            if (this.setField!=null && this.setField!=setField) {
167                logger.warn("overriding set field "+this.setField.getName()+
168                            " for "+getParent().getName()+"."+getName()+
169                            " with "+setField.getName());
170            }
171            this.setField = setField;
172        }
173    
174        /** Store the collections that are added by this method.<p> */
175        CollectionItem[] addedCollections = null;
176       
177    
178        /**
179         * Get the value of the collections that are added by this
180         * method.<p>
181         *
182         * @return value of addedCollections.  */
183    
184        public final CollectionItem[] getAddedCollections() {
185            ((ClassItem)parent).buildFieldInfo();
186            return addedCollections;
187        }
188    
189        /**
190         * Returns true if the method has at least one added collection
191         */
192        public final boolean hasAddedCollections() {
193            ((ClassItem)parent).buildFieldInfo();
194            return addedCollections!=null && addedCollections.length>0;
195        }
196    
197        /**
198         * Sets the value of the collections that are added by this method.<p>
199         *
200         * @param addedCollections value to assign to addedCollections
201         * @see #addAddedCollection(CollectionItem)
202         */
203    
204        public final void setAddedCollections(CollectionItem[] addedCollections) {
205            this.addedCollections = addedCollections;
206        }
207    
208        /**
209         * Adds a new added collection for this method.<p>
210         *
211         * @param addedCollection the collection to add
212         * @see #removeAddedCollection(CollectionItem)
213         */
214    
215        public final void addAddedCollection(CollectionItem addedCollection) {
216            if (addedCollections == null) {
217                addedCollections = new CollectionItem[] { addedCollection };
218            } else {
219                CollectionItem[] tmp = new CollectionItem[addedCollections.length + 1];
220                System.arraycopy(addedCollections, 0, tmp, 0, addedCollections.length);
221                tmp[addedCollections.length] = addedCollection;
222                addedCollections = tmp;
223            }
224        }
225    
226        /**
227         * Removes an existing added collection for this method.<p>
228         *
229         * @param collection the collection to add
230         * @see #addAddedCollection(CollectionItem) 
231         */
232        public final void removeAddedCollection(CollectionItem collection) {
233            if (addedCollections != null) {
234                Vector v = new Vector(Arrays.asList(addedCollections));
235                v.remove(collection);
236                addedCollections = new CollectionItem[v.size()];
237                System.arraycopy(v.toArray(),0,addedCollections,0,v.size());
238            }
239        }
240    
241        /**
242         * Gets the method represented by this method item.<p>
243         *
244         * @return the actual method
245         */
246    
247        public final Method getActualMethod() {
248            return (Method)delegate;
249        }
250    
251        public final String getName() {
252            return ((Method)delegate).getName();
253        }
254    
255        public final Class getType() {
256            return ((Method)delegate).getReturnType();
257        }
258    
259        public Class[] getParameterTypes() {
260            return ((Method)delegate).getParameterTypes();
261        }
262    
263        int collectionIndexArgument = -1;
264        /**
265         * Returns the number of the argument which is the index of a
266         * modified collection if any, -1 otherwise.
267         */
268        public int getCollectionIndexArgument() {
269            return collectionIndexArgument;
270        }
271        /**
272         * Sets collectionIndexArgument
273         * @see #getCollectionIndexArgument()
274         */
275        public void setCollectionIndexArgument(int collectionIndexArgument) {
276            this.collectionIndexArgument = collectionIndexArgument;
277        }
278    
279        int collectionItemArgument = -1;
280        /**
281         * Returns the number of the argument which is the item that will be added
282         * to a collection if any, -1 otherwise.
283         */
284        public int getCollectionItemArgument() {
285            return collectionItemArgument;
286        }
287        /**
288         * Sets collectionItemArgument
289         * @see #getCollectionItemArgument()
290         */
291        public void setCollectionItemArgument(int collectionItemArgument) {
292            this.collectionItemArgument = collectionItemArgument;
293        }
294       
295        public void addAccessedField(FieldItem accessedField) {
296            super.addAccessedField(accessedField);
297            if (getType()!=void.class)
298                accessedField.addDependentMethod(this);
299        }
300    
301        /**
302         * Invokes this method on the given object and with the given
303         * parameters values.
304         *
305         * @param object a class this method belongs to intance
306         * @param parameters the values of the parameters to invoke this
307         * method with 
308         */
309        public Object invoke(Object object, Object[] parameters) 
310        {
311            logger.debug(toString()+" invoke("+object+","+Arrays.asList(parameters)+")");
312            if (!isStatic() && object==null)
313                throw new NullPointerException(
314                    "Invoking instance method "+
315                    parent.getName()+"."+this+" on null");
316            try {
317                return ((Method)delegate).invoke(object,parameters);
318            } catch (IllegalArgumentException e) {
319                Class cl = (Class)getParent().getDelegate();
320                if (!cl.isAssignableFrom(object.getClass())) {
321                    throw new IllegalArgumentException(
322                        getLongName()+": called object "+Strings.hex(object)+
323                        " is not an instance of "+cl.getName());
324                }
325                if (parameters.length == getParameterCount()) {
326                    checkParameters(parameters);
327                }
328                throw e;
329            } catch (Exception e) {
330                logger.info("caught exception "+e);
331                throw new WrappedThrowableException(e);
332            }
333        }
334    
335        /**
336         * Checks the type of parameters
337         *
338         * @param parameters parameters to check
339         */    
340        void checkParameters(Object[] parameters) {
341            Class[] types = getParameterTypes();
342            for (int i=0; i<types.length; i++) {
343                if (!types[i].isAssignableFrom(parameters[i].getClass()))
344                    throw new IllegalArgumentException(
345                        getLongName()+", argument n°"+(i+1)+":"+
346                        parameters[i].getClass().getName()+
347                        " cannot be converted to "+types[i].getName());
348            }
349        }
350    
351        /**
352         * Invokes this static method with the given parameters values.
353         *
354         * @param parameters the values of the parameters to invoke this
355         * method with */
356    
357        public final Object invokeStatic(Object[] parameters) 
358        {
359            if (!isStatic())
360                throw new RuntimeException("Cannot invokeStatic a non static method: "+getLongName());
361            //      logger.debug(toString()+" invoke("+object+","+Arrays.asList(parameters)+")");
362            try {
363                return ((Method)delegate).invoke(null,parameters);
364            } catch (IllegalArgumentException e) {
365                if (parameters.length == getParameterCount()) {
366                    checkParameters(parameters);
367                }
368                throw e;
369            } catch (Exception e) {
370                logger.info("Caught exception in "+getFullName(),e);
371                throw new WrappedThrowableException(e);
372            }
373        }
374    
375        /**
376         * Invokes a method with uninitialized parameters.
377         *
378         * <p>The parameters array is initialized before the invocation
379         * with default values.
380         *
381         * @param object the target object
382         * @param parameters the maybe initialized values of the parameters
383         * to invoke this method with (primitive parameters can be null) */
384    
385        public final Object invokeWithInit(Object object,
386                                           Object[] parameters)
387            throws IllegalAccessException, InvocationTargetException
388        {
389          
390            Class[] paramTypes = getParameterTypes();
391            for (int i=0; i< parameters.length; i++) {
392                if (parameters[i]==null) {
393                    if (paramTypes[i] == float.class) {
394                        parameters[i] = new Float(0.0);
395                    } else if (paramTypes[i] == long.class) {
396                        parameters[i] = new Long(0);
397                    } else if (paramTypes[i] == double.class) {
398                        parameters[i] = new Double(0.0);
399                    } else if (paramTypes[i] == byte.class) {
400                        parameters[i] = new Byte((byte)0);
401                    } else if (paramTypes[i] == char.class) {
402                        parameters[i] = new Character(' ');
403                    } else if (paramTypes[i] == short.class) {
404                        parameters[i] = new Short((short)0);
405                    } else if (paramTypes[i] == int.class) {
406                        parameters[i] = new Integer(0);
407                    } else if (paramTypes[i] == boolean.class) {
408                        parameters[i] = Boolean.FALSE;
409                    }
410                }
411            }
412            return ((Method)delegate).invoke(object,parameters);
413        } 
414    
415        /** Tells wether the method returns the value of a field */
416        public final boolean isGetter() {
417            ((ClassItem)parent).buildFieldInfo();
418            return returnedField!=null;
419        }
420    
421        public final boolean isCollectionGetter() {
422            ((ClassItem)parent).buildFieldInfo();
423            return returnedField!=null && (returnedField instanceof CollectionItem);      
424        }
425    
426        /** Tells wether the method is <em>the</em> setter of a field */
427        public final boolean isSetter() {
428            ((ClassItem)parent).buildFieldInfo();
429            return setField!=null;
430        }
431    
432        /** Tells wether the method is an adder of a collection */
433        public final boolean isAdder() {
434            ((ClassItem)parent).buildFieldInfo();
435            return addedCollections!=null && addedCollections.length>0;
436        }
437    
438    
439        /** Stores the collections that are removed by this method.<p> */
440        CollectionItem[] removedCollections = null;
441       
442        /**
443         * Gets the value of the collections that are removed by this
444         * method.<p>
445         *
446         * @return value of removedCollections.  */
447    
448        public final CollectionItem[] getRemovedCollections() {
449            ((ClassItem)parent).buildFieldInfo();
450            return removedCollections;
451        }
452    
453        public final CollectionItem getRemovedCollection() {
454            CollectionItem[] colls = getRemovedCollections();
455            return ((colls != null) ? colls[0] : null);
456        }
457       
458        /**
459         * Returns true if the method has at least one removed collection
460         */
461    
462        public final boolean hasRemovedCollections() {
463            ((ClassItem)parent).buildFieldInfo();
464            return removedCollections!=null && removedCollections.length>0;
465        }
466    
467        /**
468         * Sets the value of the collections that are removed by this method.<p>
469         *
470         * @param removedCollections value to assign to removedCollections
471         * @see #addRemovedCollection(CollectionItem)
472         */
473    
474        public final void setRemovedCollections(CollectionItem[] removedCollections) {
475            this.removedCollections = removedCollections;
476        }
477    
478        /**
479         * Adds a new removed collection for this method.<p>
480         *
481         * @param removedCollection the collection to add
482         * @see #setRemovedCollections(CollectionItem[])
483         * @see #removeRemovedCollection(CollectionItem)
484         */
485    
486        public final void addRemovedCollection(CollectionItem removedCollection) {
487            if (removedCollections == null) {
488                removedCollections = new CollectionItem[] { removedCollection };
489            } else {
490                CollectionItem[] tmp = new CollectionItem[removedCollections.length+1];
491                System.arraycopy(removedCollections, 0, tmp, 0, 
492                                 removedCollections.length);
493                tmp[removedCollections.length] = removedCollection;
494                removedCollections = tmp;
495            }
496        }
497    
498        /**
499         * Removes an existing removed collection for this method.<p>
500         *
501         * @param collection the collection to remove
502         * @see #addRemovedCollection(CollectionItem)
503         */
504        public final void removeRemovedCollection(CollectionItem collection) {
505            if (removedCollections != null) {
506                Vector v = new Vector(Arrays.asList(removedCollections));
507                v.remove(collection);
508                removedCollections = new CollectionItem[v.size()];
509                System.arraycopy(v.toArray(),0,removedCollections,0,v.size());
510            }
511        }
512    
513        /** Tells wether the method is a remover of a collection */
514        public final boolean isRemover() {
515            ((ClassItem)parent).buildFieldInfo();
516            return removedCollections!=null && removedCollections.length>0;
517        }
518    
519        public final boolean isAccessor() {
520            ((ClassItem)parent).buildFieldInfo();
521            return accessedFields!=null && accessedFields.length>0;
522        }
523    
524        public final boolean isWriter() {
525            ((ClassItem)parent).buildFieldInfo();
526            return hasWrittenFields();
527        }
528    
529        public final boolean isCollectionAccessor() {
530            ((ClassItem)parent).buildFieldInfo();
531            if (accessedFields!=null) {
532                for (int i=0; i<accessedFields.length; i++) {
533                    if (accessedFields[i] instanceof CollectionItem) {
534                        return true;
535                    }
536                }
537            }
538            return false;
539        }
540    
541        public final boolean isReferenceAccessor() {
542            ((ClassItem)parent).buildFieldInfo();
543            if (accessedFields!=null) {
544                for (int i=0; i<accessedFields.length; i++) {
545                    if (accessedFields[i].isReference()) {
546                        return true;
547                    }
548                }
549            }
550            return false;
551        }
552    
553        public final boolean isCollectionSetter() {
554            ((ClassItem)parent).buildFieldInfo();
555            return setField instanceof CollectionItem;
556        }
557    
558        public final boolean isFieldSetter() {
559            ((ClassItem)parent).buildFieldInfo();
560            return setField!=null && setField.isPrimitive();
561        }
562    
563        public final boolean isReferenceSetter() {
564            ((ClassItem)parent).buildFieldInfo();
565            return setField!=null && setField.isReference();
566        }
567    
568        public final boolean isFieldGetter() {
569            ((ClassItem)parent).buildFieldInfo();
570            return !(returnedField instanceof CollectionItem) 
571                && returnedField.isPrimitive();
572        }
573    
574        public final boolean isReferenceGetter() {
575            ((ClassItem)parent).buildFieldInfo();
576            return returnedField!=null && returnedField.isReference();
577        }
578    
579        /** Tells wether the method was introduced by JAC */
580        public final boolean isJacMethod() {
581            return ClassRepository.isJacMethod(getName());
582        }
583    
584        public final static MethodItem[] emptyArray = new MethodItem[0];
585    }// MethodItem