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 }