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 }