001package horstmann.ch08_graphed2; 002import java.awt.Component; 003import java.awt.event.ItemEvent; 004import java.beans.BeanInfo; 005import java.beans.IntrospectionException; 006import java.beans.Introspector; 007import java.beans.PropertyDescriptor; 008import java.beans.PropertyEditor; 009import java.beans.PropertyEditorManager; 010import java.lang.reflect.InvocationTargetException; 011import java.lang.reflect.Method; 012import java.util.ArrayList; 013 014import javax.swing.JComboBox; 015import javax.swing.JLabel; 016import javax.swing.JPanel; 017import javax.swing.JTextField; 018import javax.swing.event.ChangeEvent; 019import javax.swing.event.ChangeListener; 020import javax.swing.event.DocumentEvent; 021import javax.swing.event.DocumentListener; 022 023 024/** 025 A component filled with editors for all editable properties 026 of an object. 027 */ 028@SuppressWarnings("all") 029public class PropertySheet extends JPanel 030{ 031 /** 032 Constructs a property sheet that shows the editable 033 properties of a given object. 034 @param bean the object whose properties are being edited 035 */ 036 public PropertySheet(Object bean) 037 { 038 try 039 { 040 BeanInfo info 041 = Introspector.getBeanInfo(bean.getClass()); 042 PropertyDescriptor[] descriptors 043 = (PropertyDescriptor[])info.getPropertyDescriptors().clone(); 044 setLayout(new FormLayout()); 045 for (int i = 0; i < descriptors.length; i++) 046 { 047 PropertyEditor editor 048 = getEditor(bean, descriptors[i]); 049 if (editor != null) 050 { 051 add(new JLabel(descriptors[i].getName())); 052 add(getEditorComponent(editor)); 053 } 054 } 055 } 056 catch (IntrospectionException exception) 057 { 058 exception.printStackTrace(); 059 } 060 } 061 062 /** 063 Gets the property editor for a given property, 064 and wires it so that it updates the given object. 065 @param bean the object whose properties are being edited 066 @param descriptor the descriptor of the property to 067 be edited 068 @return a property editor that edits the property 069 with the given descriptor and updates the given object 070 */ 071 public PropertyEditor getEditor(final Object bean, 072 PropertyDescriptor descriptor) 073 { 074 try 075 { 076 Method getter = descriptor.getReadMethod(); 077 if (getter == null) return null; 078 final Method setter = descriptor.getWriteMethod(); 079 if (setter == null) return null; 080 Class type = descriptor.getPropertyType(); 081 PropertyEditor ed = null; 082 Class editorClass = descriptor.getPropertyEditorClass(); 083 if (editorClass != null) 084 ed = (PropertyEditor) editorClass.newInstance(); 085 else 086 ed = PropertyEditorManager.findEditor(type); 087 if (ed == null && Enum.class.isAssignableFrom(type)) 088 ed = new EnumEditor(type); 089 if (ed == null) return null; 090 091 final PropertyEditor editor = ed; 092 093 Object value = getter.invoke(bean, new Object[] {}); 094 editor.setValue(value); 095 editor.addPropertyChangeListener(event -> { 096 try 097 { 098 setter.invoke(bean, 099 new Object[] { editor.getValue() }); 100 fireStateChanged(null); 101 } 102 catch (IllegalAccessException exception1) 103 { 104 exception1.printStackTrace(); 105 } 106 catch (InvocationTargetException exception2) 107 { 108 exception2.printStackTrace(); 109 } 110 }); 111 return editor; 112 } 113 catch (InstantiationException exception) 114 { 115 exception.printStackTrace(); 116 return null; 117 } 118 catch (IllegalAccessException exception) 119 { 120 exception.printStackTrace(); 121 return null; 122 } 123 catch (InvocationTargetException exception) 124 { 125 exception.printStackTrace(); 126 return null; 127 } 128 } 129 130 /** 131 Wraps a property editor into a component. 132 @param editor the editor to wrap 133 @return a button (if there is a custom editor), 134 combo box (if the editor has tags), or text field (otherwise) 135 */ 136 public Component getEditorComponent(final PropertyEditor editor) 137 { 138 String[] tags = editor.getTags(); 139 String text = editor.getAsText(); 140 if (editor.supportsCustomEditor()) 141 return editor.getCustomEditor(); 142 else if (tags != null) 143 { 144 // make a combo box that shows all tags 145 final JComboBox comboBox = new JComboBox(tags); 146 comboBox.setSelectedItem(text); 147 comboBox.addItemListener(event -> { 148 if (event.getStateChange() == ItemEvent.SELECTED) 149 editor.setAsText( 150 (String)comboBox.getSelectedItem()); 151 }); 152 return comboBox; 153 } 154 else 155 { 156 final JTextField textField = new JTextField(text, 10); 157 textField.getDocument().addDocumentListener(new 158 DocumentListener() 159 { 160 public void insertUpdate(DocumentEvent e) 161 { 162 try 163 { 164 editor.setAsText(textField.getText()); 165 } 166 catch (IllegalArgumentException exception) 167 { 168 } 169 } 170 public void removeUpdate(DocumentEvent e) 171 { 172 try 173 { 174 editor.setAsText(textField.getText()); 175 } 176 catch (IllegalArgumentException exception) 177 { 178 } 179 } 180 public void changedUpdate(DocumentEvent e) 181 { 182 } 183 }); 184 return textField; 185 } 186 } 187 188 /** 189 Adds a change listener to the list of listeners. 190 @param listener the listener to add 191 */ 192 public void addChangeListener(ChangeListener listener) 193 { 194 changeListeners.add(listener); 195 } 196 197 /** 198 Notifies all listeners of a state change. 199 @param event the event to propagate 200 */ 201 private void fireStateChanged(ChangeEvent event) 202 { 203 for (ChangeListener listener : changeListeners) 204 listener.stateChanged(event); 205 } 206 207 private ArrayList<ChangeListener> changeListeners 208 = new ArrayList<ChangeListener>(); 209} 210