001package horstmann.ch08_umleditor;
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