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 }