View Javadoc

1   /*
2      Copyright 2002-2006 Martin van den Bemt
3   
4      Licensed under the Apache License, Version 2.0 (the "License");
5      you may not use this file except in compliance with the License.
6      You may obtain a copy of the License at
7   
8          http://www.apache.org/licenses/LICENSE-2.0
9   
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15  */
16  package org.xulux.dataprovider.bean;
17  
18  import java.lang.reflect.InvocationTargetException;
19  import java.lang.reflect.Method;
20  import java.lang.reflect.Modifier;
21  import java.util.ArrayList;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.StringTokenizer;
25  
26  import org.xulux.api.dataprovider.IConverter;
27  import org.xulux.api.dataprovider.IField;
28  import org.xulux.api.dataprovider.IMapping;
29  import org.xulux.utils.ClassLoaderUtils;
30  
31  /**
32   * This class contains all the symantics for working
33   * on a bean.
34   *
35   * @todo investigate if this can be "static" (or registered
36   *       via a registry), since there aren't unlimited data beans
37   *       normally.
38   * @todo Need to check thread safety.
39   * @todo This class should contain all the logic for conversion
40   *       to primitive types.
41   *
42   * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
43   * @version $Id: BeanField.java,v 1.1 2005/12/18 12:58:23 mvdb Exp $
44   */
45  public class BeanField implements IField
46  {
47  
48      /**
49       * The official name of the field
50       */
51      private String name;
52  
53      /**
54       * The method of the field
55       */
56      private Method method;
57  
58      /**
59       * The alias of the field,
60       * This is the name to call this field.
61       */
62      private String alias;
63  
64      /**
65       * Place holder for the setter
66       * associated with the get method
67       */
68      private Method changeMethod;
69  
70      /**
71       * Specifies if this beanField is derived form a
72       * baseType or not
73       */
74      private boolean baseType;
75  
76      /**
77       * The parameterlist
78       */
79      private List parameterList;
80  
81      /**
82       * Parameterlist for the setmethod.
83       * is null when equal to the parameterList
84       */
85      private List setParameterList;
86  
87      /**
88       * Temporary container for args.
89       * Needs to be removed when we switch
90       * to non fixed arguments.
91       */
92      private Object[] args;
93  
94      /**
95       * Holds the real field.
96       */
97      private String realField;
98  
99      /**
100      * Temporary real field when processing strange naming
101      */
102     private boolean tempRealField;
103 
104     private BeanMapping mapping;
105 
106 
107     /**
108      * Constructor for BeanField.
109      */
110     public BeanField()
111     {
112     }
113 
114     /**
115      * Contructor that takes a method
116      *
117      * @param method - the method to use for reading the value
118      */
119     public BeanField(Method method)
120     {
121         setMethod(method);
122     }
123 
124     /**
125      * @return specifies if the field is just for display
126      *          (eg doesn't have a setter method)
127      */
128     public boolean isReadOnly() {
129         if (changeMethod == null) {
130             return true;
131         }
132         return false;
133     }
134 
135     /**
136      * Sets a new value in the field
137      * Exceptions will be eaten.
138      * @param bean - the bean to set the value on
139      * @param value - the value to set to the bean
140      * @return false on failure or if field is read only
141      * @todo add some more intensive testing
142      */
143     public Object setValue(Object bean, Object value)
144     {
145         if (isReadOnly())
146         {
147 //            if (log.isWarnEnabled()) {
148 //                log.warn("Field " + getName() + " is readonly or couldn't find the set Method");
149 //            }
150             return bean;
151         }
152         try
153         {
154             if (getRealField() != null) {
155                 Class retType = getType();
156                 BeanMapping mapping = (BeanMapping) getMapping().getDataProvider().getMapping(retType);
157 //                log.warn("Mapping : "+mapping.getFields());
158 //                System.out.println("getMethod : " + getMethod());
159                 Object childObject = getMethod().invoke(bean, getArgs());
160 //                log.warn("ChildObject ; "+childObject);
161                 if (childObject == null) {
162                     // try to create the object!
163                     // @todo mvdb : need to check which conditions actually are using the parameterList..
164                     //childObject = ClassLoaderUtils.getObjectFromClass(retType, getBeanParameterValues(parameterList));
165                     childObject = ClassLoaderUtils.getObjectFromClass(retType, getBeanParameterValues(new ArrayList()));
166 //                    log.warn("ChildObject ; "+childObject);
167                     getChangeMethod().invoke(bean, getSetMethodArgs(childObject));
168 
169                     // @todo Preform some magic to set the object to the bean!!
170                     if (childObject == null) {
171 //                        if (log.isWarnEnabled()) {
172 //                            log.warn("Cannot set value on " + toString() + "  Please set the value in a rule "
173 //                            +  "or provide an empty constructor");
174 //                        }
175                         return bean;
176                     }
177                 }
178                 BeanMapping childMapping = (BeanMapping) getMapping().getDataProvider().getMapping(childObject);
179 //                log.warn("childMapping : "+childMapping.getFields());
180                 IField field = childMapping.getField(getRealField());
181 //                log.warn("cf : "+field);
182                 return field.setValue(childObject, value);
183             }
184             try {
185 //                log.warn("Change method : "+this.changeMethod);
186 //                log.warn("args : "+Arrays.asList(getSetMethodArgs(value)));
187                 this.changeMethod.invoke(bean, getSetMethodArgs(value));
188                 realField = null;
189             } catch (IllegalArgumentException iae) {
190 //                if (log.isWarnEnabled()) {
191 //                    log.warn("Invalid argument " + value.getClass().getName() + " for method " + this.changeMethod, iae);
192 //                }
193             }
194         }
195         catch (IllegalAccessException e)
196         {
197 //            if (log.isTraceEnabled()) {
198 //                log.trace("Exception occured ", e);
199 //            }
200         }
201         catch (InvocationTargetException e)
202         {
203 //            if (log.isTraceEnabled()) {
204 //                log.trace("Exception occured ", e);
205 //            }
206         }
207         catch (Exception e) {
208 //            if (log.isWarnEnabled()) {
209 //                log.warn("Unexcpected exception ", e);
210 //            }
211         }
212         return bean;
213     }
214 
215     /**
216      * @param list the list of objects
217      * @return a list of values from a list with beanparameter objects.
218      */
219     private List getBeanParameterValues(List list) {
220         if (list == null || list.size() == 0) {
221             return null;
222         }
223         ArrayList valueList = new ArrayList();
224         for (int i = 0; i < list.size(); i++) {
225             Object object = list.get(i);
226             if (object instanceof BeanParameter) {
227                 valueList.add(((BeanParameter) object).getObject());
228             }
229         }
230 //        log.warn("valueList : "+valueList);
231         return valueList;
232     }
233 
234     /**
235      * Get the value from the specified bean.
236      * Exceptions will be eaten.
237      *
238      * @param bean - the bean to get the value from
239      * @return the value of the field
240      * or null when an error has happened or no value exists
241      *
242      */
243     public Object getValue(Object bean)
244     {
245         try
246         {
247             if (this.realField != null) {
248                 // gets the real value to get
249                 // since this field is just the parent..
250                 Class retType = getType();
251                 if (retType == Object.class) {
252                     // we need to figure out which returntype we REALLY have..
253                     Object retBean = getMethod().invoke(bean, getArgs());
254                     retType = retBean.getClass();
255                 }
256                 BeanMapping mapping = (BeanMapping) getMapping().getDataProvider().getMapping(retType);
257                 IField field = mapping.getField(realField);
258                 if (field != null) {
259                     Object fieldValue = field.getValue(getMethod().invoke(bean, getArgs()));
260                     return fieldValue;
261                 }
262             }
263             else {
264                 if (bean != null) {
265 //                    System.out.println("bean : " + bean);
266 //                    System.out.println("Args : " + getArgs());
267 //                    System.out.println("method : " + method);
268                     return this.method.invoke(bean, getArgs());
269                 } else {
270                     // try to see if it is static..
271 
272                     if (Modifier.isStatic(getMethod().getModifiers())) {
273                         return getMethod().invoke(null, getArgs());
274                     }
275 //                    if (log.isDebugEnabled()) {
276 //                        log.debug("no data found for " + getName() + ", alias " + getAlias() + ", realfield " + realField);
277 //
278 //                    }
279                 }
280             }
281         }
282         catch (IllegalAccessException e)
283         {
284 //            if (log.isTraceEnabled())
285 //            {
286 //                log.trace("Exception occured ", e);
287 //            }
288         }
289         catch (InvocationTargetException e)
290         {
291 //            if (log.isWarnEnabled())
292 //            {
293 //                log.warn("Exception occured ", e.getTargetException());
294 //            }
295         }
296         return null;
297     }
298 
299     /**
300      * @todo for now we assume all is hardcoded data need to fix that..
301      * @return the list of parameters to pass into
302      *          the method.
303      */
304     protected Object[] getArgs() {
305         if (this.args != null) {
306             return this.args;
307         }
308         if (parameterList == null) {
309             return null;
310         }
311         int listCounter = parameterList.size();
312         this.args = new Object[listCounter];
313         Iterator it = parameterList.iterator();
314         listCounter = 0;
315         while (it.hasNext()) {
316             BeanParameter parm = (BeanParameter) it.next();
317             this.args[listCounter] = parm.getObject();
318             listCounter++;
319         }
320         return this.args;
321     }
322 
323     /**
324      * No nullchecking is done in this method,
325      * the caller should check if it can correctly
326      * call this method.
327      * (eg isReadOnly should be false)
328      *
329      * @param value get the setmethod for the specified value
330      * @return the list of methods to pass into a
331      *          setmethod to correctly set a value.
332      */
333     protected Object[] getSetMethodArgs(Object value) {
334         ArrayList parms = new ArrayList();
335         Class[] clz = this.changeMethod.getParameterTypes();
336         int parmSize = 0;
337         if (parameterList != null) {
338             parmSize = parameterList.size();
339         }
340         int clzSize = clz.length;
341         // actually always the case I guess??
342        if (clzSize == 1) {
343             Class parmType = clz[0];
344             if (value != null) {
345                 if (parmType == value.getClass()) {
346                     return new Object[] {value};
347                 }
348             }
349         }
350         if (parmSize <= clzSize && clzSize != 0) {
351             if (parmSize == 0 && clzSize == 1) {
352                 if (clz[0] == String.class) {
353                     if (value == null) {
354                         return new Object[] {
355                             value
356                             };
357                     } else {
358                         return new Object[] {
359                             value.toString()
360                             };
361                     }
362                 } else {
363                     IConverter converter = getMapping().getDataProvider().getConverter(clz[0]);
364                     if (converter != null) {
365                         Object newValue = converter.getBeanValue(value);
366                         if (newValue != null) {
367                             value = newValue;
368                         }
369                     }
370                     return new Object[] {
371                         value
372                         };
373                 }
374             }
375             /* simple logistics :
376              * eg getXXX(String) should have a setXXX(Strint, Value);
377              * @todo Make more advanced, or look at external package
378              *        to handle this.
379              */
380 
381             if (parmSize == 1 && clzSize == 2) {
382                 Object[] retValue = new Object[clzSize];
383                 int currentParm = 1;
384                 retValue[0] = ((BeanParameter) parameterList.get(0)).getObject();
385                 if (clz[0] != retValue[0].getClass()) {
386                     retValue[1] = retValue[0];
387                     currentParm = 0;
388                 }
389                 if (clz[currentParm] == String.class) {
390                     retValue[currentParm] = value.toString();
391                 } else {
392                     /* We should try to make the type the same
393                      * so if the value is a string and the type
394                      * an integer, some conversion needs to take
395                      * place
396                      * @todo Fix this :)
397                      */
398                      if (value != null && clz[currentParm] == value.getClass()) {
399                         retValue[currentParm] = value;
400                      } else {
401 //                         log.warn("Cannot set value of type " + ((value == null)?"Unknown":value.getClass().getName()) + " for the parameter " + clz[1]);
402                          // we are hardheaded and set it anyway, so we can
403                          // get some kind of exception.
404                          // @todo FIX!!
405                          retValue[currentParm] = value;
406                      }
407                 }
408                 return retValue;
409             }
410 
411         }
412         return parms.toArray();
413     }
414 
415 
416     /**
417      * Returns the name.
418      *
419      * @return String
420      */
421     public String getName()
422     {
423         return this.name;
424     }
425 
426     /**
427      * Returns the method.
428      *
429      * @return Method
430      */
431     public Method getMethod()
432     {
433         return method;
434     }
435 
436     /**
437      * Sets the method.
438      *
439      * @param method The method to set
440      */
441     public void setMethod(Method method)
442     {
443         this.method = method;
444         if (method == null) {
445             return;
446         }
447         this.name = method.getName().toLowerCase();
448         if (this.name.startsWith("get")) {
449             this.name = this.name.substring("get".length());
450         } else if (this.name.startsWith("is")) {
451             this.name = this.name.substring("is".length());
452         }
453     }
454 
455     /**
456      *
457      * @return String representation of the beanField
458      */
459     public String toString()
460     {
461         StringBuffer sb = new StringBuffer();
462         if (getMethod() != null){
463             sb.append(getMethod().getName());
464         }
465         sb.append("[");
466         sb.append("Name=");
467         sb.append(getName());
468         sb.append(",");
469         sb.append("Alias=");
470         sb.append(getAlias());
471         sb.append(",");
472         sb.append("realField=");
473         sb.append(getRealField());
474         sb.append("]");
475         return sb.toString();
476     }
477 
478     /**
479      * Checks if we are talking about the same field..
480      *
481      * @param object - the object to perform the equals on
482      * @return If the object is a String it will compare it with
483      *          the Alias (case insensitive).
484      *          If the object is another BeanField, it will see
485      *          if the declaring class and the method name is the same
486      *          In all other cases it will return false
487      */
488     public boolean equals(Object object)
489     {
490         if (object instanceof String)
491         {
492             if (getAlias() != null && getAlias().equalsIgnoreCase(object.toString()))
493             {
494                 return true;
495             } else if (getMethod() != null && getMethod().getName().equalsIgnoreCase(object.toString())) {
496                 // if the method name is the same as this
497                 // return true..
498                 return true;
499             }
500         }
501         else if (object instanceof BeanField) {
502             if (object == this) {
503                 return true;
504             }
505             Method tmpMethod = ((BeanField) object).getMethod();
506             if (tmpMethod != null) {
507                 if (tmpMethod.getDeclaringClass().equals(this.getMethod().getDeclaringClass())
508                      && tmpMethod.getName().equals(this.getMethod().getName())) {
509                     return true;
510                 }
511             }
512         }
513         return false;
514     }
515 
516     /**
517      * Returns the baseType.
518      *
519      * @return boolean
520      */
521     public boolean isBaseType()
522     {
523         return baseType;
524     }
525 
526     /**
527      * Sets the baseType.
528      *
529      * @param baseType The baseType to set
530      */
531     public void setBaseType(boolean baseType)
532     {
533         this.baseType = baseType;
534     }
535 
536     /**
537      * @param method - the change method for this field.
538      */
539     public void setChangeMethod(Method method)
540     {
541         this.changeMethod = method;
542     }
543 
544     /**
545      * Convenience method to set the changemethod.
546      * It will figure out which changemethod it will
547      * be..
548      * @param clazz the class to investigate
549      * @param name the name of the change method
550      */
551     public void setChangeMethod(Class clazz, String name) {
552         Method[] methods = clazz.getMethods();
553         //System.out.println("methods : "+Arrays.asList(methods));
554         for (int i = 0; i < methods.length; i++) {
555             Method m = methods[i];
556             if (m.getName().equalsIgnoreCase(name)) {
557                 //System.out.println("Found changeMethod :"+m);
558                 setChangeMethod(m);
559                 break;
560             }
561         }
562         if (!name.startsWith("set")) {
563             setChangeMethod(clazz, "set" + name);
564         }
565     }
566 
567     /**
568      * Returns the alias.
569      *
570      * @return String
571      */
572     public String getAlias()
573     {
574         if (alias == null)
575         {
576             return getName();
577         }
578         return alias;
579     }
580 
581     /**
582      * Sets the alias.
583      *
584      * @param alias The alias to set
585      */
586     public void setAlias(String alias)
587     {
588         this.alias = alias;
589     }
590 
591     /**
592      * Sets the parameters of the beanfield.
593      * This will overwrite all previous values.
594      * Use addParameter instead if you want to preserve them
595      *
596      * @param parameters the parameters to use
597      */
598     public void setParameters(List parameters) {
599         this.parameterList = parameters;
600     }
601 
602     /**
603      *
604      * @return a list with currently know parameters or null
605      *          when no parameters are known.
606      */
607     public List getParameters() {
608         return this.parameterList;
609     }
610 
611     /**
612      * Add a single parameter to the parameter list
613      * @param parameter the parameter to add
614      */
615     public void addParameter(BeanParameter parameter) {
616         if (parameterList == null) {
617             parameterList = new ArrayList();
618         }
619         parameterList.add(parameter);
620     }
621 
622     /**
623      * @param realField the real field to use
624      */
625     public void setRealField(String realField) {
626         this.realField = realField;
627     }
628 
629     /**
630      * Set a temporary realfield, which is not dictionary
631      * based.
632      * @param realField the temporary realfield
633      */
634     public void setTempRealField(String realField) {
635         this.realField = realField;
636         this.tempRealField = true;
637     }
638 
639     /**
640      * @return if this field has a temporary realfield
641      */
642     public boolean hasTempRealField() {
643         return this.tempRealField;
644     }
645 
646     /**
647      * Remove the temporaray real field
648      */
649     public void removeTempRealField() {
650         this.realField = null;
651         this.tempRealField = false;
652     }
653 
654     /**
655      *
656      * @return the real underlying field.
657      */
658     public String getRealField() {
659         return this.realField;
660     }
661 
662     /**
663      *
664      * @return the returntype of the getmethod.
665      */
666     public Class getType() {
667         return this.method.getReturnType();
668     }
669 
670     /**
671      * @return the changemethod
672      */
673     protected Method getChangeMethod() {
674         return this.changeMethod;
675     }
676 
677     /**
678      * @see org.xulux.api.dataprovider.IField#getMapping()
679      */
680     public IMapping getMapping() {
681         return this.mapping;
682     }
683     
684     /**
685      * Convenience method..
686      * @return the mapping
687      */
688     BeanMapping getBeanMapping() {
689         return this.mapping;
690     }
691         
692     /**
693      * Set the mapping for reference
694      * @param mapping the mapping
695      */
696     public void setMapping(BeanMapping mapping) {
697         this.mapping = mapping;
698     }
699 
700     /**
701      * @see org.xulux.api.dataprovider.IField#setProperty(java.lang.String, java.lang.String)
702      */
703     public void setProperty(String name, String value) {
704         if (name == null) {
705             return;
706         }
707         if (name.equalsIgnoreCase("alias")) {
708             setAlias(value);
709         } else if (name.equalsIgnoreCase("parameter")) {
710             StringTokenizer stn = new StringTokenizer(value, ",");
711             addParameter(new BeanParameter(stn.nextToken(), stn.nextToken()));
712         } else if (name.equalsIgnoreCase("changemethod")) {
713             StringTokenizer stn = new StringTokenizer(value, ",");
714             String className = stn.nextToken();
715             Class clazz = ClassLoaderUtils.getClass(className);
716             setChangeMethod(clazz, stn.nextToken());
717         }
718     }
719 
720 }