KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > jac > core > rtti > FieldItem


1 /*
2   Copyright (C) 2001-2003 Renaud Pawlak <renaud@aopsys.com>,
3                           Laurent Martelli <laurent@aopsys.com>
4   
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU Lesser General Public License as
7   published by the Free Software Foundation; either version 2 of the
8   License, or (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   GNU Lesser General Public License for more details.
14
15   You should have received a copy of the GNU Lesser General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */

18
19 package org.objectweb.jac.core.rtti;
20
21 import java.lang.NoSuchMethodException JavaDoc;
22 import java.lang.reflect.*;
23 import java.lang.reflect.Modifier JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.Collection JavaDoc;
26 import java.util.HashSet JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.List JavaDoc;
29 import java.util.Vector JavaDoc;
30 import org.apache.log4j.Logger;
31 import org.objectweb.jac.util.*;
32
33 /**
34  * This class defines a meta item that corresponds to the
35  * <code>java.lang.reflect.Field</code> meta element.<p>
36  *
37  * <p>In addition to the <code>java.lang.reflect</code> classical
38  * features, this RTTI method element is able to tell if a field is
39  * accessed for reading or writting by a given method.
40  *
41  * <p>It also provides some modification methods that are aspect compliant.
42  *
43  * <p>For the moment, default meta informations are setted by the
44  * <code>ClassRepository</code> class using some naming
45  * conventions. In a close future, these informations will be deduced
46  * from the class bytecodes analysis at load-time.
47  *
48  * @see java.lang.reflect.Field
49  * @see #getWritingMethods()
50  * @see #getAccessingMethods()
51  *
52  * @author Renaud Pawlak
53  * @author Laurent Martelli
54  */

