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 }