001: /*--
002: $Id: SwingEngine.java,v 1.5 2005/06/29 08:02:37 wolfpaulus Exp $
003:
004: Copyright (C) 2003-2007 Wolf Paulus.
005: All rights reserved.
006:
007: Redistribution and use in source and binary forms, with or without
008: modification, are permitted provided that the following conditions
009: are met:
010:
011: 1. Redistributions of source code must retain the above copyright
012: notice, this list of conditions, and the following disclaimer.
013:
014: 2. Redistributions in binary form must reproduce the above copyright
015: notice, this list of conditions, and the disclaimer that follows
016: these conditions in the documentation and/or other materials provided
017: with the distribution.
018:
019: 3. The end-user documentation included with the redistribution,
020: if any, must include the following acknowledgment:
021: "This product includes software developed by the
022: SWIXML Project (http://www.swixml.org/)."
023: Alternately, this acknowledgment may appear in the software itself,
024: if and wherever such third-party acknowledgments normally appear.
025:
026: 4. The name "Swixml" must not be used to endorse or promote products
027: derived from this software without prior written permission. For
028: written permission, please contact <info_AT_swixml_DOT_org>
029:
030: 5. Products derived from this software may not be called "Swixml",
031: nor may "Swixml" appear in their name, without prior written
032: permission from the Swixml Project Management.
033:
034: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
036: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037: DISCLAIMED. IN NO EVENT SHALL THE SWIXML PROJECT OR ITS
038: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
039: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
040: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
041: USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
042: ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
043: OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
044: OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
045: SUCH DAMAGE.
046: ====================================================================
047:
048: This software consists of voluntary contributions made by many
049: individuals on behalf of the Swixml Project and was originally
050: created by Wolf Paulus <wolf_AT_swixml_DOT_org>. For more information
051: on the Swixml Project, please see <http://www.swixml.org/>.
052: */
053: package org.swixml;
054:
055: import org.jdom.Document;
056: import org.jdom.input.SAXBuilder;
057:
058: import javax.swing.*;
059: import java.awt.*;
060: import java.awt.event.ActionListener;
061: import java.awt.event.WindowAdapter;
062: import java.awt.event.WindowEvent;
063: import java.awt.event.WindowListener;
064: import java.io.*;
065: import java.lang.reflect.Field;
066: import java.lang.reflect.Modifier;
067: import java.net.URL;
068: import java.security.AccessControlException;
069: import java.util.*;
070: import java.util.List;
071:
072: /**
073: * The SwingEngine class is the rendering engine able to convert an XML descriptor into a java.swing UI.
074: * <p/>
075: * <img src="doc-files/swixml_1_0.png" ALIGN="center">
076: * </p>
077: *
078: * @author <a href="mailto:wolf@paulus.com">Wolf Paulus</a>
079: * @version $Revision: 1.5 $
080: */
081: public class SwingEngine {
082: //
083: // Static Constants
084: //
085: /**
086: * Mac OSX identifier in System.getProperty(os.name)
087: */
088: public static final String MAC_OSX_OS_NAME = "mac os x";
089:
090: /**
091: * Mac OSX locale variant to localize strings like quit etc.
092: */
093: public static final String MAC_OSX_LOCALE_VARIANT = "mac";
094:
095: /**
096: * XML Error
097: */
098: private static final String XML_ERROR_MSG = "Invalid SwiXML Descriptor.";
099: /**
100: * IO Error Message.
101: */
102: private static final String IO_ERROR_MSG = "Resource could not be found ";
103: /**
104: * Mapping Error Message.
105: */
106: private static final String MAPPING_ERROR_MSG = " could not be mapped to any Object and remained un-initialized.";
107:
108: //
109: // Static Member Variables
110: //
111:
112: /**
113: * Debug / Release Mode
114: */
115: public static boolean DEBUG_MODE = false;
116: /**
117: * main frame
118: */
119: private static Frame appFrame;
120: /**
121: * static resource bundle
122: */
123: private static String default_resource_bundle_name = null;
124: /**
125: * static locale
126: */
127: private static Locale default_locale = Locale.getDefault();
128: /**
129: * Check is currently running on a Mac
130: */
131: private static boolean MAC_OSX = false;
132: /**
133: * static Mac OS X Support, set to true to support Mac UI specialties
134: */
135: private static boolean MAC_OSX_SUPPORTED = true;
136:
137: //
138: // Static Initializer
139: //
140: /** display the swing release version to system out. */
141: static {
142: System.out.println("SwixML @version@");
143: try {
144: MAC_OSX = System.getProperty("os.name").toLowerCase()
145: .startsWith(SwingEngine.MAC_OSX_OS_NAME);
146: } catch (Exception e) {
147: MAC_OSX = false;
148: }
149: }
150:
151: //
152: // Member Variables
153: //
154: /**
155: * Swixml Parser.
156: */
157: private Parser parser = new Parser(this );
158: /**
159: * Client object hosting the swingengine, alternative to extending the SwinEngine Class
160: */
161: private Object client;
162: /**
163: * Root Component for the rendered swing UI.
164: */
165: private Container root;
166: /**
167: * Swing object map, contains only those object that were given an id attribute.
168: */
169: private Map<String, Object> idmap = new HashMap<String, Object>();
170: /**
171: * Flattened Swing object tree, contains all object, even the ones without an id.
172: */
173: private Collection components = null;
174: /**
175: * access to taglib to let overwriting class add and remove tags.
176: */
177: private Localizer localizer = new Localizer();
178: //
179: // Private Constants
180: //
181: /**
182: * Classload to load resources
183: */
184: private final TagLibrary taglib = SwingTagLibrary.getInstance();
185: /**
186: * Localizer, setup by parameters found in the xml descriptor.
187: */
188: protected ClassLoader cl = this .getClass().getClassLoader();
189:
190: /**
191: * Default ctor for a SwingEngine.
192: */
193: public SwingEngine() {
194: this .client = this ;
195: this .setLocale(SwingEngine.default_locale);
196: this .getLocalizer().setResourceBundle(
197: SwingEngine.default_resource_bundle_name);
198:
199: try {
200: if (SwingEngine.isMacOSXSupported()
201: && SwingEngine.isMacOSX()) {
202: // Use apple's ScreenMenuBar instead of the MS-Window style
203: // application's own menu bar
204: System.setProperty("com.apple.macos.useScreenMenuBar",
205: "true");
206: System
207: .setProperty("apple.laf.useScreenMenuBar",
208: "true");
209:
210: // Don't let the growbox intrude other widgets
211: System.setProperty("apple.awt.showGrowBox", "true");
212: System.setProperty(
213: "com.apple.mrj.application.growbox.intrudes",
214: "false");
215: }
216: } catch (AccessControlException e) {
217: // intentionally empty
218: }
219: }
220:
221: /**
222: * Constructor to be used if the SwingEngine is not extend but used through object composition.
223: *
224: * @param client <code>Object</code> owner of this instance
225: */
226: public SwingEngine(Object client) {
227: this ();
228: this .client = client;
229: }
230:
231: /**
232: * Constructs a new SwingEngine, rendering the provided XML into a javax.swing UI
233: *
234: * @param resource <code>String</code>
235: */
236: public SwingEngine(final String resource) {
237: this (SwingEngine.class.getClassLoader(), resource);
238: }
239:
240: /**
241: * Constructs a new SwingEngine, rendering the provided XML into a javax.swing UI
242: *
243: * @param resource <code>String</code>
244: * @deprecated
245: */
246: public SwingEngine(ClassLoader cl, final String resource) {
247: this ();
248: this .setClassLoader(cl);
249: Reader reader = null;
250: try {
251: InputStream in = cl.getResourceAsStream(resource);
252: if (in == null) {
253: throw new IOException(IO_ERROR_MSG + resource);
254: }
255: reader = new InputStreamReader(in);
256: render(reader);
257: } catch (Exception e) {
258: if (SwingEngine.DEBUG_MODE)
259: System.err.println(e);
260: } finally {
261: try {
262: reader.close();
263: } catch (Exception e) {
264: // intentionally empty
265: }
266: }
267: }
268:
269: /**
270: * Gets the parsing of the XML started.
271: *
272: * @param url <code>URL</code> url pointing to an XML descriptor
273: * @return <code>Object</code>- instanced swing object tree root
274: * @throws Exception
275: */
276: public Container render(final URL url) throws Exception {
277: Reader reader = null;
278: Container obj = null;
279: try {
280: InputStream in = url.openStream();
281: if (in == null) {
282: throw new IOException(IO_ERROR_MSG + url.toString());
283: }
284: reader = new InputStreamReader(in);
285: obj = render(reader);
286: } finally {
287: try {
288: reader.close();
289: } catch (Exception ex) {
290: // intentionally empty
291: }
292: }
293: return obj;
294: }
295:
296: /**
297: * Gets the parsing of the XML file started.
298: *
299: * @param resource <code>String</code> xml-file path info
300: * @return <code>Object</code>- instanced swing object tree root
301: */
302: public Container render(final String resource) throws Exception {
303: Reader reader = null;
304: Container obj = null;
305: try {
306: InputStream in = cl.getResourceAsStream(resource);
307: if (in == null) {
308: throw new IOException(IO_ERROR_MSG + resource);
309: }
310: reader = new InputStreamReader(in);
311: obj = render(reader);
312: } finally {
313: try {
314: reader.close();
315: } catch (Exception ex) {
316: // intentionally empty
317: }
318: }
319: return obj;
320: }
321:
322: /**
323: * Gets the parsing of the XML file started.
324: *
325: * @param xml_file <code>File</code> xml-file
326: * @return <code>Object</code>- instanced swing object tree root
327: */
328: public Container render(final File xml_file) throws Exception {
329: if (xml_file == null) {
330: throw new IOException();
331: }
332: return render(new FileReader(xml_file));
333: }
334:
335: /**
336: * Gets the parsing of the XML file started.
337: *
338: * @param xml_reader <code>Reader</code> xml-file path info
339: * @return <code>Object</code>- instanced swing object tree root
340: */
341: public Container render(final Reader xml_reader) throws Exception {
342: if (xml_reader == null) {
343: throw new IOException();
344: }
345: try {
346: return render(new SAXBuilder().build(xml_reader));
347: } catch (org.xml.sax.SAXParseException e) {
348: System.err.println(e);
349: } catch (org.jdom.input.JDOMParseException e) {
350: System.err.println(e);
351: }
352: throw new Exception(SwingEngine.XML_ERROR_MSG);
353: }
354:
355: /**
356: * Gets the parsing of the XML file started.
357: *
358: * @param jdoc <code>Document</code> xml gui descritptor
359: * @return <code>Object</code>- instanced swing object tree root
360: */
361: public Container render(final Document jdoc) throws Exception {
362: idmap.clear();
363: try {
364: root = (Container) parser.parse(jdoc);
365: } catch (Exception e) {
366: if (SwingEngine.DEBUG_MODE)
367: System.err.println(e);
368: throw (e);
369: }
370: // reset components collection
371: components = null;
372: // initialize all client fields with UI components by their id
373: mapMembers(client);
374: if (Frame.class.isAssignableFrom(root.getClass())) {
375: SwingEngine.setAppFrame((Frame) root);
376: }
377: return root;
378: }
379:
380: /**
381: * Inserts swing object rendered from an XML document into the given container.
382: * <p/>
383: * <pre>
384: * Differently to the render methods, insert does NOT consider the root node of the XML document.
385: * </pre>
386: * <pre>
387: * <b>NOTE:</b><br>insert() does NOT clear() the idmap before rendering.
388: * Therefore, if this SwingEngine's parser was used before, the idmap still
389: * contains (key/value) pairs (id, JComponent obj. references).<br>If insert() is NOT
390: * used to insert in a previously (with this very SwingEngine) rendered UI,
391: * it is highly recommended to clear the idmap:
392: * <br>
393: * <div>
394: * <code>mySwingEngine.getIdMap().clear()</code>
395: * </div>
396: * </pre>
397: *
398: * @param url <code>URL</code> url pointing to an XML descriptor *
399: * @param container <code>Container</code> target, the swing obj, are added to.
400: * @throws Exception
401: */
402: public void insert(final URL url, final Container container)
403: throws Exception {
404: Reader reader = null;
405: try {
406: InputStream in = url.openStream();
407: if (in == null) {
408: throw new IOException(IO_ERROR_MSG + url.toString());
409: }
410: reader = new InputStreamReader(in);
411: insert(reader, container);
412: } finally {
413: try {
414: reader.close();
415: } catch (Exception ex) {
416: // intentionally empty
417: }
418: }
419: }
420:
421: /**
422: * Inserts swing objects rendered from an XML reader into the given container.
423: * <p/>
424: * <pre>
425: * Differently to the render methods, insert does NOT consider the root node of the XML document.
426: * </pre>
427: * <pre>
428: * <b>NOTE:</b><br>insert() does NOT clear() the idmap before rendering.
429: * Therefore, if this SwingEngine's parser was used before, the idmap still
430: * contains (key/value) pairs (id, JComponent obj. references).<br>If insert() is NOT
431: * used to insert in a previously (with this very SwingEngine) rendered UI, it is highly
432: * recommended to clear the idmap:
433: * <br>
434: * <div>
435: * <code>mySwingEngine.getIdMap().clear()</code>
436: * </div>
437: * </pre>
438: *
439: * @param reader <code>Reader</code> xml-file path info
440: * @param container <code>Container</code> target, the swing obj, are added to.
441: * @throws Exception
442: */
443: public void insert(final Reader reader, final Container container)
444: throws Exception {
445: if (reader == null) {
446: throw new IOException();
447: }
448: insert(new SAXBuilder().build(reader), container);
449: }
450:
451: /**
452: * Inserts swing objects rendered from an XML reader into the given container.
453: * <p/>
454: * <pre>
455: * Differently to the render methods, insert does NOT consider the root node of the XML document.
456: * </pre>
457: * <pre>
458: * <b>NOTE:</b><br>insert() does NOT clear() the idmap before rendering.
459: * Therefore, if this SwingEngine's parser was used before, the idmap still
460: * contains (key/value) pairs (id, JComponent obj. references).<br>
461: * If insert() is NOT used to insert in a previously (with this very SwingEngine)
462: * rendered UI, it is highly recommended to clear the idmap:
463: * <br>
464: * <div>
465: * <code>mySwingEngine.getIdMap().clear()</code>
466: * </div>
467: * </pre>
468: *
469: * @param resource <code>String</code> xml-file path info
470: * @param container <code>Container</code> target, the swing obj, are added to.
471: * @throws Exception
472: */
473: public void insert(final String resource, final Container container)
474: throws Exception {
475: Reader reader = null;
476: try {
477: InputStream in = cl.getResourceAsStream(resource);
478: if (in == null) {
479: throw new IOException(IO_ERROR_MSG + resource);
480: }
481: reader = new InputStreamReader(in);
482: insert(reader, container);
483: } finally {
484: try {
485: if (reader != null) {
486: reader.close();
487: }
488: } catch (Exception ex) {
489: // intentionally empty
490: }
491: }
492: }
493:
494: /**
495: * Inserts swing objects rendered from an XML document into the given container.
496: * <p/>
497: * <pre>
498: * Differently to the parse methods, insert does NOT consider the root node of the XML document.
499: * </pre>
500: * <pre>
501: * <b>NOTE:</b><br>insert() does NOT clear() the idmap before rendering.
502: * Therefore, if this SwingEngine's parser was used before, the idmap still
503: * contains (key/value) pairs (id, JComponent obj. references).<br>
504: * If insert() is NOT
505: * used to insert in a previously (with this very SwingEngine) rendered UI,
506: * it is highly recommended to clear the idmap:
507: * <br>
508: * <div>
509: * <code>mySwingEngine.getIdMap().clear()</code>
510: * </div>
511: * </pre>
512: *
513: * @param jdoc <code>Document</code> xml-doc path info
514: * @param container <code>Container</code> target, the swing obj, are added to
515: * @throws Exception <code>Exception</code> exception thrown by the parser
516: */
517: public void insert(final Document jdoc, final Container container)
518: throws Exception {
519: root = container;
520: try {
521: parser.parse(jdoc, container);
522: } catch (Exception e) {
523: if (SwingEngine.DEBUG_MODE)
524: System.err.println(e);
525: throw (e);
526: }
527: // reset components collection
528: components = null;
529: // initialize all client fields with UI components by their id
530: mapMembers(client);
531: }
532:
533: /**
534: * Sets the SwingEngine's global resource bundle name, to be used by all SwingEngine instances. This name can be
535: * overwritten however for a single instance, if a <code>bundle</code> attribute is places in the root tag of an XML
536: * descriptor.
537: *
538: * @param bundlename <code>String</code> the resource bundle name.
539: */
540: public static void setResourceBundleName(String bundlename) {
541: SwingEngine.default_resource_bundle_name = bundlename;
542: }
543:
544: /**
545: * Sets the SwingEngine's global locale, to be used by all SwingEngine instances. This locale can be overwritten
546: * however for a single instance, if a <code>locale</code> attribute is places in the root tag of an XML descriptor.
547: *
548: * @param locale <code>Locale</code>
549: */
550: public static void setDefaultLocale(Locale locale) {
551: SwingEngine.default_locale = locale;
552: }
553:
554: /**
555: * Sets the SwingEngine's global application frame variable, to be used as a parent for all child dialogs.
556: *
557: * @param frame <code>Window</code> the parent for all future dialogs.
558: */
559: public static void setAppFrame(Frame frame) {
560: if (frame != null) {
561: if (SwingEngine.appFrame == null) {
562: SwingEngine.appFrame = frame;
563: }
564: }
565: }
566:
567: /**
568: * @return <code>Window</code> a parent for all dialogs.
569: */
570: public static Frame getAppFrame() {
571: return SwingEngine.appFrame;
572: }
573:
574: /**
575: * Returns the object which instantiated this SwingEngine.
576: *
577: * @return <code>Objecy</code> SwingEngine client object
578: * <p/>
579: * <p><b>Note:</b><br>
580: * This is the object used through introspection the actions and fileds are set.
581: * </p>
582: */
583: public Object getClient() {
584: return client;
585: }
586:
587: /**
588: * Returns the root component of the generated Swing UI.
589: *
590: * @return <code>Component</code>- the root component of the javax.swing ui
591: */
592: public Container getRootComponent() {
593: return root;
594: }
595:
596: /**
597: * Returns an Iterator for all parsed GUI components.
598: *
599: * @return <code>Iterator</code> GUI components itearator
600: */
601: public Iterator getAllComponentItertor() {
602: if (components == null) {
603: traverse(root, components = new ArrayList());
604: }
605: return components.iterator();
606: }
607:
608: /**
609: * Returns an Iterator for id-ed parsed GUI components.
610: *
611: * @return <code>Iterator</code> GUI components itearator
612: */
613: public Iterator getIdComponentItertor() {
614: return idmap.values().iterator();
615: }
616:
617: /**
618: * Returns the id map, containing all id-ed parsed GUI components.
619: *
620: * @return <code>Map</code> GUI components map
621: */
622: public Map<String, Object> getIdMap() {
623: return idmap;
624: }
625:
626: /**
627: * Removes all un-displayable compontents from the id map and deletes the components collection (for recreation at the
628: * next request).
629: * <p/>
630: * <pre>
631: * A component is made undisplayable either when it is removed from a displayable containment hierarchy or when its
632: * containment hierarchy is made undisplayable. A containment hierarchy is made undisplayable when its ancestor
633: * window
634: * is disposed.
635: * </pre>
636: *
637: * @return <code>int</code> number of removed componentes.
638: */
639: public int cleanup() {
640: List zombies = new ArrayList();
641: Iterator it = idmap.keySet().iterator();
642: while (it != null && it.hasNext()) {
643: Object key = it.next();
644: Object obj = idmap.get(key);
645: if (obj instanceof Component
646: && !((Component) obj).isDisplayable()) {
647: zombies.add(key);
648: }
649: }
650: for (int i = 0; i < zombies.size(); i++) {
651: idmap.remove(zombies.get(i));
652: }
653: components = null;
654: return zombies.size();
655: }
656:
657: /**
658: * Removes the id from the internal from the id map, to make the given id available for re-use.
659: *
660: * @param id <code>String</code> assigned name
661: */
662: public void forget(final String id) {
663: idmap.remove(id);
664: }
665:
666: /**
667: * Returns the UI component with the given name or null.
668: *
669: * @param id <code>String</code> assigned name
670: * @return <code>Component</code>- the GUI component with the given name or null if not found.
671: */
672: public Component find(final String id) {
673: Object obj = idmap.get(id);
674: if (obj != null
675: && !Component.class.isAssignableFrom(obj.getClass())) {
676: obj = null;
677: }
678: return (Component) obj;
679: }
680:
681: /**
682: * Sets the locale to be used during parsing / String conversion
683: *
684: * @param l <code>Locale</code>
685: */
686: public void setLocale(Locale l) {
687: if (SwingEngine.isMacOSXSupported() && SwingEngine.isMacOSX()) {
688: l = new Locale(l.getLanguage(), l.getCountry(),
689: SwingEngine.MAC_OSX_LOCALE_VARIANT);
690: }
691: this .localizer.setLocale(l);
692: }
693:
694: /**
695: * Sets the ResourceBundle to be used during parsing / String conversion
696: *
697: * @param bundlename <code>String</code>
698: */
699: public void setResourceBundle(String bundlename) {
700: this .localizer.setResourceBundle(bundlename);
701: }
702:
703: /**
704: * @return <code>Localizer</code>- the Localizer, which is used for localization.
705: */
706: public Localizer getLocalizer() {
707: return localizer;
708: }
709:
710: /**
711: * @return <code>TagLibrary</code>- the Taglibray to insert custom tags.
712: * <p/>
713: * <pre><b>Note:</b>ConverterLibrary and TagLibray need to be set up before rendering is called.
714: * </pre>
715: */
716: public TagLibrary getTaglib() {
717: return taglib;
718: }
719:
720: /**
721: * Sets a classloader to be used for all <i>getResourse..()</i> and <i> loadClass()</i> calls. If no class loader is
722: * set, the SwingEngine's loader is used.
723: *
724: * @param cl <code>ClassLoader</code>
725: * @see ClassLoader#loadClass
726: * @see ClassLoader#getResource
727: */
728: public void setClassLoader(ClassLoader cl) {
729: this .cl = cl;
730: this .localizer.setClassLoader(cl);
731: }
732:
733: /**
734: * @return <code>ClassLoader</code>- the Classloader used for all <i> getResourse..()</i> and <i>loadClass()</i>
735: * calls.
736: */
737: public ClassLoader getClassLoader() {
738: return cl;
739: }
740:
741: /**
742: * Recursively Sets an ActionListener
743: * <p/>
744: * <pre>
745: * Backtracking algorithm: if al was set for a child component, its not being set for its parent
746: * </pre>.
747: *
748: * @param c <code>Component</code> start component
749: * @param al <code>ActionListener</code>
750: * @return <code>boolean</code> true, if ActionListener was set.
751: */
752: public boolean setActionListener(final Component c,
753: final ActionListener al) {
754: boolean b = false;
755: if (c != null) {
756: if (Container.class.isAssignableFrom(c.getClass())) {
757: final Component[] s = ((Container) c).getComponents();
758: for (Component value : s) {
759: b = b | setActionListener(value, al);
760: }
761: }
762: if (!b) {
763: if (JMenu.class.isAssignableFrom(c.getClass())) {
764: final JMenu m = (JMenu) c;
765: final int k = m.getItemCount();
766: for (int i = 0; i < k; i++) {
767: b = b | setActionListener(m.getItem(i), al);
768: }
769: } else if (AbstractButton.class.isAssignableFrom(c
770: .getClass())) {
771: ((AbstractButton) c).addActionListener(al);
772: b = true;
773: }
774: }
775:
776: }
777: return b;
778: }
779:
780: /**
781: * Walks the whole tree to add all components into the <code>components<code> collection.
782: *
783: * @param c <code> Component</code> recursive start component.
784: * <p/>
785: * Note:There is another collection available that only tracks
786: * those object that were provided with an <em>id</em>attribute, which hold an unique id
787: * </p>
788: * @return <code>Iterator</code> to walk all components, not just the id components.
789: */
790: public Iterator getDescendants(final Component c) {
791: List list = new ArrayList(12);
792: SwingEngine.traverse(c, list);
793: return list.iterator();
794: }
795:
796: /**
797: * Introspects the given object's class and initializes its non-transient fields with objects that have been instanced
798: * during parsing. Mappping happens based on type and field name: the fields name has to be equal to the tag id,
799: * psecified in the XML descriptor. The fields class has to be assignable (equals or super class..) from the class
800: * that was used to instance the tag.
801: *
802: * @param obj <code>Object</code> target object to be mapped with instanced tags
803: */
804: protected void mapMembers(Object obj) {
805: if (obj != null) {
806: mapMembers(obj, obj.getClass());
807: }
808: }
809:
810: private void mapMembers(Object obj, Class cls) {
811: boolean fullaccess = true;
812:
813: if (obj != null && cls != null && !Object.class.equals(cls)) {
814: Field[] flds = null;
815: try {
816: flds = cls.getDeclaredFields();
817: } catch (AccessControlException e) {
818: fullaccess = false; // applet or otherwise restricted environment
819: flds = cls.getFields();
820: }
821: //
822: // loops through class' declared fields and try to find a matching widget.
823: //
824: for (int i = 0; i < flds.length; i++) {
825: Object widget = idmap.get(flds[i].getName());
826: if (widget != null) {
827: // field and object type need to be compatible and field must not be declared Transient
828: if (flds[i].getType().isAssignableFrom(
829: widget.getClass())
830: && !Modifier.isTransient(flds[i]
831: .getModifiers())) {
832: try {
833:
834: boolean accessible = flds[i].isAccessible();
835: flds[i].setAccessible(true);
836: flds[i].set(obj, widget);
837: flds[i].setAccessible(accessible);
838: } catch (AccessControlException e) {
839: try {
840: fullaccess = false;
841: flds[i].set(obj, widget);
842: } catch (IllegalAccessException e1) {
843: }
844:
845: } catch (IllegalArgumentException e) {
846: // intentionally empty
847: } catch (IllegalAccessException e) {
848: // intentionally empty
849: }
850: }
851: }
852:
853: //
854: // If an intended mapping didn't work out the objects member would remain un-initialized.
855: // To prevent this, we try to instantiate with a default ctor.
856: //
857: if (flds[i] == null) {
858: if (!SwingEngine.DEBUG_MODE) {
859: try {
860: flds[i].set(obj, flds[i].getType()
861: .newInstance());
862: } catch (IllegalArgumentException e) {
863: // intentionally empty
864: } catch (IllegalAccessException e) {
865: // intentionally empty
866: } catch (InstantiationException e) {
867: // intentionally empty
868: }
869: } else { // SwingEngine.DEBUG_MODE)
870: System.err.println(flds[i].getType() + " : "
871: + flds[i].getName()
872: + SwingEngine.MAPPING_ERROR_MSG);
873: }
874: }
875: }
876:
877: // Since getDeclaredFields() only works on the class itself, not the super class,
878: // we need to make this recursive down to the object.class
879: if (fullaccess) {
880: // only if we have access to the declared fields do we need to visit the whole tree.
881: mapMembers(obj, cls.getSuperclass());
882: }
883: }
884: }
885:
886: /**
887: * Walks the whole tree to add all components into the <code>components<code> collection.
888: *
889: * @param c <code>Component</code> recursive start component.
890: * @param collection <code>Collection</code> target collection.
891: * <p/>
892: * Note:There is another collection available that only tracks
893: * those object that were provided with an <em>id</em>attribute, which hold an unique id
894: * </p>
895: */
896: protected static void traverse(final Component c,
897: Collection collection) {
898: if (c != null) {
899: collection.add(c);
900: if (c instanceof JMenu) {
901: final JMenu m = (JMenu) c;
902: final int k = m.getItemCount();
903: for (int i = 0; i < k; i++) {
904: traverse(m.getItem(i), collection);
905: }
906: } else if (c instanceof Container) {
907: final Component[] s = ((Container) c).getComponents();
908: for (Component value : s) {
909: traverse(value, collection);
910: }
911: }
912: }
913: }
914:
915: /**
916: * Enables or disables support of Mac OS X GUIs
917: *
918: * @param osx <code>boolean</code>
919: */
920: public static void setMacOSXSuport(boolean osx) {
921: SwingEngine.MAC_OSX_SUPPORTED = osx;
922: }
923:
924: /**
925: * Indicates state of Mac OS X support (default is true = ON).
926: *
927: * @return <code>boolean</code>- indicating MacOS support is enabled
928: */
929: public static boolean isMacOSXSupported() {
930: return SwingEngine.MAC_OSX_SUPPORTED;
931: }
932:
933: /**
934: * Indicates if currently running on Mac OS X
935: *
936: * @return <code>boolean</code>- indicating if currently running on a MAC
937: */
938: public static boolean isMacOSX() {
939: return SwingEngine.MAC_OSX;
940: }
941:
942: /**
943: * Displays the GUI during a RAD session. If the root component is neither a JFrame nor a JDialog, the a JFrame is
944: * instantiated and the root is added into the new frames contentpane.
945: */
946: public void test() {
947: WindowListener wl = new WindowAdapter() {
948: public void windowClosing(WindowEvent e) {
949: super .windowClosing(e);
950: System.exit(0);
951: }
952: };
953: if (root != null) {
954: if (JFrame.class.isAssignableFrom(root.getClass())
955: || JDialog.class.isAssignableFrom(root.getClass())) {
956: ((Window) root).addWindowListener(wl);
957: root.setVisible(true);
958: } else {
959: JFrame jf = new JFrame("SwiXml Test");
960: jf.getContentPane().add(root);
961: jf.pack();
962: jf.addWindowListener(wl);
963: jf.setVisible(true);
964: }
965: }
966: }
967: }
|