001 /* 002 Copyright (c) 2009 Olivier Chafik, All Rights Reserved 003 004 This file is part of JNAerator (http://jnaerator.googlecode.com/). 005 006 JNAerator is free software: you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation, either version 3 of the License, or 009 (at your option) any later version. 010 011 JNAerator is distributed in the hope that it will be useful, 012 but WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 GNU General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with JNAerator. If not, see <http://www.gnu.org/licenses/>. 018 */ 019 package com.ochafik.beans; 020 import java.awt.Color; 021 import java.awt.Component; 022 import java.awt.Font; 023 import java.awt.event.ActionEvent; 024 import java.awt.event.ActionListener; 025 import java.awt.event.FocusAdapter; 026 import java.awt.event.FocusEvent; 027 import java.beans.PropertyChangeListener; 028 import java.beans.PropertyChangeSupport; 029 import java.lang.reflect.Method; 030 import java.util.ArrayList; 031 import java.util.HashMap; 032 import java.util.Map; 033 import java.util.Set; 034 import java.util.TreeSet; 035 036 import javax.swing.AbstractButton; 037 import javax.swing.BorderFactory; 038 import javax.swing.JCheckBox; 039 import javax.swing.JComponent; 040 import javax.swing.JScrollPane; 041 import javax.swing.JTextArea; 042 import javax.swing.JTextField; 043 import javax.swing.event.DocumentEvent; 044 import javax.swing.event.DocumentListener; 045 import javax.swing.text.JTextComponent; 046 047 import com.ochafik.swing.FormUtils; 048 049 050 @SuppressWarnings("unchecked") 051 public class BeansController<M> { 052 M model; 053 Class<M> modelClass; 054 055 public BeansController(Class<M> modelClass) { 056 this.modelClass=modelClass; 057 } 058 059 Map<String,java.util.List<JComponent>> viewsByPropertyName=new HashMap<String,java.util.List<JComponent>>(); 060 Map<String,Class> propertiesTypes=new HashMap<String,Class>(); 061 Map<String,Object> oldValues=new HashMap<String,Object>(); 062 Map<String,Method> getterMethods=new HashMap<String,Method>(); 063 Map<String,Method> setterMethods=new HashMap<String,Method>(); 064 public PropertyChangeSupport getPropertyChangeSupport() { 065 return propertyChangeSupport; 066 } 067 PropertyChangeSupport propertyChangeSupport=new PropertyChangeSupport(this); 068 static final Class getterArgs[]=new Class[0]; 069 070 public void addPropertyChangeListener(PropertyChangeListener listener) { 071 propertyChangeSupport.addPropertyChangeListener(listener); 072 } 073 public void addPropertyChangeListener(String propertyName,PropertyChangeListener listener) { 074 propertyChangeSupport.addPropertyChangeListener(propertyName,listener); 075 } 076 public JComponent createScrollableViewComponent( 077 final String propertyName, 078 String caption, 079 String title, 080 String tooltip, 081 boolean largeComponent 082 ) {//, IllegalAccessException { 083 JComponent c=createViewComponent(propertyName,caption,largeComponent); 084 if (c==null) return c; 085 if (title!=null) c.setBorder(BorderFactory.createTitledBorder(title)); 086 if (c instanceof JTextArea) { 087 JTextArea ta=(JTextArea)c; 088 ta.setLineWrap(true); 089 ta.setWrapStyleWord(true); 090 JScrollPane jsp=new JScrollPane(ta); 091 c=jsp; 092 } 093 if (title!=null) c.setBorder(BorderFactory.createTitledBorder(title)); 094 if (tooltip!=null) c.setToolTipText(tooltip); 095 return c; 096 } 097 public static final boolean booleanTrue=true;//, booleanFalse=false; 098 //private static Object booleanTrueObject,booleanFalseObject; 099 //public static Class BooleanPrimitiveClass; 100 /*static { 101 try { 102 Class clazz=BeansController.class; 103 BooleanPrimitiveClass=clazz.getField("booleanTrue").getType(); 104 } catch (Exception ex) { 105 ex.printStackTrace(); 106 } 107 }*/ 108 public JComponent createViewComponent(final String propertyName,String caption, boolean largeComponent) {//throws NoSuchMethodException {//, IllegalAccessException { 109 try { 110 Class propertyType=getPropertyType(propertyName); 111 JComponent jc; 112 if (String.class.isAssignableFrom(propertyType)) { 113 jc=largeComponent ? new JTextArea() : new JTextField(); 114 final JTextComponent jtc=(JTextComponent )jc; 115 FormUtils.addUndoRedoSupport(jtc); 116 jtc.addFocusListener(new FocusAdapter() { 117 @Override 118 public void focusGained(FocusEvent arg0) { 119 jtc.selectAll(); 120 } 121 @Override 122 public void focusLost(FocusEvent arg0) { 123 // TODO Auto-generated method stub 124 jtc.setSelectionStart(0); 125 jtc.setSelectionEnd(0); 126 } 127 }); 128 129 } else if (isBoolean(propertyType)) { 130 jc=caption==null ? new JCheckBox() : new JCheckBox(caption); 131 } else if (isInteger(propertyType)) { 132 jc=new JTextField(); 133 } else { 134 System.err.println("IMPLEMENTME! Don't know how to create a view component for model class "+propertyType.getName()); 135 jc=null; 136 } 137 attachViewComponent(jc,propertyName); 138 return jc; 139 } catch (NoSuchMethodException ex) { 140 throw new RuntimeException("No such field in "+modelClass.getName()+" : "+propertyName); 141 } 142 } 143 public void attachViewComponent(JComponent jc, final String propertyName) throws NoSuchMethodException {//, IllegalAccessException { 144 Class propertyType=getPropertyType(propertyName); 145 if (String.class.isAssignableFrom(propertyType)) { 146 final JTextComponent c=(JTextComponent)jc; 147 c.getDocument().addDocumentListener( new DocumentListener() { 148 public void changedUpdate(DocumentEvent e) { fireViewChange(c,propertyName,c.getText()); } 149 public void insertUpdate(DocumentEvent e) { fireViewChange(c,propertyName,c.getText()); } 150 public void removeUpdate(DocumentEvent e) { fireViewChange(c,propertyName,c.getText()); } 151 }); 152 } else if (isBoolean(propertyType)) { 153 final AbstractButton c=(AbstractButton)jc; 154 c.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { 155 fireViewChange(c,propertyName,new Boolean(c.isSelected())); 156 }}); 157 } else if (isInteger(propertyType)){ 158 final JTextComponent c=(JTextComponent)jc; 159 c.getDocument().addDocumentListener(new DocumentListener() { 160 public void changedUpdate(DocumentEvent e) { fireViewChange(c,propertyName,c.getText()); } 161 public void insertUpdate(DocumentEvent e) { fireViewChange(c,propertyName,c.getText()); } 162 public void removeUpdate(DocumentEvent e) { fireViewChange(c,propertyName,c.getText()); } 163 }); 164 165 } else { 166 System.err.println("IMPLEMENTME! Don't know how to create a view component for model class "+propertyType.getName()); 167 } 168 if (jc!=null) { 169 java.util.List<JComponent> views=viewsByPropertyName.get(propertyName); 170 if (views==null) { 171 views=new ArrayList<JComponent>(); 172 viewsByPropertyName.put(propertyName,views); 173 } 174 views.add(jc); 175 } 176 jc.setEnabled(model!=null); 177 } 178 Method getGetterMethod(String propertyName) throws NoSuchMethodException {//, IllegalAccessException { 179 Method getterMethod=getterMethods.get(propertyName); 180 if (getterMethod==null) { 181 try { 182 getterMethod=modelClass.getMethod(getGetterMethodName(propertyName),getterArgs); 183 } catch (NoSuchMethodException ex) { 184 getterMethod=modelClass.getMethod(getIsGetterMethodName(propertyName),getterArgs); 185 } 186 getterMethods.put(propertyName,getterMethod); 187 } 188 return getterMethod; 189 } 190 Class getPropertyType(String propertyName) throws NoSuchMethodException { //, IllegalAccessException { 191 Class propertyType=propertiesTypes.get(propertyName); 192 if (propertyType==null) { 193 propertyType=getGetterMethod(propertyName).getReturnType(); 194 propertiesTypes.put(propertyName,propertyType); 195 } 196 return propertyType; 197 } 198 Method getSetterMethod(String propertyName) throws NoSuchMethodException { //, IllegalAccessException { 199 Method setterMethod=setterMethods.get(propertyName); 200 if (setterMethod==null) { 201 Class propertyType=getPropertyType(propertyName); 202 Class setterArgs[]=new Class[] { propertyType }; 203 setterMethod=modelClass.getMethod(getSetterMethodName(propertyName),setterArgs); 204 setterMethods.put(propertyName,setterMethod); 205 } 206 return setterMethod; 207 } 208 boolean updatingModel=false; 209 public M getModel() { return model; } 210 public void setModel(M model) { 211 this.model=model; 212 if (model!=null) { 213 if (!modelClass.isAssignableFrom(model.getClass())) throw new ClassCastException(model.getClass().getName()+" not a subclass of "+modelClass.getName()); 214 215 } 216 modelUpdated(); 217 } 218 boolean firingPropertyChange=false; 219 Set<String> propertiesBeingFired=new TreeSet<String>(); 220 public void fireViewChange(Component eventSource,String propertyName, Object newValue) { 221 //System.out.println("FireViewChange : propertyName="+propertyName+", firingPropertyChange="+firingPropertyChange+", model="+model); 222 223 /// Do not fire change events if somebody is just setting the model 224 if (updatingModel) return; 225 226 if (propertiesBeingFired.contains(propertyName)) { 227 return; 228 } else { 229 propertiesBeingFired.add(propertyName); 230 } 231 if (model!=null) { 232 try { 233 Class propertyType=getPropertyType(propertyName); 234 Object oldValue=oldValues.get(propertyName); 235 boolean validValue=true; 236 if (String.class.isAssignableFrom(propertyType)) { 237 getSetterMethod(propertyName).invoke(model,newValue); 238 oldValues.put(propertyName,newValue); 239 240 for (JComponent view : viewsByPropertyName.get(propertyName)) { 241 if (view!=eventSource) { 242 JTextComponent tc=(JTextComponent)view; 243 //System.out.println("propertyName="+propertyName+" set at "+newValue 244 tc.setText((String)newValue); 245 } 246 } 247 } else if (isBoolean(propertyType)) { 248 getSetterMethod(propertyName).invoke(model,newValue); 249 oldValues.put(propertyName,newValue); 250 251 for (JComponent view : viewsByPropertyName.get(propertyName)) { 252 if (view!=eventSource) { 253 AbstractButton cb=(AbstractButton)view; 254 cb.setSelected(((Boolean)newValue).booleanValue()); 255 } 256 } 257 } else if (isInteger(propertyType)) { 258 try { 259 int intValue=Integer.parseInt(((String)newValue).trim()); 260 getSetterMethod(propertyName).invoke(model,new Integer(intValue)); 261 oldValues.put(propertyName,newValue); 262 263 for (JComponent view : viewsByPropertyName.get(propertyName)) { 264 view.setFont(view.getFont().deriveFont(Font.PLAIN)); 265 view.setForeground(Color.black); 266 if (view!=eventSource) { 267 JTextComponent tc=(JTextComponent)view; 268 tc.setText(intValue+""); 269 } 270 } 271 } catch (NumberFormatException ex) { 272 validValue=false; 273 if (eventSource instanceof Component) { 274 Component eventSourceComponent=(Component)eventSource; 275 eventSourceComponent.setFont(eventSourceComponent.getFont().deriveFont(Font.BOLD|Font.ITALIC)); 276 eventSourceComponent.setForeground(Color.red); 277 } 278 } 279 } 280 if (propertyChangeSupport!=null&&validValue) { 281 propertyChangeSupport.firePropertyChange( 282 propertyName, 283 oldValue, 284 newValue 285 ); 286 } 287 288 } catch (Exception ex) { 289 ex.printStackTrace(); 290 } 291 } 292 //firingPropertyChange=false; 293 propertiesBeingFired.remove(propertyName); 294 } 295 private static boolean isBoolean(Class propertyType) { 296 return Boolean.class.isAssignableFrom(propertyType) || propertyType.getName().equals("boolean"); 297 } 298 public void modelUpdated() { 299 if (updatingModel) return; 300 updatingModel=true; 301 boolean nullModel=model==null; 302 //System.out.println("Model updated "+nullModel); 303 for (String propertyName : propertiesTypes.keySet()) { 304 try { 305 Class propertyType=getPropertyType(propertyName); 306 307 Object value=model==null ? 308 null : 309 getGetterMethod(propertyName).invoke(model); 310 //System.out.println("\tValue("+propertyName+") = "+value); 311 for (JComponent view : viewsByPropertyName.get(propertyName)) { 312 if (String.class.isAssignableFrom(propertyType)) { 313 JTextComponent tc=(JTextComponent)view; 314 String svalue=(String)value; 315 tc.setText(svalue==null ? "" : svalue); 316 tc.setEnabled(!nullModel); 317 } else if (isBoolean(propertyType)) { 318 //TODO 319 AbstractButton cb=(AbstractButton)view; 320 Boolean bvalue=(Boolean)value; 321 cb.setEnabled(!nullModel); 322 cb.setSelected(bvalue!=null && bvalue.booleanValue()); 323 } else if (isInteger(propertyType)) { 324 JTextComponent tc=(JTextComponent)view; 325 Integer ivalue=(Integer)value; 326 tc.setText(ivalue==null ? "" : ivalue.toString()); 327 tc.setEnabled(!nullModel); 328 } 329 } 330 } catch (Exception ex) { 331 System.err.println("Error while updating views for property '"+propertyName+"' of model "+modelClass.getName()); 332 ex.printStackTrace(); 333 } 334 } 335 updatingModel=false; 336 } 337 private static final boolean isInteger(Class c) { 338 return Integer.class.isAssignableFrom(c); 339 } 340 final static String getGetterMethodName(String field) { 341 return "get"+capitalizeFirstLetter(field); 342 } 343 final static String getIsGetterMethodName(String field) { 344 return "is"+capitalizeFirstLetter(field); 345 } 346 final static String getSetterMethodName(String field) { 347 return "set"+capitalizeFirstLetter(field); 348 } 349 final static String capitalizeFirstLetter(String s) { 350 int sLength=s.length(); 351 if (sLength<=1) return s.toUpperCase(); 352 else { 353 return s.substring(0,1).toUpperCase()+s.substring(1); 354 } 355 } 356 }