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 gnu.regexp.RE;
022    import java.lang.reflect.InvocationTargetException;
023    import java.lang.reflect.Modifier;
024    import java.util.Collection;
025    import java.util.Hashtable;
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.Vector;
029    import org.apache.log4j.Logger;
030    import java.lang.reflect.Constructor;
031    
032    /**
033     * This class defines a meta item that corresponds to the
034     * <code>java.lang.reflect.Class</code> meta element.<p>
035     *
036     * @author Renaud Pawlak
037     * @author Laurent Martelli
038     */
039    
040    public class ClassItem extends MetaItemDelegate {
041    
042        static Logger logger = Logger.getLogger("rtti.class");
043    
044        static Class wrappeeClass = ClassRepository.wrappeeClass;
045    
046        /**
047         * Default contructor to create a new class item object.<p>
048         *
049         * @param delegate the <code>java.lang.reflect.Class</code> actual
050         * meta item 
051         */
052        public ClassItem(Class delegate) throws InvalidDelegateException {
053            super(delegate);
054            Class superClass = delegate.getSuperclass();
055            if (superClass!=Object.class && superClass!=null) {
056                setSuperClass(ClassRepository.get().getClass(superClass));
057            }
058        }
059    
060        private int collsCount = 0;
061        private int refsCount = 0;
062        private int primsCount = 0;
063        private int methodsCount = 0;
064        private int constructorsCount = 0;
065    
066        /**
067         * Stores the fields of the class.<p> */
068    
069        protected Hashtable fields = new Hashtable();
070    
071        /**
072         * Stores the methods of the class. */
073    
074        protected Hashtable methods = new Hashtable();
075    
076        boolean rttiBuilt = false;
077    
078        protected void buildFieldInfo() {
079            if (!rttiBuilt) {
080                rttiBuilt = true;
081                ClassRepository.get().buildDefaultFieldRTTI(this);
082            }
083        }
084    
085        /**
086         * Gets a field from its name. Throw an exception if the field does
087         * not exist.<p>
088         *
089         * @param name the field name. If name contains a dot, it is
090         * considered an expression field and is automatically created if
091         * it does not exist yet.
092         * @return a <code>FieldItem</code> instance 
093         * @see #getFieldNoError(String) 
094         */
095        public FieldItem getField(String name) {
096            buildFieldInfo();
097            FieldItem ret = getFieldNoError(name);
098            if (ret==null) {
099                throw new NoSuchFieldException(this,name);
100            }
101            return ret;
102        }
103    
104        /**
105         * Gets a field from its name. Don't throw an exception if the
106         * field does not exist.<p>
107         *
108         * @param name the field name
109         * @return a <code>FieldItem</code> instance, or null if no such
110         * field exists in the class 
111         * @see #getField(String)
112         */
113        public FieldItem getFieldNoError(String name) {
114            buildFieldInfo();
115            FieldItem field = (FieldItem)fields.get(name);
116            if (field==null) {
117                if (name.indexOf('.')!=-1) {
118                    try {
119                        List path = parseExpression(name);
120                        if (path.get(path.size()-1) instanceof CollectionItem) {
121                            field = new CollectionItem(name,path,this);
122                        } else {
123                            field = new FieldItem(name,path,this);
124                        }
125                        addField(field);
126                    } catch (Exception e) {
127                        field = null;
128                        logger.error("Expression field "+this.getName()+"."+name+
129                                     " instanciation failed: "+e);
130                        e.printStackTrace();
131                    }
132                }
133            }
134            if (field==null && superClass!=null) {
135                field = superClass.getFieldNoError(name);
136                if (field!=null) {
137                    field = field.clone(this);
138                    addField(field);
139                }
140            }
141            return field;
142        }
143    
144        /**
145         * Parse the expression and computes its type.
146         * @param expression the expression (<field>.<field>.<field>...)
147         */
148        public List parseExpression(String expression) {
149            Vector path = new Vector();
150            String current = expression;
151            ClassItem curClass = this;
152            int dot = current.indexOf('.');
153            FieldItem field = null;
154            while (dot!=-1) {
155                String fieldName = current.substring(0,dot);
156                field = curClass.getField(fieldName);
157                path.add(field);
158                if (field instanceof CollectionItem)
159                    curClass = ((CollectionItem)field).getComponentType();
160                else
161                    curClass = field.getTypeItem();
162                current = current.substring(dot+1);
163                dot = current.indexOf('.');
164            }
165    
166            field = curClass.getField(current);
167            path.add(field);
168          
169            /*
170              if (field!=null) {
171              type = field.getType();
172              setter = field.getSetter();
173              }
174            */
175    
176            return path;
177        }
178    
179        /**
180         * Gets a collection from its name.<p>
181         *
182         * @param name the collection name
183         * @return a <code>CollectionItem</code> instance
184         */
185    
186        public CollectionItem getCollection(String name) {
187            buildFieldInfo();
188            return (CollectionItem) fields.get(name);
189        }
190    
191        /**
192         * Gets all the fields for this class item.<p>
193         *
194         * @return an array of field items
195         */
196    
197        public FieldItem[] getFields() {
198            buildFieldInfo();
199            Collection c = fields.values();
200            FieldItem[] res = new FieldItem[c.size()];
201            Iterator it = c.iterator();
202            int i = 0;
203            while (it.hasNext()) {
204                res[i] = (FieldItem) it.next();
205                i++;
206            }
207            return res;
208        }
209    
210        /**
211         * Gets all the fields for this class item
212         *
213         * @return the fields as a collection
214         */
215    
216        public Collection getAllFields() {
217            buildFieldInfo();
218            return fields.values();
219        }
220    
221        /**
222         * Gets all the current class item's fields that are tagged or not
223         * tagged with the given attribute.<p>
224         *
225         * @param attribute the attribute
226         * @param not if true, returns fields not tagged with attribute
227         *
228         * @return a collection of field items 
229         */
230        public Collection getTaggedFields(String attribute, boolean not) {
231            buildFieldInfo();
232            Collection c = fields.values();
233            Iterator it = c.iterator();
234            Vector result = new Vector();
235            int i = 0;
236            while (it.hasNext()) {
237                FieldItem field = (FieldItem) it.next();
238                if (field.getAttribute(attribute)!=null ^ not) {
239                    result.add(field);
240                }
241            }
242            return result;
243        }
244    
245        /**
246         * @param expression something like [!](static|transient|private|public|protected)
247         */
248        public Collection filterFields(String expression) { 
249            logger.debug(this+".filterFields "+expression);
250            String keyword;
251            boolean not = false;
252            if (expression.charAt(0)=='!') {
253                not = true;
254                expression = expression.substring(1,expression.length());
255            }
256            int filter = 0;
257            if (expression.equals("static")) 
258                filter = Modifier.STATIC;
259            else if (expression.equals("public")) 
260                filter = Modifier.PUBLIC;
261            else if (expression.equals("transient")) 
262                filter = Modifier.TRANSIENT;
263            else if (expression.equals("private")) 
264                filter = Modifier.PRIVATE;
265            else if (expression.equals("protected")) 
266                filter = Modifier.PROTECTED;
267            Vector result = new Vector();      
268            Iterator it = fields.values().iterator();
269            while (it.hasNext()) {
270                FieldItem field = (FieldItem) it.next();
271                if (((field.getModifiers() & filter) != 0) ^ not) {
272                    result.add(field);
273                }
274            }
275            logger.debug("    -> "+result);
276            return result;
277        }
278    
279        public Collection getTaggedMethods(String attribute, boolean not) {
280            Collection c = getAllMethods();
281            Iterator it = c.iterator();
282            Vector result = new Vector();
283            int i = 0;
284            while (it.hasNext()) {
285                AbstractMethodItem method = (AbstractMethodItem) it.next();
286                if (method.getAttribute(attribute)!=null ^ not) {
287                    result.add(method);
288                }
289            }
290            return result;
291        }
292    
293        public Collection getTaggedMembers(String attribute, boolean not) {
294            Collection result = getTaggedMethods(attribute,not);
295            result.addAll(getTaggedFields(attribute,not));
296            return result;
297        }
298    
299        /**
300         * Returns the members that are tagged by a given attribute that
301         * has a given value.
302         *
303         * @param attribute the attribute id
304         * @param value the value of the attribute (must not be null!!) */
305    
306        public Collection getTaggedMembers(String attribute, Object value) {
307            Collection result = getAllMembers();
308            Collection result2 = new Vector();
309            Iterator it = result.iterator();
310            while (it.hasNext()) {
311                MemberItem member = (MemberItem)it.next();         
312                if (member.getAttribute(attribute)!=null
313                    && value.equals(member.getAttribute(attribute)))
314                {
315                    result2.add(member);
316                }
317            }
318            return result2;
319        }
320    
321        /**
322         * Returns the member (method or field) of this class with the
323         * specified name.
324         * @param name the name of the member that you want.
325         * @return the member with the specified name
326         * @see #getMembers(String[]) */
327        public MemberItem getMember(String name) {
328            MemberItem result = null;
329            try {
330                if (name.indexOf('(')!=-1)
331                    result = getAbstractMethod(name);
332                else
333                    result = getField(name);
334            } catch(NoSuchFieldException e) {
335                if (result==null) {
336                    try {
337                        result = getAbstractMethod(name);
338                    } catch (NoSuchMethodException e2) {
339                        throw new NoSuchMemberException(this,name);
340                    }
341                }
342            }
343            return result;
344        }
345    
346        /**
347         * Returns a MemberItem array.
348         *
349         * @param names the names of the members
350         * @return a MemberItem array members such as
351         * members[i].getName().equals(names[i]). If no member with a given
352         * name is found, it is ignored (but a warning is issued).
353         * @see #getMember(String) 
354         */
355        public MemberItem[] getMembers(String[] names) {
356            MemberItem[] tmp = new MemberItem[names.length];
357            int j = 0;
358            for(int i=0; i<names.length; i++) {
359                try {
360                    tmp[j] = getMember(names[i]);
361                    j++;
362                } catch (NoSuchMemberException e) {
363                    logger.warn(e);
364                }
365            }
366            MemberItem[] members = new MemberItem[j];
367            System.arraycopy(tmp,0,members,0,j);
368            return members;
369        }
370    
371        public Collection getAllMembers() {
372            Collection result = getAllMethods();
373            result.addAll(getAllFields());
374            return result;
375        }
376    
377        /**
378         * Returns a FieldItem array.
379         *
380         * @param names the names of the members
381         * @return a FieldItem array fields such as
382         * fields[i].getName().equals(names[i]). If no field with a given
383         * name is found, it is ignored (but a warning is issued).
384         * @see #getField(String) */
385        public FieldItem[] getFields(String[] names) {
386            FieldItem[] tmp = new FieldItem[names.length];
387            int j=0;
388            for(int i=0;i<names.length;i++) {
389                try {
390                    tmp[j] = getField(names[i]);
391                    j++;
392                } catch (NoSuchFieldException e) {
393                    logger.warn(e);
394                }
395            }
396            FieldItem[] fields = new FieldItem[j];
397            System.arraycopy(tmp,0,fields,0,j);
398            return fields;
399        }
400    
401    
402        /**
403         * Returns a MethodItem array.
404         *
405         * @param names the names of the members
406         * @return a MethodItem array methods such as
407         * methods[i].getName().equals(names[i]). If no method with a given
408         * name is found, it is ignored (but a warning is issued).
409         * @see #getMethod(String) 
410         */
411        public MethodItem[] getMethods(String[] names) {
412            MethodItem[] tmp = new MethodItem[names.length];
413            int j=0;
414            for(int i=0;i<names.length;i++) {
415                try {
416                    tmp[j] = getMethod(names[i]);
417                    j++;
418                } catch (NoSuchMethodException e) {
419                    logger.warn(e);
420                }
421            }
422            MethodItem[] methods = new MethodItem[j];
423            System.arraycopy(tmp,0,methods,0,j);
424            return methods;
425        }
426    
427        /**
428         * Gets all the primitive fields for this class item.<p>
429         *
430         * @return an array of field items
431         */
432    
433        public FieldItem[] getPrimitiveFields() {
434            buildFieldInfo();
435            Collection c = fields.values();
436            FieldItem[] res = new FieldItem[primsCount];
437            Iterator it = c.iterator();
438            int i = 0;
439            while ( it.hasNext() ) {
440                FieldItem field = (FieldItem) it.next();
441                if (field.isPrimitive()) {
442                    res[i] = field;
443                    i++;
444                }
445            }
446            return res;
447        }
448    
449        /**
450         * Gets all the references for this class item.<p>
451         *
452         * @return an array of field items
453         */
454    
455        public FieldItem[] getReferences() {
456            buildFieldInfo();
457            Collection c = fields.values();
458            FieldItem[] res = new FieldItem[refsCount];
459            Iterator it = c.iterator();
460            int i = 0;
461            while ( it.hasNext() ) {
462                FieldItem field = (FieldItem) it.next();
463                if (field.isReference()) {
464                    res[i] = field;
465                    i++;
466                }
467            }
468            return res;
469        }
470    
471        /**
472         * Gets all the references and collections that match the
473         * expression for this class item.<p>
474         *
475         * @return a vector of field items */
476    
477        public Collection getMatchingRelations(String expr) {
478            buildFieldInfo();
479            Vector res = new Vector();
480            Collection c = fields.values();
481            Iterator it = c.iterator();
482            RE re;
483            try {
484                re = new RE(expr);
485            } catch (Exception e) {
486                logger.error("getMatchingRelations "+expr,e);
487                return null;
488            }
489            while (it.hasNext()) {
490                FieldItem field = (FieldItem) it.next();
491                if (field.isReference() || (field instanceof CollectionItem)) {
492                    if (re.isMatch(field.getName())) {
493                        res.add( field );
494                    }
495                }
496            }
497            return res;
498        }
499    
500        /**
501         * Gets all the collections for this class item.<p>
502         *
503         * @return an array of field items
504         */
505    
506        public CollectionItem[] getCollections() {
507            buildFieldInfo();
508            Collection c = fields.values();
509            CollectionItem[] res = new CollectionItem[collsCount];
510            Iterator it = c.iterator();
511            int i = 0;
512            while ( it.hasNext() ) {
513                FieldItem field = (FieldItem) it.next();
514                if (field instanceof CollectionItem) {
515                    res[i] = (CollectionItem)field;
516                    i++;
517                }
518            }
519            return res;
520        }
521    
522        /**
523         * Add a field item to this class item. The parent of the field is
524         * set to this.<p>
525         *
526         * @param field the new field 
527         */ 
528        void addField(FieldItem field) {
529            buildFieldInfo();
530            boolean override = fields.containsKey(field.getName());
531            if (override) {
532                logger.warn("Overriding field "+fields.get(field.getName())+
533                            " with "+field);
534            }
535            try {
536                field.setParent(this);
537            } catch(Exception e) {
538                logger.error("addField "+field.getName(),e);
539                return;
540            }
541            fields.put(field.getName(), field);
542            if (field instanceof CollectionItem && !override) {
543                collsCount++;
544            } else if (field.isReference() && !override) {
545                refsCount++;
546            } else if (!override) {
547                primsCount++;
548            }  
549        }
550       
551        /**
552         * Gets a set of homonym methods (including constructors) from
553         * their names.<p>
554         *
555         * @param name the name of the methods
556         * @return a <code>MethodItem</code> instance
557         */
558        public AbstractMethodItem[] getAbstractMethods(String name) 
559            throws NoSuchMethodException
560        {
561            if (name.startsWith("<init>"))
562                name = getShortName()+name.substring(6);
563            //Log.trace("rtti.method","getAbstractMethods("+name+")");
564            AbstractMethodItem[] res=null;
565            if (name.endsWith(")")) {
566                String name2 = name.substring(0,name.indexOf("("));
567                //Log.trace("rtti.method","name2 : "+name2);
568                AbstractMethodItem[] meths = 
569                    (AbstractMethodItem[])methods.get(name2);
570                if (meths!=null) {
571                    for (int i=0; i<meths.length; i++) {
572                        //Log.trace("rtti.method","trying "+meths[i].getFullName());
573                        if (meths[i].getFullName().endsWith(name)) {
574                            res = new AbstractMethodItem[] {meths[i]};
575                        }
576                    }
577                }
578            } else {
579                res = (AbstractMethodItem[])methods.get(name);
580            }
581            if (res == null) {
582                throw new NoSuchMethodException(
583                    "ClassItem.getAbstractMethods: no such method "+name+" in "+this);
584            }
585            return res;
586        }
587    
588        Hashtable methodCache = new Hashtable();
589    
590        /**
591         * Gets an abstract method from its name.
592         *
593         * <p>If this method has homonym(s), then an
594         * <code>AmbiguousMethodNameException</code> is thrown.
595         *
596         * <p>An abstract method can be a static, an instance method or a
597         * constructor.
598         *
599         * @param name the name of the method to search
600         * @return the corresponding method if found
601         * @see #getMethod(String)
602         * @see #getAbstractMethods(String) 
603         */
604        public AbstractMethodItem getAbstractMethod(String name) 
605            throws NoSuchMethodException, AmbiguousMethodNameException
606        {
607            AbstractMethodItem cachedMethod =
608                (AbstractMethodItem)methodCache.get(name);
609            if (cachedMethod!=null) {
610                return cachedMethod;
611            } else {
612                AbstractMethodItem[] res = getAbstractMethods(name);      
613                if (res.length>1) {
614                    throw new NoSuchMethodException("Ambiguous method name "+
615                                                    this+"."+name);
616                }
617                methodCache.put(name,res[0]);
618                return res[0];
619            }
620        }
621    
622        /**
623         * Tells wether the class contains a method. All methods of the
624         * class are examined (even the private and protected ones wich
625         * are not regisered as MethodItem)
626         *
627         * @param name name of the searched method
628         * @param paramTypes types of the parameters of the searched method 
629         * @see Class#getDeclaredMethod(String,Class[])
630         */
631        public boolean hasMethod(String name, Class[] paramTypes) {
632            try {
633                ((Class)delegate).getDeclaredMethod(name,paramTypes);
634                return true;
635            } catch(java.lang.NoSuchMethodException e) {
636                return false;
637            }
638        }
639    
640        /**
641         * Strip the arguments part from a full method name.
642         * aMethod(aType) -> aMethod
643         * aMethod -> aMethod
644         * @param name a method name
645         */
646        static String stripArgs(String name) {
647            int index = name.indexOf("(");
648            if (index!=-1)
649                return name.substring(0,index);
650            else
651                return name;
652        }
653    
654        /**
655         * Gets a set of homonym methods (excluding constructors) from
656         * their names.<p>
657         *
658         * @param name the name of the methods
659         * @return a <code>MethodItem</code> instance */
660    
661        public MethodItem[] getMethods(String name) 
662            throws NoSuchMethodException
663        {
664            MethodItem[] res = null;
665    
666            //Log.trace("rtti.method","getMethods("+name+")");
667            if (name.endsWith(")")) {
668                String name2 = name.substring(0,name.indexOf("("));
669                //Log.trace("rtti.method","name2 : "+name2);
670                MethodItem[] meths = (MethodItem[])methods.get(name2);
671                if (meths!=null) {
672                    for (int i=0; i<meths.length; i++) {
673                        //Log.trace("rtti.method","trying "+meths[i].getFullName());
674                        if (meths[i].getFullName().endsWith(name)) {
675                            res = new MethodItem[] {meths[i]};
676                        }
677                    }
678                }
679            } else {
680                res = (MethodItem[])methods.get(name);
681            }
682    
683            /*ClassItem superClass=getSuperclass();
684         
685              if(res==null && superClass!=null) {
686              res=superClass.getMethods(name);
687              }*/
688    
689            if (res == null) {
690                //Log.trace("rtti.method",2,"methods = "+methods);
691                throw new NoSuchMethodException(
692                    "ClassItem.getMethods: no such method "+name+" in class "+this);
693            }
694            return res;
695        }
696    
697        /**
698         * Gets a method from its name.
699         *
700         * <p>If this method has homonym(s), then an
701         * <code>AmbiguousMethodNameException</code> is thrown.
702         *
703         * <p>A method can be a static or instance method but not a
704         * constructor.
705         *
706         * @param name the name of the method to search
707         * @return the corresponding method if found
708         * @see #getAbstractMethod(String)
709         * @see #getMethods(String) */
710    
711        public MethodItem getMethod(String name) 
712            throws NoSuchMethodException, AmbiguousMethodNameException
713        {
714            //Log.trace("rtti.method","getMethod("+name+")");      
715            MethodItem[] res= getMethods(name);
716            if (res.length>1) {
717                throw new AmbiguousMethodNameException(
718                    "Ambiguous method name "+this+"."+name+" choice is:"
719                    +java.util.Arrays.asList(res));
720            }
721            return res[0];
722        }
723    
724        /**
725         * Gets a set of arrays containing all the method items for this
726         * class item.
727         *
728         * <p>Each arrays contains the methods of the same name.
729         *
730         * @return a collection containing the methods 
731         */
732        public Collection getMethods() {
733            Collection c = methods.values();
734            Vector res = new Vector();
735            Iterator it = c.iterator();
736            while (it.hasNext()) {
737                Object[] methods = (Object[]) it.next();
738                if (methods[0] instanceof MethodItem) {
739                    res.add(methods);
740                }
741            }
742            return res;
743        }
744    
745        /**
746         * Gets all the method items for this class item.<p>
747         *
748         * @return a collection containing the methods
749         */
750        public Collection getAllMethods() {
751            Collection c = methods.values();
752            Vector res = new Vector();
753            Iterator it = c.iterator();
754            while (it.hasNext()) {
755                Object[] methods = (Object[]) it.next();
756                for (int i=0; i<methods.length; i++) {
757                    if ( methods[i] instanceof MethodItem && 
758                         !ClassRepository.isJacMethod(
759                             ((MethodItem)methods[i]).getName()) ) {
760                        res.add(methods[i]);
761                    }
762                }
763            }
764            return res;
765        }
766    
767        /**
768         * Gets all the method items for this class item.<p>
769         *
770         * @return a collection containing the methods
771         */
772        public Collection getMixinMethods() {
773            Collection c = methods.values();
774            Vector res = new Vector();
775            Iterator it = c.iterator();
776            while (it.hasNext()) {
777                Object[] methods = (Object[]) it.next();
778                for (int i=0; i<methods.length; i++) {
779                    if ( methods[i] instanceof MixinMethodItem && 
780                         !ClassRepository.isJacMethod(
781                             ((MethodItem)methods[i]).getName()) ) {
782                        res.add(methods[i]);
783                    }
784                }
785            }
786            return res;
787        }
788    
789        /**
790         * @return a collection of static MethodItem 
791         */
792        public Collection getAllStaticMethods() {
793            Collection c = methods.values();
794            Vector res = new Vector();
795            Iterator it = c.iterator();
796            while (it.hasNext()) {
797                Object[] methods = (Object[]) it.next();
798                //Log.trace("rtti.static","getStatic checks "+methods[0]);
799                for (int i=0; i<methods.length; i++) {
800                    if ( methods[i] instanceof MethodItem && 
801                         !ClassRepository.isJacMethod(
802                             ((MethodItem)methods[i]).getName()) && 
803                         ((MethodItem)methods[i]).isStatic()) {
804                        //Log.trace("rtti.static","getStatic adds "+methods[0]);
805                        res.add(methods[i]);
806                    }
807                }
808            }
809            return res;
810        }
811    
812        /**
813         * @return a collection of non static MethodItem 
814         */
815        public Collection getAllInstanceMethods() {
816            Collection c = methods.values();
817            Vector res = new Vector();
818            Iterator it = c.iterator();
819            while (it.hasNext()) {
820                Object[] methods = (Object[]) it.next();
821                for (int i=0; i<methods.length; i++) {
822                    if ( methods[i] instanceof MethodItem && 
823                         !ClassRepository.isJacMethod(
824                             ((MethodItem)methods[i]).getName()) && 
825                         !((MethodItem)methods[i]).isStatic()) {
826                        res.add(methods[i]);
827                    }
828                }
829            }
830            return res;
831        }
832    
833        /**
834         * Gets all the method items that modify the state of the instances
835         * for this class item.
836         *
837         * @return a collection of MethodItem containing the modifiers 
838         */
839        public Collection getAllModifiers() {
840            Iterator it = getAllMethods().iterator();
841            Vector modifiers = new Vector();
842            while(it.hasNext()) {
843                MethodItem method = (MethodItem)it.next();
844                if (method.isModifier()) {
845                    modifiers.add(method);
846                }
847            }
848            return modifiers;
849        }
850    
851    
852        /**
853         * Gets all the method items that modify the state of the instances
854         * for this class item.
855         *
856         * @return a collection of MethodItem containing the modifiers 
857         */
858        public Collection getAllSetters() {
859            Iterator it = getAllMethods().iterator();
860            Vector modifiers = new Vector();
861            while(it.hasNext()) {
862                MethodItem method = (MethodItem)it.next();
863                if (method.isSetter()) {
864                    modifiers.add(method);
865                }
866            }
867            return modifiers;
868        }
869    
870        /**
871         * Gets all the method items that modify a field of the instances
872         * for this class item.
873         *
874         * @return a collection of MethodItem containing the writers 
875         */
876        public Collection getAllWriters() {
877            Iterator it = getAllMethods().iterator();
878            Vector modifiers = new Vector();
879            while(it.hasNext()) {
880                MethodItem method = (MethodItem)it.next();
881                if (method.hasWrittenFields()) {
882                    modifiers.add(method);
883                }
884            }
885            return modifiers;
886        }
887    
888        /**
889         * Gets all the getter methods of the class.
890         *
891         * @return a collection containing the getters 
892         * @see MethodItem#isGetter()
893         */
894        public Collection getAllGetters() {
895            Vector modifiers = new Vector();
896            Iterator it = getAllMethods().iterator();
897            while(it.hasNext()) {
898                MethodItem method = (MethodItem)it.next();
899                if (method.isGetter()) {
900                    modifiers.add(method);
901                }
902            }
903            return modifiers;
904        }
905    
906        /**
907         * Gets all the method items that access the state of the instances
908         * for this class item.
909         *
910         * @return a collection containing the accessors 
911         */
912        public Collection getAllAccessors() {
913            Vector accessors = new Vector();
914            Iterator it = getAllMethods().iterator();
915            while(it.hasNext()) {
916                MethodItem method = (MethodItem)it.next();
917                if (method.isAccessor()) {
918                    accessors.add(method);
919                }
920            }
921            return accessors;
922        }
923    
924    
925        /**
926         * Gets all the method items that removes an object from a
927         * collection of this class item.
928         *
929         * @return a collection containing the modifiers 
930         */
931        public Collection getAllRemovers() {
932            Vector removers = new Vector();
933            Iterator it = getAllMethods().iterator();
934            while(it.hasNext()) {
935                MethodItem method = (MethodItem)it.next();
936                if (method.isRemover()) {
937                    removers.add(method);
938                }
939            }
940            return removers;
941        }
942    
943        /**
944         * Gets all the method items that adds an object from a
945         * collection of this class item.
946         *
947         * @return a collection containing the modifiers 
948         */
949        public Collection getAllAdders() {
950            Collection methods = getAllMethods();
951            Iterator it = methods.iterator();
952            Vector adders = new Vector();
953    
954            while(it.hasNext()) {
955                MethodItem method = (MethodItem)it.next();
956                if (method.getAddedCollections() != null &&
957                    method.getAddedCollections().length>0) {
958                    adders.add(method);
959                }
960            }
961            return adders;
962        }
963    
964        /**
965         * Gets all the constructors items for this class item.<p>
966         *
967         * @return a collection containing the constructors 
968         */
969        public Collection getConstructors() {
970            Collection c = methods.values();
971            Vector res = new Vector();
972            Iterator it = c.iterator();
973            while ( it.hasNext() ) {
974                Object[] methods = (Object[]) it.next();
975                //Log.trace("rtti.constructor","checking "+methods[0]);
976                if (methods[0] instanceof ConstructorItem) {
977                    //Log.trace("rtti.constructor","adding "+methods[0]);
978                    res.addAll(java.util.Arrays.asList(methods));
979                }
980            }
981            return res;
982        }
983    
984        public ConstructorItem getConstructor(Class[] parameterTypes) 
985            throws NoSuchMethodException
986        {
987            Iterator i = getConstructors().iterator();
988            while (i.hasNext()) {
989                ConstructorItem constructor = (ConstructorItem)i.next();
990                if (java.util.Arrays.equals(constructor.getParameterTypes(),
991                                            parameterTypes)) {
992                    return constructor;
993                }
994            }
995            return null;
996        }
997    
998        /**
999         * Get a constructor with given parameter types
1000         * @param parameterTypes the types of the constructor
1001         * parameters. For instance "(java.lang.Object,java.lang.String)".
1002         */
1003        public ConstructorItem getConstructor(String parameterTypes) {
1004            return (ConstructorItem)getAbstractMethod(getShortName()+parameterTypes);
1005        }
1006    
1007        /**
1008         * Gets the constructor item of this class item that matches the
1009         * given <code>java.lang.reflect.Constructor</code>.
1010         *
1011         * @return the corresponding constructor item 
1012         */
1013        public ConstructorItem getConstructor(Constructor constructor) {
1014            //Log.trace("rtti","getConstructor("+constructor+")");
1015            Collection constructors = getConstructors();
1016            Iterator it = constructors.iterator();
1017            while ( it.hasNext() ) {
1018                ConstructorItem[] current = (ConstructorItem[])it.next();
1019                for (int i=0; i<current.length; i++) {
1020                    //Log.trace("rtti","compare with "+current[i]);
1021                    if (current[i].getActualConstructor().toString().equals(constructor.toString())) {
1022                        return current[i];
1023                    }
1024                }
1025            }
1026            return null;
1027        }
1028    
1029        /**
1030         * Creates a new instance of this class item by using the default
1031         * constructor (the one with no parameters).
1032         *
1033         * @return the newly created instance */
1034    
1035        public Object newInstance() 
1036            throws InstantiationException, IllegalAccessException {
1037            return getActualClass().newInstance();
1038        }
1039    
1040        /**
1041         * Creates a new instance of this class item by using the 
1042         * constructor that matches the given parameter types.
1043         *
1044         * @param types the types of the constructor arguments
1045         * @param values the arguments values
1046         * @return the newly created instance */
1047    
1048        public Object newInstance(Class[] types,Object[] values) 
1049            throws InstantiationException, IllegalAccessException, 
1050            java.lang.NoSuchMethodException, InvocationTargetException {
1051            Constructor c = getActualClass().getConstructor(types);
1052            return c.newInstance(values);
1053        }
1054    
1055        /**
1056         * Create a new instance of the class, using the first constructor
1057         * suitable for the given parameters.
1058         *
1059         * @param parameters parameters of the constructor */
1060        public Object newInstance(Object[] parameters) 
1061            throws InstantiationException, IllegalAccessException, 
1062            java.lang.NoSuchMethodException, InvocationTargetException
1063        {
1064            Iterator i = getConstructors().iterator();
1065            ConstructorItem constructor = null;
1066            while (i.hasNext()) {
1067                constructor = (ConstructorItem)i.next();
1068                Class[] parameterTypes = constructor.getParameterTypes();
1069                if (parameterTypes.length==parameters.length) {
1070                    int j;
1071                    for (j=0; j<parameters.length; j++) {
1072                        // WARNING: this test does not work for primitive typed
1073                        // parameters               
1074                        if (!parameterTypes[j]
1075                            .isAssignableFrom(parameters[j].getClass())) {
1076                            break;
1077                        }
1078                    }
1079                    if (j==parameters.length) {
1080                        return constructor.newInstance(parameters);
1081                    }
1082                }
1083            }
1084            throw new InstantiationException(
1085                "Could not find a suitable constructor for class "+getName()+
1086                " with arguments "+java.util.Arrays.asList(parameters));
1087        }
1088    
1089        /**
1090         * Gets the class represented by this class item.<p>
1091         *
1092         * @return the actual class
1093         * @see #getType()
1094         */
1095    
1096        public Class getActualClass() {
1097            return (Class)delegate;
1098        }
1099    
1100        ClassItem[] interfaceItems = null;
1101        /**
1102         * Returns the interfaces implemented by this class.
1103         */
1104        public ClassItem[] getInterfaceItems() {
1105            if (interfaceItems==null) {
1106                Class[] interfaces = getActualClass().getInterfaces();
1107                interfaceItems = new ClassItem[interfaces.length];
1108                ClassRepository cr = ClassRepository.get();
1109                for (int i=0; i<interfaces.length; i++) {
1110                    interfaceItems[i] = cr.getClass(interfaces[i]);
1111                }
1112            }
1113            return interfaceItems;
1114        }
1115    
1116        /** the super class */
1117        ClassItem superClass;
1118    
1119        /**
1120         * Set the super class of this class and updates the children of
1121         * the super class.
1122         * @param superClass the super class
1123         */
1124        protected void setSuperClass(ClassItem superClass) {
1125            this.superClass = superClass;
1126            superClass.addChild(this);
1127        }
1128    
1129        /**
1130         * Gets the superclass of this class item.<p>
1131         *
1132         * @return the superclass, null if the superclass is Object
1133         */
1134        public ClassItem getSuperclass() {
1135            return superClass;
1136        }
1137    
1138        /** A list of ClassItem whose super class is this class */
1139        Vector children = new Vector();
1140    
1141        public Collection getChildren() {
1142            return children;
1143        }
1144    
1145        public void addChild(ClassItem child) {
1146            children.add(child);
1147        }
1148    
1149        /**
1150         * Tells wether the class inherits from a subclass whose name
1151         * matches a regular expression.
1152         * @param classNameRE the regular expression
1153         */
1154        public boolean isSubClassOf(RE classNameRE) {
1155            if (classNameRE.isMatch(getName())) {
1156                return true;
1157            } else if (superClass!=null) {
1158                return superClass.isSubClassOf(classNameRE);
1159            } else {
1160                ClassItem[] interfaces = getInterfaceItems();
1161                for (int i=0; i<interfaces.length; i++) {
1162                    if (interfaces[i].isSubClassOf(classNameRE))
1163                        return true;
1164                }
1165                return false;
1166            }
1167        }
1168    
1169        /**
1170         * Tells wether the class inherits from a subclass 
1171         *
1172         * @param cl the regular expression
1173         */
1174        public boolean isSubClassOf(ClassItem cl) {
1175            if (cl==this) {
1176                return true;
1177            } else if (superClass!=null) {
1178                return superClass.isSubClassOf(cl);
1179            } else {
1180                ClassItem[] interfaces = getInterfaceItems();
1181                for (int i=0; i<interfaces.length; i++) {
1182                    if (interfaces[i].isSubClassOf(cl))
1183                        return true;
1184                }
1185                return false;
1186            }
1187        }
1188    
1189        public int getModifiers() {
1190            return ((Class)delegate).getModifiers();
1191        }
1192    
1193        /**
1194         * Synonym of <code>getActualClass</code>.
1195         *
1196         * @return the actual class
1197         * @see #getActualClass()
1198         */
1199    
1200        public Class getType() {
1201            return getActualClass();
1202        }
1203    
1204        /**
1205         * Add a method item to this class item.
1206         *
1207         * @param method the new method
1208         */
1209        public void addMethod(MethodItem method) {
1210            String name = method.getName();
1211            //Log.trace("rtti.method","addMethod: "+name+" -> "+getName()+"."+method);
1212            if (!methods.containsKey(name)) {
1213                methods.put(name, new MethodItem[] { method } );
1214            } else {
1215                MethodItem[] meths = (MethodItem[])methods.get(name);
1216                MethodItem[] newMeths = new MethodItem[meths.length + 1];
1217                System.arraycopy(meths, 0, newMeths, 0, meths.length);
1218                newMeths[meths.length] = method;
1219                methods.remove(name);
1220                methods.put(name, newMeths);
1221            }
1222            methodsCount++;
1223            if (method instanceof MixinMethodItem) {
1224                Iterator i = children.iterator();
1225                while (i.hasNext()) {
1226                    ClassItem subclass = (ClassItem)i.next();
1227                    subclass.addMethod(method);
1228                }
1229            }
1230            try {
1231                method.setParent(this);
1232            } catch(Exception e) {
1233                logger.error("addMethod "+method.getFullName()+
1234                             ": could not set the parent of the method",e);
1235            }
1236        }   
1237    
1238        Vector mixinMethods = new Vector();
1239    
1240        /**
1241         * Add a constructor item to this class item.<p>
1242         *
1243         * @param constructor the new constructor
1244         */
1245        public void addConstructor(ConstructorItem constructor) {
1246            String name = NamingConventions.getShortConstructorName(constructor.getActualConstructor());
1247            //Log.trace("rtti.method","addConstructor: "+name+" -> "+constructor);
1248            if ( ! methods.containsKey( name )) {
1249                methods.put( name, new ConstructorItem[] { constructor } );
1250            } else {
1251                ConstructorItem[] constructors = 
1252                    (ConstructorItem[]) methods.get( name );
1253                ConstructorItem[] newConstructors = 
1254                    new ConstructorItem[constructors.length + 1];
1255                System.arraycopy(constructors, 0, newConstructors, 0, constructors.length);
1256                newConstructors[constructors.length] = constructor;
1257                methods.remove(constructor.getName());
1258                methods.put(name, newConstructors);
1259            }
1260            constructorsCount++;
1261            try {
1262                constructor.setParent(this);
1263            } catch( Exception e ) {
1264                logger.error("addConstructor "+constructor.getFullName(),e);
1265            }
1266        }   
1267       
1268        /**
1269         * Tests if a method exist in this class item.<p>
1270         * 
1271         * @return true if exist
1272         */
1273        public boolean hasMethod(String name) {
1274            try {
1275                getAbstractMethod(name);
1276                return true;
1277            } catch (NoSuchMethodException e) {
1278                return false;
1279            } catch (AmbiguousMethodNameException e) {
1280                return true;
1281            }
1282        }
1283    
1284        public boolean hasMethod(MethodItem method) {
1285            return method.parent==this;
1286        }
1287    
1288        /**
1289         * Tests if a field exist in this class item.<p>
1290         * 
1291         * @return true if exist
1292         */
1293    
1294        public boolean hasField( String name ) {
1295            buildFieldInfo();
1296            return (name!=null) && fields.containsKey( name );
1297        }
1298    
1299        public String getName() {
1300            return ((Class)delegate).getName();
1301        }
1302    
1303        public String getShortName() {
1304            String name = getName();
1305            int index = name.lastIndexOf('.');
1306            return index==-1 ? name : name.substring(index+1);
1307        }
1308    
1309        /**
1310         * A shortcut to invoke a method (avoid to get the method item).
1311         *
1312         * @param object the object to perform the invocation on (may be
1313         * null for a static method)
1314         * @param methodName the name of the method to invoke
1315         * @param parameters the parameters passed to the method
1316         * @see MethodItem#invoke(Object,Object[]) */
1317    
1318        public Object invoke(Object object, String methodName, Object[] parameters)
1319            throws IllegalAccessException, InvocationTargetException 
1320        {
1321            return getMethod(methodName).invoke(object, parameters);
1322        }
1323    
1324        /**
1325         * Tells wether this class is an inner class of some other class 
1326         *
1327         * @return true if the class is an inner class
1328         */
1329        public boolean isInner() {
1330            return getName().indexOf('$')!=-1;
1331        }
1332    
1333        public boolean isAbstract() {
1334            return Modifier.isAbstract(getModifiers());
1335        }
1336    
1337        /**
1338         * Gets the class this class is an inner class of.
1339         *
1340         * @return the ClassItem this class is an inner class of, or null.
1341         */
1342        public ClassItem getOwnerClassItem() {
1343            int index = getName().indexOf('$');
1344            if (index==-1) {
1345                return null;
1346            } else {
1347                return ClassRepository.get().getClass(getName().substring(0,index));
1348            }
1349        }
1350    
1351        /**
1352         * Finds the collection of this class item that contains the given
1353         * object.
1354         *
1355         * @param substance the instance of the current class item to seek
1356         * in
1357         * @param object the object to find
1358         * @return the collection item, null if not found */
1359    
1360        public CollectionItem findCollectionFor(Object substance,Object object) {
1361            CollectionItem[] collections=getCollections();
1362            for(int i=0;i<collections.length;i++) {
1363                Collection cur=collections[i].getActualCollection(substance);
1364                if(cur.contains(object)) {
1365                    return collections[i];
1366                } 
1367            }
1368            return null;
1369        }
1370    
1371        /**
1372         * Get an attribute by searching recursively through all super classes.
1373         */
1374        public Object getAttribute(String name) {
1375            ClassItem cur = this;
1376            Object result = null;
1377            while (cur!=null && result==null) {
1378                result = cur.superGetAttribute(name);
1379                cur = cur.getSuperclass();
1380            }
1381            return result;
1382        }
1383    
1384        public Object superGetAttribute(String name) {
1385            return super.getAttribute(name);
1386        }
1387    
1388        Vector constraints;
1389        /**
1390         * Return all field and collection items whose component type is
1391         * this class.
1392         * @return a collection of FieldItem
1393         */
1394        public Collection getConstraints() {
1395            if (constraints==null) {
1396                constraints = new Vector();
1397                Object[] classes = ClassRepository.get().getClasses();
1398                for (int i=0; i<classes.length;i++) {
1399                    if (classes[i] instanceof ClassItem) {
1400                        ClassItem cl = (ClassItem)classes[i];
1401                        FieldItem[] fields = cl.getFields();
1402                        for (int j=0; j<fields.length; j++) {
1403                            if (fields[j] instanceof CollectionItem) {
1404                                if (((CollectionItem)fields[j]).getComponentType()==this)
1405                                    constraints.add(fields[j]);
1406                            } else if (fields[j].getTypeItem()==this) {
1407                                constraints.add(fields[j]);
1408                            }
1409                        }
1410                    }
1411                }
1412            }
1413            return constraints;
1414        }
1415    
1416        /**
1417         * The exception that is thrown when the accessed method has some
1418         * synonymes (methods with same names but different parameter
1419         * types). */
1420        public static class AmbiguousMethodNameException extends RuntimeException {
1421            public AmbiguousMethodNameException(String msg) { super(msg); }
1422            public AmbiguousMethodNameException() { super(); }
1423        }
1424    }