55
56 public class FieldItem extends MemberItem {
57
58     static Class JavaDoc wrappeeClass = ClassRepository.wrappeeClass;
59     static Logger logger = Logger.getLogger("rtti.field");
60
61     /**
62      * Transforms a field items array into a fields array containing
63      * the <code>java.lang.reflect</code> fields wrapped by the method
64      * items.<p>
65      *
66      * @param fieldItems the field items
67      * @return the actual fields in <code>java.lang.reflect</code>
68      */

69
70     public static Field[] toFields(FieldItem[] fieldItems) {
71         Field[] res = new Field[fieldItems.length];
72         for (int i=0; i<fieldItems.length; i++) {
73             if (fieldItems[i] == null) {
74                 res[i] = null;
75             } else {
76                 res[i] = fieldItems[i].getActualField();
77             }
78         }
79         return res;
80     }
81
82     /**
83      * Default contructor to create a new field item object.<p>
84      *
85      * @param delegate the <code>java.lang.reflect.Field</code> actual
86      * meta item */

87
88     public FieldItem(Field delegate, ClassItem parent)
89         throws InvalidDelegateException
90     {
91         super(delegate,parent);
92         name = delegate.getName();
93     }
94
95     public FieldItem(ClassItem parent) {
96         super(parent);
97     }
98
99     /**
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 JavaDoc 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 JavaDoc 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 JavaDoc expression, List JavaDoc 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 JavaDoc name;
145     Class JavaDoc type;
146
147     // Used if isExpression==true FieldItem[]
148
List JavaDoc 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 JavaDoc getAttribute(String JavaDoc name) {
159         Object JavaDoc 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 JavaDoc getSubstance(Object JavaDoc substance) {
319         if (isExpression) {
320             Iterator JavaDoc 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 JavaDoc getSubstances(Object JavaDoc substance) {
343         Vector JavaDoc substances = new Vector JavaDoc();
344         substances.add(substance);
345         if (isExpression) {
346             Iterator JavaDoc it = path.iterator();
347             while (it.hasNext() && substance!=null) {
348                 FieldItem field = (FieldItem)it.next();
349                 if (!it.hasNext()) {
350                     break;
351                 }
352                 Vector JavaDoc current = substances;
353                 substances = new Vector JavaDoc(current.size());
354                 for(Iterator JavaDoc j = current.iterator(); j.hasNext();) {
355                     Object JavaDoc o = j.next();
356                     if (o!=null) {
357                         if (field instanceof CollectionItem) {
358                             substances.addAll(
359                                 ((CollectionItem)field).getActualCollectionThroughAccessor(o));
360                         } else {
361                             substances.add(field.getThroughAccessor(o));
362                         }
363                     }
364                 }
365             }
366         }
367         return substances;
368     }
369
370     /**
371      * Returns the actual field. In the case of an expression field,
372      * this is the last element of the path, otherwise it is the field
373      * itself.
374      */

375     public FieldItem getField() {
376         if (isExpression) {
377             return getPathTop();
378         } else {
379             return this;
380         }
381     }
382
383     /**
384      * Get the value of this field item for a given object.<p>
385      *
386      * @param object the object that supports the field
387      * @return the field value in the given object
388      * @see #set(Object,Object)
389      */

390     public final Object JavaDoc get(Object JavaDoc object) {
391         Object JavaDoc ret = null;
392         try {
393             ret = ((Field)delegate).get(object);
394         } catch (Exception JavaDoc e) {
395             logger.error("Failed to get value of field "+this+" for "+object+
396                          (object!=null?(" ("+object.getClass().getName()+")"):""),
397                          e);
398         }
399         return ret;
400     }
401
402     /**
403      * Get a field value through accessor if it exists.<p>
404      *
405      * @param substance the object that supports the field
406      */

407     public Object JavaDoc getThroughAccessor(Object JavaDoc substance)
408     {
409         ((ClassItem)parent).buildFieldInfo();
410         if (isExpression) {
411             Iterator JavaDoc it = path.iterator();
412             while (it.hasNext() && substance!=null) {
413                 FieldItem field = (FieldItem)it.next();
414                 substance = field.getThroughAccessor(substance);
415             }
416             return substance;
417         } else {
418             Object JavaDoc value = null;
419             String JavaDoc name;
420             MethodItem getter = getGetter();
421             if (getter!=null) {
422                 //Log.trace("rtti.field",this+": invoking "+accessors[i]);
423
return (getter.invoke(substance,ExtArrays.emptyObjectArray));
424             } else {
425                 logger.warn("No accessor found for field " + this);
426                 return get(substance);
427             }
428         }
429     }
430
431     /**
432      * Gets the leaves of an object path.
433      *
434      * <p>If path is not an expression field, returns
435      * <code>getActualCollectionThroughAccessor()</code> if it's a
436      * CollectionItem or a CollectionItem containing
437      * <code>getThroughAccessor()</code> otherwise. If path is an
438      * expression field, getPathLeaves() is called recursively on all
439      * the component fields of the expression field.</p>
440      *
441      * @param path the path
442      * @param root the root object the path will be applied to.
443      */

444     public static Collection JavaDoc getPathLeaves(FieldItem path, Object JavaDoc root) {
445         ((ClassItem)path.parent).buildFieldInfo();
446
447         if (path.isExpression) {
448             HashSet JavaDoc currentSet = new HashSet JavaDoc();
449             currentSet.add(root);
450             Iterator JavaDoc it = path.path.iterator();
451             while (it.hasNext()) {
452                 FieldItem field = (FieldItem)it.next();
453                 HashSet JavaDoc newSet = new HashSet JavaDoc();
454                 Iterator JavaDoc j = currentSet.iterator();
455                 while(j.hasNext()) {
456                     Object JavaDoc o = j.next();
457                     newSet.addAll(getPathLeaves(field,o));
458                 }
459                 currentSet = newSet;
460             }
461             return currentSet;
462         } else {
463             if (path instanceof CollectionItem)
464                 return ((CollectionItem)path).getActualCollectionThroughAccessor(root);
465             else {
466                 ArrayList JavaDoc singleton = new ArrayList JavaDoc(1);
467                 singleton.add(path.getThroughAccessor(root));
468                 return singleton;
469             }
470         }
471     }
472
473     /**
474      * Sets the value of this field item for a given object.<p>
475      *
476      * @param object the object whose field must be set
477      * @param value the value to set
478      * @see #get(Object)
479      * @see #setConvert(Object,Object)
480      */

481     public final void set(Object JavaDoc object, Object JavaDoc value)
482         throws IllegalAccessException JavaDoc, IllegalArgumentException JavaDoc
483     {
484         if (value==null && getType().isPrimitive()) {
485             logger.error("Cannot set primitive field "+this+" to null", new Exception JavaDoc());
486         } else {
487             ((Field)delegate).set(object, value);
488         }
489     }
490
491     /**
492      * Sets the value of this field item for a given object. If the
493      * value is not assignable to the field, tries to convert it.<p>
494      *
495      * <p>It can convert floats and doubles to int or long, and
496      * anything to String.</p>
497      *
498      * @param object the object whose field must be set
499      * @param value the value to set. Must not be null.
500      * @return true if value had to be converted, false otherwise
501      *
502      * @see #set(Object,Object) */

503     public final boolean setConvert(Object JavaDoc object, Object JavaDoc value)
504         throws IllegalAccessException JavaDoc, IllegalArgumentException JavaDoc,
505         InstantiationException JavaDoc, InvocationTargetException, NoSuchMethodException JavaDoc
506     {
507         try {
508             set(object,value);
509             return false;
510         } catch(IllegalArgumentException JavaDoc e) {
511             Object JavaDoc convertedValue = RttiAC.convert(value,getType());
512             set(object,convertedValue);
513             return true;
514         }
515     }
516
517     /**
518      * Sets the value of this field item by using its setter method if
519      * any (else use the <code>set</code> method.
520      *
521      * @param substance the object to set the field of
522      * @param value the new value of the field
523      * @see #set(Object,Object)
524      */

525     public final void setThroughWriter(Object JavaDoc substance, Object JavaDoc value)
526         throws IllegalAccessException JavaDoc, IllegalArgumentException JavaDoc
527     {
528         ((ClassItem)parent).buildFieldInfo();
529         if (isExpression) {
530             Iterator JavaDoc it = path.iterator();
531             while (it.hasNext()) {
532                 FieldItem field = (FieldItem)it.next();
533                 if (it.hasNext()) {
534                     substance = field.getThroughAccessor(substance);
535                 } else {
536                     field.setThroughWriter(substance,value);
537                     return;
538                 }
539             }
540         } else {
541             logger.debug("setThroughWriter "+substance+"."+getName()+","+value);
542             String JavaDoc name;
543             if (setter!=null) {
544                 try {
545                     logger.debug(this+": invoking "+setter);
546                     setter.invoke(substance,new Object JavaDoc[] { value });
547                     return;
548                 } catch (WrappedThrowableException e) {
549                     Throwable JavaDoc target = e.getWrappedThrowable();
550                     if (target instanceof IllegalArgumentException JavaDoc)
551                         logger.error("setThroughWriter: IllegalArgumentException for "+substance+"."+getName()+
552                                   " = "+Strings.hex(value)+"("+value+")");
553                     throw e;
554                 }
555             }
556         }
557
558         if (isCalculated()) {
559             throw new RuntimeException JavaDoc("Cannot set calculted field "+getLongName());
560         }
561         logger.warn("No setter found for field "+this);
562         set(substance,value);
563     }
564
565     /* <em>the</em> setter of the field */
566     MethodItem setter;
567
568     public MethodItem getSetter() {
569         ((ClassItem)parent).buildFieldInfo();
570         if (setter!=null)
571             return setter;
572         else if (isExpression)
573             return getPathTop().getSetter();
574         else
575             return null;
576     }
577
578     public void setSetter(MethodItem setter) {
579         if (this.setter!=null) {
580             logger.warn("overriding setter "+
581                         this.setter.getFullName()+" for field "+
582                         this+" with "+setter.getFullName());
583         }
584         this.setter = setter;
585         addWritingMethod(setter);
586     }
587
588     /* <em>the</em> getter of the field */
589     MethodItem getter;
590     /**
591      * Returns <em>the</em> getter of the field, if any.
592      */

593     public MethodItem getGetter() {
594         if (!isExpression)
595             ((ClassItem)parent).buildFieldInfo();
596         return getter;
597     }
598
599     public void setGetter(MethodItem getter) {
600         ((ClassItem)parent).buildFieldInfo();
601         if (this.getter!=null) {
602             if (getType().isAssignableFrom(getter.getType())) {
603                 setType(getter.getType());
604             } else {
605                 logger.warn("overriding getter "+
606                             this.getter.getLongName()+
607                             " for field "+this+" with "+getter.getLongName());
608             }
609         }
610         if (!isCalculated && getType()!=getter.getType() &&
611             getType().isAssignableFrom(getter.getType())) {
612             setType(getter.getType());
613         }
614         this.getter = getter;
615         addAccessingMethod(getter);
616     }
617
618     public String JavaDoc getName() {
619         return name;
620     }
621
622     public Class JavaDoc getType() {
623         if (type!=null)
624             return type;
625         else if (getter!=null)
626             return getter.getType();
627         else
628             return ((Field)delegate).getType();
629     }
630
631     public void setType(Class JavaDoc type) {
632         logger.info("overriding field type of "+this+
633                     " with "+type.getName());
634         this.type = type;
635     }
636
637     /**
638      * Tells if this field item represents a primitive type (that is to
639      * say it is of a type that is not a reference towards a Jac
640      * object).
641      *
642      * <p>Allways returns false on collections (use
643      * <code>isWrappable</code> to know if the field is wrappable).
644      *
645      * @return true if primitive
646      * @see #isReference() */

647  
648     public boolean isPrimitive() {
649         return !isReference();
650     }
651
652     /**
653      * Tells if this field item represents a reference type (that is to
654      * say it is of a type that is a reference towards a Jac
655      * object).
656      *
657      * <p>Allways returns false on collections (use
658      * <code>isWrappable</code> to know if the field is wrappable).
659      *
660      * @return true if reference
661      * @see #isPrimitive()
662      * @see #isWrappable(Object)
663      */

664
665     public boolean isReference() {
666         return wrappeeClass.isAssignableFrom(getType());
667     }
668
669     /**
670      * Tells if the field item represents a wrappable type.
671      *
672      * <p>This method can be used on all kinds of field item including
673      * collections (on contrary to <code>isPrimitive</code> and
674      * <code>isReference</code>).
675      *
676      * @return true if wrappable
677      * @see #isPrimitive()
678      * @see #isReference() */

679
680     public boolean isWrappable(Object JavaDoc substance) {
681         Object JavaDoc value = get(substance);
682         if (value==null) {
683             return wrappeeClass.isAssignableFrom(getType());
684         }
685         return wrappeeClass.isAssignableFrom(value.getClass());
686     }
687
688     /**
689      * Tells whether the field is transient or not.
690      */

691     public boolean isTransient() {
692         return isCalculated ? true
693             : Modifier.isTransient(((Member)delegate).getModifiers());
694     }
695    
696     public boolean isFinal() {
697         return isCalculated ? false
698             : Modifier.isFinal(((Member)delegate).getModifiers());
699     }
700
701     public boolean isStatic() {
702         return isCalculated ? ( isExpression ? lastField().isStatic() : getter.isStatic() )
703             : Modifier.isStatic(((Member)delegate).getModifiers());
704     }
705
706     public int getModifiers() {
707         if (isCalculated)
708             return Modifier.PUBLIC | Modifier.TRANSIENT;
709         else
710             return super.getModifiers();
711     }
712
713     protected FieldItem lastField() {
714         return (FieldItem)path.get(path.size()-1);
715     }
716
717     /**
718      * Tells whether the field is transient or not.
719      */

720     public boolean isCalculated() {
721         return isCalculated;
722     }
723
724     /**
725      * Copies this field to an other class.
726      * @param parent the class to copy the field to
727      */

728     public FieldItem clone(ClassItem parent) {
729         FieldItem clone = null;
730         try {
731             if (isCalculated)
732                 clone = new FieldItem(name,getter,parent);
733             else
734                 clone = new FieldItem((Field)delegate,parent);
735         } catch(Exception JavaDoc e) {
736             logger.error("Failed to clone field "+this);
737         }
738         return clone;
739     }
740
741     boolean isAggregation = false;
742     public void setAggregation(boolean isAggregation) {
743         this.isAggregation = isAggregation;
744     }
745     public boolean isAggregation() {
746         return isAggregation;
747     }
748
749     public boolean startsWith(FieldItem field) {
750         return this!=field && name.startsWith(field.getName());
751     }
752
753     public FieldItem getRelativeField(FieldItem base) {
754         if (base instanceof CollectionItem)
755             return ((CollectionItem)base).getComponentType().getField(name.substring(base.getName().length()+1));
756         else
757             return base.getTypeItem().getField(name.substring(base.getName().length()+1));
758     }
759
760     FieldItem oppositeRole;
761     public FieldItem getOppositeRole() {
762         if (oppositeRole!=null)
763             return oppositeRole;
764         else
765             return (FieldItem)getAttribute(RttiAC.OPPOSITE_ROLE);
766     }
767     public void setOppositeRole(FieldItem oppositeRole) {
768         this.oppositeRole = oppositeRole;
769         setAttribute(RttiAC.OPPOSITE_ROLE,oppositeRole);
770     }
771
772     public static final FieldItem[] emptyArray = new FieldItem[0];
773 }
774
Popular Tags