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.*;
023    import java.lang.reflect.Modifier;
024    import java.util.ArrayList;
025    import java.util.Collection;
026    import java.util.HashSet;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Vector;
030    import org.apache.log4j.Logger;
031    import org.objectweb.jac.util.*;
032    
033    /**
034     * This class defines a meta item that corresponds to the
035     * <code>java.lang.reflect.Field</code> meta element.<p>
036     *
037     * <p>In addition to the <code>java.lang.reflect</code> classical
038     * features, this RTTI method element is able to tell if a field is
039     * accessed for reading or writting by a given method.
040     *
041     * <p>It also provides some modification methods that are aspect compliant.
042     *
043     * <p>For the moment, default meta informations are setted by the
044     * <code>ClassRepository</code> class using some naming
045     * conventions. In a close future, these informations will be deduced
046     * from the class bytecodes analysis at load-time.
047     *
048     * @see java.lang.reflect.Field
049     * @see #getWritingMethods()
050     * @see #getAccessingMethods()
051     *
052     * @author Renaud Pawlak
053     * @author Laurent Martelli
054     */
055    
056    public class FieldItem extends MemberItem {
057    
058        static Class wrappeeClass = ClassRepository.wrappeeClass;
059        static Logger logger = Logger.getLogger("rtti.field");
060    
061        /**
062         * Transforms a field items array into a fields array containing
063         * the <code>java.lang.reflect</code> fields wrapped by the method
064         * items.<p>
065         *
066         * @param fieldItems the field items
067         * @return the actual fields in <code>java.lang.reflect</code>
068         */
069    
070        public static Field[] toFields(FieldItem[] fieldItems) {
071            Field[] res = new Field[fieldItems.length];
072            for (int i=0; i<fieldItems.length; i++) {
073                if (fieldItems[i] == null) {
074                    res[i] = null;
075                } else {
076                    res[i] = fieldItems[i].getActualField();
077                }
078            }
079            return res;
080        }
081    
082        /**
083         * Default contructor to create a new field item object.<p>
084         *
085         * @param delegate the <code>java.lang.reflect.Field</code> actual
086         * meta item */
087    
088        public FieldItem(Field delegate, ClassItem parent) 
089            throws InvalidDelegateException 
090        {
091            super(delegate,parent);
092            name = delegate.getName();
093        }
094    
095        public FieldItem(ClassItem parent) {
096            super(parent);
097        }
098    
099        /**
100         * Creates a calculated FieldItem
101         * @param name name of the field
102         * @param getter the getter method of the field
103         */
104        public FieldItem(String name, MethodItem getter, ClassItem parent) {
105            super(parent);
106            isCalculated = true;
107            this.name = name;
108            addAccessingMethod(getter);
109            setGetter(getter);
110            getter.addAccessedField(this);
111            getter.setReturnedField(this);
112        }
113    
114        /**
115         * Creates a FieldItem with specific getter and setter
116         * @param name name of the field
117         * @param getter the getter method of the field
118         * @param setter the setter method of the field
119         */
120        public FieldItem(String name, MethodItem getter, MethodItem setter, 
121                         ClassItem parent) 
122        {
123            super(parent);
124            this.name = name;
125            setGetter(getter);
126            setSetter(setter);
127        }
128    
129        /**
130         * Creates an expression FieldItem
131         * @param expression expression of the field
132         */
133        public FieldItem(String expression, List path, ClassItem parent) {
134            super(parent);
135            isCalculated = true;
136            isExpression = true;
137            this.name = expression;
138            this.path = path;
139            type = getPathTop().getType();
140        }
141    
142        boolean isCalculated = false;
143        boolean isExpression = false;
144        String name;
145        Class type;
146    
147        // Used if isExpression==true FieldItem[]
148        List path;
149    
150        public FieldItem getPathTop() {
151            return (FieldItem)path.get(path.size()-1);
152        }
153    
154        /**
155         * If the field does not have a value for the request attribute,
156         * tries on the superclass.
157         */
158        public final Object getAttribute(String name) {
159            Object value = super.getAttribute(name);
160            if (value==null) {
161                ClassItem parent = ((ClassItem)getParent()).getSuperclass();
162                if (parent!=null) {
163                    if (parent.hasField(getName()))  {
164                        value = parent.getField(this.name).getAttribute(name);
165                    }
166                }
167            }
168            if (isExpression && value==null) {
169                return ((FieldItem)path.get(path.size()-1)).getAttribute(name);
170            }
171            return value;
172        }
173    
174    
175        MethodItem[] accessingMethods;
176    
177        /**
178         * Get the methods that access this field for reading.<p>
179         *
180         * @return value of accessingMethods.
181         */
182        public final MethodItem[] getAccessingMethods() {
183            ((ClassItem)parent).buildFieldInfo();
184            return accessingMethods;
185        }
186       
187        public final boolean hasAccessingMethods() {
188            ((ClassItem)parent).buildFieldInfo();
189            return accessingMethods!=null && accessingMethods.length>0;
190        }
191    
192        /**
193         * Set the methods that access this field for reading.<p>
194         *
195         * @param accessingMethods value to assign to accessingMethods.
196         */
197    
198        public final void setAccessingMethods(MethodItem[] accessingMethods) {
199            this.accessingMethods = accessingMethods;
200        }
201    
202        /**
203         * Add a new accessing method for this field.<p>
204         *
205         * @param accessingMethod the method to add
206         */
207    
208        public final void addAccessingMethod(MethodItem accessingMethod) {
209            if (accessingMethods == null) {
210                accessingMethods = new MethodItem[] { accessingMethod };
211            } else {
212                MethodItem[] tmp = new MethodItem[accessingMethods.length + 1];
213                System.arraycopy(accessingMethods, 0, tmp, 0, accessingMethods.length);
214                tmp[accessingMethods.length] = accessingMethod;
215                accessingMethods = tmp;
216            }
217        }
218       
219        MethodItem[] writingMethods;
220       
221        /**
222         * Get the methods that access this field for writing.<p>
223         *
224         * @return value of writingMethods.
225         */
226        public final MethodItem[] getWritingMethods() {
227            ((ClassItem)parent).buildFieldInfo();
228            return writingMethods;
229        }
230        public final boolean hasWritingMethods() {
231            ((ClassItem)parent).buildFieldInfo();
232            return writingMethods!=null && writingMethods.length>0;
233        }
234    
235        /**
236         * Set the methods that access this field for writing.<p>
237         *
238         * @param writingMethods value to assign to writingMethods.
239         */
240    
241        public final void setWritingMethods(MethodItem[] writingMethods) {
242            this.writingMethods = writingMethods;
243        }
244    
245        /**
246         * Add a new writing method for this field.<p>
247         *
248         * @param writingMethod the method to add
249         */
250    
251        public final void addWritingMethod(MethodItem writingMethod) {
252            if (writingMethods == null) {
253                writingMethods = new MethodItem[] { writingMethod };
254            } else {
255                MethodItem[] tmp = new MethodItem[writingMethods.length + 1];
256                System.arraycopy(writingMethods, 0, tmp, 0, writingMethods.length);
257                tmp[writingMethods.length] = writingMethod;
258                writingMethods = tmp;
259            }
260        }
261       
262        /**
263         * Remove accessing and writing methods
264         */
265        public void clearMethods() {
266            if (writingMethods != null) {
267                for (int i=0; i<writingMethods.length; i++) {
268                    writingMethods[i].removeWrittenField(this);
269                }
270                writingMethods = null;
271            }
272            if (accessingMethods != null) {
273                for (int i=0; i<accessingMethods.length; i++) {
274                    accessingMethods[i].removeAccessedField(this);
275                }
276                accessingMethods = null;
277            }
278        }
279    
280        FieldItem[] dependentFields = FieldItem.emptyArray;
281        /**
282         * @see #getDependentFields()
283         */
284        public final void addDependentField(FieldItem field) {
285            FieldItem[] tmp = new FieldItem[dependentFields.length+1];
286            System.arraycopy(dependentFields, 0, tmp, 0, dependentFields.length);
287            tmp[dependentFields.length] = field;
288            dependentFields = tmp;
289            ClassItem superClass = getClassItem().getSuperclass();
290            if (superClass!=null) {
291                FieldItem superField = superClass.getFieldNoError(getName());
292                if (superField!=null)
293                    superField.addDependentField(field);
294            }
295        }
296        /**
297         * Returns an array of calculated fields which depend on the field.
298         * @see #addDependentField(FieldItem)
299         */
300        public final FieldItem[] getDependentFields() {
301            return dependentFields;
302        }
303    
304        /**
305         * Get the field represented by this field item.<p>
306         *
307         * @return the actual field
308         */
309    
310        public final Field getActualField() {
311            return (Field)delegate;
312        }
313    
314        /**
315         * Returns proper substance to invoke methods on for expression
316         * fields.
317         */
318        public Object getSubstance(Object substance) {
319            if (isExpression) {
320                Iterator it = path.iterator();
321                while (it.hasNext() && substance!=null) {
322                    FieldItem field = (FieldItem)it.next();
323                    if (!it.hasNext()) {
324                        return substance;
325                    }
326                    substance = field.getThroughAccessor(substance);
327                }
328                return null;
329            } else {
330                return substance;
331            }
332        }
333    
334        /**
335         * Returns the substances list for expression fields.
336         * 
337         * <p>Since expression fields can contain collections, there can be
338         * multiple substances related to them.
339         * @param substance
340         * @return a list (can be empty but never null)
341         */    
342        public List getSubstances(Object substance) {
343            Vector substances = new Vector();
344            substances.add(substance);
345            if (isExpression) {
346                Iterator it = path.iterator();
347                while (it.hasNext() && substance!=null) {
348                    FieldItem field = (FieldItem)it.next();
349                    if (!it.hasNext()) {
350                        break;
351                    }
352                    Vector current = substances;
353                    substances = new Vector(current.size());
354                    for(Iterator j = current.iterator(); j.hasNext();) {
355                        Object o = j.next();
356                        if (field instanceof CollectionItem) {
357                            substances.addAll(
358                                ((CollectionItem)field).getActualCollectionThroughAccessor(o));
359                        } else {
360                            substances.add(field.getThroughAccessor(o));
361                        }
362                    }
363                }
364            }
365            return substances;
366        }
367    
368        /**
369         * Returns the actual field. In the case of an expression field,
370         * this is the last element of the path, otherwise it is the field
371         * itself.
372         */
373        public FieldItem getField() {
374            if (isExpression) {
375                return getPathTop();
376            } else {
377                return this;
378            }
379        }
380    
381        /**
382         * Get the value of this field item for a given object.<p>
383         *
384         * @param object the object that supports the field
385         * @return the field value in the given object
386         * @see #set(Object,Object) 
387         */
388        public final Object get(Object object) {
389            Object ret = null;
390            try {
391                ret = ((Field)delegate).get(object);
392            } catch (Exception e) {
393                logger.error("Failed to get value of field "+this+" for "+object+
394                             (object!=null?(" ("+object.getClass().getName()+")"):""),
395                             e);
396            }
397            return ret;
398        }
399    
400        /**
401         * Get a field value through accessor if it exists.<p>
402         *
403         * @param substance the object that supports the field
404         */
405        public Object getThroughAccessor(Object substance) 
406        {
407            ((ClassItem)parent).buildFieldInfo();
408            if (isExpression) {
409                Iterator it = path.iterator();
410                while (it.hasNext() && substance!=null) {
411                    FieldItem field = (FieldItem)it.next();
412                    substance = field.getThroughAccessor(substance);
413                }
414                return substance;
415            } else {
416                Object value = null;
417                String name;
418                MethodItem getter = getGetter();
419                if (getter!=null) {
420                    //Log.trace("rtti.field",this+": invoking "+accessors[i]);
421                    return (getter.invoke(substance,ExtArrays.emptyObjectArray));
422                } else {
423                    logger.warn("No accessor found for field " + this);
424                    return get(substance);
425                }
426            }
427        }
428    
429        /**
430         * Gets the leaves of an object path.
431         *
432         * <p>If path is not an expression field, returns
433         * <code>getActualCollectionThroughAccessor()</code> if it's a
434         * CollectionItem or a CollectionItem containing
435         * <code>getThroughAccessor()</code> otherwise. If path is an
436         * expression field, getPathLeaves() is called recursively on all
437         * the component fields of the expression field.</p> 
438         *
439         * @param path the path
440         * @param root the root object the path will be applied to.
441         */
442        public static Collection getPathLeaves(FieldItem path, Object root) {
443            ((ClassItem)path.parent).buildFieldInfo();
444    
445            if (path.isExpression) {
446                HashSet currentSet = new HashSet();
447                currentSet.add(root);
448                Iterator it = path.path.iterator();
449                while (it.hasNext()) {
450                    FieldItem field = (FieldItem)it.next();
451                    HashSet newSet = new HashSet();
452                    Iterator j = currentSet.iterator();
453                    while(j.hasNext()) {
454                        Object o = j.next();
455                        newSet.addAll(getPathLeaves(field,o));
456                    }
457                    currentSet = newSet;
458                }
459                return currentSet;
460            } else {
461                if (path instanceof CollectionItem)
462                    return ((CollectionItem)path).getActualCollectionThroughAccessor(root);
463                else {
464                    ArrayList singleton = new ArrayList(1);
465                    singleton.add(path.getThroughAccessor(root));
466                    return singleton;
467                }
468            }
469        }
470    
471        /**
472         * Sets the value of this field item for a given object.<p>
473         *
474         * @param object the object whose field must be set
475         * @param value the value to set
476         * @see #get(Object) 
477         * @see #setConvert(Object,Object)
478         */
479        public final void set(Object object, Object value) 
480            throws IllegalAccessException, IllegalArgumentException
481        {
482            if (value==null && getType().isPrimitive()) {
483                logger.error("Cannot set primitive field "+this+" to null", new Exception());
484            } else {
485                ((Field)delegate).set(object, value);
486            }
487        }
488    
489        /**
490         * Sets the value of this field item for a given object. If the
491         * value is not assignable to the field, tries to convert it.<p>
492         *
493         * <p>It can convert floats and doubles to int or long, and
494         * anything to String.</p>
495         *
496         * @param object the object whose field must be set
497         * @param value the value to set. Must not be null.
498         * @return true if value had to be converted, false otherwise
499         *
500         * @see #set(Object,Object) */
501        public final boolean setConvert(Object object, Object value) 
502            throws IllegalAccessException, IllegalArgumentException, 
503            InstantiationException, InvocationTargetException, NoSuchMethodException
504        {
505            try {
506                set(object,value);
507                return false;
508            } catch(IllegalArgumentException e) {
509                Object convertedValue = RttiAC.convert(value,getType());
510                set(object,convertedValue);
511                return true;
512            }
513        }
514    
515        /**
516         * Sets the value of this field item by using its setter method if
517         * any (else use the <code>set</code> method.
518         *
519         * @param substance the object to set the field of 
520         * @param value the new value of the field
521         * @see #set(Object,Object) 
522         */
523        public final void setThroughWriter(Object substance, Object value) 
524            throws IllegalAccessException, IllegalArgumentException
525        {
526            ((ClassItem)parent).buildFieldInfo();
527            if (isExpression) {
528                Iterator it = path.iterator();
529                while (it.hasNext()) {
530                    FieldItem field = (FieldItem)it.next();
531                    if (it.hasNext()) {
532                        substance = field.getThroughAccessor(substance);
533                    } else {
534                        field.setThroughWriter(substance,value);
535                        return;
536                    }
537                }
538            } else {
539                logger.debug("setThroughWriter "+substance+"."+getName()+","+value);
540                String name;
541                if (setter!=null) {
542                    try {
543                        logger.debug(this+": invoking "+setter);
544                        setter.invoke(substance,new Object[] { value });
545                        return;
546                    } catch (WrappedThrowableException e) {
547                        Throwable target = e.getWrappedThrowable();
548                        if (target instanceof IllegalArgumentException)
549                            logger.error("setThroughWriter: IllegalArgumentException for "+substance+"."+getName()+
550                                      " = "+Strings.hex(value)+"("+value+")");
551                        throw e;
552                    } 
553                }
554            }
555    
556            if (isCalculated()) {
557                throw new RuntimeException("Cannot set calculted field "+getLongName());
558            }
559            logger.warn("No setter found for field "+this);
560            set(substance,value);
561        }
562    
563        /* <em>the</em> setter of the field */
564        MethodItem setter;
565    
566        public MethodItem getSetter() {
567            ((ClassItem)parent).buildFieldInfo();
568            if (setter!=null)
569                return setter;
570            else if (isExpression)
571                return getPathTop().getSetter();
572            else
573                return null;
574        }
575    
576        public void setSetter(MethodItem setter) {
577            if (this.setter!=null) {
578                logger.warn("overriding setter "+
579                            this.setter.getFullName()+" for field "+
580                            this+" with "+setter.getFullName());
581            }
582            this.setter = setter;
583            addWritingMethod(setter);
584        }
585    
586        /* <em>the</em> getter of the field */
587        MethodItem getter;
588        /**
589         * Returns <em>the</em> getter of the field, if any.
590         */
591        public MethodItem getGetter() {
592            if (!isExpression)
593                ((ClassItem)parent).buildFieldInfo();
594            return getter;
595        }
596    
597        public void setGetter(MethodItem getter) {
598            ((ClassItem)parent).buildFieldInfo();
599            if (this.getter!=null) {
600                if (getType().isAssignableFrom(getter.getType())) {
601                    setType(getter.getType());
602                } else {
603                    logger.warn("overriding getter "+
604                                this.getter.getFullName()+
605                                " for field "+this+" with "+getter.getFullName());
606                }
607            }
608            if (!isCalculated && getType()!=getter.getType() && 
609                getType().isAssignableFrom(getter.getType())) {
610                setType(getter.getType());
611            }
612            this.getter = getter;
613            addAccessingMethod(getter);      
614        }
615    
616        public String getName() {
617            return name;
618        }
619    
620        public Class getType() {
621            if (type!=null)
622                return type;
623            else if (getter!=null)
624                return getter.getType();
625            else
626                return ((Field)delegate).getType();
627        }
628    
629        public void setType(Class type) {
630            logger.info("overriding field type of "+this+
631                        " with "+type.getName());
632            this.type = type;
633        }
634    
635        /**
636         * Tells if this field item represents a primitive type (that is to
637         * say it is of a type that is not a reference towards a Jac
638         * object).
639         *
640         * <p>Allways returns false on collections (use
641         * <code>isWrappable</code> to know if the field is wrappable).
642         *
643         * @return true if primitive
644         * @see #isReference() */
645     
646        public boolean isPrimitive() {
647            return !isReference();
648        }
649    
650        /**
651         * Tells if this field item represents a reference type (that is to
652         * say it is of a type that is a reference towards a Jac
653         * object).
654         *
655         * <p>Allways returns false on collections (use
656         * <code>isWrappable</code> to know if the field is wrappable).
657         *
658         * @return true if reference
659         * @see #isPrimitive()
660         * @see #isWrappable(Object) 
661         */
662    
663        public boolean isReference() {
664            return wrappeeClass.isAssignableFrom(getType());
665        }
666    
667        /**
668         * Tells if the field item represents a wrappable type.
669         *
670         * <p>This method can be used on all kinds of field item including
671         * collections (on contrary to <code>isPrimitive</code> and
672         * <code>isReference</code>).
673         *
674         * @return true if wrappable
675         * @see #isPrimitive()
676         * @see #isReference() */
677    
678        public boolean isWrappable(Object substance) {
679            Object value = get(substance);
680            if (value==null) {
681                return wrappeeClass.isAssignableFrom(getType());
682            }
683            return wrappeeClass.isAssignableFrom(value.getClass());
684        }
685    
686        /**
687         * Tells whether the field is transient or not.
688         */
689        public boolean isTransient() {
690            return isCalculated ? true 
691                : Modifier.isTransient(((Member)delegate).getModifiers());
692        }
693       
694        public boolean isFinal() {
695            return isCalculated ? false
696                : Modifier.isFinal(((Member)delegate).getModifiers());
697        }
698    
699        public boolean isStatic() {
700            return isCalculated ? ( isExpression ? lastField().isStatic() : getter.isStatic() )
701                : Modifier.isStatic(((Member)delegate).getModifiers());
702        }
703    
704        public int getModifiers() {
705            if (isCalculated)
706                return Modifier.PUBLIC | Modifier.TRANSIENT; 
707            else
708                return super.getModifiers();
709        }
710    
711        protected FieldItem lastField() {
712            return (FieldItem)path.get(path.size()-1);
713        }
714    
715        /**
716         * Tells whether the field is transient or not.
717         */
718        public boolean isCalculated() {
719            return isCalculated;
720        }
721    
722        /**
723         * Copies this field to an other class.
724         * @param parent the class to copy the field to
725         */
726        public FieldItem clone(ClassItem parent) {
727            FieldItem clone = null;
728            try {
729                if (isCalculated)
730                    clone = new FieldItem(name,getter,parent);
731                else 
732                    clone = new FieldItem((Field)delegate,parent);
733            } catch(Exception e) {
734                logger.error("Failed to clone field "+this);
735            }
736            return clone;
737        }
738    
739        boolean isAggregation = false;
740        public void setAggregation(boolean isAggregation) {
741            this.isAggregation = isAggregation;
742        }
743        public boolean isAggregation() {
744            return isAggregation;
745        }
746    
747        public boolean startsWith(FieldItem field) {
748            return this!=field && name.startsWith(field.getName());
749        }
750    
751        public FieldItem getRelativeField(FieldItem base) {
752            if (base instanceof CollectionItem)
753                return ((CollectionItem)base).getComponentType().getField(name.substring(base.getName().length()+1));
754            else
755                return base.getTypeItem().getField(name.substring(base.getName().length()+1));
756        }
757    
758        FieldItem oppositeRole;
759        public FieldItem getOppositeRole() {
760            if (oppositeRole!=null)
761                return oppositeRole;
762            else
763                return (FieldItem)getAttribute(RttiAC.OPPOSITE_ROLE);
764        }
765        public void setOppositeRole(FieldItem oppositeRole) {
766            this.oppositeRole = oppositeRole;
767            setAttribute(RttiAC.OPPOSITE_ROLE,oppositeRole);
768        }
769    
770        public static final FieldItem[] emptyArray = new FieldItem[0];
771    }