001    /*
002     * $Id: BeanInfoSupport.java 3271 2009-02-24 19:28:06Z kschaefe $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    package org.jdesktop.swingx;
022    
023    import java.awt.Image;
024    import java.beans.BeanDescriptor;
025    import java.beans.BeanInfo;
026    import java.beans.EventSetDescriptor;
027    import java.beans.Introspector;
028    import java.beans.MethodDescriptor;
029    import java.beans.PropertyDescriptor;
030    import java.beans.SimpleBeanInfo;
031    import java.net.URL;
032    import java.util.HashMap;
033    import java.util.Map;
034    import java.util.TreeMap;
035    import java.util.logging.Level;
036    import java.util.logging.Logger;
037    import javax.swing.ImageIcon;
038    
039    /**
040     * Useful baseclass for BeanInfos. With this class, normal introspection occurs
041     * and then you are given the opportunity to reconfigure portions of the
042     * bean info in the <code>initialize</code> method.
043     *
044     * @author rbair, Jan Stola
045     */
046    public abstract class BeanInfoSupport extends SimpleBeanInfo {
047        private static Logger LOG = Logger.getLogger(BeanInfoSupport.class.getName());
048        
049        /**
050         * Indicates whether I am introspecting state for the give class. This
051         * helps prevent infinite loops
052         */
053        private static Map<Class<?>, Boolean> introspectingState = new HashMap<Class<?>, Boolean>();
054        /**
055         * The class of the bean that this BeanInfoSupport is for
056         */
057        private Class<?> beanClass;
058        
059        /**
060         * @see BeanInfo
061         */
062        private int defaultPropertyIndex = -1;
063        /**
064         * @see BeanInfo
065         */
066        private int defaultEventIndex = -1;
067        /**
068         * The 16x16 color icon
069         */
070        private Image iconColor16 = null;
071        /**
072         * The 32x32 color icon
073         */
074        private Image iconColor32 = null;
075        /**
076         * The 16x16 monochrome icon
077         */
078        private Image iconMono16 = null;
079        /**
080         * The 32x32 monochrome icon
081         */
082        private Image iconMono32 = null;
083        /**
084         * A reference to the icon. This String must be of a form that
085         * ImageIO can use to locate and load the icon image
086         */
087        private String iconNameC16 = null;
088        /**
089         * A reference to the icon. This String must be of a form that
090         * ImageIO can use to locate and load the icon image
091         */
092        private String iconNameC32 = null;
093        /**
094         * A reference to the icon. This String must be of a form that
095         * ImageIO can use to locate and load the icon image
096         */
097        private String iconNameM16 = null;
098        /**
099         * A reference to the icon. This String must be of a form that
100         * ImageIO can use to locate and load the icon image
101         */
102        private String iconNameM32 = null;
103        
104        private BeanDescriptor beanDescriptor;
105        
106        private Map<String, PropertyDescriptor> properties = new TreeMap<String, PropertyDescriptor>();
107        private Map<String, EventSetDescriptor> events = new TreeMap<String, EventSetDescriptor>();
108        private Map<String, MethodDescriptor> methods = new TreeMap<String, MethodDescriptor>();
109    
110        /**
111         * Creates a new instance of BeanInfoSupport.
112         * 
113         * @param beanClass class of the bean.
114         */
115        public BeanInfoSupport(Class<?> beanClass) {
116            this.beanClass = beanClass;
117            if (!isIntrospecting()) {
118                introspectingState.put(beanClass, Boolean.TRUE);
119                try {
120                    Class<?> superClass = beanClass.getSuperclass();
121                    while (superClass != null) {
122                        Introspector.flushFromCaches(superClass);
123                        superClass = superClass.getSuperclass();
124                    }
125                    BeanInfo info = Introspector.getBeanInfo(beanClass);
126                    beanDescriptor = info.getBeanDescriptor();
127                    if (beanDescriptor != null) {
128                        Class<?> customizerClass = getCustomizerClass();
129                        beanDescriptor = new BeanDescriptor(beanDescriptor.getBeanClass(),
130                                customizerClass == null ? beanDescriptor.getCustomizerClass()
131                                : customizerClass);
132                    } else {
133                        beanDescriptor = new BeanDescriptor(beanClass, getCustomizerClass());
134                    }
135                    for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
136                        properties.put(pd.getName(), pd);
137                    }
138                    for (EventSetDescriptor esd : info.getEventSetDescriptors()) {
139                        events.put(esd.getName(), esd);
140                    }
141                    for (MethodDescriptor md : info.getMethodDescriptors()) {
142                        methods.put(md.getName(), md);
143                    }
144                    
145                    defaultPropertyIndex = info.getDefaultPropertyIndex();
146                    defaultEventIndex = info.getDefaultEventIndex();
147                                    
148                    iconColor16 = loadStandardImage(info, BeanInfo.ICON_COLOR_16x16);
149                    iconColor32 = loadStandardImage(info, BeanInfo.ICON_COLOR_32x32);
150                    iconMono16 = loadStandardImage(info, BeanInfo.ICON_MONO_16x16);
151                    iconMono32 = loadStandardImage(info, BeanInfo.ICON_MONO_32x32);
152                } catch (Exception e) {
153                    e.printStackTrace();
154                }
155                introspectingState.put(beanClass, Boolean.FALSE);
156                initialize();
157            }
158        }
159        
160        private boolean isIntrospecting() {
161            Boolean b = introspectingState.get(beanClass);
162            return b == null ? false : b.booleanValue();
163        }
164    
165        /**
166         * attempts to load a png icon from the
167         * resource directory beneath beaninfo, named like:
168         *   JXTaskPaneContainer16.png
169         *   JXTaskPaneContainer16-mono.png
170         *   JXTaskPaneContainer32.png
171         *   JXTaskPaneContainer32-mono.png
172         * 
173         * if any of the icons is missing, an attempt is made to
174         * get an icon via introspection. If that fails, the icon
175         * will be set to placeholder16.png or one of the derivitives
176         */
177        private Image loadStandardImage(BeanInfo info, int size) {
178            String s = "";
179            switch (size) {
180                case BeanInfo.ICON_COLOR_16x16: s = "16"; break;
181                case BeanInfo.ICON_COLOR_32x32: s = "32"; break;
182                case BeanInfo.ICON_MONO_16x16: s = "16-mono"; break;
183                case BeanInfo.ICON_MONO_32x32: s = "32-mono"; break;
184            }
185            String iconName = beanClass.getSimpleName() + s + ".png";
186            
187            Image image = null;
188            try {
189                image = loadImage("resources/" + iconName);
190            } catch (Exception e) {
191                LOG.info("No icon named " + iconName + " was found");
192            }
193            
194            return image;
195        }
196        
197        @Override
198        public Image loadImage(final String resourceName) {
199            URL url = getClass().getResource(resourceName);
200            return (url == null) ? null : new ImageIcon(url).getImage();
201        }
202    
203        /**
204         * Called by the constructor during the proper time so that subclasses
205         * can override the settings/values for the various beaninfo properties.
206         * For example, you could call setDisplayName("Foo Name", "foo") to change
207         * the foo properties display name
208         */
209        protected abstract void initialize();
210    
211        /**
212         * Override this method if you want to return a custom customizer class
213         * for the bean
214         * 
215         * @return <code>null</code>.
216         */
217        protected Class<?> getCustomizerClass() {
218            return null;
219        }
220        
221        //------------------------------------ Methods for mutating the BeanInfo    
222        /**
223         * Specify the name/url/path to the small 16x16 color icon
224         * 
225         * @param name name of the icon.
226         */
227        protected void setSmallColorIconName(String name) {
228            iconNameC16 = name;
229        }
230        
231        /**
232         * Specify the name/url/path to the 32x32 color icon
233         * 
234         * @param name name of the icon.
235         */
236        protected void setColorIconName(String name) {
237            iconNameC32 = name;
238        }
239    
240        /**
241         * Specify the name/url/path to the small 16x16 monochrome icon
242         * 
243         * @param name name of the icon.
244         */
245        protected void setSmallMonoIconName(String name) {
246            iconNameM16 = name;
247        }
248    
249        /**
250         * Specify the name/url/path to the 32x32 monochrome icon
251         * 
252         * @param name name of the icon.
253         */
254        protected void setMonoIconName(String name) {
255            iconNameM32 = name;
256        }
257        
258        /**
259         * Changes the display name of the given named property. Property names
260         * are always listed last to allow for varargs
261         * 
262         * @param displayName display name of the property.
263         * @param propertyName name of the property.
264         */
265        protected void setDisplayName(String displayName, String propertyName) {
266            PropertyDescriptor pd = properties.get(propertyName);
267            if (pd != null) {
268                pd.setDisplayName(displayName);
269            } else {
270                LOG.log(Level.WARNING, "Failed to set display name for property '" +
271                        propertyName + "'. No such property was found");
272            }
273        }
274        
275        /**
276         * Sets the given named properties to be "hidden".
277         * 
278         * @param hidden determines whether the properties should be marked as hidden or not.
279         * @param propertyNames name of properties.
280         * @see PropertyDescriptor
281         */
282        protected void setHidden(boolean hidden, String... propertyNames) {
283            for (String propertyName : propertyNames) {
284                PropertyDescriptor pd = properties.get(propertyName);
285                if (pd != null) {
286                    pd.setHidden(hidden);
287                } else {
288                    LOG.log(Level.WARNING, "Failed to set hidden attribute for property '" +
289                            propertyName + "'. No such property was found");
290                }
291            }
292        }
293        
294        protected void setExpert(boolean expert, String... propertyNames) {
295            for (String propertyName : propertyNames) {
296                PropertyDescriptor pd = properties.get(propertyName);
297                if (pd != null) {
298                    pd.setExpert(expert);
299                } else {
300                    LOG.log(Level.WARNING, "Failed to set expert attribute for property '" +
301                            propertyName + "'. No such property was found");
302                }
303            }
304        }
305        
306        protected void setPreferred(boolean preferred, String... propertyNames) {
307            for (String propertyName : propertyNames) {
308                PropertyDescriptor pd = properties.get(propertyName);
309                if (pd != null) {
310                    pd.setPreferred(preferred);
311                } else {
312                    LOG.log(Level.WARNING, "Failed to set preferred attribute for property '" +
313                            propertyName + "'. No such property was found");
314                }
315            }
316        }
317        
318        protected void setBound(boolean bound, String... propertyNames) {
319            for (String propertyName : propertyNames) {
320                PropertyDescriptor pd = properties.get(propertyName);
321                if (pd != null) {
322                    pd.setBound(bound);
323                } else {
324                    LOG.log(Level.WARNING, "Failed to set bound attribute for property '" +
325                            propertyName + "'. No such property was found");
326                }
327            }
328        }
329        
330        protected void setConstrained(boolean constrained, String... propertyNames) {
331            for (String propertyName : propertyNames) {
332                PropertyDescriptor pd = properties.get(propertyName);
333                if (pd != null) {
334                    pd.setConstrained(constrained);
335                } else {
336                    LOG.log(Level.WARNING, "Failed to set constrained attribute for property '" +
337                            propertyName + "'. No such property was found");
338                }
339            }
340        }
341        
342        protected void setCategory(String categoryName, String... propertyNames) {
343            for (String propertyName : propertyNames) {
344                PropertyDescriptor pd = properties.get(propertyName);
345                if (pd != null) {
346                    pd.setValue("category", categoryName);
347                } else {
348                    LOG.log(Level.WARNING, "Failed to set category for property '" +
349                            propertyName + "'. No such property was found");
350                }
351            }
352        }
353        
354        protected void setPropertyEditor(Class<?> editorClass, String... propertyNames) {
355            for (String propertyName : propertyNames) {
356                PropertyDescriptor pd = properties.get(propertyName);
357                if (pd != null) {
358                    pd.setPropertyEditorClass(editorClass);
359                } else {
360                    LOG.log(Level.WARNING, "Failed to set property editor for property '" +
361                            propertyName + "'. No such property was found");
362                }
363            }
364        }
365        
366        protected void setEnumerationValues(EnumerationValue[] values, String... propertyNames) {
367            if (values == null) {
368                return;
369            }
370            
371            Object[] enumValues = new Object[values.length * 3];
372            int index = 0;
373            for (EnumerationValue ev : values) {
374                enumValues[index++] = ev.getName();
375                enumValues[index++] = ev.getValue();
376                enumValues[index++] = ev.getJavaInitializationString();
377            }
378            
379            for (String propertyName : propertyNames) {
380                PropertyDescriptor pd = properties.get(propertyName);
381                if (pd != null) {
382                    pd.setValue("enumerationValues", enumValues);
383                } else {
384                    LOG.log(Level.WARNING, "Failed to set enumeration values for property '" +
385                            propertyName + "'. No such property was found");
386                }
387            }
388        }
389        
390        //----------------------------------------------------- BeanInfo methods
391        /**
392         * Gets the bean's <code>BeanDescriptor</code>s.
393         *
394         * @return BeanDescriptor describing the editable
395         * properties of this bean.  May return null if the
396         * information should be obtained by automatic analysis.
397         */
398        @Override
399        public BeanDescriptor getBeanDescriptor() {
400            return isIntrospecting() ? null : beanDescriptor;
401        }
402        
403        /**
404         * Gets the bean's <code>PropertyDescriptor</code>s.
405         *
406         * @return An array of PropertyDescriptors describing the editable
407         * properties supported by this bean.  May return null if the
408         * information should be obtained by automatic analysis.
409         * <p>
410         * If a property is indexed, then its entry in the result array will
411         * belong to the IndexedPropertyDescriptor subclass of PropertyDescriptor.
412         * A client of getPropertyDescriptors can use "instanceof" to check
413         * if a given PropertyDescriptor is an IndexedPropertyDescriptor.
414         */
415        @Override
416        public PropertyDescriptor[] getPropertyDescriptors() {
417            return isIntrospecting() 
418                ? null
419                : properties.values().toArray(new PropertyDescriptor[0]);
420        }
421        
422        /**
423         * Gets the bean's <code>EventSetDescriptor</code>s.
424         *
425         * @return  An array of EventSetDescriptors describing the kinds of
426         * events fired by this bean.  May return null if the information
427         * should be obtained by automatic analysis.
428         */
429        @Override
430        public EventSetDescriptor[] getEventSetDescriptors() {
431            return isIntrospecting()
432                ? null
433                : events.values().toArray(new EventSetDescriptor[0]);
434        }
435        
436        /**
437         * Gets the bean's <code>MethodDescriptor</code>s.
438         *
439         * @return  An array of MethodDescriptors describing the methods
440         * implemented by this bean.  May return null if the information
441         * should be obtained by automatic analysis.
442         */
443        @Override
444        public MethodDescriptor[] getMethodDescriptors() {
445            return isIntrospecting()
446                ? null
447                : methods.values().toArray(new MethodDescriptor[0]);
448        }
449        
450        /**
451         * A bean may have a "default" property that is the property that will
452         * mostly commonly be initially chosen for update by human's who are
453         * customizing the bean.
454         * @return  Index of default property in the PropertyDescriptor array
455         *      returned by getPropertyDescriptors.
456         * <P>    Returns -1 if there is no default property.
457         */
458        @Override
459        public int getDefaultPropertyIndex() {
460            return isIntrospecting() ? -1 : defaultPropertyIndex;
461        }
462        
463        /**
464         * A bean may have a "default" event that is the event that will
465         * mostly commonly be used by human's when using the bean.
466         * @return Index of default event in the EventSetDescriptor array
467         *      returned by getEventSetDescriptors.
468         * <P>    Returns -1 if there is no default event.
469         */
470        @Override
471        public int getDefaultEventIndex() {
472            return isIntrospecting() ? -1 : defaultEventIndex;
473        }
474        
475        /**
476         * This method returns an image object that can be used to
477         * represent the bean in toolboxes, toolbars, etc.   Icon images
478         * will typically be GIFs, but may in future include other formats.
479         * <p>
480         * Beans aren't required to provide icons and may return null from
481         * this method.
482         * <p>
483         * There are four possible flavors of icons (16x16 color,
484         * 32x32 color, 16x16 mono, 32x32 mono).  If a bean choses to only
485         * support a single icon we recommend supporting 16x16 color.
486         * <p>
487         * We recommend that icons have a "transparent" background
488         * so they can be rendered onto an existing background.
489         *
490         * @param  iconKind  The kind of icon requested.  This should be
491         *    one of the constant values ICON_COLOR_16x16, ICON_COLOR_32x32,
492         *    ICON_MONO_16x16, or ICON_MONO_32x32.
493         * @return  An image object representing the requested icon.  May
494         *    return null if no suitable icon is available.
495         */
496        @Override
497        public java.awt.Image getIcon(int iconKind) {
498            switch ( iconKind ) {
499                case ICON_COLOR_16x16:
500                    return getImage(iconNameC16, iconColor16);
501                case ICON_COLOR_32x32:
502                    return getImage(iconNameC32, iconColor32);
503                case ICON_MONO_16x16:
504                    return getImage(iconNameM16, iconMono16);
505                case ICON_MONO_32x32:
506                    return getImage(iconNameM32, iconMono32);
507                default:
508                    return null;
509            }
510        }
511        
512        private java.awt.Image getImage(String name, java.awt.Image img) {
513            if (img == null) {
514                if (name != null) {
515                    img = loadImage(name);
516                }
517            }
518            return img;
519        }
520    }