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 }