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.Method;
19  import java.lang.reflect.Modifier;
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import org.xulux.api.dataprovider.IDataProvider;
24  import org.xulux.api.dataprovider.IField;
25  import org.xulux.api.dataprovider.IMapping;
26  import org.xulux.utils.BooleanUtils;
27  import org.xulux.utils.ClassLoaderUtils;
28  
29  /**
30   * Contains the the Bean to Name mapping
31   * Every field in the mapping is represented by a BeanField
32   *
33   * @todo Probably should use some kind of discovery / bean
34   *       package to handle basic bean patterns. Just for Proof
35   *       of concept I am reinventing the wheel a bit..;)
36   * @todo Also fix the set when realField is used.
37   *
38   * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
39   * @version $Id: BeanMapping.java,v 1.1 2005/12/18 12:58:23 mvdb Exp $
40   */
41  public class BeanMapping implements IMapping
42  {
43  
44      /**
45       * The name of the mapping
46       */
47      private String name;
48      /**
49       * the bean to use for the mapping
50       */
51      private Class bean;
52      /**
53       * use discvovery for this mapping
54       */
55      private boolean discovery;
56      /**
57       * is this bean already discovered
58       */
59      private boolean isDiscovered;
60      /**
61       * the fields of the mapping
62       */
63      private FieldList fields;
64  
65      /**
66       * The dataprovider
67       */
68      private BeanDataProvider dataProvider;
69      /**
70       * Constructor for BeanMapping.
71       */
72      public BeanMapping() {
73      }
74      /**
75       * Creates a BeanMapping with the specified name
76       *
77       * @param name - the beanMapping name
78       */
79      public BeanMapping(String name)
80      {
81          setName(name);
82      }
83  
84      /**
85       *
86       * @return the name of the beanMapping
87       */
88      public String getName()
89      {
90          return name;
91      }
92  
93      /**
94       * Sets the BeanMapping name.
95       *
96       * @param name The name to set
97       */
98      public void setName(String name)
99      {
100         this.name = name;
101     }
102 
103     /**
104      * Returns the bean.
105      *
106      * @return Class
107      */
108     public Class getBean()
109     {
110         return bean;
111     }
112 
113     /**
114      * Sets the bean.
115      *
116      * @param bean The bean to set
117      */
118     public void setBean(Class bean)
119     {
120         this.bean = bean;
121     }
122 
123     /**
124      * Returns the discovery.
125      *
126      * @return boolean
127      */
128     public boolean isDiscovery()
129     {
130         return discovery;
131     }
132 
133     /**
134      * Sets the discovery.
135      *
136      * @param discovery The discovery to set
137      */
138     public void setDiscovery(boolean discovery)
139     {
140         this.discovery = discovery;
141     }
142 
143     /**
144      * Creates a beanfield based on the passed name
145      * If no beanField can be created based on the name
146      * null will be returned.
147      *
148      * @param name - the name of the field methods to discover.
149      * @return the beanfield with the specified name
150      */
151     public IField createField(Object object)
152     {
153         if (!(object instanceof String)) {
154             return null;
155         }
156         String name =(String) object;
157         Method method = findMethod(name, false);
158         Method setMethod = findMethod(name, true);
159         if (method != null) {
160             BeanField field = new BeanField(method);
161             if (setMethod != null) {
162                 field.setChangeMethod(setMethod);
163             }
164             return field;
165         }
166         return null;
167     }
168 
169     /**
170      *
171      * @param name the name the is contained in the method..getXXX/setXXX where XXX is the name
172      *                (case insensitive..)
173      * @param setMethod discover the setMethod (true) or the getMethod (false)
174      * @return the method found
175      */
176     private Method findMethod(String name, boolean setMethod)
177     {
178         if (getBean() == null) {
179 //            if (log.isWarnEnabled()) {
180 //                log.warn("No Bean specified");
181 //            }
182             return null;
183         }
184         Method[] methods = getBean().getMethods();
185         String pre = null;
186         if (setMethod) {
187             pre = "set";
188         } else {
189             pre = "get";
190         }
191         Method result = null;
192         for (int i = 0; i < methods.length; i++) {
193             Method method = methods[i];
194             if (method.getName().equalsIgnoreCase(pre + name)  ||
195                 (!setMethod && method.getName().equalsIgnoreCase("is" + name))) {
196                 if (result != null && result.getParameterTypes().length != 0) {
197                     continue;
198                 }
199                 result = method;
200                 if (result.getParameterTypes().length == 0) {
201                     // we found the correct one..
202                     result = method;
203                     break;
204                 }
205             }
206         }
207         return result;
208     }
209 
210     /**
211      * Adds a field to the mapping
212      *
213      * @param f - the field
214      */
215     public void addField(IField f)
216     {
217         if (fields == null)
218         {
219             fields = new FieldList();
220         }
221 
222         if (f instanceof BeanField)
223         {
224             BeanField field = (BeanField) f;
225             Class clazz = field.getMethod().getReturnType();
226             Class baseClass = getProvider().getBaseClass();
227             boolean discoverNestedBean = false;
228             boolean isBaseTypeField = false;
229             if (baseClass != null)
230             {
231                 if (baseClass.isInterface())
232                 {
233                     Class[] ifaces = clazz.getInterfaces();
234                     for (int i = 0; i < ifaces.length; i++)
235                     {
236                         if (ifaces[i] == baseClass)
237                         {
238                             discoverNestedBean = true;
239                             isBaseTypeField = true;
240                         }
241                     }
242                 }
243                 else
244                 {
245                     Class tmpClass = clazz;
246                     // discover if the baseclass is the superclazz...
247                     if (baseClass == tmpClass)
248                     {
249                         discoverNestedBean = false;
250                     }
251                     else
252                     {
253                         while (tmpClass != null && tmpClass != Object.class)
254                         {
255                             if (baseClass == tmpClass.getSuperclass())
256                             {
257                                 discoverNestedBean = true;
258                                 isBaseTypeField = true;
259                                 break;
260                             }
261                             tmpClass = tmpClass.getSuperclass();
262                         }
263                     }
264                 }
265             }
266             if (discoverNestedBean)
267             {
268                 if (dataProvider.getMapping(dataProvider.getPossibleMappingName(clazz)) == null
269                     && dataProvider.getMapping(dataProvider.getPlainBeanName(clazz)) == null
270                     && field.getMethod().getDeclaringClass() != clazz)
271                 {
272                     if (!dataProvider.isInCache(clazz))
273                     {
274                         dataProvider.getMapping(clazz);
275                     }
276                 }
277             }
278             field.setBaseType(isBaseTypeField);
279         }
280         else
281         {
282             // other fields not yet supported.
283             return;
284         }
285         fields.add(f);
286     }
287 
288     /**
289      * @return all the fields in an arraylist
290      */
291     public List getFields()
292     {
293         return fields;
294     }
295 
296     /**
297      * This method will also search aliases
298      * of the field.
299      * @see org.xulux.dataprovider.IMapping#getField(java.lang.Object)
300      */
301     public IField getField(Object f)
302     {
303         if (fields == null || f == null) {
304             return null;
305         }
306         String name = f.toString();
307         // we by default strip out the widget pointer
308         if (name.startsWith("?")) {
309             int dotIndex = name.indexOf('.');
310             if (dotIndex != -1) {
311                 name = name.substring(dotIndex + 1);
312             }
313         }
314 
315         int dotIndex = name.indexOf(".");
316         String realField = null;
317         if (dotIndex != -1) {
318             String field = name.substring(0, dotIndex);
319             realField = name.substring(dotIndex + 1);
320             name = field;
321         }
322         int index = fields.indexOf(name);
323         if (index == -1 && realField != null) {
324             name = name+"."+realField;
325             dotIndex = name.lastIndexOf(".");
326             if (dotIndex != -1) {
327                 String field = name.substring(0, dotIndex);
328                 realField = name.substring(dotIndex + 1);
329                 name = field;
330             } 
331             // try a last indexOf to see if other conventions can work
332             index = fields.indexOf(name);
333         }
334         if (index != -1)
335         {
336             BeanField bf =  (BeanField) fields.get(index);
337             // set the real field if there is one..
338             if (realField != null) {
339                 bf.setTempRealField(realField);
340             } else if (bf.hasTempRealField()) {
341                 // if the beanfield had a temporary real field
342                 // and not having a new realfield, remove it.
343                 bf.removeTempRealField();
344             }
345 
346             return bf;
347         }
348         return null;
349     }
350 
351     /**
352      * Will discover the fields in the bean.
353      * if discovery is set and discovery has not yet taken
354      * place. Discovery will methods which are private or protected.
355      * 
356      * It will also try to discover the set method that is connected
357      * to the getter (assuming it is not a read only field).
358      *
359      * @todo What to do with collections ?
360      */
361     public void discover()
362     {
363         if (!isDiscovered && isDiscovery())
364         {
365             Method[] methods = bean.getMethods();
366             for (int i = 0; i < methods.length; i++)
367             {
368                 Method method = methods[i];
369                 if ((!method.getName().equals("getClass")
370                     && method.getModifiers() != Modifier.PRIVATE
371                     && method.getModifiers() != Modifier.PROTECTED))
372                 {
373                     if (!method.getName().startsWith("get") && !method.getName().startsWith("is")) {
374                         continue;
375                     }
376                     BeanField field = new BeanField(method);
377                     // try to find the setter..
378                     String fieldName = field.getName();
379                     if (fieldName.length() > 0) {
380                         fieldName = field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
381                     }
382                     Method setMethod = findMethod(fieldName, true);
383                     field.setChangeMethod(setMethod);
384                     BeanField previousField = (BeanField) getField(fieldName);
385                     if (previousField != null) {
386                     	if (previousField.getMethod().getParameterTypes().length == 0) {
387                     		continue;
388                     	} else {
389                     		if (field.getMethod().getParameterTypes().length == 0) {
390                     			fields.remove(previousField);
391                     		}
392                     		
393                     	}
394                     }
395                     addField(field);
396                 }
397             }
398             isDiscovered = true;
399         }
400     }
401 
402     /**
403      * Discovers a specific field, and ads it to the cache.
404      *
405      * @param field the field to discover
406      */
407     public void discover(String field) {
408     }
409     /**
410      * @see java.lang.Object#equals(java.lang.Object)
411      */
412     public boolean equals(Object object)
413     {
414         if (object instanceof BeanMapping) {
415             String mappingName = ((BeanMapping) object).getName();
416             if (mappingName != null) {
417                 return mappingName.equals(this.getName());
418             }
419         }
420         return false;
421     }
422 
423 
424     /**
425      * @see java.lang.Object#toString()
426      */
427     public String toString() {
428         return getName();
429     }
430 
431     /**
432      * Inner ArrayList with an overriden indexOf
433      * Which checks equals on the object In
434      * the arraylist instead of the object passed
435      *
436      * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
437      */
438     public static class FieldList extends ArrayList
439     {
440         /**
441          * The fieldlist constructor
442          */
443         public FieldList() {
444             super();
445         }
446 
447         /**
448          * Override the indexOf, since the java one is
449          * calling equals on elem and we want to call
450          * equals on the alement in the list.
451          * Null will always return -1.
452          *
453          * @param elem the element to find
454          * @return int - the position or -1 when not found
455          */
456         public int indexOf(Object elem) {
457             if (elem == null) {
458                 return -1;
459             }
460             for (int i = 0; i < size(); i++) {
461                 Object data = get(i);
462                 if (data != null && data.equals(elem)) {
463                     return i;
464                 }
465             }
466             return -1;
467         }
468     }
469 
470     /**
471      * @see org.xulux.dataprovider.IMapping#setValue(java.lang.String, java.lang.Object, java.lang.Object)
472      */
473     public Object setValue(String field, Object object, Object value) {
474         IField iField = getField(field);
475         if (iField != null) {
476             return iField.setValue(object, value);
477         }
478         return null;
479     }
480     /**
481      * @see org.xulux.dataprovider.IMapping#getValue(java.lang.String, java.lang.Object)
482      */
483     public Object getValue(String field, Object object) {
484       IField iField = getField(field);
485       if (iField != null) {
486           return iField.getValue(object);
487       }
488       return null;
489     }
490 
491     
492     /**
493      * Set the dataprovider.
494      * @param dataProvider the dataprovider
495      */
496     public void setDataProvider(BeanDataProvider dataProvider) {
497         this.dataProvider = dataProvider;
498     }
499 
500     /**
501      * Convenience method
502      * @return the dataprovider
503      */
504     BeanDataProvider getProvider() {
505         return this.dataProvider;
506     }
507 
508     /**
509      * @see org.xulux.api.dataprovider.IMapping#getDataProvider()
510      */
511     public IDataProvider getDataProvider() {
512         return this.dataProvider;
513     }
514 
515     /**
516      * @see org.xulux.api.dataprovider.IMapping#setProperty(java.lang.String, java.lang.String)
517      */
518     public void setProperty(String name, String value) {
519         if (name == null) {
520             return;
521         }
522         if (name.equalsIgnoreCase("bean")) {
523             setBean(ClassLoaderUtils.getClass(value));
524         } else if (name.equalsIgnoreCase("discovery")) {
525             setDiscovery(BooleanUtils.toBoolean(value));
526         }
527         
528     }
529 
530 }