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 }