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