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