1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package com.ochafik.beans;
20 import java.awt.Color;
21 import java.awt.Component;
22 import java.awt.Font;
23 import java.awt.event.ActionEvent;
24 import java.awt.event.ActionListener;
25 import java.awt.event.FocusAdapter;
26 import java.awt.event.FocusEvent;
27 import java.beans.PropertyChangeListener;
28 import java.beans.PropertyChangeSupport;
29 import java.lang.reflect.Method;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.TreeSet;
35
36 import javax.swing.AbstractButton;
37 import javax.swing.BorderFactory;
38 import javax.swing.JCheckBox;
39 import javax.swing.JComponent;
40 import javax.swing.JScrollPane;
41 import javax.swing.JTextArea;
42 import javax.swing.JTextField;
43 import javax.swing.event.DocumentEvent;
44 import javax.swing.event.DocumentListener;
45 import javax.swing.text.JTextComponent;
46
47 import com.ochafik.swing.FormUtils;
48
49
50 @SuppressWarnings("unchecked")
51 public class BeansController<M> {
52 M model;
53 Class<M> modelClass;
54
55 public BeansController(Class<M> modelClass) {
56 this.modelClass=modelClass;
57 }
58
59 Map<String,java.util.List<JComponent>> viewsByPropertyName=new HashMap<String,java.util.List<JComponent>>();
60 Map<String,Class> propertiesTypes=new HashMap<String,Class>();
61 Map<String,Object> oldValues=new HashMap<String,Object>();
62 Map<String,Method> getterMethods=new HashMap<String,Method>();
63 Map<String,Method> setterMethods=new HashMap<String,Method>();
64 public PropertyChangeSupport getPropertyChangeSupport() {
65 return propertyChangeSupport;
66 }
67 PropertyChangeSupport propertyChangeSupport=new PropertyChangeSupport(this);
68 static final Class getterArgs[]=new Class[0];
69
70 public void addPropertyChangeListener(PropertyChangeListener listener) {
71 propertyChangeSupport.addPropertyChangeListener(listener);
72 }
73 public void addPropertyChangeListener(String propertyName,PropertyChangeListener listener) {
74 propertyChangeSupport.addPropertyChangeListener(propertyName,listener);
75 }
76 public JComponent createScrollableViewComponent(
77 final String propertyName,
78 String caption,
79 String title,
80 String tooltip,
81 boolean largeComponent
82 ) {
83 JComponent c=createViewComponent(propertyName,caption,largeComponent);
84 if (c==null) return c;
85 if (title!=null) c.setBorder(BorderFactory.createTitledBorder(title));
86 if (c instanceof JTextArea) {
87 JTextArea ta=(JTextArea)c;
88 ta.setLineWrap(true);
89 ta.setWrapStyleWord(true);
90 JScrollPane jsp=new JScrollPane(ta);
91 c=jsp;
92 }
93 if (title!=null) c.setBorder(BorderFactory.createTitledBorder(title));
94 if (tooltip!=null) c.setToolTipText(tooltip);
95 return c;
96 }
97 public static final boolean booleanTrue=true;
98
99
100
101
102
103
104
105
106
107
108 public JComponent createViewComponent(final String propertyName,String caption, boolean largeComponent) {
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
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 {
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 {
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 {
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 {
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
222
223
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
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
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
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
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
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 }