1 /*
   2  * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.swing.text.html;
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import java.beans.*;
  31 import java.util.*;
  32 import javax.swing.*;
  33 import javax.swing.event.*;
  34 import javax.swing.text.*;
  35 import javax.accessibility.*;
  36 import java.text.BreakIterator;
  37 
  38 /*
  39  * The AccessibleHTML class provide information about the contents
  40  * of a HTML document to assistive technologies.
  41  *
  42  * @author  Lynn Monsanto
  43  */
  44 class AccessibleHTML implements Accessible {
  45 
  46     /**
  47      * The editor.
  48      */
  49     private JEditorPane editor;
  50     /**
  51      * Current model.
  52      */
  53     private Document model;
  54     /**
  55      * DocumentListener installed on the current model.
  56      */
  57     private DocumentListener docListener;
  58     /**
  59      * PropertyChangeListener installed on the editor
  60      */
  61     private PropertyChangeListener propChangeListener;
  62     /**
  63      * The root ElementInfo for the document
  64      */
  65     private ElementInfo rootElementInfo;
  66     /*
  67      * The root accessible context for the document
  68      */
  69     private RootHTMLAccessibleContext rootHTMLAccessibleContext;
  70 
  71     public AccessibleHTML(JEditorPane pane) {
  72         editor = pane;
  73         propChangeListener = new PropertyChangeHandler();
  74         setDocument(editor.getDocument());
  75 
  76         docListener = new DocumentHandler();
  77     }
  78 
  79     /**
  80      * Sets the document.
  81      */
  82     private void setDocument(Document document) {
  83         if (model != null) {
  84             model.removeDocumentListener(docListener);
  85         }
  86         if (editor != null) {
  87             editor.removePropertyChangeListener(propChangeListener);
  88         }
  89         this.model = document;
  90         if (model != null) {
  91             if (rootElementInfo != null) {
  92                 rootElementInfo.invalidate(false);
  93             }
  94             buildInfo();
  95             model.addDocumentListener(docListener);
  96         }
  97         else {
  98             rootElementInfo = null;
  99         }
 100         if (editor != null) {
 101             editor.addPropertyChangeListener(propChangeListener);
 102         }
 103     }
 104 
 105     /**
 106      * Returns the Document currently presenting information for.
 107      */
 108     private Document getDocument() {
 109         return model;
 110     }
 111 
 112     /**
 113      * Returns the JEditorPane providing information for.
 114      */
 115     private JEditorPane getTextComponent() {
 116         return editor;
 117     }
 118 
 119     /**
 120      * Returns the ElementInfo representing the root Element.
 121      */
 122     private ElementInfo getRootInfo() {
 123         return rootElementInfo;
 124     }
 125 
 126     /**
 127      * Returns the root <code>View</code> associated with the current text
 128      * component.
 129      */
 130     private View getRootView() {
 131         return getTextComponent().getUI().getRootView(getTextComponent());
 132     }
 133 
 134     /**
 135      * Returns the bounds the root View will be rendered in.
 136      */
 137     private Rectangle getRootEditorRect() {
 138         Rectangle alloc = getTextComponent().getBounds();
 139         if ((alloc.width > 0) && (alloc.height > 0)) {
 140             alloc.x = alloc.y = 0;
 141             Insets insets = editor.getInsets();
 142             alloc.x += insets.left;
 143             alloc.y += insets.top;
 144             alloc.width -= insets.left + insets.right;
 145             alloc.height -= insets.top + insets.bottom;
 146             return alloc;
 147         }
 148         return null;
 149     }
 150 
 151     /**
 152      * If possible acquires a lock on the Document.  If a lock has been
 153      * obtained a key will be retured that should be passed to
 154      * <code>unlock</code>.
 155      */
 156     private Object lock() {
 157         Document document = getDocument();
 158 
 159         if (document instanceof AbstractDocument) {
 160             ((AbstractDocument)document).readLock();
 161             return document;
 162         }
 163         return null;
 164     }
 165 
 166     /**
 167      * Releases a lock previously obtained via <code>lock</code>.
 168      */
 169     private void unlock(Object key) {
 170         if (key != null) {
 171             ((AbstractDocument)key).readUnlock();
 172         }
 173     }
 174 
 175     /**
 176      * Rebuilds the information from the current info.
 177      */
 178     private void buildInfo() {
 179         Object lock = lock();
 180 
 181         try {
 182             Document doc = getDocument();
 183             Element root = doc.getDefaultRootElement();
 184 
 185             rootElementInfo = new ElementInfo(root);
 186             rootElementInfo.validate();
 187         } finally {
 188             unlock(lock);
 189         }
 190     }
 191 
 192     /*
 193      * Create an ElementInfo subclass based on the passed in Element.
 194      */
 195     ElementInfo createElementInfo(Element e, ElementInfo parent) {
 196         AttributeSet attrs = e.getAttributes();
 197 
 198         if (attrs != null) {
 199             Object name = attrs.getAttribute(StyleConstants.NameAttribute);
 200 
 201             if (name == HTML.Tag.IMG) {
 202                 return new IconElementInfo(e, parent);
 203             }
 204             else if (name == HTML.Tag.CONTENT || name == HTML.Tag.CAPTION) {
 205                 return new TextElementInfo(e, parent);
 206             }
 207             else if (name == HTML.Tag.TABLE) {
 208                 return new TableElementInfo(e, parent);
 209             }
 210         }
 211         return null;
 212     }
 213 
 214     /**
 215      * Returns the root AccessibleContext for the document
 216      */
 217     public AccessibleContext getAccessibleContext() {
 218         if (rootHTMLAccessibleContext == null) {
 219             rootHTMLAccessibleContext =
 220                 new RootHTMLAccessibleContext(rootElementInfo);
 221         }
 222         return rootHTMLAccessibleContext;
 223     }
 224 
 225     /*
 226      * The roow AccessibleContext for the document
 227      */
 228     private class RootHTMLAccessibleContext extends HTMLAccessibleContext {
 229 
 230         public RootHTMLAccessibleContext(ElementInfo elementInfo) {
 231             super(elementInfo);
 232         }
 233 
 234         /**
 235          * Gets the accessibleName property of this object.  The accessibleName
 236          * property of an object is a localized String that designates the purpose
 237          * of the object.  For example, the accessibleName property of a label
 238          * or button might be the text of the label or button itself.  In the
 239          * case of an object that doesn't display its name, the accessibleName
 240          * should still be set.  For example, in the case of a text field used
 241          * to enter the name of a city, the accessibleName for the en_US locale
 242          * could be 'city.'
 243          *
 244          * @return the localized name of the object; null if this
 245          * object does not have a name
 246          *
 247          * @see #setAccessibleName
 248          */
 249         public String getAccessibleName() {
 250             if (model != null) {
 251                 return (String)model.getProperty(Document.TitleProperty);
 252             } else {
 253                 return null;
 254             }
 255         }
 256 
 257         /**
 258          * Gets the accessibleDescription property of this object.  If this
 259          * property isn't set, returns the content type of this
 260          * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
 261          *
 262          * @return the localized description of the object; <code>null</code>
 263          *      if this object does not have a description
 264          *
 265          * @see #setAccessibleName
 266          */
 267         public String getAccessibleDescription() {
 268             return editor.getContentType();
 269         }
 270 
 271         /**
 272          * Gets the role of this object.  The role of the object is the generic
 273          * purpose or use of the class of this object.  For example, the role
 274          * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
 275          * AccessibleRole are provided so component developers can pick from
 276          * a set of predefined roles.  This enables assistive technologies to
 277          * provide a consistent interface to various tweaked subclasses of
 278          * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
 279          * that act like a push button) as well as distinguish between subclasses
 280          * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
 281          * and AccessibleRole.RADIO_BUTTON for radio buttons).
 282          * <p>Note that the AccessibleRole class is also extensible, so
 283          * custom component developers can define their own AccessibleRole's
 284          * if the set of predefined roles is inadequate.
 285          *
 286          * @return an instance of AccessibleRole describing the role of the object
 287          * @see AccessibleRole
 288          */
 289         public AccessibleRole getAccessibleRole() {
 290             return AccessibleRole.TEXT;
 291         }
 292     }
 293 
 294     /*
 295      * Base AccessibleContext class for HTML elements
 296      */
 297     protected abstract class HTMLAccessibleContext extends AccessibleContext
 298         implements Accessible, AccessibleComponent {
 299 
 300         protected ElementInfo elementInfo;
 301 
 302         public HTMLAccessibleContext(ElementInfo elementInfo) {
 303             this.elementInfo = elementInfo;
 304         }
 305 
 306         // begin AccessibleContext implementation ...
 307         public AccessibleContext getAccessibleContext() {
 308             return this;
 309         }
 310 
 311         /**
 312          * Gets the state set of this object.
 313          *
 314          * @return an instance of AccessibleStateSet describing the states
 315          * of the object
 316          * @see AccessibleStateSet
 317          */
 318         public AccessibleStateSet getAccessibleStateSet() {
 319             AccessibleStateSet states = new AccessibleStateSet();
 320             Component comp = getTextComponent();
 321 
 322             if (comp.isEnabled()) {
 323                 states.add(AccessibleState.ENABLED);
 324             }
 325             if (comp instanceof JTextComponent &&
 326                 ((JTextComponent)comp).isEditable()) {
 327 
 328                 states.add(AccessibleState.EDITABLE);
 329                 states.add(AccessibleState.FOCUSABLE);
 330             }
 331             if (comp.isVisible()) {
 332                 states.add(AccessibleState.VISIBLE);
 333             }
 334             if (comp.isShowing()) {
 335                 states.add(AccessibleState.SHOWING);
 336             }
 337             return states;
 338         }
 339 
 340         /**
 341          * Gets the 0-based index of this object in its accessible parent.
 342          *
 343          * @return the 0-based index of this object in its parent; -1 if this
 344          * object does not have an accessible parent.
 345          *
 346          * @see #getAccessibleParent
 347          * @see #getAccessibleChildrenCount
 348          * @see #getAccessibleChild
 349          */
 350         public int getAccessibleIndexInParent() {
 351             return elementInfo.getIndexInParent();
 352         }
 353 
 354         /**
 355          * Returns the number of accessible children of the object.
 356          *
 357          * @return the number of accessible children of the object.
 358          */
 359         public int getAccessibleChildrenCount() {
 360             return elementInfo.getChildCount();
 361         }
 362 
 363         /**
 364          * Returns the specified Accessible child of the object.  The Accessible
 365          * children of an Accessible object are zero-based, so the first child
 366          * of an Accessible child is at index 0, the second child is at index 1,
 367          * and so on.
 368          *
 369          * @param i zero-based index of child
 370          * @return the Accessible child of the object
 371          * @see #getAccessibleChildrenCount
 372          */
 373         public Accessible getAccessibleChild(int i) {
 374             ElementInfo childInfo = elementInfo.getChild(i);
 375             if (childInfo != null && childInfo instanceof Accessible) {
 376                 return (Accessible)childInfo;
 377             } else {
 378                 return null;
 379             }
 380         }
 381 
 382         /**
 383          * Gets the locale of the component. If the component does not have a
 384          * locale, then the locale of its parent is returned.
 385          *
 386          * @return this component's locale.  If this component does not have
 387          * a locale, the locale of its parent is returned.
 388          *
 389          * @exception IllegalComponentStateException
 390          * If the Component does not have its own locale and has not yet been
 391          * added to a containment hierarchy such that the locale can be
 392          * determined from the containing parent.
 393          */
 394         public Locale getLocale() throws IllegalComponentStateException {
 395             return editor.getLocale();
 396         }
 397         // ... end AccessibleContext implementation
 398 
 399         // begin AccessibleComponent implementation ...
 400         public AccessibleComponent getAccessibleComponent() {
 401             return this;
 402         }
 403 
 404         /**
 405          * Gets the background color of this object.
 406          *
 407          * @return the background color, if supported, of the object;
 408          * otherwise, null
 409          * @see #setBackground
 410          */
 411         public Color getBackground() {
 412             return getTextComponent().getBackground();
 413         }
 414 
 415         /**
 416          * Sets the background color of this object.
 417          *
 418          * @param c the new Color for the background
 419          * @see #setBackground
 420          */
 421         public void setBackground(Color c) {
 422             getTextComponent().setBackground(c);
 423         }
 424 
 425         /**
 426          * Gets the foreground color of this object.
 427          *
 428          * @return the foreground color, if supported, of the object;
 429          * otherwise, null
 430          * @see #setForeground
 431          */
 432         public Color getForeground() {
 433             return getTextComponent().getForeground();
 434         }
 435 
 436         /**
 437          * Sets the foreground color of this object.
 438          *
 439          * @param c the new Color for the foreground
 440          * @see #getForeground
 441          */
 442         public void setForeground(Color c) {
 443             getTextComponent().setForeground(c);
 444         }
 445 
 446         /**
 447          * Gets the Cursor of this object.
 448          *
 449          * @return the Cursor, if supported, of the object; otherwise, null
 450          * @see #setCursor
 451          */
 452         public Cursor getCursor() {
 453             return getTextComponent().getCursor();
 454         }
 455 
 456         /**
 457          * Sets the Cursor of this object.
 458          *
 459          * @param cursor the new Cursor for the object
 460          * @see #getCursor
 461          */
 462         public void setCursor(Cursor cursor) {
 463             getTextComponent().setCursor(cursor);
 464         }
 465 
 466         /**
 467          * Gets the Font of this object.
 468          *
 469          * @return the Font,if supported, for the object; otherwise, null
 470          * @see #setFont
 471          */
 472         public Font getFont() {
 473             return getTextComponent().getFont();
 474         }
 475 
 476         /**
 477          * Sets the Font of this object.
 478          *
 479          * @param f the new Font for the object
 480          * @see #getFont
 481          */
 482         public void setFont(Font f) {
 483             getTextComponent().setFont(f);
 484         }
 485 
 486         /**
 487          * Gets the FontMetrics of this object.
 488          *
 489          * @param f the Font
 490          * @return the FontMetrics, if supported, the object; otherwise, null
 491          * @see #getFont
 492          */
 493         public FontMetrics getFontMetrics(Font f) {
 494             return getTextComponent().getFontMetrics(f);
 495         }
 496 
 497         /**
 498          * Determines if the object is enabled.  Objects that are enabled
 499          * will also have the AccessibleState.ENABLED state set in their
 500          * AccessibleStateSets.
 501          *
 502          * @return true if object is enabled; otherwise, false
 503          * @see #setEnabled
 504          * @see AccessibleContext#getAccessibleStateSet
 505          * @see AccessibleState#ENABLED
 506          * @see AccessibleStateSet
 507          */
 508         public boolean isEnabled() {
 509             return getTextComponent().isEnabled();
 510         }
 511 
 512         /**
 513          * Sets the enabled state of the object.
 514          *
 515          * @param b if true, enables this object; otherwise, disables it
 516          * @see #isEnabled
 517          */
 518         public void setEnabled(boolean b) {
 519             getTextComponent().setEnabled(b);
 520         }
 521 
 522         /**
 523          * Determines if the object is visible.  Note: this means that the
 524          * object intends to be visible; however, it may not be
 525          * showing on the screen because one of the objects that this object
 526          * is contained by is currently not visible.  To determine if an object
 527          * is showing on the screen, use isShowing().
 528          * <p>Objects that are visible will also have the
 529          * AccessibleState.VISIBLE state set in their AccessibleStateSets.
 530          *
 531          * @return true if object is visible; otherwise, false
 532          * @see #setVisible
 533          * @see AccessibleContext#getAccessibleStateSet
 534          * @see AccessibleState#VISIBLE
 535          * @see AccessibleStateSet
 536          */
 537         public boolean isVisible() {
 538             return getTextComponent().isVisible();
 539         }
 540 
 541         /**
 542          * Sets the visible state of the object.
 543          *
 544          * @param b if true, shows this object; otherwise, hides it
 545          * @see #isVisible
 546          */
 547         public void setVisible(boolean b) {
 548             getTextComponent().setVisible(b);
 549         }
 550 
 551         /**
 552          * Determines if the object is showing.  This is determined by checking
 553          * the visibility of the object and its ancestors.
 554          * Note: this
 555          * will return true even if the object is obscured by another (for
 556          * example, it is underneath a menu that was pulled down).
 557          *
 558          * @return true if object is showing; otherwise, false
 559          */
 560         public boolean isShowing() {
 561             return getTextComponent().isShowing();
 562         }
 563 
 564         /**
 565          * Checks whether the specified point is within this object's bounds,
 566          * where the point's x and y coordinates are defined to be relative
 567          * to the coordinate system of the object.
 568          *
 569          * @param p the Point relative to the coordinate system of the object
 570          * @return true if object contains Point; otherwise false
 571          * @see #getBounds
 572          */
 573         public boolean contains(Point p) {
 574             Rectangle r = getBounds();
 575             if (r != null) {
 576                 return r.contains(p.x, p.y);
 577             } else {
 578                 return false;
 579             }
 580         }
 581 
 582         /**
 583          * Returns the location of the object on the screen.
 584          *
 585          * @return the location of the object on screen; null if this object
 586          * is not on the screen
 587          * @see #getBounds
 588          * @see #getLocation
 589          */
 590         public Point getLocationOnScreen() {
 591             Point editorLocation = getTextComponent().getLocationOnScreen();
 592             Rectangle r = getBounds();
 593             if (r != null) {
 594                 return new Point(editorLocation.x + r.x,
 595                                  editorLocation.y + r.y);
 596             } else {
 597                 return null;
 598             }
 599         }
 600 
 601         /**
 602          * Gets the location of the object relative to the parent in the form
 603          * of a point specifying the object's top-left corner in the screen's
 604          * coordinate space.
 605          *
 606          * @return An instance of Point representing the top-left corner of the
 607          * object's bounds in the coordinate space of the screen; null if
 608          * this object or its parent are not on the screen
 609          * @see #getBounds
 610          * @see #getLocationOnScreen
 611          */
 612         public Point getLocation() {
 613             Rectangle r = getBounds();
 614             if (r != null) {
 615                 return new Point(r.x, r.y);
 616             } else {
 617                 return null;
 618             }
 619         }
 620 
 621         /**
 622          * Sets the location of the object relative to the parent.
 623          * @param p the new position for the top-left corner
 624          * @see #getLocation
 625          */
 626         public void setLocation(Point p) {
 627         }
 628 
 629         /**
 630          * Gets the bounds of this object in the form of a Rectangle object.
 631          * The bounds specify this object's width, height, and location
 632          * relative to its parent.
 633          *
 634          * @return A rectangle indicating this component's bounds; null if
 635          * this object is not on the screen.
 636          * @see #contains
 637          */
 638         public Rectangle getBounds() {
 639             return elementInfo.getBounds();
 640         }
 641 
 642         /**
 643          * Sets the bounds of this object in the form of a Rectangle object.
 644          * The bounds specify this object's width, height, and location
 645          * relative to its parent.
 646          *
 647          * @param r rectangle indicating this component's bounds
 648          * @see #getBounds
 649          */
 650         public void setBounds(Rectangle r) {
 651         }
 652 
 653         /**
 654          * Returns the size of this object in the form of a Dimension object.
 655          * The height field of the Dimension object contains this object's
 656          * height, and the width field of the Dimension object contains this
 657          * object's width.
 658          *
 659          * @return A Dimension object that indicates the size of this component;
 660          * null if this object is not on the screen
 661          * @see #setSize
 662          */
 663         public Dimension getSize() {
 664             Rectangle r = getBounds();
 665             if (r != null) {
 666                 return new Dimension(r.width, r.height);
 667             } else {
 668                 return null;
 669             }
 670         }
 671 
 672         /**
 673          * Resizes this object so that it has width and height.
 674          *
 675          * @param d The dimension specifying the new size of the object.
 676          * @see #getSize
 677          */
 678         public void setSize(Dimension d) {
 679             Component comp = getTextComponent();
 680             comp.setSize(d);
 681         }
 682 
 683         /**
 684          * Returns the Accessible child, if one exists, contained at the local
 685          * coordinate Point.
 686          *
 687          * @param p The point relative to the coordinate system of this object.
 688          * @return the Accessible, if it exists, at the specified location;
 689          * otherwise null
 690          */
 691         public Accessible getAccessibleAt(Point p) {
 692             ElementInfo innerMostElement = getElementInfoAt(rootElementInfo, p);
 693             if (innerMostElement instanceof Accessible) {
 694                 return (Accessible)innerMostElement;
 695             } else {
 696                 return null;
 697             }
 698         }
 699 
 700         private ElementInfo getElementInfoAt(ElementInfo elementInfo, Point p) {
 701             if (elementInfo.getBounds() == null) {
 702                 return null;
 703             }
 704             if (elementInfo.getChildCount() == 0 &&
 705                 elementInfo.getBounds().contains(p)) {
 706                 return elementInfo;
 707 
 708             } else {
 709                 if (elementInfo instanceof TableElementInfo) {
 710                     // Handle table caption as a special case since it's the
 711                     // only table child that is not a table row.
 712                     ElementInfo captionInfo =
 713                         ((TableElementInfo)elementInfo).getCaptionInfo();
 714                     if (captionInfo != null) {
 715                         Rectangle bounds = captionInfo.getBounds();
 716                         if (bounds != null && bounds.contains(p)) {
 717                             return captionInfo;
 718                         }
 719                     }
 720                 }
 721                 for (int i = 0; i < elementInfo.getChildCount(); i++)
 722 {
 723                     ElementInfo childInfo = elementInfo.getChild(i);
 724                     ElementInfo retValue = getElementInfoAt(childInfo, p);
 725                     if (retValue != null) {
 726                         return retValue;
 727                     }
 728                 }
 729             }
 730             return null;
 731         }
 732 
 733         /**
 734          * Returns whether this object can accept focus or not.   Objects that
 735          * can accept focus will also have the AccessibleState.FOCUSABLE state
 736          * set in their AccessibleStateSets.
 737          *
 738          * @return true if object can accept focus; otherwise false
 739          * @see AccessibleContext#getAccessibleStateSet
 740          * @see AccessibleState#FOCUSABLE
 741          * @see AccessibleState#FOCUSED
 742          * @see AccessibleStateSet
 743          */
 744         public boolean isFocusTraversable() {
 745             Component comp = getTextComponent();
 746             if (comp instanceof JTextComponent) {
 747                 if (((JTextComponent)comp).isEditable()) {
 748                     return true;
 749                 }
 750             }
 751             return false;
 752         }
 753 
 754         /**
 755          * Requests focus for this object.  If this object cannot accept focus,
 756          * nothing will happen.  Otherwise, the object will attempt to take
 757          * focus.
 758          * @see #isFocusTraversable
 759          */
 760         public void requestFocus() {
 761             // TIGER - 4856191
 762             if (! isFocusTraversable()) {
 763                 return;
 764             }
 765 
 766             Component comp = getTextComponent();
 767             if (comp instanceof JTextComponent) {
 768 
 769                 comp.requestFocusInWindow();
 770 
 771                 try {
 772                     if (elementInfo.validateIfNecessary()) {
 773                         // set the caret position to the start of this component
 774                         Element elem = elementInfo.getElement();
 775                         ((JTextComponent)comp).setCaretPosition(elem.getStartOffset());
 776 
 777                         // fire a AccessibleState.FOCUSED property change event
 778                         AccessibleContext ac = editor.getAccessibleContext();
 779                         PropertyChangeEvent pce = new PropertyChangeEvent(this,
 780                             AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
 781                             null, AccessibleState.FOCUSED);
 782                         ac.firePropertyChange(
 783                             AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
 784                             null, pce);
 785                     }
 786                 } catch (IllegalArgumentException e) {
 787                     // don't fire property change event
 788                 }
 789             }
 790         }
 791 
 792         /**
 793          * Adds the specified focus listener to receive focus events from this
 794          * component.
 795          *
 796          * @param l the focus listener
 797          * @see #removeFocusListener
 798          */
 799         public void addFocusListener(FocusListener l) {
 800             getTextComponent().addFocusListener(l);
 801         }
 802 
 803         /**
 804          * Removes the specified focus listener so it no longer receives focus
 805          * events from this component.
 806          *
 807          * @param l the focus listener
 808          * @see #addFocusListener
 809          */
 810         public void removeFocusListener(FocusListener l) {
 811             getTextComponent().removeFocusListener(l);
 812         }
 813         // ... end AccessibleComponent implementation
 814     } // ... end HTMLAccessibleContext
 815 
 816 
 817 
 818     /*
 819      * ElementInfo for text
 820      */
 821     class TextElementInfo extends ElementInfo implements Accessible {
 822 
 823         TextElementInfo(Element element, ElementInfo parent) {
 824             super(element, parent);
 825         }
 826 
 827         // begin AccessibleText implementation ...
 828         private AccessibleContext accessibleContext;
 829 
 830         public AccessibleContext getAccessibleContext() {
 831             if (accessibleContext == null) {
 832                 accessibleContext = new TextAccessibleContext(this);
 833             }
 834             return accessibleContext;
 835         }
 836 
 837         /*
 838          * AccessibleContext for text elements
 839          */
 840         public class TextAccessibleContext extends HTMLAccessibleContext
 841             implements AccessibleText {
 842 
 843             public TextAccessibleContext(ElementInfo elementInfo) {
 844                 super(elementInfo);
 845             }
 846 
 847             public AccessibleText getAccessibleText() {
 848                 return this;
 849             }
 850 
 851             /**
 852              * Gets the accessibleName property of this object.  The accessibleName
 853              * property of an object is a localized String that designates the purpose
 854              * of the object.  For example, the accessibleName property of a label
 855              * or button might be the text of the label or button itself.  In the
 856              * case of an object that doesn't display its name, the accessibleName
 857              * should still be set.  For example, in the case of a text field used
 858              * to enter the name of a city, the accessibleName for the en_US locale
 859              * could be 'city.'
 860              *
 861              * @return the localized name of the object; null if this
 862              * object does not have a name
 863              *
 864              * @see #setAccessibleName
 865              */
 866             public String getAccessibleName() {
 867                 if (model != null) {
 868                     return (String)model.getProperty(Document.TitleProperty);
 869                 } else {
 870                     return null;
 871                 }
 872             }
 873 
 874             /**
 875              * Gets the accessibleDescription property of this object.  If this
 876              * property isn't set, returns the content type of this
 877              * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
 878              *
 879              * @return the localized description of the object; <code>null</code>
 880              *  if this object does not have a description
 881              *
 882              * @see #setAccessibleName
 883              */
 884             public String getAccessibleDescription() {
 885                 return editor.getContentType();
 886             }
 887 
 888             /**
 889              * Gets the role of this object.  The role of the object is the generic
 890              * purpose or use of the class of this object.  For example, the role
 891              * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
 892              * AccessibleRole are provided so component developers can pick from
 893              * a set of predefined roles.  This enables assistive technologies to
 894              * provide a consistent interface to various tweaked subclasses of
 895              * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
 896              * that act like a push button) as well as distinguish between subclasses
 897              * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
 898              * and AccessibleRole.RADIO_BUTTON for radio buttons).
 899              * <p>Note that the AccessibleRole class is also extensible, so
 900              * custom component developers can define their own AccessibleRole's
 901              * if the set of predefined roles is inadequate.
 902              *
 903              * @return an instance of AccessibleRole describing the role of the object
 904              * @see AccessibleRole
 905              */
 906             public AccessibleRole getAccessibleRole() {
 907                 return AccessibleRole.TEXT;
 908             }
 909 
 910             /**
 911              * Given a point in local coordinates, return the zero-based index
 912              * of the character under that Point.  If the point is invalid,
 913              * this method returns -1.
 914              *
 915              * @param p the Point in local coordinates
 916              * @return the zero-based index of the character under Point p; if
 917              * Point is invalid returns -1.
 918              */
 919             public int getIndexAtPoint(Point p) {
 920                 View v = getView();
 921                 if (v != null) {
 922                     return v.viewToModel(p.x, p.y, getBounds());
 923                 } else {
 924                     return -1;
 925                 }
 926             }
 927 
 928             /**
 929              * Determine the bounding box of the character at the given
 930              * index into the string.  The bounds are returned in local
 931              * coordinates.  If the index is invalid an empty rectangle is
 932              * returned.
 933              *
 934              * @param i the index into the String
 935              * @return the screen coordinates of the character's the bounding box,
 936              * if index is invalid returns an empty rectangle.
 937              */
 938             public Rectangle getCharacterBounds(int i) {
 939                 try {
 940                     return editor.getUI().modelToView(editor, i);
 941                 } catch (BadLocationException e) {
 942                     return null;
 943                 }
 944             }
 945 
 946             /**
 947              * Return the number of characters (valid indicies)
 948              *
 949              * @return the number of characters
 950              */
 951             public int getCharCount() {
 952                 if (validateIfNecessary()) {
 953                     Element elem = elementInfo.getElement();
 954                     return elem.getEndOffset() - elem.getStartOffset();
 955                 }
 956                 return 0;
 957             }
 958 
 959             /**
 960              * Return the zero-based offset of the caret.
 961              *
 962              * Note: That to the right of the caret will have the same index
 963              * value as the offset (the caret is between two characters).
 964              * @return the zero-based offset of the caret.
 965              */
 966             public int getCaretPosition() {
 967                 View v = getView();
 968                 if (v == null) {
 969                     return -1;
 970                 }
 971                 Container c = v.getContainer();
 972                 if (c == null) {
 973                     return -1;
 974                 }
 975                 if (c instanceof JTextComponent) {
 976                     return ((JTextComponent)c).getCaretPosition();
 977                 } else {
 978                     return -1;
 979                 }
 980             }
 981 
 982             /**
 983              * IndexedSegment extends Segment adding the offset into the
 984              * the model the <code>Segment</code> was asked for.
 985              */
 986             private class IndexedSegment extends Segment {
 987                 /**
 988                  * Offset into the model that the position represents.
 989                  */
 990                 public int modelOffset;
 991             }
 992 
 993             public String getAtIndex(int part, int index) {
 994                 return getAtIndex(part, index, 0);
 995             }
 996 
 997 
 998             public String getAfterIndex(int part, int index) {
 999                 return getAtIndex(part, index, 1);
1000             }
1001 
1002             public String getBeforeIndex(int part, int index) {
1003                 return getAtIndex(part, index, -1);
1004             }
1005 
1006             /**
1007              * Gets the word, sentence, or character at <code>index</code>.
1008              * If <code>direction</code> is non-null this will find the
1009              * next/previous word/sentence/character.
1010              */
1011             private String getAtIndex(int part, int index, int direction) {
1012                 if (model instanceof AbstractDocument) {
1013                     ((AbstractDocument)model).readLock();
1014                 }
1015                 try {
1016                     if (index < 0 || index >= model.getLength()) {
1017                         return null;
1018                     }
1019                     switch (part) {
1020                     case AccessibleText.CHARACTER:
1021                         if (index + direction < model.getLength() &&
1022                             index + direction >= 0) {
1023                             return model.getText(index + direction, 1);
1024                         }
1025                         break;
1026 
1027 
1028                     case AccessibleText.WORD:
1029                     case AccessibleText.SENTENCE:
1030                         IndexedSegment seg = getSegmentAt(part, index);
1031                         if (seg != null) {
1032                             if (direction != 0) {
1033                                 int next;
1034 
1035 
1036                                 if (direction < 0) {
1037                                     next = seg.modelOffset - 1;
1038                                 }
1039                                 else {
1040                                     next = seg.modelOffset + direction * seg.count;
1041                                 }
1042                                 if (next >= 0 && next <= model.getLength()) {
1043                                     seg = getSegmentAt(part, next);
1044                                 }
1045                                 else {
1046                                     seg = null;
1047                                 }
1048                             }
1049                             if (seg != null) {
1050                                 return new String(seg.array, seg.offset,
1051                                                   seg.count);
1052                             }
1053                         }
1054                         break;
1055 
1056                     default:
1057                         break;
1058                     }
1059                 } catch (BadLocationException e) {
1060                 } finally {
1061                     if (model instanceof AbstractDocument) {
1062                         ((AbstractDocument)model).readUnlock();
1063                     }
1064                 }
1065                 return null;
1066             }
1067 
1068             /*
1069              * Returns the paragraph element for the specified index.
1070              */
1071             private Element getParagraphElement(int index) {
1072                 if (model instanceof PlainDocument ) {
1073                     PlainDocument sdoc = (PlainDocument)model;
1074                     return sdoc.getParagraphElement(index);
1075                 } else if (model instanceof StyledDocument) {
1076                     StyledDocument sdoc = (StyledDocument)model;
1077                     return sdoc.getParagraphElement(index);
1078                 } else {
1079                     Element para;
1080                     for (para = model.getDefaultRootElement(); ! para.isLeaf(); ) {
1081                         int pos = para.getElementIndex(index);
1082                         para = para.getElement(pos);
1083                     }
1084                     if (para == null) {
1085                         return null;
1086                     }
1087                     return para.getParentElement();
1088                 }
1089             }
1090 
1091             /*
1092              * Returns a <code>Segment</code> containing the paragraph text
1093              * at <code>index</code>, or null if <code>index</code> isn't
1094              * valid.
1095              */
1096             private IndexedSegment getParagraphElementText(int index)
1097                 throws BadLocationException {
1098                 Element para = getParagraphElement(index);
1099 
1100 
1101                 if (para != null) {
1102                     IndexedSegment segment = new IndexedSegment();
1103                     try {
1104                         int length = para.getEndOffset() - para.getStartOffset();
1105                         model.getText(para.getStartOffset(), length, segment);
1106                     } catch (BadLocationException e) {
1107                         return null;
1108                     }
1109                     segment.modelOffset = para.getStartOffset();
1110                     return segment;
1111                 }
1112                 return null;
1113             }
1114 
1115 
1116             /**
1117              * Returns the Segment at <code>index</code> representing either
1118              * the paragraph or sentence as identified by <code>part</code>, or
1119              * null if a valid paragraph/sentence can't be found. The offset
1120              * will point to the start of the word/sentence in the array, and
1121              * the modelOffset will point to the location of the word/sentence
1122              * in the model.
1123              */
1124             private IndexedSegment getSegmentAt(int part, int index)
1125                 throws BadLocationException {
1126 
1127                 IndexedSegment seg = getParagraphElementText(index);
1128                 if (seg == null) {
1129                     return null;
1130                 }
1131                 BreakIterator iterator;
1132                 switch (part) {
1133                 case AccessibleText.WORD:
1134                     iterator = BreakIterator.getWordInstance(getLocale());
1135                     break;
1136                 case AccessibleText.SENTENCE:
1137                     iterator = BreakIterator.getSentenceInstance(getLocale());
1138                     break;
1139                 default:
1140                     return null;
1141                 }
1142                 seg.first();
1143                 iterator.setText(seg);
1144                 int end = iterator.following(index - seg.modelOffset + seg.offset);
1145                 if (end == BreakIterator.DONE) {
1146                     return null;
1147                 }
1148                 if (end > seg.offset + seg.count) {
1149                     return null;
1150                 }
1151                 int begin = iterator.previous();
1152                 if (begin == BreakIterator.DONE ||
1153                     begin >= seg.offset + seg.count) {
1154                     return null;
1155                 }
1156                 seg.modelOffset = seg.modelOffset + begin - seg.offset;
1157                 seg.offset = begin;
1158                 seg.count = end - begin;
1159                 return seg;
1160             }
1161 
1162             /**
1163              * Return the AttributeSet for a given character at a given index
1164              *
1165              * @param i the zero-based index into the text
1166              * @return the AttributeSet of the character
1167              */
1168             public AttributeSet getCharacterAttribute(int i) {
1169                 if (model instanceof StyledDocument) {
1170                     StyledDocument doc = (StyledDocument)model;
1171                     Element elem = doc.getCharacterElement(i);
1172                     if (elem != null) {
1173                         return elem.getAttributes();
1174                     }
1175                 }
1176                 return null;
1177             }
1178 
1179             /**
1180              * Returns the start offset within the selected text.
1181              * If there is no selection, but there is
1182              * a caret, the start and end offsets will be the same.
1183              *
1184              * @return the index into the text of the start of the selection
1185              */
1186             public int getSelectionStart() {
1187                 return editor.getSelectionStart();
1188             }
1189 
1190             /**
1191              * Returns the end offset within the selected text.
1192              * If there is no selection, but there is
1193              * a caret, the start and end offsets will be the same.
1194              *
1195              * @return the index into the text of the end of the selection
1196              */
1197             public int getSelectionEnd() {
1198                 return editor.getSelectionEnd();
1199             }
1200 
1201             /**
1202              * Returns the portion of the text that is selected.
1203              *
1204              * @return the String portion of the text that is selected
1205              */
1206             public String getSelectedText() {
1207                 return editor.getSelectedText();
1208             }
1209 
1210             /*
1211              * Returns the text substring starting at the specified
1212              * offset with the specified length.
1213              */
1214             private String getText(int offset, int length)
1215                 throws BadLocationException {
1216 
1217                 if (model != null && model instanceof StyledDocument) {
1218                     StyledDocument doc = (StyledDocument)model;
1219                     return model.getText(offset, length);
1220                 } else {
1221                     return null;
1222                 }
1223             }
1224         }
1225     }
1226 
1227     /*
1228      * ElementInfo for images
1229      */
1230     private class IconElementInfo extends ElementInfo implements Accessible {
1231 
1232         private int width = -1;
1233         private int height = -1;
1234 
1235         IconElementInfo(Element element, ElementInfo parent) {
1236             super(element, parent);
1237         }
1238 
1239         protected void invalidate(boolean first) {
1240             super.invalidate(first);
1241             width = height = -1;
1242         }
1243 
1244         private int getImageSize(Object key) {
1245             if (validateIfNecessary()) {
1246                 int size = getIntAttr(getAttributes(), key, -1);
1247 
1248                 if (size == -1) {
1249                     View v = getView();
1250 
1251                     size = 0;
1252                     if (v instanceof ImageView) {
1253                         Image img = ((ImageView)v).getImage();
1254                         if (img != null) {
1255                             if (key == HTML.Attribute.WIDTH) {
1256                                 size = img.getWidth(null);
1257                             }
1258                             else {
1259                                 size = img.getHeight(null);
1260                             }
1261                         }
1262                     }
1263                 }
1264                 return size;
1265             }
1266             return 0;
1267         }
1268 
1269         // begin AccessibleIcon implementation ...
1270         private AccessibleContext accessibleContext;
1271 
1272         public AccessibleContext getAccessibleContext() {
1273             if (accessibleContext == null) {
1274                 accessibleContext = new IconAccessibleContext(this);
1275             }
1276             return accessibleContext;
1277         }
1278 
1279         /*
1280          * AccessibleContext for images
1281          */
1282         protected class IconAccessibleContext extends HTMLAccessibleContext
1283             implements AccessibleIcon  {
1284 
1285             public IconAccessibleContext(ElementInfo elementInfo) {
1286                 super(elementInfo);
1287             }
1288 
1289             /**
1290              * Gets the accessibleName property of this object.  The accessibleName
1291              * property of an object is a localized String that designates the purpose
1292              * of the object.  For example, the accessibleName property of a label
1293              * or button might be the text of the label or button itself.  In the
1294              * case of an object that doesn't display its name, the accessibleName
1295              * should still be set.  For example, in the case of a text field used
1296              * to enter the name of a city, the accessibleName for the en_US locale
1297              * could be 'city.'
1298              *
1299              * @return the localized name of the object; null if this
1300              * object does not have a name
1301              *
1302              * @see #setAccessibleName
1303              */
1304             public String getAccessibleName() {
1305                 return getAccessibleIconDescription();
1306             }
1307 
1308             /**
1309              * Gets the accessibleDescription property of this object.  If this
1310              * property isn't set, returns the content type of this
1311              * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1312              *
1313              * @return the localized description of the object; <code>null</code>
1314              *  if this object does not have a description
1315              *
1316              * @see #setAccessibleName
1317              */
1318             public String getAccessibleDescription() {
1319                 return editor.getContentType();
1320             }
1321 
1322             /**
1323              * Gets the role of this object.  The role of the object is the generic
1324              * purpose or use of the class of this object.  For example, the role
1325              * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
1326              * AccessibleRole are provided so component developers can pick from
1327              * a set of predefined roles.  This enables assistive technologies to
1328              * provide a consistent interface to various tweaked subclasses of
1329              * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1330              * that act like a push button) as well as distinguish between subclasses
1331              * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1332              * and AccessibleRole.RADIO_BUTTON for radio buttons).
1333              * <p>Note that the AccessibleRole class is also extensible, so
1334              * custom component developers can define their own AccessibleRole's
1335              * if the set of predefined roles is inadequate.
1336              *
1337              * @return an instance of AccessibleRole describing the role of the object
1338              * @see AccessibleRole
1339              */
1340             public AccessibleRole getAccessibleRole() {
1341                 return AccessibleRole.ICON;
1342             }
1343 
1344             public AccessibleIcon [] getAccessibleIcon() {
1345                 AccessibleIcon [] icons = new AccessibleIcon[1];
1346                 icons[0] = this;
1347                 return icons;
1348             }
1349 
1350             /**
1351              * Gets the description of the icon.  This is meant to be a brief
1352              * textual description of the object.  For example, it might be
1353              * presented to a blind user to give an indication of the purpose
1354              * of the icon.
1355              *
1356              * @return the description of the icon
1357              */
1358             public String getAccessibleIconDescription() {
1359                 return ((ImageView)getView()).getAltText();
1360             }
1361 
1362             /**
1363              * Sets the description of the icon.  This is meant to be a brief
1364              * textual description of the object.  For example, it might be
1365              * presented to a blind user to give an indication of the purpose
1366              * of the icon.
1367              *
1368              * @param description the description of the icon
1369              */
1370             public void setAccessibleIconDescription(String description) {
1371             }
1372 
1373             /**
1374              * Gets the width of the icon
1375              *
1376              * @return the width of the icon.
1377              */
1378             public int getAccessibleIconWidth() {
1379                 if (width == -1) {
1380                     width = getImageSize(HTML.Attribute.WIDTH);
1381                 }
1382                 return width;
1383             }
1384 
1385             /**
1386              * Gets the height of the icon
1387              *
1388              * @return the height of the icon.
1389              */
1390             public int getAccessibleIconHeight() {
1391                 if (height == -1) {
1392                     height = getImageSize(HTML.Attribute.HEIGHT);
1393                 }
1394                 return height;
1395             }
1396         }
1397         // ... end AccessibleIconImplementation
1398     }
1399 
1400 
1401     /**
1402      * TableElementInfo encapsulates information about a HTML.Tag.TABLE.
1403      * To make access fast it crates a grid containing the children to
1404      * allow for access by row, column. TableElementInfo will contain
1405      * TableRowElementInfos, which will contain TableCellElementInfos.
1406      * Any time one of the rows or columns becomes invalid the table is
1407      * invalidated.  This is because any time one of the child attributes
1408      * changes the size of the grid may have changed.
1409      */
1410     private class TableElementInfo extends ElementInfo
1411         implements Accessible {
1412 
1413         protected ElementInfo caption;
1414 
1415         /**
1416          * Allocation of the table by row x column. There may be holes (eg
1417          * nulls) depending upon the html, any cell that has a rowspan/colspan
1418          * > 1 will be contained multiple times in the grid.
1419          */
1420         private TableCellElementInfo[][] grid;
1421 
1422 
1423         TableElementInfo(Element e, ElementInfo parent) {
1424             super(e, parent);
1425         }
1426 
1427         public ElementInfo getCaptionInfo() {
1428             return caption;
1429         }
1430 
1431         /**
1432          * Overriden to update the grid when validating.
1433          */
1434         protected void validate() {
1435             super.validate();
1436             updateGrid();
1437         }
1438 
1439         /**
1440          * Overriden to only alloc instances of TableRowElementInfos.
1441          */
1442         protected void loadChildren(Element e) {
1443 
1444             for (int counter = 0; counter < e.getElementCount(); counter++) {
1445                 Element child = e.getElement(counter);
1446                 AttributeSet attrs = child.getAttributes();
1447 
1448                 if (attrs.getAttribute(StyleConstants.NameAttribute) ==
1449                                        HTML.Tag.TR) {
1450                     addChild(new TableRowElementInfo(child, this, counter));
1451 
1452                 } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
1453                                        HTML.Tag.CAPTION) {
1454                     // Handle captions as a special case since all other
1455                     // children are table rows.
1456                     caption = createElementInfo(child, this);
1457                 }
1458             }
1459         }
1460 
1461         /**
1462          * Updates the grid.
1463          */
1464         private void updateGrid() {
1465             // Determine the max row/col count.
1466             int delta = 0;
1467             int maxCols = 0;
1468             int rows;
1469             for (int counter = 0; counter < getChildCount(); counter++) {
1470                 TableRowElementInfo row = getRow(counter);
1471                 int prev = 0;
1472                 for (int y = 0; y < delta; y++) {
1473                     prev = Math.max(prev, getRow(counter - y - 1).
1474                                     getColumnCount(y + 2));
1475                 }
1476                 delta = Math.max(row.getRowCount(), delta);
1477                 delta--;
1478                 maxCols = Math.max(maxCols, row.getColumnCount() + prev);
1479             }
1480             rows = getChildCount() + delta;
1481 
1482             // Alloc
1483             grid = new TableCellElementInfo[rows][];
1484             for (int counter = 0; counter < rows; counter++) {
1485                 grid[counter] = new TableCellElementInfo[maxCols];
1486             }
1487             // Update
1488             for (int counter = 0; counter < rows; counter++) {
1489                 getRow(counter).updateGrid(counter);
1490             }
1491         }
1492 
1493         /**
1494          * Returns the TableCellElementInfo at the specified index.
1495          */
1496         public TableRowElementInfo getRow(int index) {
1497             return (TableRowElementInfo)getChild(index);
1498         }
1499 
1500         /**
1501          * Returns the TableCellElementInfo by row and column.
1502          */
1503         public TableCellElementInfo getCell(int r, int c) {
1504             if (validateIfNecessary() && r < grid.length &&
1505                                          c < grid[0].length) {
1506                 return grid[r][c];
1507             }
1508             return null;
1509         }
1510 
1511         /**
1512          * Returns the rowspan of the specified entry.
1513          */
1514         public int getRowExtentAt(int r, int c) {
1515             TableCellElementInfo cell = getCell(r, c);
1516 
1517             if (cell != null) {
1518                 int rows = cell.getRowCount();
1519                 int delta = 1;
1520 
1521                 while ((r - delta) >= 0 && grid[r - delta][c] == cell) {
1522                     delta++;
1523                 }
1524                 return rows - delta + 1;
1525             }
1526             return 0;
1527         }
1528 
1529         /**
1530          * Returns the colspan of the specified entry.
1531          */
1532         public int getColumnExtentAt(int r, int c) {
1533             TableCellElementInfo cell = getCell(r, c);
1534 
1535             if (cell != null) {
1536                 int cols = cell.getColumnCount();
1537                 int delta = 1;
1538 
1539                 while ((c - delta) >= 0 && grid[r][c - delta] == cell) {
1540                     delta++;
1541                 }
1542                 return cols - delta + 1;
1543             }
1544             return 0;
1545         }
1546 
1547         /**
1548          * Returns the number of rows in the table.
1549          */
1550         public int getRowCount() {
1551             if (validateIfNecessary()) {
1552                 return grid.length;
1553             }
1554             return 0;
1555         }
1556 
1557         /**
1558          * Returns the number of columns in the table.
1559          */
1560         public int getColumnCount() {
1561             if (validateIfNecessary() && grid.length > 0) {
1562                 return grid[0].length;
1563             }
1564             return 0;
1565         }
1566 
1567         // begin AccessibleTable implementation ...
1568         private AccessibleContext accessibleContext;
1569 
1570         public AccessibleContext getAccessibleContext() {
1571             if (accessibleContext == null) {
1572                 accessibleContext = new TableAccessibleContext(this);
1573             }
1574             return accessibleContext;
1575         }
1576 
1577         /*
1578          * AccessibleContext for tables
1579          */
1580         public class TableAccessibleContext extends HTMLAccessibleContext
1581             implements AccessibleTable {
1582 
1583             private AccessibleHeadersTable rowHeadersTable;
1584 
1585             public TableAccessibleContext(ElementInfo elementInfo) {
1586                 super(elementInfo);
1587             }
1588 
1589             /**
1590              * Gets the accessibleName property of this object.  The accessibleName
1591              * property of an object is a localized String that designates the purpose
1592              * of the object.  For example, the accessibleName property of a label
1593              * or button might be the text of the label or button itself.  In the
1594              * case of an object that doesn't display its name, the accessibleName
1595              * should still be set.  For example, in the case of a text field used
1596              * to enter the name of a city, the accessibleName for the en_US locale
1597              * could be 'city.'
1598              *
1599              * @return the localized name of the object; null if this
1600              * object does not have a name
1601              *
1602              * @see #setAccessibleName
1603              */
1604             public String getAccessibleName() {
1605                 // return the role of the object
1606                 return getAccessibleRole().toString();
1607             }
1608 
1609             /**
1610              * Gets the accessibleDescription property of this object.  If this
1611              * property isn't set, returns the content type of this
1612              * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1613              *
1614              * @return the localized description of the object; <code>null</code>
1615              *  if this object does not have a description
1616              *
1617              * @see #setAccessibleName
1618              */
1619             public String getAccessibleDescription() {
1620                 return editor.getContentType();
1621             }
1622 
1623             /**
1624              * Gets the role of this object.  The role of the object is the generic
1625              * purpose or use of the class of this object.  For example, the role
1626              * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
1627              * AccessibleRole are provided so component developers can pick from
1628              * a set of predefined roles.  This enables assistive technologies to
1629              * provide a consistent interface to various tweaked subclasses of
1630              * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1631              * that act like a push button) as well as distinguish between subclasses
1632              * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1633              * and AccessibleRole.RADIO_BUTTON for radio buttons).
1634              * <p>Note that the AccessibleRole class is also extensible, so
1635              * custom component developers can define their own AccessibleRole's
1636              * if the set of predefined roles is inadequate.
1637              *
1638              * @return an instance of AccessibleRole describing the role of the object
1639              * @see AccessibleRole
1640              */
1641             public AccessibleRole getAccessibleRole() {
1642                 return AccessibleRole.TABLE;
1643             }
1644 
1645             /**
1646              * Gets the 0-based index of this object in its accessible parent.
1647              *
1648              * @return the 0-based index of this object in its parent; -1 if this
1649              * object does not have an accessible parent.
1650              *
1651              * @see #getAccessibleParent
1652              * @see #getAccessibleChildrenCount
1653              * @gsee #getAccessibleChild
1654              */
1655             public int getAccessibleIndexInParent() {
1656                 return elementInfo.getIndexInParent();
1657             }
1658 
1659             /**
1660              * Returns the number of accessible children of the object.
1661              *
1662              * @return the number of accessible children of the object.
1663              */
1664             public int getAccessibleChildrenCount() {
1665                 return ((TableElementInfo)elementInfo).getRowCount() *
1666                     ((TableElementInfo)elementInfo).getColumnCount();
1667             }
1668 
1669             /**
1670              * Returns the specified Accessible child of the object.  The Accessible
1671              * children of an Accessible object are zero-based, so the first child
1672              * of an Accessible child is at index 0, the second child is at index 1,
1673              * and so on.
1674              *
1675              * @param i zero-based index of child
1676              * @return the Accessible child of the object
1677              * @see #getAccessibleChildrenCount
1678              */
1679             public Accessible getAccessibleChild(int i) {
1680                 int rowCount = ((TableElementInfo)elementInfo).getRowCount();
1681                 int columnCount = ((TableElementInfo)elementInfo).getColumnCount();
1682                 int r = i / rowCount;
1683                 int c = i % columnCount;
1684                 if (r < 0 || r >= rowCount || c < 0 || c >= columnCount) {
1685                     return null;
1686                 } else {
1687                     return getAccessibleAt(r, c);
1688                 }
1689             }
1690 
1691             public AccessibleTable getAccessibleTable() {
1692                 return this;
1693             }
1694 
1695             /**
1696              * Returns the caption for the table.
1697              *
1698              * @return the caption for the table
1699              */
1700             public Accessible getAccessibleCaption() {
1701                 ElementInfo captionInfo = getCaptionInfo();
1702                 if (captionInfo instanceof Accessible) {
1703                     return (Accessible)caption;
1704                 } else {
1705                     return null;
1706                 }
1707             }
1708 
1709             /**
1710              * Sets the caption for the table.
1711              *
1712              * @param a the caption for the table
1713              */
1714             public void setAccessibleCaption(Accessible a) {
1715             }
1716 
1717             /**
1718              * Returns the summary description of the table.
1719              *
1720              * @return the summary description of the table
1721              */
1722             public Accessible getAccessibleSummary() {
1723                 return null;
1724             }
1725 
1726             /**
1727              * Sets the summary description of the table
1728              *
1729              * @param a the summary description of the table
1730              */
1731             public void setAccessibleSummary(Accessible a) {
1732             }
1733 
1734             /**
1735              * Returns the number of rows in the table.
1736              *
1737              * @return the number of rows in the table
1738              */
1739             public int getAccessibleRowCount() {
1740                 return ((TableElementInfo)elementInfo).getRowCount();
1741             }
1742 
1743             /**
1744              * Returns the number of columns in the table.
1745              *
1746              * @return the number of columns in the table
1747              */
1748             public int getAccessibleColumnCount() {
1749                 return ((TableElementInfo)elementInfo).getColumnCount();
1750             }
1751 
1752             /**
1753              * Returns the Accessible at a specified row and column
1754              * in the table.
1755              *
1756              * @param r zero-based row of the table
1757              * @param c zero-based column of the table
1758              * @return the Accessible at the specified row and column
1759              */
1760             public Accessible getAccessibleAt(int r, int c) {
1761                 TableCellElementInfo cellInfo = getCell(r, c);
1762                 if (cellInfo != null) {
1763                     return cellInfo.getAccessible();
1764                 } else {
1765                     return null;
1766                 }
1767             }
1768 
1769             /**
1770              * Returns the number of rows occupied by the Accessible at
1771              * a specified row and column in the table.
1772              *
1773              * @return the number of rows occupied by the Accessible at a
1774              * given specified (row, column)
1775              */
1776             public int getAccessibleRowExtentAt(int r, int c) {
1777                 return ((TableElementInfo)elementInfo).getRowExtentAt(r, c);
1778             }
1779 
1780             /**
1781              * Returns the number of columns occupied by the Accessible at
1782              * a specified row and column in the table.
1783              *
1784              * @return the number of columns occupied by the Accessible at a
1785              * given specified row and column
1786              */
1787             public int getAccessibleColumnExtentAt(int r, int c) {
1788                 return ((TableElementInfo)elementInfo).getColumnExtentAt(r, c);
1789             }
1790 
1791             /**
1792              * Returns the row headers as an AccessibleTable.
1793              *
1794              * @return an AccessibleTable representing the row
1795              * headers
1796              */
1797             public AccessibleTable getAccessibleRowHeader() {
1798                 return rowHeadersTable;
1799             }
1800 
1801             /**
1802              * Sets the row headers.
1803              *
1804              * @param table an AccessibleTable representing the
1805              * row headers
1806              */
1807             public void setAccessibleRowHeader(AccessibleTable table) {
1808             }
1809 
1810             /**
1811              * Returns the column headers as an AccessibleTable.
1812              *
1813              * @return an AccessibleTable representing the column
1814              * headers
1815              */
1816             public AccessibleTable getAccessibleColumnHeader() {
1817                 return null;
1818             }
1819 
1820             /**
1821              * Sets the column headers.
1822              *
1823              * @param table an AccessibleTable representing the
1824              * column headers
1825              */
1826             public void setAccessibleColumnHeader(AccessibleTable table) {
1827             }
1828 
1829             /**
1830              * Returns the description of the specified row in the table.
1831              *
1832              * @param r zero-based row of the table
1833              * @return the description of the row
1834              */
1835             public Accessible getAccessibleRowDescription(int r) {
1836                 return null;
1837             }
1838 
1839             /**
1840              * Sets the description text of the specified row of the table.
1841              *
1842              * @param r zero-based row of the table
1843              * @param a the description of the row
1844              */
1845             public void setAccessibleRowDescription(int r, Accessible a) {
1846             }
1847 
1848             /**
1849              * Returns the description text of the specified column in the table.
1850              *
1851              * @param c zero-based column of the table
1852              * @return the text description of the column
1853              */
1854             public Accessible getAccessibleColumnDescription(int c) {
1855                 return null;
1856             }
1857 
1858             /**
1859              * Sets the description text of the specified column in the table.
1860              *
1861              * @param c zero-based column of the table
1862              * @param a the text description of the column
1863              */
1864             public void setAccessibleColumnDescription(int c, Accessible a) {
1865             }
1866 
1867             /**
1868              * Returns a boolean value indicating whether the accessible at
1869              * a specified row and column is selected.
1870              *
1871              * @param r zero-based row of the table
1872              * @param c zero-based column of the table
1873              * @return the boolean value true if the accessible at the
1874              * row and column is selected. Otherwise, the boolean value
1875              * false
1876              */
1877             public boolean isAccessibleSelected(int r, int c) {
1878                 if (validateIfNecessary()) {
1879                     if (r < 0 || r >= getAccessibleRowCount() ||
1880                         c < 0 || c >= getAccessibleColumnCount()) {
1881                         return false;
1882                     }
1883                     TableCellElementInfo cell = getCell(r, c);
1884                     if (cell != null) {
1885                         Element elem = cell.getElement();
1886                         int start = elem.getStartOffset();
1887                         int end = elem.getEndOffset();
1888                         return start >= editor.getSelectionStart() &&
1889                             end <= editor.getSelectionEnd();
1890                     }
1891                 }
1892                 return false;
1893             }
1894 
1895             /**
1896              * Returns a boolean value indicating whether the specified row
1897              * is selected.
1898              *
1899              * @param r zero-based row of the table
1900              * @return the boolean value true if the specified row is selected.
1901              * Otherwise, false.
1902              */
1903             public boolean isAccessibleRowSelected(int r) {
1904                 if (validateIfNecessary()) {
1905                     if (r < 0 || r >= getAccessibleRowCount()) {
1906                         return false;
1907                     }
1908                     int nColumns = getAccessibleColumnCount();
1909 
1910                     TableCellElementInfo startCell = getCell(r, 0);
1911                     if (startCell == null) {
1912                         return false;
1913                     }
1914                     int start = startCell.getElement().getStartOffset();
1915 
1916                     TableCellElementInfo endCell = getCell(r, nColumns-1);
1917                     if (endCell == null) {
1918                         return false;
1919                     }
1920                     int end = endCell.getElement().getEndOffset();
1921 
1922                     return start >= editor.getSelectionStart() &&
1923                         end <= editor.getSelectionEnd();
1924                 }
1925                 return false;
1926             }
1927 
1928             /**
1929              * Returns a boolean value indicating whether the specified column
1930              * is selected.
1931              *
1932              * @param c zero-based column of the table
1933              * @return the boolean value true if the specified column is selected.
1934              * Otherwise, false.
1935              */
1936             public boolean isAccessibleColumnSelected(int c) {
1937                 if (validateIfNecessary()) {
1938                     if (c < 0 || c >= getAccessibleColumnCount()) {
1939                         return false;
1940                     }
1941                     int nRows = getAccessibleRowCount();
1942 
1943                     TableCellElementInfo startCell = getCell(0, c);
1944                     if (startCell == null) {
1945                         return false;
1946                     }
1947                     int start = startCell.getElement().getStartOffset();
1948 
1949                     TableCellElementInfo endCell = getCell(nRows-1, c);
1950                     if (endCell == null) {
1951                         return false;
1952                     }
1953                     int end = endCell.getElement().getEndOffset();
1954                     return start >= editor.getSelectionStart() &&
1955                         end <= editor.getSelectionEnd();
1956                 }
1957                 return false;
1958             }
1959 
1960             /**
1961              * Returns the selected rows in a table.
1962              *
1963              * @return an array of selected rows where each element is a
1964              * zero-based row of the table
1965              */
1966             public int [] getSelectedAccessibleRows() {
1967                 if (validateIfNecessary()) {
1968                     int nRows = getAccessibleRowCount();
1969                     Vector<Integer> vec = new Vector<Integer>();
1970 
1971                     for (int i = 0; i < nRows; i++) {
1972                         if (isAccessibleRowSelected(i)) {
1973                             vec.addElement(Integer.valueOf(i));
1974                         }
1975                     }
1976                     int retval[] = new int[vec.size()];
1977                     for (int i = 0; i < retval.length; i++) {
1978                         retval[i] = vec.elementAt(i).intValue();
1979                     }
1980                     return retval;
1981                 }
1982                 return new int[0];
1983             }
1984 
1985             /**
1986              * Returns the selected columns in a table.
1987              *
1988              * @return an array of selected columns where each element is a
1989              * zero-based column of the table
1990              */
1991             public int [] getSelectedAccessibleColumns() {
1992                 if (validateIfNecessary()) {
1993                     int nColumns = getAccessibleRowCount();
1994                     Vector<Integer> vec = new Vector<Integer>();
1995 
1996                     for (int i = 0; i < nColumns; i++) {
1997                         if (isAccessibleColumnSelected(i)) {
1998                             vec.addElement(Integer.valueOf(i));
1999                         }
2000                     }
2001                     int retval[] = new int[vec.size()];
2002                     for (int i = 0; i < retval.length; i++) {
2003                         retval[i] = vec.elementAt(i).intValue();
2004                     }
2005                     return retval;
2006                 }
2007                 return new int[0];
2008             }
2009 
2010             // begin AccessibleExtendedTable implementation -------------
2011 
2012             /**
2013              * Returns the row number of an index in the table.
2014              *
2015              * @param index the zero-based index in the table
2016              * @return the zero-based row of the table if one exists;
2017              * otherwise -1.
2018              */
2019             public int getAccessibleRow(int index) {
2020                 if (validateIfNecessary()) {
2021                     int numCells = getAccessibleColumnCount() *
2022                         getAccessibleRowCount();
2023                     if (index >= numCells) {
2024                         return -1;
2025                     } else {
2026                         return index / getAccessibleColumnCount();
2027                     }
2028                 }
2029                 return -1;
2030             }
2031 
2032             /**
2033              * Returns the column number of an index in the table.
2034              *
2035              * @param index the zero-based index in the table
2036              * @return the zero-based column of the table if one exists;
2037              * otherwise -1.
2038              */
2039             public int getAccessibleColumn(int index) {
2040                 if (validateIfNecessary()) {
2041                     int numCells = getAccessibleColumnCount() *
2042                         getAccessibleRowCount();
2043                     if (index >= numCells) {
2044                         return -1;
2045                     } else {
2046                         return index % getAccessibleColumnCount();
2047                     }
2048                 }
2049                 return -1;
2050             }
2051 
2052             /**
2053              * Returns the index at a row and column in the table.
2054              *
2055              * @param r zero-based row of the table
2056              * @param c zero-based column of the table
2057              * @return the zero-based index in the table if one exists;
2058              * otherwise -1.
2059              */
2060             public int getAccessibleIndex(int r, int c) {
2061                 if (validateIfNecessary()) {
2062                     if (r >= getAccessibleRowCount() ||
2063                         c >= getAccessibleColumnCount()) {
2064                         return -1;
2065                     } else {
2066                         return r * getAccessibleColumnCount() + c;
2067                     }
2068                 }
2069                 return -1;
2070             }
2071 
2072             /**
2073              * Returns the row header at a row in a table.
2074              * @param r zero-based row of the table
2075              *
2076              * @return a String representing the row header
2077              * if one exists; otherwise null.
2078              */
2079             public String getAccessibleRowHeader(int r) {
2080                 if (validateIfNecessary()) {
2081                     TableCellElementInfo cellInfo = getCell(r, 0);
2082                     if (cellInfo.isHeaderCell()) {
2083                         View v = cellInfo.getView();
2084                         if (v != null && model != null) {
2085                             try {
2086                                 return model.getText(v.getStartOffset(),
2087                                                      v.getEndOffset() -
2088                                                      v.getStartOffset());
2089                             } catch (BadLocationException e) {
2090                                 return null;
2091                             }
2092                         }
2093                     }
2094                 }
2095                 return null;
2096             }
2097 
2098             /**
2099              * Returns the column header at a column in a table.
2100              * @param c zero-based column of the table
2101              *
2102              * @return a String representing the column header
2103              * if one exists; otherwise null.
2104              */
2105             public String getAccessibleColumnHeader(int c) {
2106                 if (validateIfNecessary()) {
2107                     TableCellElementInfo cellInfo = getCell(0, c);
2108                     if (cellInfo.isHeaderCell()) {
2109                         View v = cellInfo.getView();
2110                         if (v != null && model != null) {
2111                             try {
2112                                 return model.getText(v.getStartOffset(),
2113                                                      v.getEndOffset() -
2114                                                      v.getStartOffset());
2115                             } catch (BadLocationException e) {
2116                                 return null;
2117                             }
2118                         }
2119                     }
2120                 }
2121                 return null;
2122             }
2123 
2124             public void addRowHeader(TableCellElementInfo cellInfo, int rowNumber) {
2125                 if (rowHeadersTable == null) {
2126                     rowHeadersTable = new AccessibleHeadersTable();
2127                 }
2128                 rowHeadersTable.addHeader(cellInfo, rowNumber);
2129             }
2130             // end of AccessibleExtendedTable implementation ------------
2131 
2132             protected class AccessibleHeadersTable implements AccessibleTable {
2133 
2134                 // Header information is modeled as a Hashtable of
2135                 // ArrayLists where each Hashtable entry represents
2136                 // a row containing one or more headers.
2137                 private Hashtable<Integer, ArrayList<TableCellElementInfo>> headers =
2138                         new Hashtable<Integer, ArrayList<TableCellElementInfo>>();
2139                 private int rowCount = 0;
2140                 private int columnCount = 0;
2141 
2142                 public void addHeader(TableCellElementInfo cellInfo, int rowNumber) {
2143                     Integer rowInteger = Integer.valueOf(rowNumber);
2144                     ArrayList<TableCellElementInfo> list = headers.get(rowInteger);
2145                     if (list == null) {
2146                         list = new ArrayList<TableCellElementInfo>();
2147                         headers.put(rowInteger, list);
2148                     }
2149                     list.add(cellInfo);
2150                 }
2151 
2152                 /**
2153                  * Returns the caption for the table.
2154                  *
2155                  * @return the caption for the table
2156                  */
2157                 public Accessible getAccessibleCaption() {
2158                     return null;
2159                 }
2160 
2161                 /**
2162                  * Sets the caption for the table.
2163                  *
2164                  * @param a the caption for the table
2165                  */
2166                 public void setAccessibleCaption(Accessible a) {
2167                 }
2168 
2169                 /**
2170                  * Returns the summary description of the table.
2171                  *
2172                  * @return the summary description of the table
2173                  */
2174                 public Accessible getAccessibleSummary() {
2175                     return null;
2176                 }
2177 
2178                 /**
2179                  * Sets the summary description of the table
2180                  *
2181                  * @param a the summary description of the table
2182                  */
2183                 public void setAccessibleSummary(Accessible a) {
2184                 }
2185 
2186                 /**
2187                  * Returns the number of rows in the table.
2188                  *
2189                  * @return the number of rows in the table
2190                  */
2191                 public int getAccessibleRowCount() {
2192                     return rowCount;
2193                 }
2194 
2195                 /**
2196                  * Returns the number of columns in the table.
2197                  *
2198                  * @return the number of columns in the table
2199                  */
2200                 public int getAccessibleColumnCount() {
2201                     return columnCount;
2202                 }
2203 
2204                 private TableCellElementInfo getElementInfoAt(int r, int c) {
2205                     ArrayList<TableCellElementInfo> list = headers.get(Integer.valueOf(r));
2206                     if (list != null) {
2207                         return list.get(c);
2208                     } else {
2209                         return null;
2210                     }
2211                 }
2212 
2213                 /**
2214                  * Returns the Accessible at a specified row and column
2215                  * in the table.
2216                  *
2217                  * @param r zero-based row of the table
2218                  * @param c zero-based column of the table
2219                  * @return the Accessible at the specified row and column
2220                  */
2221                 public Accessible getAccessibleAt(int r, int c) {
2222                     ElementInfo elementInfo = getElementInfoAt(r, c);
2223                     if (elementInfo instanceof Accessible) {
2224                         return (Accessible)elementInfo;
2225                     } else {
2226                         return null;
2227                     }
2228                 }
2229 
2230                 /**
2231                  * Returns the number of rows occupied by the Accessible at
2232                  * a specified row and column in the table.
2233                  *
2234                  * @return the number of rows occupied by the Accessible at a
2235                  * given specified (row, column)
2236                  */
2237                 public int getAccessibleRowExtentAt(int r, int c) {
2238                     TableCellElementInfo elementInfo = getElementInfoAt(r, c);
2239                     if (elementInfo != null) {
2240                         return elementInfo.getRowCount();
2241                     } else {
2242                         return 0;
2243                     }
2244                 }
2245 
2246                 /**
2247                  * Returns the number of columns occupied by the Accessible at
2248                  * a specified row and column in the table.
2249                  *
2250                  * @return the number of columns occupied by the Accessible at a
2251                  * given specified row and column
2252                  */
2253                 public int getAccessibleColumnExtentAt(int r, int c) {
2254                     TableCellElementInfo elementInfo = getElementInfoAt(r, c);
2255                     if (elementInfo != null) {
2256                         return elementInfo.getRowCount();
2257                     } else {
2258                         return 0;
2259                     }
2260                 }
2261 
2262                 /**
2263                  * Returns the row headers as an AccessibleTable.
2264                  *
2265                  * @return an AccessibleTable representing the row
2266                  * headers
2267                  */
2268                 public AccessibleTable getAccessibleRowHeader() {
2269                     return null;
2270                 }
2271 
2272                 /**
2273                  * Sets the row headers.
2274                  *
2275                  * @param table an AccessibleTable representing the
2276                  * row headers
2277                  */
2278                 public void setAccessibleRowHeader(AccessibleTable table) {
2279                 }
2280 
2281                 /**
2282                  * Returns the column headers as an AccessibleTable.
2283                  *
2284                  * @return an AccessibleTable representing the column
2285                  * headers
2286                  */
2287                 public AccessibleTable getAccessibleColumnHeader() {
2288                     return null;
2289                 }
2290 
2291                 /**
2292                  * Sets the column headers.
2293                  *
2294                  * @param table an AccessibleTable representing the
2295                  * column headers
2296                  */
2297                 public void setAccessibleColumnHeader(AccessibleTable table) {
2298                 }
2299 
2300                 /**
2301                  * Returns the description of the specified row in the table.
2302                  *
2303                  * @param r zero-based row of the table
2304                  * @return the description of the row
2305                  */
2306                 public Accessible getAccessibleRowDescription(int r) {
2307                     return null;
2308                 }
2309 
2310                 /**
2311                  * Sets the description text of the specified row of the table.
2312                  *
2313                  * @param r zero-based row of the table
2314                  * @param a the description of the row
2315                  */
2316                 public void setAccessibleRowDescription(int r, Accessible a) {
2317                 }
2318 
2319                 /**
2320                  * Returns the description text of the specified column in the table.
2321                  *
2322                  * @param c zero-based column of the table
2323                  * @return the text description of the column
2324                  */
2325                 public Accessible getAccessibleColumnDescription(int c) {
2326                     return null;
2327                 }
2328 
2329                 /**
2330                  * Sets the description text of the specified column in the table.
2331                  *
2332                  * @param c zero-based column of the table
2333                  * @param a the text description of the column
2334                  */
2335                 public void setAccessibleColumnDescription(int c, Accessible a) {
2336                 }
2337 
2338                 /**
2339                  * Returns a boolean value indicating whether the accessible at
2340                  * a specified row and column is selected.
2341                  *
2342                  * @param r zero-based row of the table
2343                  * @param c zero-based column of the table
2344                  * @return the boolean value true if the accessible at the
2345                  * row and column is selected. Otherwise, the boolean value
2346                  * false
2347                  */
2348                 public boolean isAccessibleSelected(int r, int c) {
2349                     return false;
2350                 }
2351 
2352                 /**
2353                  * Returns a boolean value indicating whether the specified row
2354                  * is selected.
2355                  *
2356                  * @param r zero-based row of the table
2357                  * @return the boolean value true if the specified row is selected.
2358                  * Otherwise, false.
2359                  */
2360                 public boolean isAccessibleRowSelected(int r) {
2361                     return false;
2362                 }
2363 
2364                 /**
2365                  * Returns a boolean value indicating whether the specified column
2366                  * is selected.
2367                  *
2368                  * @param c zero-based column of the table
2369                  * @return the boolean value true if the specified column is selected.
2370                  * Otherwise, false.
2371                  */
2372                 public boolean isAccessibleColumnSelected(int c) {
2373                     return false;
2374                 }
2375 
2376                 /**
2377                  * Returns the selected rows in a table.
2378                  *
2379                  * @return an array of selected rows where each element is a
2380                  * zero-based row of the table
2381                  */
2382                 public int [] getSelectedAccessibleRows() {
2383                     return new int [0];
2384                 }
2385 
2386                 /**
2387                  * Returns the selected columns in a table.
2388                  *
2389                  * @return an array of selected columns where each element is a
2390                  * zero-based column of the table
2391                  */
2392                 public int [] getSelectedAccessibleColumns() {
2393                     return new int [0];
2394                 }
2395             }
2396         } // ... end AccessibleHeadersTable
2397 
2398         /*
2399          * ElementInfo for table rows
2400          */
2401         private class TableRowElementInfo extends ElementInfo {
2402 
2403             private TableElementInfo parent;
2404             private int rowNumber;
2405 
2406             TableRowElementInfo(Element e, TableElementInfo parent, int rowNumber) {
2407                 super(e, parent);
2408                 this.parent = parent;
2409                 this.rowNumber = rowNumber;
2410             }
2411 
2412             protected void loadChildren(Element e) {
2413                 for (int x = 0; x < e.getElementCount(); x++) {
2414                     AttributeSet attrs = e.getElement(x).getAttributes();
2415 
2416                     if (attrs.getAttribute(StyleConstants.NameAttribute) ==
2417                             HTML.Tag.TH) {
2418                         TableCellElementInfo headerElementInfo =
2419                             new TableCellElementInfo(e.getElement(x), this, true);
2420                         addChild(headerElementInfo);
2421 
2422                         AccessibleTable at =
2423                             parent.getAccessibleContext().getAccessibleTable();
2424                         TableAccessibleContext tableElement =
2425                             (TableAccessibleContext)at;
2426                         tableElement.addRowHeader(headerElementInfo, rowNumber);
2427 
2428                     } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
2429                             HTML.Tag.TD) {
2430                         addChild(new TableCellElementInfo(e.getElement(x), this,
2431                                                           false));
2432                     }
2433                 }
2434             }
2435 
2436             /**
2437              * Returns the max of the rowspans of the cells in this row.
2438              */
2439             public int getRowCount() {
2440                 int rowCount = 1;
2441                 if (validateIfNecessary()) {
2442                     for (int counter = 0; counter < getChildCount();
2443                          counter++) {
2444 
2445                         TableCellElementInfo cell = (TableCellElementInfo)
2446                                                     getChild(counter);
2447 
2448                         if (cell.validateIfNecessary()) {
2449                             rowCount = Math.max(rowCount, cell.getRowCount());
2450                         }
2451                     }
2452                 }
2453                 return rowCount;
2454             }
2455 
2456             /**
2457              * Returns the sum of the column spans of the individual
2458              * cells in this row.
2459              */
2460             public int getColumnCount() {
2461                 int colCount = 0;
2462                 if (validateIfNecessary()) {
2463                     for (int counter = 0; counter < getChildCount();
2464                          counter++) {
2465                         TableCellElementInfo cell = (TableCellElementInfo)
2466                                                     getChild(counter);
2467 
2468                         if (cell.validateIfNecessary()) {
2469                             colCount += cell.getColumnCount();
2470                         }
2471                     }
2472                 }
2473                 return colCount;
2474             }
2475 
2476             /**
2477              * Overriden to invalidate the table as well as
2478              * TableRowElementInfo.
2479              */
2480             protected void invalidate(boolean first) {
2481                 super.invalidate(first);
2482                 getParent().invalidate(true);
2483             }
2484 
2485             /**
2486              * Places the TableCellElementInfos for this element in
2487              * the grid.
2488              */
2489             private void updateGrid(int row) {
2490                 if (validateIfNecessary()) {
2491                     boolean emptyRow = false;
2492 
2493                     while (!emptyRow) {
2494                         for (int counter = 0; counter < grid[row].length;
2495                                  counter++) {
2496                             if (grid[row][counter] == null) {
2497                                 emptyRow = true;
2498                                 break;
2499                             }
2500                         }
2501                         if (!emptyRow) {
2502                             row++;
2503                         }
2504                     }
2505                     for (int col = 0, counter = 0; counter < getChildCount();
2506                              counter++) {
2507                         TableCellElementInfo cell = (TableCellElementInfo)
2508                                                     getChild(counter);
2509 
2510                         while (grid[row][col] != null) {
2511                             col++;
2512                         }
2513                         for (int rowCount = cell.getRowCount() - 1;
2514                              rowCount >= 0; rowCount--) {
2515                             for (int colCount = cell.getColumnCount() - 1;
2516                                  colCount >= 0; colCount--) {
2517                                 grid[row + rowCount][col + colCount] = cell;
2518                             }
2519                         }
2520                         col += cell.getColumnCount();
2521                     }
2522                 }
2523             }
2524 
2525             /**
2526              * Returns the column count of the number of columns that have
2527              * a rowcount >= rowspan.
2528              */
2529             private int getColumnCount(int rowspan) {
2530                 if (validateIfNecessary()) {
2531                     int cols = 0;
2532                     for (int counter = 0; counter < getChildCount();
2533                          counter++) {
2534                         TableCellElementInfo cell = (TableCellElementInfo)
2535                                                     getChild(counter);
2536 
2537                         if (cell.getRowCount() >= rowspan) {
2538                             cols += cell.getColumnCount();
2539                         }
2540                     }
2541                     return cols;
2542                 }
2543                 return 0;
2544             }
2545         }
2546 
2547         /**
2548          * TableCellElementInfo is used to represents the cells of
2549          * the table.
2550          */
2551         private class TableCellElementInfo extends ElementInfo {
2552 
2553             private Accessible accessible;
2554             private boolean isHeaderCell;
2555 
2556             TableCellElementInfo(Element e, ElementInfo parent) {
2557                 super(e, parent);
2558                 this.isHeaderCell = false;
2559             }
2560 
2561             TableCellElementInfo(Element e, ElementInfo parent,
2562                                  boolean isHeaderCell) {
2563                 super(e, parent);
2564                 this.isHeaderCell = isHeaderCell;
2565             }
2566 
2567             /*
2568              * Returns whether this table cell is a header
2569              */
2570             public boolean isHeaderCell() {
2571                 return this.isHeaderCell;
2572             }
2573 
2574             /*
2575              * Returns the Accessible representing this table cell
2576              */
2577             public Accessible getAccessible() {
2578                 accessible = null;
2579                 getAccessible(this);
2580                 return accessible;
2581             }
2582 
2583             /*
2584              * Gets the outermost Accessible in the table cell
2585              */
2586             private void getAccessible(ElementInfo elementInfo) {
2587                 if (elementInfo instanceof Accessible) {
2588                     accessible = (Accessible)elementInfo;
2589                 } else {
2590                     for (int i = 0; i < elementInfo.getChildCount(); i++) {
2591                         getAccessible(elementInfo.getChild(i));
2592                     }
2593                 }
2594             }
2595 
2596             /**
2597              * Returns the rowspan attribute.
2598              */
2599             public int getRowCount() {
2600                 if (validateIfNecessary()) {
2601                     return Math.max(1, getIntAttr(getAttributes(),
2602                                                   HTML.Attribute.ROWSPAN, 1));
2603                 }
2604                 return 0;
2605             }
2606 
2607             /**
2608              * Returns the colspan attribute.
2609              */
2610             public int getColumnCount() {
2611                 if (validateIfNecessary()) {
2612                     return Math.max(1, getIntAttr(getAttributes(),
2613                                                   HTML.Attribute.COLSPAN, 1));
2614                 }
2615                 return 0;
2616             }
2617 
2618             /**
2619              * Overriden to invalidate the TableRowElementInfo as well as
2620              * the TableCellElementInfo.
2621              */
2622             protected void invalidate(boolean first) {
2623                 super.invalidate(first);
2624                 getParent().invalidate(true);
2625             }
2626         }
2627     }
2628 
2629 
2630     /**
2631      * ElementInfo provides a slim down view of an Element.  Each ElementInfo
2632      * can have any number of child ElementInfos that are not necessarily
2633      * direct children of the Element. As the Document changes various
2634      * ElementInfos become invalidated. Before accessing a particular portion
2635      * of an ElementInfo you should make sure it is valid by invoking
2636      * <code>validateIfNecessary</code>, this will return true if
2637      * successful, on the other hand a false return value indicates the
2638      * ElementInfo is not valid and can never become valid again (usually
2639      * the result of the Element the ElementInfo encapsulates being removed).
2640      */
2641     private class ElementInfo {
2642 
2643         /**
2644          * The children of this ElementInfo.
2645          */
2646         private ArrayList<ElementInfo> children;
2647         /**
2648          * The Element this ElementInfo is providing information for.
2649          */
2650         private Element element;
2651         /**
2652          * The parent ElementInfo, will be null for the root.
2653          */
2654         private ElementInfo parent;
2655         /**
2656          * Indicates the validity of the ElementInfo.
2657          */
2658         private boolean isValid;
2659         /**
2660          * Indicates if the ElementInfo can become valid.
2661          */
2662         private boolean canBeValid;
2663 
2664 
2665         /**
2666          * Creates the root ElementInfo.
2667          */
2668         ElementInfo(Element element) {
2669             this(element, null);
2670         }
2671 
2672         /**
2673          * Creates an ElementInfo representing <code>element</code> with
2674          * the specified parent.
2675          */
2676         ElementInfo(Element element, ElementInfo parent) {
2677             this.element = element;
2678             this.parent = parent;
2679             isValid = false;
2680             canBeValid = true;
2681         }
2682 
2683         /**
2684          * Validates the receiver. This recreates the children as well. This
2685          * will be invoked within a <code>readLock</code>. If this is overriden
2686          * it MUST invoke supers implementation first!
2687          */
2688         protected void validate() {
2689             isValid = true;
2690             loadChildren(getElement());
2691         }
2692 
2693         /**
2694          * Recreates the direct children of <code>info</code>.
2695          */
2696         protected void loadChildren(Element parent) {
2697             if (!parent.isLeaf()) {
2698                 for (int counter = 0, maxCounter = parent.getElementCount();
2699                     counter < maxCounter; counter++) {
2700                     Element e = parent.getElement(counter);
2701                     ElementInfo childInfo = createElementInfo(e, this);
2702 
2703                     if (childInfo != null) {
2704                         addChild(childInfo);
2705                     }
2706                     else {
2707                         loadChildren(e);
2708                     }
2709                 }
2710             }
2711         }
2712 
2713         /**
2714          * Returns the index of the child in the parent, or -1 for the
2715          * root or if the parent isn't valid.
2716          */
2717         public int getIndexInParent() {
2718             if (parent == null || !parent.isValid()) {
2719                 return -1;
2720             }
2721             return parent.indexOf(this);
2722         }
2723 
2724         /**
2725          * Returns the Element this <code>ElementInfo</code> represents.
2726          */
2727         public Element getElement() {
2728             return element;
2729         }
2730 
2731         /**
2732          * Returns the parent of this Element, or null for the root.
2733          */
2734         public ElementInfo getParent() {
2735             return parent;
2736         }
2737 
2738         /**
2739          * Returns the index of the specified child, or -1 if
2740          * <code>child</code> isn't a valid child.
2741          */
2742         public int indexOf(ElementInfo child) {
2743             ArrayList<ElementInfo> children = this.children;
2744 
2745             if (children != null) {
2746                 return children.indexOf(child);
2747             }
2748             return -1;
2749         }
2750 
2751         /**
2752          * Returns the child ElementInfo at <code>index</code>, or null
2753          * if <code>index</code> isn't a valid index.
2754          */
2755         public ElementInfo getChild(int index) {
2756             if (validateIfNecessary()) {
2757                 ArrayList<ElementInfo> children = this.children;
2758 
2759                 if (children != null && index >= 0 &&
2760                                         index < children.size()) {
2761                     return children.get(index);
2762                 }
2763             }
2764             return null;
2765         }
2766 
2767         /**
2768          * Returns the number of children the ElementInfo contains.
2769          */
2770         public int getChildCount() {
2771             validateIfNecessary();
2772             return (children == null) ? 0 : children.size();
2773         }
2774 
2775         /**
2776          * Adds a new child to this ElementInfo.
2777          */
2778         protected void addChild(ElementInfo child) {
2779             if (children == null) {
2780                 children = new ArrayList<ElementInfo>();
2781             }
2782             children.add(child);
2783         }
2784 
2785         /**
2786          * Returns the View corresponding to this ElementInfo, or null
2787          * if the ElementInfo can't be validated.
2788          */
2789         protected View getView() {
2790             if (!validateIfNecessary()) {
2791                 return null;
2792             }
2793             Object lock = lock();
2794             try {
2795                 View rootView = getRootView();
2796                 Element e = getElement();
2797                 int start = e.getStartOffset();
2798 
2799                 if (rootView != null) {
2800                     return getView(rootView, e, start);
2801                 }
2802                 return null;
2803             } finally {
2804                 unlock(lock);
2805             }
2806         }
2807 
2808         /**
2809          * Returns the Bounds for this ElementInfo, or null
2810          * if the ElementInfo can't be validated.
2811          */
2812         public Rectangle getBounds() {
2813             if (!validateIfNecessary()) {
2814                 return null;
2815             }
2816             Object lock = lock();
2817             try {
2818                 Rectangle bounds = getRootEditorRect();
2819                 View rootView = getRootView();
2820                 Element e = getElement();
2821 
2822                 if (bounds != null && rootView != null) {
2823                     try {
2824                         return rootView.modelToView(e.getStartOffset(),
2825                                                     Position.Bias.Forward,
2826                                                     e.getEndOffset(),
2827                                                     Position.Bias.Backward,
2828                                                     bounds).getBounds();
2829                     } catch (BadLocationException ble) { }
2830                 }
2831             } finally {
2832                 unlock(lock);
2833             }
2834             return null;
2835         }
2836 
2837         /**
2838          * Returns true if this ElementInfo is valid.
2839          */
2840         protected boolean isValid() {
2841             return isValid;
2842         }
2843 
2844         /**
2845          * Returns the AttributeSet associated with the Element, this will
2846          * return null if the ElementInfo can't be validated.
2847          */
2848         protected AttributeSet getAttributes() {
2849             if (validateIfNecessary()) {
2850                 return getElement().getAttributes();
2851             }
2852             return null;
2853         }
2854 
2855         /**
2856          * Returns the AttributeSet associated with the View that is
2857          * representing this Element, this will
2858          * return null if the ElementInfo can't be validated.
2859          */
2860         protected AttributeSet getViewAttributes() {
2861             if (validateIfNecessary()) {
2862                 View view = getView();
2863 
2864                 if (view != null) {
2865                     return view.getElement().getAttributes();
2866                 }
2867                 return getElement().getAttributes();
2868             }
2869             return null;
2870         }
2871 
2872         /**
2873          * Convenience method for getting an integer attribute from the passed
2874          * in AttributeSet.
2875          */
2876         protected int getIntAttr(AttributeSet attrs, Object key, int deflt) {
2877             if (attrs != null && attrs.isDefined(key)) {
2878                 int i;
2879                 String val = (String)attrs.getAttribute(key);
2880                 if (val == null) {
2881                     i = deflt;
2882                 }
2883                 else {
2884                     try {
2885                         i = Math.max(0, Integer.parseInt(val));
2886                     } catch (NumberFormatException x) {
2887                         i = deflt;
2888                     }
2889                 }
2890                 return i;
2891             }
2892             return deflt;
2893         }
2894 
2895         /**
2896          * Validates the ElementInfo if necessary.  Some ElementInfos may
2897          * never be valid again.  You should check <code>isValid</code> before
2898          * using one.  This will reload the children and invoke
2899          * <code>validate</code> if the ElementInfo is invalid and can become
2900          * valid again. This will return true if the receiver is valid.
2901          */
2902         protected boolean validateIfNecessary() {
2903             if (!isValid() && canBeValid) {
2904                 children = null;
2905                 Object lock = lock();
2906 
2907                 try {
2908                     validate();
2909                 } finally {
2910                     unlock(lock);
2911                 }
2912             }
2913             return isValid();
2914         }
2915 
2916         /**
2917          * Invalidates the ElementInfo. Subclasses should override this
2918          * if they need to reset state once invalid.
2919          */
2920         protected void invalidate(boolean first) {
2921             if (!isValid()) {
2922                 if (canBeValid && !first) {
2923                     canBeValid = false;
2924                 }
2925                 return;
2926             }
2927             isValid = false;
2928             canBeValid = first;
2929             if (children != null) {
2930                 for (ElementInfo child : children) {
2931                     child.invalidate(false);
2932                 }
2933                 children = null;
2934             }
2935         }
2936 
2937         private View getView(View parent, Element e, int start) {
2938             if (parent.getElement() == e) {
2939                 return parent;
2940             }
2941             int index = parent.getViewIndex(start, Position.Bias.Forward);
2942 
2943             if (index != -1 && index < parent.getViewCount()) {
2944                 return getView(parent.getView(index), e, start);
2945             }
2946             return null;
2947         }
2948 
2949         private int getClosestInfoIndex(int index) {
2950             for (int counter = 0; counter < getChildCount(); counter++) {
2951                 ElementInfo info = getChild(counter);
2952 
2953                 if (index < info.getElement().getEndOffset() ||
2954                     index == info.getElement().getStartOffset()) {
2955                     return counter;
2956                 }
2957             }
2958             return -1;
2959         }
2960 
2961         private void update(DocumentEvent e) {
2962             if (!isValid()) {
2963                 return;
2964             }
2965             ElementInfo parent = getParent();
2966             Element element = getElement();
2967 
2968             do {
2969                 DocumentEvent.ElementChange ec = e.getChange(element);
2970                 if (ec != null) {
2971                     if (element == getElement()) {
2972                         // One of our children changed.
2973                         invalidate(true);
2974                     }
2975                     else if (parent != null) {
2976                         parent.invalidate(parent == getRootInfo());
2977                     }
2978                     return;
2979                 }
2980                 element = element.getParentElement();
2981             } while (parent != null && element != null &&
2982                      element != parent.getElement());
2983 
2984             if (getChildCount() > 0) {
2985                 Element elem = getElement();
2986                 int pos = e.getOffset();
2987                 int index0 = getClosestInfoIndex(pos);
2988                 if (index0 == -1 &&
2989                     e.getType() == DocumentEvent.EventType.REMOVE &&
2990                     pos >= elem.getEndOffset()) {
2991                     // Event beyond our offsets. We may have represented this,
2992                     // that is the remove may have removed one of our child
2993                     // Elements that represented this, so, we should foward
2994                     // to last element.
2995                     index0 = getChildCount() - 1;
2996                 }
2997                 ElementInfo info = (index0 >= 0) ? getChild(index0) : null;
2998                 if (info != null &&
2999                     (info.getElement().getStartOffset() == pos) && (pos > 0)) {
3000                     // If at a boundary, forward the event to the previous
3001                     // ElementInfo too.
3002                     index0 = Math.max(index0 - 1, 0);
3003                 }
3004                 int index1;
3005                 if (e.getType() != DocumentEvent.EventType.REMOVE) {
3006                     index1 = getClosestInfoIndex(pos + e.getLength());
3007                     if (index1 < 0) {
3008                         index1 = getChildCount() - 1;
3009                     }
3010                 }
3011                 else {
3012                     index1 = index0;
3013                     // A remove may result in empty elements.
3014                     while ((index1 + 1) < getChildCount() &&
3015                            getChild(index1 + 1).getElement().getEndOffset() ==
3016                            getChild(index1 + 1).getElement().getStartOffset()){
3017                         index1++;
3018                     }
3019                 }
3020                 index0 = Math.max(index0, 0);
3021                 // The check for isValid is here as in the process of
3022                 // forwarding update our child may invalidate us.
3023                 for (int i = index0; i <= index1 && isValid(); i++) {
3024                     getChild(i).update(e);
3025                 }
3026             }
3027         }
3028     }
3029 
3030     /**
3031      * DocumentListener installed on the current Document.  Will invoke
3032      * <code>update</code> on the <code>RootInfo</code> in response to
3033      * any event.
3034      */
3035     private class DocumentHandler implements DocumentListener {
3036         public void insertUpdate(DocumentEvent e) {
3037             getRootInfo().update(e);
3038         }
3039         public void removeUpdate(DocumentEvent e) {
3040             getRootInfo().update(e);
3041         }
3042         public void changedUpdate(DocumentEvent e) {
3043             getRootInfo().update(e);
3044         }
3045     }
3046 
3047     /*
3048      * PropertyChangeListener installed on the editor.
3049      */
3050     private class PropertyChangeHandler implements PropertyChangeListener {
3051         public void propertyChange(PropertyChangeEvent evt) {
3052             if (evt.getPropertyName().equals("document")) {
3053                 // handle the document change
3054                 setDocument(editor.getDocument());
3055             }
3056         }
3057     }
3058 }