Source Code Cross Referenced for ComponentReference.java in  » Testing » abbot-1.0.1 » abbot » script » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Testing » abbot 1.0.1 » abbot.script 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        package abbot.script;
0002:
0003:        import java.applet.Applet;
0004:        import java.awt.*;
0005:        import java.io.*;
0006:        import java.lang.ref.WeakReference;
0007:        import java.util.*;
0008:
0009:        import javax.swing.*;
0010:        import javax.swing.border.*;
0011:        import javax.accessibility.*;
0012:
0013:        import org.jdom.*;
0014:        import org.jdom.input.SAXBuilder;
0015:
0016:        import abbot.Log;
0017:        import abbot.i18n.Strings;
0018:        import abbot.finder.*;
0019:        import abbot.tester.*;
0020:        import abbot.tester.Robot;
0021:        import abbot.util.ExtendedComparator;
0022:        import abbot.util.AWT;
0023:
0024:        /** Encapsulate as much information as is available to identify a GUI
0025:         * component. Usage:<br>
0026:         * <blockquote><code>
0027:         * &lt;component id="..." class="..." [...]&gt;<br>
0028:         * </code></blockquote>
0029:         * The component reference ID may be used in scripts in place of the actual
0030:         * component to which this reference refers.  The conversion will be made when
0031:         * the actual component is needed.  The ID is arbitrary, and may be changed in
0032:         * scripts to any unique string (just remember to change all references to the
0033:         * ID in other places in the script as well).<p>
0034:         * A number of optional tags are supported to provide an increasingly precise
0035:         * specification of the desired component:<br>
0036:         * <ul>
0037:         * <li><b><code>weighted</code></b> refers to the name of an available
0038:         * attribute which should be weighted more heavily in comparisons, e.g. the
0039:         * label on a JButton.<br>
0040:         * <li><b><code>parent</code></b> the reference id of this component's
0041:         * parent.<br> 
0042:         * </ul>
0043:         * <p>
0044:         * ComponentReferences may be created in one of three ways, each of which has
0045:         * slightly different implications.
0046:         * <ul>
0047:         * <li>Resolver.addComponent(Component) - creates a reference if one doesn't
0048:         * already exist and modifiers the Resolver to include it.
0049:         * <li>getReference(Resolver, Component, Map) - create a reference only if a
0050:         * matching one does not exist, but does not modify the Resolver.
0051:         * <li>ComponentReference<init> - create a new reference.
0052:         * </ul>
0053:         */
0054:        // TODO: lose exact hierarchy match, cf Xt resource specifier, e.g.
0055:        // TODO: add window appearance order
0056:        //   JRootPane.<name>|<class>.*.JPanel
0057:        // Other attributes that might be useful: getAccessibleRole,
0058:        // getAccessibleDescription, tooltip, accessibleRelation, selection start/end 
0059:        /*
0060:         To extend, probably want to make a static function here that stores
0061:         attributes and a lookup interface to read that attribute.  Do this only if
0062:         it is directly needed.
0063:
0064:         Should the JRE class be used instead of a custom class?  
0065:         pros: doesn't save custom classes, which might change
0066:         cons: ?
0067:         Should class mismatches be allowed?
0068:         cons: can't change classes 
0069:         pros: exact (or derived) class matching eliminates a lot of comparisons
0070:         */
0071:
0072:        /*
0073:         Optimization note:  All lookups are cached, so that at most we have to
0074:         traverse the hierarchy once.
0075:         Successful lookups are cached until the referenced ocmponent is GCd,
0076:         removed from the hierarchy, or otherwise marked invalid.
0077:         Unsuccessful lookups are cached for the duration of a particular lookup.
0078:         These happen in several places.
0079:         1) when resolving a cref into a component (getComponent())
0080:         2) when a script is checking for existing references prior to creating a
0081:         new one (getReference()).
0082:         3) when creating a new reference (ComponentReference().
0083:         4) when looking for a matching, existing reference (matchExisting()).
0084:         In these cases, the failed lookup cache is cleared only after the
0085:         entire operation is complete.
0086:         see also NOTES
0087:         */
0088:
0089:        public class ComponentReference implements  XMLConstants, XMLifiable,
0090:                Comparable {
0091:
0092:            public static final String SHARED_FRAME_ID = "shared frame";
0093:
0094:            // Matching weights for various attributes
0095:            private static final int MW_NAME = 100;
0096:            private static final int MW_ROOT = 25;
0097:            //private static final int MW_WEIGHTED = 50;
0098:            private static final int MW_TAG = 50;
0099:            private static final int MW_PARENT = 25; //
0100:            private static final int MW_WINDOW = 25;
0101:            private static final int MW_INVOKER = 25;
0102:            private static final int MW_TITLE = 25;
0103:            private static final int MW_BORDER_TITLE = 25;
0104:            private static final int MW_LABEL = 25;
0105:            private static final int MW_TEXT = 25; //
0106:            private static final int MW_ICON = 25;
0107:            private static final int MW_INDEX = 10; //
0108:            private static final int MW_CLASS = 1;
0109:            // Pretty much for applets only, or other embedded frames
0110:            private static final int MW_PARAMS = 1;
0111:            private static final int MW_DOCBASE = 1;
0112:            // Mostly for distinguishing between multiple components that would
0113:            // otherwise all match 
0114:            private static final int MW_HORDER = 1;
0115:            private static final int MW_VORDER = 1;
0116:            //private static final int MW_ENABLED = 1;
0117:            //private static final int MW_FOCUSED = 1;
0118:            private static final int MW_SHOWING = 1;
0119:            /** Match weight corresponding to no possible match. */
0120:            public static final int MW_FAILURE = 0;
0121:            static final String ANON_INNER_CLASS = "/^.*\\$[0-9]+$/";
0122:
0123:            private Resolver resolver;
0124:            private Map attributes = new HashMap();
0125:            // This helps component reference creation by an order of magnitude,
0126:            // especially when dealing with ordered attributes.
0127:            private WeakReference cachedLookup;
0128:            /** This ThreadLocal allows us to keep track of unresolved components on a
0129:             * per-thread (basically per-lookup) basis.
0130:             */
0131:            private static ThreadLocal lookupFailures = new ThreadLocal() {
0132:                protected synchronized Object initialValue() {
0133:                    return new HashMap();
0134:                }
0135:            };
0136:            /** This ThreadLocal allows us to keep track of non-showing, resolved
0137:             * components on a per-thread (basically per-lookup) basis.
0138:             */
0139:            private static ThreadLocal nonShowingMatches = new ThreadLocal() {
0140:                protected synchronized Object initialValue() {
0141:                    return new HashMap();
0142:                }
0143:            };
0144:            /** Keep track of which ComponentReference ctor is the first one. */
0145:            private static ThreadLocal ownsFailureCache = new ThreadLocal() {
0146:                protected synchronized Object initialValue() {
0147:                    return Boolean.TRUE;
0148:                }
0149:            };
0150:            /** Cached XML representation. */
0151:            private String xml;
0152:
0153:            /** Disable immediate cacheing of components when a reference is created
0154:                based on a Component.  Cacheing will first be done when the reference
0155:                is resolved for the first time after creation.  For testing purposes
0156:                only. 
0157:             */
0158:            static boolean cacheOnCreation = true;
0159:
0160:            /** For creation from XML.
0161:             */
0162:            public ComponentReference(Resolver resolver, Element el)
0163:                    throws InvalidScriptException {
0164:                this .resolver = resolver;
0165:                fromXML(el, true);
0166:            }
0167:
0168:            /** Create a reference to an instance of the given class, given an array
0169:                of name/value pairs of attributes.
0170:             */
0171:            public ComponentReference(Resolver r, Class cls,
0172:                    String[][] attributes) {
0173:                this (r, cls, createAttributeMap(attributes));
0174:            }
0175:
0176:            /** Create a reference to an instance of the given class, given a Map of
0177:                attributes.   
0178:             */
0179:            public ComponentReference(Resolver resolver, Class cls,
0180:                    Map attributes) {
0181:                // sort of a hack to provide a 'default' resolver
0182:                this .resolver = resolver;
0183:                this .attributes.putAll(attributes);
0184:                this .attributes.put(TAG_CLASS, Robot.getCanonicalClass(cls)
0185:                        .getName());
0186:                if (resolver != null) {
0187:                    if (this .attributes.get(TAG_ID) == null) {
0188:                        this .attributes.put(TAG_ID, getUniqueID(new HashMap()));
0189:                    }
0190:                    resolver.addComponentReference(this );
0191:                }
0192:            }
0193:
0194:            /** Create a reference based on the given component.  Will not use or
0195:                create any ancestor components/references.
0196:             */
0197:            public ComponentReference(Resolver resolver, Component comp) {
0198:                this (resolver, comp, false, new HashMap());
0199:            }
0200:
0201:            /** Create a reference based on the given component.  May recursively
0202:                create other components required to identify this one.
0203:             */
0204:            public ComponentReference(Resolver resolver, Component comp,
0205:                    Map newReferences) {
0206:                this (resolver, comp, true, newReferences);
0207:            }
0208:
0209:            /** Create a reference based on the given component.  May recursively
0210:                create other components required to identify this one if
0211:                <code>includeHierarchy</code> is true.  If <code>newReferences</code>
0212:                is non-null, new ancestor references will be added to it; if null,
0213:                they will be added to the resolver instead.
0214:             */
0215:            private ComponentReference(Resolver resolver, Component comp,
0216:                    boolean includeHierarchyAttributes, Map newReferences) {
0217:                // This method may be called recursively (indirectly through
0218:                // Resolver.addComponent) in order to add references for parent
0219:                // components.  Make note of whether this instantiation needs 
0220:                // to clear the failure cache when it's done. 
0221:                boolean cleanup = ((Boolean) ownsFailureCache.get())
0222:                        .booleanValue();
0223:                ownsFailureCache.set(Boolean.FALSE);
0224:
0225:                Log.debug("ctor: " + comp);
0226:                this .resolver = resolver;
0227:
0228:                if (AWT.isSharedInvisibleFrame(comp)) {
0229:                    setAttribute(TAG_ID, SHARED_FRAME_ID);
0230:                    setAttribute(TAG_CLASS, comp.getClass().getName());
0231:                } else {
0232:                    Class refClass = Robot.getCanonicalClass(comp.getClass());
0233:                    setAttribute(TAG_CLASS, refClass.getName());
0234:                }
0235:                String name = Robot.getName(comp);
0236:                if (name != null)
0237:                    setAttribute(TAG_NAME, name);
0238:
0239:                // Only generate a tag attribute for custom components; using a tag
0240:                // attribute for standard components is deprecated.
0241:                String cname = comp.getClass().getName();
0242:                if (!(cname.startsWith("java.awt.") || cname
0243:                        .startsWith("javax.swing."))) {
0244:                    String tag = ComponentTester.getTag(comp);
0245:                    if (tag != null)
0246:                        setAttribute(TAG_TAG, tag);
0247:                }
0248:
0249:                // only take the title on a Frame/Dialog
0250:                // using the window title for other components is obsolete 
0251:                String title = Robot.getTitle(comp);
0252:                if (title != null)
0253:                    setAttribute(TAG_TITLE, title);
0254:
0255:                String borderTitle = Robot.getBorderTitle(comp);
0256:                if (borderTitle != null)
0257:                    setAttribute(TAG_BORDER_TITLE, borderTitle);
0258:
0259:                String label = Robot.getLabel(comp);
0260:                if (label != null)
0261:                    setAttribute(TAG_LABEL, label);
0262:                String text = Robot.getText(comp);
0263:                if (text != null)
0264:                    setAttribute(TAG_TEXT, text);
0265:                String icon = Robot.getIconName(comp);
0266:                if (icon != null)
0267:                    setAttribute(TAG_ICON, icon);
0268:
0269:                if (comp instanceof  Applet) {
0270:                    Applet applet = (Applet) comp;
0271:                    setAttribute(TAG_PARAMS, encodeParams(applet));
0272:                    java.net.URL url = applet.getDocumentBase();
0273:                    setAttribute(TAG_DOCBASE, url != null ? url.toString()
0274:                            : "null");
0275:                }
0276:
0277:                // Work the the unique name based on the previous values
0278:                String id = getUniqueID(newReferences);
0279:                setAttribute(TAG_ID, id);
0280:                Log.debug("Unique ID is " + id);
0281:
0282:                // Populate the new reference now with this ID, this needs to be before
0283:                // the parent lookup to prevent some repetition of values in certain
0284:                // complex UI cases.
0285:                //
0286:                newReferences.put(id, this );
0287:
0288:                // Finally work out the parent
0289:
0290:                Container parent = resolver.getHierarchy().getParent(comp);
0291:                if (null != parent) {
0292:                    // Don't save window indices, they're not sufficiently reliable
0293:                    if (!(comp instanceof  Window)) {
0294:                        int index = Robot.getIndex(parent, comp);
0295:                        if (index != -1)
0296:                            setAttribute(TAG_INDEX, String.valueOf(index));
0297:                    }
0298:                } else if (comp instanceof  Window) {
0299:                    setAttribute(TAG_ROOT, "true");
0300:                }
0301:
0302:                try {
0303:                    if (includeHierarchyAttributes) {
0304:                        // Provide either the invoker or the window
0305:                        boolean needWindow = !(comp instanceof  Window);
0306:                        Component invoker = null;
0307:                        if (comp instanceof  JPopupMenu) {
0308:                            invoker = ((JPopupMenu) comp).getInvoker();
0309:                            ComponentReference ref = getReference(resolver,
0310:                                    invoker, newReferences);
0311:                            setAttribute(TAG_INVOKER, ref.getID());
0312:                            needWindow = false;
0313:                        } else if (parent != null) {
0314:                            needWindow = !(parent instanceof  Window);
0315:                            addParent(parent, newReferences);
0316:                        }
0317:
0318:                        if (needWindow && !(comp instanceof  Window)) {
0319:                            Window win = AWT.getWindow(comp);
0320:                            if (win != null) {
0321:                                ComponentReference wref = getReference(
0322:                                        resolver, win, newReferences);
0323:                                setAttribute(TAG_WINDOW, wref.getID());
0324:                            }
0325:                        }
0326:
0327:                        validate(comp, newReferences);
0328:                    }
0329:                } finally {
0330:                    if (cleanup) {
0331:                        getLookupFailures().clear();
0332:                        getNonShowingMatches().clear();
0333:                        ownsFailureCache.set(Boolean.TRUE);
0334:                    }
0335:                }
0336:
0337:                // Set the cache immediately
0338:                if (cacheOnCreation || AWT.isSharedInvisibleFrame(comp)) {
0339:                    Log.debug("Cacheing initial match");
0340:                    cachedLookup = new WeakReference(comp);
0341:                } else {
0342:                    cachedLookup = null;
0343:                }
0344:
0345:            }
0346:
0347:            /** Return the component in the current Hierarchy that best matches this
0348:                reference.
0349:             */
0350:            public Component getComponent() throws ComponentNotFoundException,
0351:                    MultipleComponentsFoundException {
0352:
0353:                if (resolver == null)
0354:                    throw new ComponentNotFoundException(
0355:                            "No default hierarchy has been provided");
0356:                return getComponent(resolver.getHierarchy());
0357:            }
0358:
0359:            /** Return the component in the given Hierarchy that best matches this
0360:                reference.
0361:             */
0362:            public Component getComponent(Hierarchy hierarchy)
0363:                    throws ComponentNotFoundException,
0364:                    MultipleComponentsFoundException {
0365:
0366:                try {
0367:                    return findInHierarchy(null, hierarchy, 1, new HashMap());
0368:                } finally {
0369:                    // never called recursively, so we can clear the cache here
0370:                    getLookupFailures().clear();
0371:                    getNonShowingMatches().clear();
0372:                }
0373:            }
0374:
0375:            private void addParent(Container parent, Map newReferences) {
0376:                ComponentReference ref = getReference(resolver, parent,
0377:                        newReferences);
0378:                setAttribute(TAG_PARENT, ref.getID());
0379:            }
0380:
0381:            /** Returns whether the given component is reachable from the root of the
0382:             * current hierarchy.
0383:             * Popups' transient elements may already have gone away, and will be
0384:             * unreachable. 
0385:             */
0386:            private boolean reachableInHierarchy(Component c) {
0387:                Window w = AWT.getWindow(c);
0388:                if (w == null)
0389:                    return false;
0390:                Window parent = (Window) resolver.getHierarchy().getParent(w);
0391:                return (parent == null) ? resolver.getHierarchy().getRoots()
0392:                        .contains(w) : reachableInHierarchy(parent);
0393:            }
0394:
0395:            /** Ensure the reference can be used to actually look up the given
0396:             * component.  This can be a compute-intensive search, and thus is omitted
0397:             * from the basic constructor.
0398:             */
0399:            private void validate(Component comp, Map newReferences) {
0400:                // Under certain situations where we know the component is
0401:                // unreachable from the root of the hierarchy, or if a lookup will
0402:                // fail for other reasons, simply check for a match.
0403:                // WARNING: this leaves a hole if the component actually needs an
0404:                // ORDER attribute, but the ORDER attribute is intended for applets
0405:                // only.
0406:                if (!reachableInHierarchy(comp)) {
0407:                    int wt = getMatchWeight(comp, newReferences);
0408:                    int exact = getExactMatchWeight();
0409:                    if (wt < exact) {
0410:                        String msg = Strings.get("component.creation_mismatch",
0411:                                new Object[] { toXMLString(), comp.toString(),
0412:                                        new Integer(wt), new Integer(exact), });
0413:                        throw new Error(msg);
0414:                    }
0415:                } else {
0416:                    try {
0417:                        Log.debug("Finding in hierarchy ("
0418:                                + resolver.getHierarchy() + ")");
0419:                        findInHierarchy(null, resolver.getHierarchy(),
0420:                                getExactMatchWeight(), newReferences);
0421:                    } catch (MultipleComponentsFoundException multiples) {
0422:                        try {
0423:                            // More than one match found, so add more information
0424:                            Log.debug("Disambiguating");
0425:                            disambiguate(comp, multiples.getComponents(),
0426:                                    newReferences);
0427:                        } catch (ComponentSearchException e) {
0428:                            if (!(e instanceof  MultipleComponentsFoundException))
0429:                                Log.warn(e);
0430:                            throw new Error(
0431:                                    "Reverse lookup failed to uniquely match "
0432:                                            + Robot.toString(comp) + ": " + e);
0433:                        }
0434:                    } catch (ComponentNotFoundException e) {
0435:                        // This indicates a failure in the reference recording
0436:                        // mechanism, and requires a fix.
0437:                        throw new Error("Reverse lookup failed looking for "
0438:                                + Robot.toString(comp) + " using "
0439:                                + toXMLString() + ": " + e);
0440:                    }
0441:                }
0442:            }
0443:
0444:            /** Return a descriptive name for the given component for use in UI
0445:             * text (may be localized if appropriate and need not be re-usable
0446:             * across locales.
0447:             * @deprecated Use {@link Robot#getDescriptiveName(Component)} instead
0448:             */
0449:            public static String getDescriptiveName(Component c) {
0450:                return Robot.getDescriptiveName(c);
0451:            }
0452:
0453:            /** Return a suitably descriptive name for this reference, for use as an
0454:                ID (returns the ID itself if already set).  Will never return an empty
0455:                String.
0456:             */
0457:            public String getDescriptiveName() {
0458:                String id = getAttribute(TAG_ID);
0459:                if (id == null) {
0460:                    String[] attributes = { TAG_NAME, TAG_TITLE, TAG_TEXT,
0461:                            TAG_LABEL, TAG_ICON, };
0462:                    for (int i = 0; i < attributes.length; i++) {
0463:                        String att = getAttribute(attributes[i]);
0464:                        if (att != null && !"".equals(att)) {
0465:                            id = att;
0466:                            break;
0467:                        }
0468:                    }
0469:                    // Fall back to "<classname> Instance" if all else fails
0470:                    if (id == null) {
0471:                        String cname = getAttribute(TAG_CLASS);
0472:                        cname = cname.substring(cname.lastIndexOf(".") + 1);
0473:                        id = cname + " Instance";
0474:                    }
0475:                }
0476:                return id;
0477:            }
0478:
0479:            public String getID() {
0480:                return getAttribute(TAG_ID);
0481:            }
0482:
0483:            public String getRefClassName() {
0484:                return getAttribute(TAG_CLASS);
0485:            }
0486:
0487:            public String getAttribute(String key) {
0488:                return (String) attributes.get(key);
0489:            }
0490:
0491:            public Map getAttributes() {
0492:                return new TreeMap(attributes);
0493:            }
0494:
0495:            public void setAttribute(String key, String value) {
0496:                xml = null;
0497:                attributes.put(key, value);
0498:            }
0499:
0500:            /** Return whether a cast to the given class name from the given class
0501:                would work.
0502:             */
0503:            private boolean isAssignableFrom(String refClassName, Class cls) {
0504:                return refClassName.equals(cls.getName())
0505:                        || (!Component.class.equals(cls) && isAssignableFrom(
0506:                                refClassName, cls.getSuperclass()));
0507:            }
0508:
0509:            /** Return whether this reference has the same class or is a superclass of
0510:             * the given component's class.  Simply compare class names to avoid class
0511:             * loader conflicts.  Note that this does not take into account interfaces
0512:             * (which is okay, since with GUI components we're only concerned with
0513:             * class inheritance). 
0514:             */
0515:            public boolean isAssignableFrom(Class cls) {
0516:                return cls != null && Component.class.isAssignableFrom(cls)
0517:                        && isAssignableFrom(getAttribute(TAG_CLASS), cls);
0518:            }
0519:
0520:            public ComponentReference getParentReference(Map newRefs) {
0521:                String parentID = getAttribute(TAG_PARENT);
0522:                ComponentReference pref = null;
0523:                if (parentID != null) {
0524:                    pref = resolver.getComponentReference(parentID);
0525:                    if (pref == null)
0526:                        pref = (ComponentReference) newRefs.get(parentID);
0527:                }
0528:                return pref;
0529:            }
0530:
0531:            /** Reference ID of this component's parent window (optional). */
0532:            public ComponentReference getWindowReference(Map newReferences) {
0533:                String windowID = getAttribute(TAG_WINDOW);
0534:                ComponentReference wref = null;
0535:                if (windowID != null) {
0536:                    wref = resolver.getComponentReference(windowID);
0537:                    if (wref == null)
0538:                        wref = (ComponentReference) newReferences.get(windowID);
0539:                }
0540:                return wref;
0541:            }
0542:
0543:            public ComponentReference getInvokerReference(Map newReferences) {
0544:                String invokerID = getAttribute(TAG_INVOKER);
0545:                ComponentReference iref = null;
0546:                if (invokerID != null) {
0547:                    iref = resolver.getComponentReference(invokerID);
0548:                    if (iref == null)
0549:                        iref = (ComponentReference) newReferences
0550:                                .get(invokerID);
0551:                }
0552:                return iref;
0553:            }
0554:
0555:            /** Set all options based on the given XML.
0556:                @deprecated
0557:             */
0558:            // This is only used when editing scripts, since we don't want to have to
0559:            // hunt down existing references
0560:            public void fromXML(String input) throws InvalidScriptException,
0561:                    IOException {
0562:                StringReader reader = new StringReader(input);
0563:                try {
0564:                    SAXBuilder builder = new SAXBuilder();
0565:                    Document doc = builder.build(reader);
0566:                    Element el = doc.getRootElement();
0567:                    if (el == null)
0568:                        throw new InvalidScriptException(
0569:                                "Invalid ComponentReference" + " XML '" + input
0570:                                        + "'");
0571:                    fromXML(el, false);
0572:                } catch (JDOMException e) {
0573:                    throw new InvalidScriptException(e.getMessage()
0574:                            + " (when parsing " + input + ")");
0575:                }
0576:            }
0577:
0578:            /** Parse settings from the given XML.   Only overwrite the ID if
0579:                useGivenID is set.
0580:                @throws InvalidScriptException if the given Element is not valid XML
0581:                for a ComponentReference.
0582:             */
0583:            private void fromXML(Element el, boolean useIDFromXML)
0584:                    throws InvalidScriptException {
0585:
0586:                Iterator iter = el.getAttributes().iterator();
0587:                while (iter.hasNext()) {
0588:                    Attribute att = (Attribute) iter.next();
0589:                    String nodeName = att.getName();
0590:                    String value = att.getValue();
0591:                    if (nodeName.equals(TAG_ID) && !useIDFromXML)
0592:                        continue;
0593:
0594:                    setAttribute(nodeName, value);
0595:                }
0596:                if (getAttribute(TAG_CLASS) == null) {
0597:                    throw new InvalidScriptException("Class must be specified",
0598:                            el);
0599:                }
0600:                String id = getID();
0601:                if (useIDFromXML) {
0602:                    // Make sure the ID we read in is not already in use by the manager
0603:                    if (id != null) {
0604:                        if (resolver.getComponentReference(id) != null) {
0605:                            String msg = "Persistent ID '" + id
0606:                                    + "' is already in use";
0607:                            throw new InvalidScriptException(msg, el);
0608:                        }
0609:                    }
0610:                }
0611:                if (id == null) {
0612:                    Log.warn("null ID");
0613:                    setAttribute(TAG_ID, getUniqueID(new HashMap()));
0614:                }
0615:            }
0616:
0617:            /** Generate an XML representation of this object. */
0618:            public Element toXML() {
0619:                Element el = new Element(TAG_COMPONENT);
0620:                Iterator iter = new TreeMap(attributes).keySet().iterator();
0621:                while (iter.hasNext()) {
0622:                    String key = (String) iter.next();
0623:                    String value = getAttribute(key);
0624:                    if (value != null)
0625:                        el.setAttribute(key, value);
0626:                }
0627:                return el;
0628:            }
0629:
0630:            /** @deprecated Used to be used to edit XML in a text editor. */
0631:            public String toEditableString() {
0632:                return toXMLString();
0633:            }
0634:
0635:            /** Two ComponentReferences with identical XML representations should 
0636:                be equal. */
0637:            public boolean equals(Object obj) {
0638:                return this  == obj
0639:                        || (obj instanceof  ComponentReference)
0640:                        && toXMLString().equals(
0641:                                ((ComponentReference) obj).toXMLString());
0642:            }
0643:
0644:            /** Return a human-readable representation. */
0645:            public String toString() {
0646:                String id = getID();
0647:                String cname = getAttribute(TAG_CLASS);
0648:                if (cname.startsWith("javax.swing."))
0649:                    cname = cname.substring(12);
0650:                else if (cname.startsWith("java.awt."))
0651:                    cname = cname.substring(9);
0652:                StringBuffer buf = new StringBuffer(id != null ? id
0653:                        : (cname + " (no id yet)"));
0654:                if (id != null && id.indexOf("Instance") == -1) {
0655:                    buf.append(" (");
0656:                    buf.append(cname);
0657:                    buf.append(")");
0658:                }
0659:                return buf.toString();
0660:            }
0661:
0662:            public String toXMLString() {
0663:                if (xml == null)
0664:                    xml = Step.toXMLString(this );
0665:                return xml;
0666:            }
0667:
0668:            /** Return which of the otherwise indistinguishable components provides
0669:             * the best match, or throw a MultipleComponentsFoundException if no
0670:             * distinction is possible.  Assumes that all given components return an
0671:             * equivalent match weight.
0672:             */
0673:            private Component bestMatch(Set set)
0674:                    throws MultipleComponentsFoundException {
0675:                Component[] matches = (Component[]) set
0676:                        .toArray(new Component[set.size()]);
0677:                int weights[] = new int[matches.length];
0678:                for (int i = 0; i < weights.length; i++) {
0679:                    // Prefer showing to non-showing
0680:                    Window w = AWT.getWindow(matches[i]);
0681:                    if (w != null && w.isShowing()) {
0682:                        weights[i] = MW_SHOWING;
0683:                    } else {
0684:                        weights[i] = 0;
0685:                    }
0686:                    // Preferring one enabled/focused state is dangerous to do:
0687:                    // An enabled component might be preferred over a disabled one,
0688:                    // but it will fail if you're trying to examine state on the
0689:                    // disabled component.  Ditto for focused.
0690:                }
0691:                String horder = getAttribute(TAG_HORDER);
0692:                if (horder != null) {
0693:                    for (int i = 0; i < matches.length; i++) {
0694:                        String order = getOrder(matches[i], matches, true);
0695:                        if (horder.equals(order)) {
0696:                            weights[i] += MW_HORDER;
0697:                        }
0698:                    }
0699:                }
0700:                String vorder = getAttribute(TAG_VORDER);
0701:                if (vorder != null) {
0702:                    for (int i = 0; i < matches.length; i++) {
0703:                        String order = getOrder(matches[i], matches, false);
0704:                        if (vorder.equals(order)) {
0705:                            weights[i] += MW_VORDER;
0706:                        }
0707:                    }
0708:                }
0709:                // Figure out the best match, if any
0710:                ArrayList best = new ArrayList();
0711:                best.add(matches[0]);
0712:                int max = 0;
0713:                for (int i = 1; i < weights.length; i++) {
0714:                    if (weights[i] > weights[max]) {
0715:                        max = i;
0716:                        best.clear();
0717:                        best.add(matches[i]);
0718:                    } else if (weights[i] == weights[max]) {
0719:                        best.add(matches[i]);
0720:                    }
0721:                }
0722:                if (best.size() == 1) {
0723:                    return (Component) best.get(0);
0724:                }
0725:                // Finally, see if any match the old cached value
0726:                Component cache = getCachedLookup(resolver.getHierarchy());
0727:                if (cache != null) {
0728:                    Iterator iter = best.iterator();
0729:                    while (iter.hasNext()) {
0730:                        Component c = (Component) iter.next();
0731:                        if (cache == c) {
0732:                            return cache;
0733:                        }
0734:                    }
0735:                }
0736:                String msg = "Could not distinguish between " + best.size()
0737:                        + " components using " + toXMLString();
0738:                matches = (Component[]) best
0739:                        .toArray(new Component[best.size()]);
0740:                throw new MultipleComponentsFoundException(msg, matches);
0741:            }
0742:
0743:            /** Return the order of the given component among the array given, sorted
0744:             * by horizontal or vertical screen position.  All components with the
0745:             * same effective value will have the same order.
0746:             */
0747:            static String getOrder(Component original, Component[] matchList,
0748:                    boolean horizontal) {
0749:                Comparator c = horizontal ? HORDER_COMPARATOR
0750:                        : VORDER_COMPARATOR;
0751:                Component[] matches = (Component[]) matchList.clone();
0752:                Arrays.sort(matches, c);
0753:                int order = 0;
0754:                for (int i = 0; i < matches.length; i++) {
0755:                    // Only change the order magnitude if there is a difference
0756:                    // between consecutive objects. 
0757:                    if (i > 0 && c.compare(matches[i - 1], matches[i]) != 0)
0758:                        ++order;
0759:                    if (matches[i] == original) {
0760:                        return String.valueOf(order);
0761:                    }
0762:                }
0763:                return null;
0764:            }
0765:
0766:            /** Add sufficient information to the reference to distinguish it among
0767:                the given components.
0768:                Note that the ordering attributes can only be evaluated when looking
0769:                at several otherwise identical components.
0770:             */
0771:            private void disambiguate(Component original, Component[] matches,
0772:                    Map newReferences) throws ComponentNotFoundException,
0773:                    MultipleComponentsFoundException {
0774:                Log.debug("Attempting to disambiguate multiple matches");
0775:                Container parent = resolver.getHierarchy().getParent(original);
0776:                boolean retryOnFailure = false;
0777:                String order = null;
0778:                try {
0779:                    String cname = original.getClass().getName();
0780:                    // Use the inner class name unless it's numeric (numeric values
0781:                    // can easily change).
0782:                    if (!cname.equals(getAttribute(TAG_CLASS))
0783:                            && !expressionMatch(ANON_INNER_CLASS, cname)) {
0784:                        setAttribute(TAG_CLASS, original.getClass().getName());
0785:                        retryOnFailure = true;
0786:                    } else if (parent != null
0787:                            && getAttribute(TAG_PARENT) == null
0788:                            && !(original instanceof  JPopupMenu)) {
0789:                        Log.debug("Adding parent");
0790:                        addParent(parent, newReferences);
0791:                        retryOnFailure = true;
0792:                    } else if (getAttribute(TAG_HORDER) == null
0793:                            && (order = getOrder(original, matches, true)) != null) {
0794:                        Log.debug("Adding horder");
0795:                        setAttribute(TAG_HORDER, order);
0796:                        retryOnFailure = true;
0797:                    } else if (getAttribute(TAG_VORDER) == null
0798:                            && (order = getOrder(original, matches, false)) != null) {
0799:                        Log.debug("Adding vorder");
0800:                        setAttribute(TAG_VORDER, order);
0801:                        retryOnFailure = true;
0802:                    }
0803:                    // Try the lookup again to make sure it works this time
0804:                    Log.debug("Retrying lookup with new values");
0805:                    // Remove this cref and its ancestors from the failure
0806:                    // cache so we don't automatically fail
0807:                    getLookupFailures().remove(this );
0808:                    findInHierarchy(null, resolver.getHierarchy(),
0809:                            getExactMatchWeight(), newReferences);
0810:                    Log.debug("Success!");
0811:                } catch (MultipleComponentsFoundException multiples) {
0812:                    if (retryOnFailure) {
0813:                        disambiguate(original, multiples.getComponents(),
0814:                                newReferences);
0815:                    } else
0816:                        throw multiples;
0817:                }
0818:            }
0819:
0820:            /** Return a measure of how well the given component matches the given
0821:             * component reference.  The weight performs two functions; one is to
0822:             * loosely match so that we can find a component even if some of its
0823:             * attributes have changed.  The other is to distinguish between similar
0824:             * components. <p>
0825:             * In general, we want to match if we get any weight at all, and there's
0826:             * only one component that matches.
0827:             */
0828:            int getMatchWeight(Component comp) {
0829:                return getMatchWeight(comp, new HashMap());
0830:            }
0831:
0832:            /** Return a measure of how well the given component matches the given
0833:             * component reference.  The weight performs two functions; one is to
0834:             * loosely match so that we can find a component even if some of its
0835:             * attributes have changed.  The other is to distinguish between similar
0836:             * components. <p>
0837:             * In general, we want to match if we get any weight at all, and there's
0838:             * only one component that matches.
0839:             */
0840:            private int getMatchWeight(Component comp, Map newReferences) {
0841:                // Match weights may be positive or negative.  They should only be
0842:                // negative if the attribute is highly unlikely to change.
0843:
0844:                int weight = MW_FAILURE;
0845:
0846:                if (null == comp) {
0847:                    return MW_FAILURE;
0848:                }
0849:
0850:                // FIXME might want to allow changing the class?  or should we just
0851:                // ask the user to fix the script by hand?
0852:                if (!isAssignableFrom(comp.getClass())) {
0853:                    return MW_FAILURE;
0854:                }
0855:
0856:                weight += MW_CLASS;
0857:                // Exact class matches are better than non-exact matches
0858:                if (getAttribute(TAG_CLASS).equals(comp.getClass().getName()))
0859:                    weight += MW_CLASS;
0860:
0861:                String refTag = getAttribute(TAG_TAG);
0862:                String compTag = null;
0863:                if (null != refTag) {
0864:                    compTag = ComponentTester.getTag(comp);
0865:                    if (compTag != null && expressionMatch(refTag, compTag)) {
0866:                        weight += MW_TAG;
0867:                    }
0868:                }
0869:
0870:                String refName = getAttribute(TAG_NAME);
0871:                String compName = Robot.getName(comp);
0872:                if (null != refName) {
0873:                    if (compName != null && expressionMatch(refName, compName)) {
0874:                        weight += MW_NAME;
0875:                    } else {
0876:                        weight -= MW_NAME;
0877:                    }
0878:                } else {
0879:                    Log.log("Component name set as " + compName
0880:                            + "but the reference name was null");
0881:                }
0882:
0883:                if (null != getAttribute(TAG_INVOKER)) {
0884:                    ComponentReference iref = getInvokerReference(newReferences);
0885:                    Component invoker = (comp instanceof  JPopupMenu) ? ((JPopupMenu) comp)
0886:                            .getInvoker()
0887:                            : null;
0888:                    if (invoker == iref
0889:                            .resolveComponent(invoker, newReferences)) {
0890:                        weight += MW_INVOKER;
0891:                    } else {
0892:                        // Invoking components aren't likely to change
0893:                        weight -= MW_INVOKER;
0894:                    }
0895:                }
0896:
0897:                if (null != getAttribute(TAG_PARENT)) {
0898:                    ComponentReference pref = getParentReference(newReferences);
0899:                    Component parent = resolver.getHierarchy().getParent(comp);
0900:                    if (parent == pref.resolveComponent(parent, newReferences)) {
0901:                        weight += MW_PARENT;
0902:                    }
0903:                    // Don't detract on parent mismatch, since changing a parent is
0904:                    // not that big a change (e.g. adding a scroll pane)
0905:                }
0906:                // ROOT and PARENT are mutually exclusive
0907:                else if (null != getAttribute(TAG_ROOT)) {
0908:                    weight += MW_ROOT;
0909:                }
0910:
0911:                if (null != getAttribute(TAG_WINDOW)) {
0912:                    ComponentReference wref = getWindowReference(newReferences);
0913:                    Window w = AWT.getWindow(comp);
0914:                    if (w == wref.resolveComponent(w, newReferences)) {
0915:                        weight += MW_WINDOW;
0916:                    } else if (w != null) {
0917:                        // Changing windows is a big change and not very likely
0918:                        weight -= MW_WINDOW;
0919:                    }
0920:                }
0921:
0922:                // TITLE is no longer used except by Frames, Dialogs, and
0923:                // JInternalFrames, being superseded by the ancestor window 
0924:                // reference.  For other components, it represents an available
0925:                // ancestor window title (deprecated usage only).   
0926:                String title = getAttribute(TAG_TITLE);
0927:                if (null != title) {
0928:                    String title2 = (comp instanceof  Frame
0929:                            || comp instanceof  Dialog || comp instanceof  JInternalFrame) ? Robot
0930:                            .getTitle(comp)
0931:                            : getComponentWindowTitle(comp);
0932:                    if (title2 != null && expressionMatch(title, title2)) {
0933:                        weight += MW_TITLE;
0934:                    }
0935:                    // Don't subtract on mismatch, since title changes are common
0936:                }
0937:
0938:                String borderTitle = getAttribute(TAG_BORDER_TITLE);
0939:                if (null != borderTitle) {
0940:                    String bt2 = Robot.getBorderTitle(comp);
0941:                    if (bt2 != null && expressionMatch(borderTitle, bt2)) {
0942:                        weight += MW_BORDER_TITLE;
0943:                    }
0944:                }
0945:
0946:                String label = getAttribute(TAG_LABEL);
0947:                if (null != label) {
0948:                    String label2 = Robot.getLabel(comp);
0949:                    if (label2 != null && expressionMatch(label, label2)) {
0950:                        weight += MW_LABEL;
0951:                    }
0952:                }
0953:
0954:                String text = getAttribute(TAG_TEXT);
0955:                if (null != text) {
0956:                    String text2 = Robot.getText(comp);
0957:                    if (text2 != null && expressionMatch(text, text2)) {
0958:                        weight += MW_TEXT;
0959:                    }
0960:                }
0961:
0962:                String icon = getAttribute(TAG_ICON);
0963:                if (null != icon) {
0964:                    String icon2 = Robot.getIconName(comp);
0965:                    if (icon2 != null && expressionMatch(icon, icon2)) {
0966:                        weight += MW_ICON;
0967:                    }
0968:                }
0969:
0970:                String idx = getAttribute(TAG_INDEX);
0971:                if (null != idx) {
0972:                    Container parent = resolver.getHierarchy().getParent(comp);
0973:                    if (null != parent) {
0974:                        int i = Robot.getIndex(parent, comp);
0975:                        if (expressionMatch(idx, String.valueOf(i))) {
0976:                            weight += MW_INDEX;
0977:                        }
0978:                    }
0979:                    // Don't subtract for index mismatch, since ordering changes are
0980:                    // common. 
0981:                }
0982:
0983:                if (comp instanceof  Applet) {
0984:                    Applet applet = (Applet) comp;
0985:                    String params = getAttribute(TAG_PARAMS);
0986:                    if (null != params) {
0987:                        String params2 = encodeParams(applet);
0988:                        if (expressionMatch(params, params2))
0989:                            weight += MW_PARAMS;
0990:                    }
0991:                    String docBase = getAttribute(TAG_DOCBASE);
0992:                    if (null != docBase) {
0993:                        java.net.URL url = applet.getDocumentBase();
0994:                        if (url != null
0995:                                && expressionMatch(docBase, url.toString()))
0996:                            weight += MW_DOCBASE;
0997:                    }
0998:                    // No negative weighting here
0999:                }
1000:
1001:                if (Log.isClassDebugEnabled(ComponentReference.class))
1002:                    Log.debug("Compared " + Robot.toString(comp) + " to "
1003:                            + toXMLString() + " weight is " + weight);
1004:
1005:                return weight;
1006:            }
1007:
1008:            /** Return the total weight required for an exact match. */
1009:            private int getExactMatchWeight() {
1010:                int weight = MW_CLASS;
1011:                if (getAttribute(TAG_NAME) != null)
1012:                    weight += MW_NAME;
1013:                if (getAttribute(TAG_TAG) != null)
1014:                    weight += MW_TAG;
1015:                if (getAttribute(TAG_INVOKER) != null)
1016:                    weight += MW_INVOKER;
1017:                if (getAttribute(TAG_ROOT) != null)
1018:                    weight += MW_ROOT;
1019:                if (getAttribute(TAG_PARENT) != null)
1020:                    weight += MW_PARENT;
1021:                if (getAttribute(TAG_WINDOW) != null)
1022:                    weight += MW_WINDOW;
1023:                if (getAttribute(TAG_TITLE) != null)
1024:                    weight += MW_TITLE;
1025:                if (getAttribute(TAG_BORDER_TITLE) != null)
1026:                    weight += MW_BORDER_TITLE;
1027:                if (getAttribute(TAG_INDEX) != null)
1028:                    weight += MW_INDEX;
1029:                if (getAttribute(TAG_LABEL) != null)
1030:                    weight += MW_LABEL;
1031:                if (getAttribute(TAG_TEXT) != null)
1032:                    weight += MW_TEXT;
1033:                if (getAttribute(TAG_ICON) != null)
1034:                    weight += MW_ICON;
1035:                if (getAttribute(TAG_PARAMS) != null)
1036:                    weight += MW_PARAMS;
1037:                if (getAttribute(TAG_DOCBASE) != null)
1038:                    weight += MW_DOCBASE;
1039:
1040:                if (Log.isClassDebugEnabled(ComponentReference.class))
1041:                    Log.debug("Exact match weight for " + toXMLString()
1042:                            + " is " + weight);
1043:                return weight;
1044:            }
1045:
1046:            /** Returns an existing component which matches this reference; the given
1047:                Component is the one that is expected to match.  Returns null if no
1048:                match or multiple matches are found and the preferred Component is not
1049:                among them.<p> 
1050:                This method is used in two instances:
1051:                <ul>
1052:                <li>Resolving a component's ancestors (window, parent, or invoker),
1053:                the ancestor reference is checked against the ancestor of the
1054:                Component currently being compared.
1055:                <li>When referring to a component, determining if a reference to it
1056:                already exists, all references are resolved to see if any resolves to
1057:                the preferred Component.
1058:                </ul>
1059:                While there is a subtle difference between the two cases (when running
1060:                a test it is expected that there will be some match, whereas when
1061:                creating a new reference there may or may not be a match, based on the
1062:                current script contents), it is not a useful distinction.
1063:             */
1064:            private Component resolveComponent(Component preferred,
1065:                    Map newReferences) {
1066:                // This call should be equivalent to getComponent(), but without
1067:                // clearing the lookup failure cache on completion
1068:                if (Log.isClassDebugEnabled(ComponentReference.class))
1069:                    Log.debug("Looking up " + toXMLString() + " in hierarchy");
1070:                Component found = null;
1071:                try {
1072:                    found = findInHierarchy(null, resolver.getHierarchy(), 1,
1073:                            newReferences);
1074:                } catch (MultipleComponentsFoundException e) {
1075:                    Component[] list = e.getComponents();
1076:                    for (int i = 0; i < list.length; i++) {
1077:                        if (list[i] == preferred)
1078:                            return preferred;
1079:                    }
1080:                    //Log.warn("Preferred not found among many");
1081:                } catch (ComponentNotFoundException e) {
1082:                    // If the preferred component is not reachable in the hierarchy
1083:                    // (if it has just been removed from the hierarchy, or an ancestor
1084:                    // pane was replaced), require an exact match to avoid
1085:                    // spurious matches. 
1086:                    int minWeight = getExactMatchWeight();
1087:                    if (getAttribute(TAG_WINDOW) != null)
1088:                        minWeight -= MW_WINDOW;
1089:                    if (getAttribute(TAG_PARENT) != null)
1090:                        minWeight -= MW_PARENT;
1091:                    if (AWT.getWindow(preferred) == null
1092:                            && getMatchWeight(preferred) >= minWeight) {
1093:                        Log.debug("Using preferred component: "
1094:                                + Robot.toString(preferred));
1095:                        found = preferred;
1096:                    }
1097:                }
1098:                return found;
1099:            }
1100:
1101:            /** Returns a reference to the given component, preferring an existing
1102:             * reference if a matching one is available or creating a new one if not. 
1103:             * The new references are <i>not</i> added to the resolver.
1104:             */
1105:            // FIXME: keep newly-created ancestors in a collection and let the
1106:            // resolver add them.  (maybe create everything, then let the resolver
1107:            // sort out duplicates when adding).
1108:            // TODO: require exact matches, otherwise create a new ref; this means
1109:            // that we need to provide a method to repair refs.
1110:            public static ComponentReference getReference(Resolver r,
1111:                    Component comp, Map newReferences) {
1112:                Log
1113:                        .debug("Looking for a reference for "
1114:                                + Robot.toString(comp));
1115:                // Preserve the failure cache across both lookup and creation
1116:                boolean cleanup = ((Boolean) ownsFailureCache.get())
1117:                        .booleanValue();
1118:                ownsFailureCache.set(Boolean.FALSE);
1119:
1120:                // Allow the resolver to do cacheing if it needs to; otherwise we'd
1121:                // call matchExisting directly.
1122:                ComponentReference ref = r.getComponentReference(comp);
1123:                try {
1124:
1125:                    // In the case where  we are looking at a window we might find
1126:                    // that  window property has been populated
1127:
1128:                    if (ref == null && comp instanceof  Window) {
1129:                        ref = ComponentReference.matchExisting(comp,
1130:                                newReferences.values(), newReferences);
1131:
1132:                        if (ref != null) {
1133:                            // check tha match is a good one
1134:                            int exactMatch = ref.getExactMatchWeight();
1135:                            // Make sure that newReferences are passed in
1136:                            int componentMatch = ref.getMatchWeight(comp,
1137:                                    newReferences);
1138:                            if (componentMatch < exactMatch) {
1139:                                ref = null;
1140:                            }
1141:                        }
1142:                    }
1143:
1144:                    //
1145:                    if (ref == null) {
1146:                        Log
1147:                                .debug("No existing reference found, creating a new one");
1148:                        ref = new ComponentReference(r, comp, newReferences);
1149:                    }
1150:                } finally {
1151:                    if (cleanup) {
1152:                        getLookupFailures().clear();
1153:                        getNonShowingMatches().clear();
1154:                        ownsFailureCache.set(Boolean.TRUE);
1155:                    }
1156:                }
1157:                return ref;
1158:            }
1159:
1160:            /** Match the given component against an existing set of references. */
1161:            public static ComponentReference matchExisting(
1162:                    final Component comp, Collection existing) {
1163:                return matchExisting(comp, existing, Collections.EMPTY_MAP);
1164:            }
1165:
1166:            /** Match the given component against an existing set of references.
1167:             *  Extended method that also takes in a list of new references that
1168:             *  might have been created in this cycle
1169:             */
1170:            public static ComponentReference matchExisting(
1171:                    final Component comp, Collection existing, Map newReferences) {
1172:
1173:                Log.debug("Matching " + Robot.toString(comp)
1174:                        + " against existing refs");
1175:
1176:                // This method might be called recursively (indirectly through
1177:                // Resolver.addComponent) in order to add references for parent
1178:                // components.  Make note of whether this level of invocation needs 
1179:                // to clear the failure cache when it's done. 
1180:                boolean cleanup = ((Boolean) ownsFailureCache.get())
1181:                        .booleanValue();
1182:                ownsFailureCache.set(Boolean.FALSE);
1183:
1184:                ComponentReference match = null;
1185:                Iterator iter = existing.iterator();
1186:                // Sort such that the best match comes first
1187:                Map matches = new TreeMap(new Comparator() {
1188:                    public int compare(Object o1, Object o2) {
1189:                        return ((ComponentReference) o2).getMatchWeight(comp)
1190:                                - ((ComponentReference) o1)
1191:                                        .getMatchWeight(comp);
1192:                    }
1193:                });
1194:                while (iter.hasNext()) {
1195:                    ComponentReference ref = (ComponentReference) iter.next();
1196:                    if (comp == ref
1197:                            .getCachedLookup(ref.resolver.getHierarchy())
1198:                            || comp == ref
1199:                                    .resolveComponent(comp, newReferences)) {
1200:                        matches.put(ref, Boolean.TRUE);
1201:                    }
1202:                }
1203:                if (matches.size() > 0) {
1204:                    match = (ComponentReference) matches.keySet().iterator()
1205:                            .next();
1206:                }
1207:
1208:                if (cleanup) {
1209:                    // Clear failures only after we've attempted a match for *all* refs
1210:                    getLookupFailures().clear();
1211:                    getNonShowingMatches().clear();
1212:                    ownsFailureCache.set(Boolean.TRUE);
1213:                }
1214:
1215:                Log.debug(match != null ? "Found" : "Not found");
1216:                return match;
1217:            }
1218:
1219:            /** Return whether the given pattern matches the given string.  Performs
1220:             * variable substitution on the pattern.
1221:             */
1222:            boolean expressionMatch(String pattern, String actual) {
1223:                pattern = ArgumentParser.substitute(resolver, pattern);
1224:                return ExtendedComparator.stringsMatch(pattern, actual);
1225:            }
1226:
1227:            /** Convert the given applet's parameters into a simple String. */
1228:            private String encodeParams(Applet applet) {
1229:                // TODO: is there some other way of digging out the full set of
1230:                // parameters that were passed the applet? b/c here we rely on the
1231:                // applet having been properly written to tell us about supported
1232:                // parameters.
1233:                StringBuffer sb = new StringBuffer();
1234:                String[][] info = applet.getParameterInfo();
1235:                if (info == null) {
1236:                    // Default implementation of applet returns null
1237:                    return "null";
1238:                }
1239:                for (int i = 0; i < info.length; i++) {
1240:                    sb.append(info[i][0]);
1241:                    sb.append("=");
1242:                    String param = applet.getParameter(info[i][0]);
1243:                    sb.append(param != null ? param : "null");
1244:                    sb.append(";");
1245:                }
1246:                return sb.toString();
1247:            }
1248:
1249:            /** Return the cached component match, if any. */
1250:            Component getCachedLookup(Hierarchy hierarchy) {
1251:                if (cachedLookup != null) {
1252:                    Component c = (Component) cachedLookup.get();
1253:                    // Discard if the component has been gc'd, is no longer in the
1254:                    // hierarchy, or is no longer reachable from a Window.
1255:                    if (c != null && hierarchy.contains(c)
1256:                            && AWT.getWindow(c) != null) {
1257:                        return c;
1258:                    }
1259:                    Log.debug("Discarding cached value: " + Robot.toString(c));
1260:                    cachedLookup = null;
1261:                }
1262:                return null;
1263:            }
1264:
1265:            /** Compare this ComponentReference against each component below the given
1266:             * root in the given hierarchy whose match weight exceeds the given
1267:             * minimum.  If a valid cached lookup exists, that is returned
1268:             * immediately. 
1269:             */
1270:            // TODO: refactor this to extract the finder/lookup logic into a separate
1271:            // class.  the ref should only store attributes.
1272:            private Component findInHierarchy(Container root,
1273:                    Hierarchy hierarchy, int weight, Map newReferences)
1274:                    throws ComponentNotFoundException,
1275:                    MultipleComponentsFoundException {
1276:                Component match = null;
1277:
1278:                ComponentSearchException cse = (ComponentSearchException) getLookupFailures()
1279:                        .get(this );
1280:                if (cse instanceof  ComponentNotFoundException) {
1281:                    Log.debug("lookup already failed: " + cse);
1282:                    throw (ComponentNotFoundException) cse;
1283:                }
1284:                if (cse instanceof  MultipleComponentsFoundException) {
1285:                    Log.debug("lookup already failed: " + cse);
1286:                    throw (MultipleComponentsFoundException) cse;
1287:                }
1288:
1289:                Set set = new HashSet();
1290:                match = getCachedLookup(hierarchy);
1291:                if (match != null) {
1292:                    // This is always valid
1293:                    if (AWT.isSharedInvisibleFrame(match))
1294:                        return match;
1295:                    // TODO: always use the cached lookup; since TestHierarchy
1296:                    // auto-disposes, only improperly disposed components will still
1297:                    // match.  Codify this behavior with an explicit test.
1298:
1299:                    // Normally, we'd always want to use the cached lookup, but there
1300:                    // are instances where a component hierarchy may be used in a
1301:                    // transient way, so a given reference may need to match more than
1302:                    // one object without the first having been properly disposed.
1303:                    // Consider a createDialog() method, which creates an identical
1304:                    // dialog on each invocation, with an OK button.  Every call of
1305:                    // the method is semantically providing the same component,
1306:                    // although the implementation may create a new one each time.  If
1307:                    // previous instances have not been properly disposed, we need a
1308:                    // way to prefer a brand new instance over an old one.  We do that
1309:                    // by checking the cache window's showing state.
1310:                    // A showing match will trump a non-showing one,
1311:                    // but if there are multiple, non-showing matches, the cached
1312:                    // lookup will win.  
1313:                    // We check the window, not the component itself, because some
1314:                    // components hide their children.
1315:                    Window w = AWT.getWindow(match);
1316:                    if (w != null
1317:                            && (w.isShowing() || getNonShowingMatches().get(
1318:                                    this ) == match)) {
1319:                        Log.debug("Using cached lookup for " + getID()
1320:                                + " (hierarchy=" + hierarchy + ")");
1321:                        return match;
1322:                    }
1323:                    Log
1324:                            .debug("Skipping non-showing match (once) "
1325:                                    + hashCode());
1326:                }
1327:
1328:                weight = findMatchesInHierarchy(root, hierarchy, weight, set,
1329:                        newReferences);
1330:
1331:                Log.debug("Found " + set.size() + " matches for "
1332:                        + toXMLString());
1333:                if (set.size() == 1) {
1334:                    match = (Component) set.iterator().next();
1335:                } else if (set.size() > 0) {
1336:                    // Distinguish between more than one match with the exact same
1337:                    // weight 
1338:                    try {
1339:                        match = bestMatch(set);
1340:                    } catch (MultipleComponentsFoundException e) {
1341:                        getLookupFailures().put(this , e);
1342:                        throw e;
1343:                    }
1344:                }
1345:                if (match == null) {
1346:                    String msg = "No component found which matches "
1347:                            + toXMLString();
1348:                    ComponentNotFoundException e = new ComponentNotFoundException(
1349:                            msg);
1350:                    getLookupFailures().put(this , e);
1351:                    throw e;
1352:                }
1353:                // This provides significant speedup when many similar components are
1354:                // in play.
1355:                Log.debug("Cacheing match: "
1356:                        + Integer.toHexString(match.hashCode()));
1357:                cachedLookup = new WeakReference(match);
1358:                if (!match.isShowing()) {
1359:                    getNonShowingMatches().put(this , match);
1360:                }
1361:                return match;
1362:            }
1363:
1364:            /** Return the the set of all components under the given component's
1365:             * hierarchy (inclusive) which match the given reference.
1366:             */
1367:            private int findMatchesInHierarchy(Component root,
1368:                    Hierarchy hierarchy, int currentMaxWeight, Set currentSet,
1369:                    Map newReferences) {
1370:
1371:                if (root == null) {
1372:                    // Examine all top-level components and their owned windows.
1373:                    Iterator iter = hierarchy.getRoots().iterator();
1374:                    while (iter.hasNext()) {
1375:                        currentMaxWeight = findMatchesInHierarchy((Window) iter
1376:                                .next(), hierarchy, currentMaxWeight,
1377:                                currentSet, newReferences);
1378:                    }
1379:                    return currentMaxWeight;
1380:                }
1381:
1382:                if (!hierarchy.contains(root)) {
1383:                    Log.debug("Component not in hierarchy");
1384:                    return currentMaxWeight;
1385:                }
1386:
1387:                int weight = getMatchWeight(root, newReferences);
1388:                if (weight > currentMaxWeight) {
1389:                    currentSet.clear();
1390:                    currentMaxWeight = weight;
1391:                    currentSet.add(root);
1392:                } else if (weight == currentMaxWeight) {
1393:                    currentSet.add(root);
1394:                }
1395:
1396:                // TODO: don't check window contents in the hierarchy if the cref is a
1397:                // Window.  oops, how do you tell the cref is a Window?
1398:                // (no window tag, parent tag or root tag, no index tag)
1399:                // no guarantee, though
1400:                Collection kids = hierarchy.getComponents(root);
1401:                Iterator iter = kids.iterator();
1402:                while (iter.hasNext()) {
1403:                    Component child = (Component) iter.next();
1404:                    currentMaxWeight = findMatchesInHierarchy(child, hierarchy,
1405:                            currentMaxWeight, currentSet, newReferences);
1406:                }
1407:
1408:                return currentMaxWeight;
1409:            }
1410:
1411:            /** Given an array of name, value pairs, generate a map suitable for
1412:                creating a ComponentReference.
1413:             */
1414:            private static Map createAttributeMap(String[][] values) {
1415:                Map map = new HashMap();
1416:                for (int i = 0; i < values.length; i++) {
1417:                    map.put(values[i][0], values[i][1]);
1418:                }
1419:                return map;
1420:            }
1421:
1422:            private static final Comparator HORDER_COMPARATOR = new Comparator() {
1423:                public int compare(Object o1, Object o2) {
1424:                    Component c1 = (Component) o1;
1425:                    Component c2 = (Component) o2;
1426:                    int x1 = -100000;
1427:                    int x2 = -100000;
1428:                    try {
1429:                        x1 = c1.getLocationOnScreen().x;
1430:                    } catch (Exception e) {
1431:                    }
1432:                    try {
1433:                        x2 = c2.getLocationOnScreen().x;
1434:                    } catch (Exception e) {
1435:                    }
1436:                    return x1 - x2;
1437:                }
1438:            };
1439:
1440:            private static final Comparator VORDER_COMPARATOR = new Comparator() {
1441:                public int compare(Object o1, Object o2) {
1442:                    Component c1 = (Component) o1;
1443:                    Component c2 = (Component) o2;
1444:                    int y1 = -100000;
1445:                    int y2 = -100000;
1446:                    try {
1447:                        y1 = c1.getLocationOnScreen().y;
1448:                    } catch (Exception e) {
1449:                    }
1450:                    try {
1451:                        y2 = c2.getLocationOnScreen().y;
1452:                    } catch (Exception e) {
1453:                    }
1454:                    return y1 - y2;
1455:                }
1456:            };
1457:
1458:            public int compareTo(Object o) {
1459:                return getID().compareTo(((ComponentReference) o).getID());
1460:            }
1461:
1462:            private String getComponentWindowTitle(Component c) {
1463:                Component parent = c;
1464:                while (!(c instanceof  Frame || c instanceof  Dialog)
1465:                        && (c = resolver.getHierarchy().getParent(parent)) != null) {
1466:                    parent = c;
1467:                }
1468:                String title = null;
1469:                if (parent instanceof  Frame) {
1470:                    title = ((Frame) parent).getTitle();
1471:                } else if (parent instanceof  Dialog) {
1472:                    title = ((Dialog) parent).getTitle();
1473:                }
1474:                return title;
1475:            }
1476:
1477:            private static Map getLookupFailures() {
1478:                return (Map) lookupFailures.get();
1479:            }
1480:
1481:            private static Map getNonShowingMatches() {
1482:                return (Map) nonShowingMatches.get();
1483:            }
1484:
1485:            public String getUniqueID(Map refs) {
1486:                String id = getDescriptiveName();
1487:                String ext = "";
1488:                int count = 2;
1489:                while (refs.get(id + ext) != null
1490:                        || resolver.getComponentReference(id + ext) != null) {
1491:                    ext = " " + count++;
1492:                }
1493:                return id + ext;
1494:            }
1495:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.