package org.openswing.swing.form.model.client; import java.beans.*; import java.lang.reflect.*; import java.util.*; import org.openswing.swing.form.client.*; import org.openswing.swing.logger.client.*; import org.openswing.swing.message.receive.java.*; import org.openswing.swing.util.client.ClientSettings; import java.math.BigDecimal; /** * <p>Title: OpenSwing Framework</p> * <p>Description: Data model linked to a Form panel.</p> * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p> * * <p> This file is part of OpenSwing Framework. * This library is free software; you can redistribute it and/or * modify it under the terms of the (LGPL) Lesser General Public * License as published by the Free Software Foundation; * * GNU LESSER GENERAL PUBLIC LICENSE * Version 2.1, February 1999 * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * The author may be contacted at: * maurocarniel@tin.it</p> * * @author Mauro Carniel * @version 1.0 */ public class VOModel { /** value object class associated to this data model */ private Class valueObjectClass = null; /** valueObject info */ private BeanInfo info; /** value object stored inside the model */ private ValueObject valueObject = null; /** value changed listeners */ private ArrayList valueChangeListeners = new ArrayList(); /** collection of <attribute name,Method[] getters+setter> */ private Hashtable voSetterMethods = new Hashtable(); /** collection of <attribute name,Method[] getters> */ private Hashtable voGetterMethods = new Hashtable(); /** flag used to define if an inner v.o. must be automatically instantiated when a setter method is invoked */ private boolean createInnerVO = true; /** linked Form */ private Form form = null; /** last value object */ private ValueObject oldValueObject = null; /** * Constructor. * @param valueObjectClass value object class associated to this data model * @param createInnerVO flag used to define if an inner v.o. must be automatically instantiated when a setter method is invoked * @throws java.lang.Exception if an error occours */ public VOModel(Class valueObjectClass,boolean createInnerVO,Form form) throws Exception { this.valueObjectClass = valueObjectClass; this.createInnerVO = createInnerVO; this.form = form; // retrieve attribute properties... if (Beans.isDesignTime()) return; analyzeClassFields("",new Method[0],valueObjectClass); setValueObject( (ValueObject) valueObjectClass.newInstance()); } /** * Set the value object class that this model will use. * @param valueObjectClass value object class * @throws java.lang.Exception if an error occours */ public final void setValueObject(Class valueObjectClass) throws Exception { this.valueObjectClass = valueObjectClass; this.valueObject = null; voSetterMethods.clear(); voGetterMethods.clear(); analyzeClassFields("",new Method[0],valueObjectClass); setValueObject( (ValueObject) valueObjectClass.newInstance()); } /** * Define if an inner v.o. must be automatically instantiated when a setter method is invoked. * @param createInnerVO define if an inner v.o. must be automatically instantiated when a setter method is invoked */ public final void setCreateInnerVO(boolean createInnerVO) { this.createInnerVO = createInnerVO; } /** * Analyze class fields and fill in "voSetterMethods","voGetterMethods","indexes",reverseIndexes" attributes. * @param prefix e.g. "attrx.attry." * @param parentMethods getter methods of parent v.o. * @param classType class to analyze */ private void analyzeClassFields(String prefix,Method[] parentMethods,Class classType) { try { if (prefix.split("\\.").length>ClientSettings.MAX_NR_OF_LOOPS_IN_ANALYZE_VO) return; info = Introspector.getBeanInfo(classType); // retrieve attribute properties... PropertyDescriptor[] props = info.getPropertyDescriptors(); for (int i = 0; i < props.length; i++) { if (props[i].getReadMethod()!=null && props[i].getReadMethod().getParameterTypes().length==0) { // check if attribute must be ignored because it's not compatible... if (!(ValueObject.class.isAssignableFrom(props[i].getReadMethod().getReturnType()) || Boolean.class.isAssignableFrom(props[i].getReadMethod().getReturnType()) || Number.class.isAssignableFrom(props[i].getReadMethod().getReturnType()) || Character.class.isAssignableFrom(props[i].getReadMethod().getReturnType()) || byte[].class.isAssignableFrom(props[i].getReadMethod().getReturnType()) || String.class.isAssignableFrom(props[i].getReadMethod().getReturnType()) || java.util.Date.class.isAssignableFrom(props[i].getReadMethod().getReturnType()) || Boolean.TYPE.isAssignableFrom(props[i].getReadMethod().getReturnType()) || Short.TYPE.isAssignableFrom(props[i].getReadMethod().getReturnType()) || Integer.TYPE.isAssignableFrom(props[i].getReadMethod().getReturnType()) || Long.TYPE.isAssignableFrom(props[i].getReadMethod().getReturnType()) || Float.TYPE.isAssignableFrom(props[i].getReadMethod().getReturnType()) || Double.TYPE.isAssignableFrom(props[i].getReadMethod().getReturnType()) || Character.TYPE.isAssignableFrom(props[i].getReadMethod().getReturnType()) || ArrayList.class.isAssignableFrom(props[i].getReadMethod().getReturnType()) || java.util.List.class.isAssignableFrom(props[i].getReadMethod().getReturnType()) )) continue; } if (props[i].getName().substring(0,1).equals(props[i].getName().substring(0,1).toUpperCase())) { // fix of PropertyDescriptor bug: an attribute name "xXxxx" becomes "XXxxx" and this is not correct! try { Class c = classType; boolean attributeFound = false; while(!c.equals(Object.class)) { try { c.getDeclaredField(props[i].getName()); attributeFound = true; break; } catch (Throwable ex2) { c = c.getSuperclass(); } } if (!attributeFound) { // now trying to find an attribute having the first character in lower case (e.g. "xXxxx") String name = props[i].getName().substring(0,1).toLowerCase()+props[i].getName().substring(1); c = classType; while(!c.equals(Object.class)) { try { c.getDeclaredField(name); attributeFound = true; break; } catch (Throwable ex2) { c = c.getSuperclass(); } } if (attributeFound) props[i].setName(name); } } catch (Throwable ex1) { } } if (props[i].getReadMethod()!=null && props[i].getReadMethod().getParameterTypes().length==0 && ValueObject.class.isAssignableFrom( props[i].getReadMethod().getReturnType() ) && !form.getLazyInitializedAttributes().contains(props[i].getName()) ) { Method[] newparentMethods = new Method[parentMethods.length+1]; System.arraycopy(parentMethods,0,newparentMethods,0,parentMethods.length); newparentMethods[parentMethods.length] = props[i].getReadMethod(); analyzeClassFields(prefix+props[i].getName()+".",newparentMethods,props[i].getReadMethod().getReturnType()); } Method[] newparentMethods = new Method[parentMethods.length+1]; System.arraycopy(parentMethods,0,newparentMethods,0,parentMethods.length); newparentMethods[parentMethods.length] = props[i].getReadMethod(); voGetterMethods.put(prefix+props[i].getName(),newparentMethods); Method[] newparentMethods2 = new Method[parentMethods.length+1]; System.arraycopy(parentMethods,0,newparentMethods2,0,parentMethods.length); newparentMethods2[parentMethods.length] = props[i].getWriteMethod(); voSetterMethods.put(prefix+props[i].getName(),newparentMethods2); } } catch (Throwable ex) { Logger.error(this.getClass().getName(),"analyzeClassFields","Error on analyzing the object",ex); } } /** * @return value object class associated to this data model */ public final Class getValueObjectType() { return valueObjectClass; } /** * Set the value object that this model will use. * @param valueObject value object to store */ public final void setValueObject(ValueObject valueObject) { if (valueObject != null && !valueObjectClass.isAssignableFrom(valueObject.getClass())) { // !valueObject.getClass().equals(valueObjectClass)) { throw new RuntimeException("The specified value object has not type '"+valueObjectClass.toString()+"'"); } oldValueObject = this.valueObject; this.valueObject = valueObject; if (valueObject==null && oldValueObject!=null || valueObject!=null && oldValueObject==null || valueObject!=null && !valueObject.equals(oldValueObject)) { // fire value changed events... PropertyDescriptor prop = null; Method[] readMethods = null; Object oldValue = null; Object newValue = null; String attrName = null; Enumeration en = voGetterMethods.keys(); Object obj = null; while(en.hasMoreElements()) { attrName = en.nextElement().toString(); if(form.getLazyInitializedAttributes().contains(attrName)) continue; readMethods = (Method[])voGetterMethods.get(attrName); if (readMethods != null) { try { obj = oldValueObject; if (obj!=null) for(int i=0;i<readMethods.length-1;i++) { obj = readMethods[i].invoke(obj,new Object[0]); // check if the inner v.o. is null... if(obj == null) { if (!createInnerVO) break; else obj = (ValueObject)readMethods[i].getReturnType().newInstance(); } } oldValue = obj != null ? readMethods[readMethods.length - 1].invoke(obj, new Object[0]) : null; obj = valueObject; if (obj!=null) for(int i=0;i<readMethods.length-1;i++) { obj = readMethods[i].invoke(obj,new Object[0]); // check if the inner v.o. is null... if(obj == null) { if (!createInnerVO) break; else obj = (ValueObject)readMethods[i].getReturnType().newInstance(); } } newValue = obj!=null?readMethods[readMethods.length-1].invoke(obj, new Object[0]):null; } catch (Exception ex) { String msg = ""; try { msg = "Object: "+obj.getClass().getName()+"\n"; for(int i=0;i<readMethods.length;i++) msg += "Getter: "+ readMethods[i].getReturnType().getName()+" "+ readMethods[i].getName()+ "("+ (readMethods[i].getParameterTypes().length==1?readMethods[i].getParameterTypes()[0].getName():"")+ ")\n"; } catch (Exception exx) { } Logger.error(this.getClass().getName(),"setValueObject","Error while setting the value object attribute '"+attrName+"'\n"+msg,ex); continue; } } if (newValue==null && oldValue!=null || newValue!=null && oldValue==null || newValue!=null && oldValue!=null && !newValue.equals(oldValue)) fireValueChanged(attrName,oldValue,newValue); } } } /** * @return value object stored inside the model */ public final ValueObject getValueObject() { return valueObject; } /** * @param attributeName attribute name of the value object * @return type of the value associated to the specified attribute name */ public final Class getAttributeType(String attributeName) { try { Method[] readMethods = (Method[])voGetterMethods.get(attributeName); if (readMethods != null) { return readMethods[readMethods.length-1].getReturnType(); } else return null; } catch (Throwable ex) { Logger.error(this.getClass().getName(),"getAttributeType","Error while analyzing the value object attribute '"+attributeName+"'",ex); } return null; } /** * @param attributeName attribute name of the value object * @return value value associated to the specified attribute name */ public final Object getValue(String attributeName) { if (getValueObject() == null) { return null; } try { Method[] readMethods = (Method[])voGetterMethods.get(attributeName); Method[] writeMethods = (Method[])voSetterMethods.get(attributeName); String[] attrs = attributeName.split("\\."); if (readMethods != null) { Object obj = getValueObject(); Object lastObj = null; if (obj!=null) for(int i=0;i<readMethods.length-1;i++) { lastObj = obj; obj = readMethods[i].invoke(obj,new Object[0]); // check if the inner v.o. is null... if(obj == null) { if (!createInnerVO) return null; else { obj = (ValueObject)readMethods[i].getReturnType().newInstance(); lastObj.getClass().getMethod( "set"+attrs[i].substring(0,1).toUpperCase()+attrs[i].substring(1), new Class[]{readMethods[i].getReturnType()} ).invoke(lastObj,new Object[]{obj}); } } } return obj!=null?readMethods[readMethods.length-1].invoke(obj, new Object[0]):null; } } catch (Throwable ex) { Logger.error(this.getClass().getName(),"getValue","Error while reading the value object attribute '"+attributeName+"'",ex); } return null; } /** * @param attributeName attribute name of the value object * @param valueobject value object used to fetch the attribute value * @return value value associated to the specified attribute name */ public final Object getValue(String attributeName,ValueObject valueobject) { if (valueobject == null) { return null; } try { Method[] readMethods = (Method[])voGetterMethods.get(attributeName); if (readMethods != null) { Object obj = valueobject; if (obj!=null) for(int i=0;i<readMethods.length-1;i++) { obj = readMethods[i].invoke(obj,new Object[0]); // check if the inner v.o. is null... if(obj == null) { if (!createInnerVO) return null; else obj = (ValueObject)readMethods[i].getReturnType().newInstance(); } } return obj!=null?readMethods[readMethods.length-1].invoke(obj, new Object[0]):null; } } catch (Throwable ex) { Logger.error(this.getClass().getName(),"getValue","Error while reading the value object attribute '"+attributeName+"'",ex); } return null; } /** * Set a value in the value object for an attribute name. * @param attributeName attribute name of the value object * @return value value to set for the specified attribute name */ public final void setValue(String attributeName,Object value) { if (getValueObject() == null) { return; } try { Method[] writeMethods = (Method[])voSetterMethods.get(attributeName); if (writeMethods != null) { Object oldValue = getValue(attributeName); if (value!=null) { if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(java.sql.Date.class) && value.getClass().equals(java.util.Date.class)) value = new java.sql.Date(((java.util.Date)value).getTime()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(Character.class) && value.getClass().equals(String.class)) value = value.toString().length()==0?null:new Character(value.toString().charAt(0)); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(java.sql.Timestamp.class) && value.getClass().equals(java.util.Date.class)) value = new java.sql.Timestamp(((java.util.Date)value).getTime()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(java.sql.Timestamp.class) && value.getClass().equals(java.sql.Date.class)) value = new java.sql.Timestamp(((java.sql.Date)value).getTime()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(Integer.class) && value.getClass().equals(java.math.BigDecimal.class)) value = new Integer(((java.math.BigDecimal)value).intValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(Integer.TYPE) && value.getClass().equals(java.math.BigDecimal.class)) value = new Integer(((java.math.BigDecimal)value).intValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(Long.class) && value.getClass().equals(java.math.BigDecimal.class)) value = new Long(((java.math.BigDecimal)value).longValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(Long.TYPE) && value.getClass().equals(java.math.BigDecimal.class)) value = new Long(((java.math.BigDecimal)value).longValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(Double.class) && value.getClass().equals(java.math.BigDecimal.class)) value = new Double(((java.math.BigDecimal)value).doubleValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(Double.TYPE) && value.getClass().equals(java.math.BigDecimal.class)) value = new Double(((java.math.BigDecimal)value).doubleValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(Float.class) && value.getClass().equals(java.math.BigDecimal.class)) value = new Float(((java.math.BigDecimal)value).floatValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(Float.TYPE) && value.getClass().equals(java.math.BigDecimal.class)) value = new Float(((java.math.BigDecimal)value).floatValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(Short.class) && value.getClass().equals(java.math.BigDecimal.class)) value = new Short(((java.math.BigDecimal)value).shortValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(Short.TYPE) && value.getClass().equals(java.math.BigDecimal.class)) value = new Short(((java.math.BigDecimal)value).shortValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(BigDecimal.class) && value.getClass().equals(Double.class)) value = new BigDecimal(((Double)value).doubleValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(BigDecimal.class) && value.getClass().equals(Long.class)) value = new BigDecimal(((Long)value).longValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(BigDecimal.class) && value.getClass().equals(Float.class)) value = new BigDecimal(((Float)value).floatValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(BigDecimal.class) && value.getClass().equals(Integer.class)) value = new BigDecimal(((Integer)value).intValue()); else if (writeMethods[writeMethods.length-1].getParameterTypes()[0].equals(BigDecimal.class) && value.getClass().equals(Short.class)) value = new BigDecimal(((Short)value).shortValue()); } Object obj = getValueObject(); if (obj!=null) for(int i=0;i<writeMethods.length-1;i++) { obj = writeMethods[i].invoke(obj,new Object[0]); // check if the inner v.o. is null... if(obj == null) { if (!createInnerVO) return; else obj = (ValueObject)writeMethods[i].getReturnType().newInstance(); } } if (value==null && oldValue!=null || value!=null && oldValue==null || value!=null && oldValue!=null && !value.equals(oldValue)) { boolean isOk = form.getFormController()==null ? true : form.getFormController().validateControl( attributeName, oldValue, value ); if (isOk) { writeMethods[writeMethods.length-1].invoke(obj, new Object[]{value}); fireValueChanged(attributeName,oldValue,value); } else { form.pull(attributeName); } } } } catch (Exception ex) { Logger.error(this.getClass().getName(),"setValue","Error while writing the value object attribute '"+attributeName+"'.\n Maybe incompatible type?",ex); } } /** * Method used to add a value changed listener. * @param listener value changed listener to add */ public final void addValueChangeListener(ValueChangeListener listener) { valueChangeListeners.add(listener); } /** * Method used to remove a value changed listener. * @param listener value changed listener to remove */ public final void removeValueChangeListener(ValueChangeListener listener) { valueChangeListeners.remove(listener); } /** * @return list of value changed listeners added to this model */ public final ValueChangeListener[] getValueChangeListeners() { return (ValueChangeListener[])valueChangeListeners.toArray(new ValueChangeListener[valueChangeListeners.size()]); } /** * Method called by setValue and setValueObject methods to fire value changed events. * @param attributeName attribute name * @param oldValue old value of the attribute * @param newValue new value of the attribute */ private void fireValueChanged(String attributeName,Object oldValue,Object newValue) { ValueChangeListener[] formListeners = getValueChangeListeners(); ValueChangeEvent e = new ValueChangeEvent(this,attributeName,oldValue,newValue); if (valueChangeListeners != null) { for (int i = 0; i < valueChangeListeners.size(); i++) { ValueChangeListener vcl = (ValueChangeListener)valueChangeListeners.get(i); vcl.valueChanged(e); } } } /** * @return ValueObject old value object */ public final ValueObject getOldValueObject() { return oldValueObject; } }