1 /*
   2  * Copyright (c) 2000, 2014, 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             @SuppressWarnings("deprecation")
 920             public int getIndexAtPoint(Point p) {
 921                 View v = getView();
 922                 if (v != null) {
 923                     return v.viewToModel(p.x, p.y, getBounds());
 924                 } else {
 925                     return -1;
 926                 }
 927             }
 928 
 929             /**
 930              * Determine the bounding box of the character at the given
 931              * index into the string.  The bounds are returned in local
 932              * coordinates.  If the index is invalid an empty rectangle is
 933              * returned.
 934              *
 935              * @param i the index into the String
 936              * @return the screen coordinates of the character's the bounding box,
 937              * if index is invalid returns an empty rectangle.
 938              */
 939             public Rectangle getCharacterBounds(int i) {
 940                 try {
 941                     return editor.getUI().modelToView(editor, i);
 942                 } catch (BadLocationException e) {
 943                     return null;
 944                 }
 945             }
 946 
 947             /**
 948              * Return the number of characters (valid indicies)
 949              *
 950              * @return the number of characters
 951              */
 952             public int getCharCount() {
 953                 if (validateIfNecessary()) {
 954                     Element elem = elementInfo.getElement();
 955                     return elem.getEndOffset() - elem.getStartOffset();
 956                 }
 957                 return 0;
 958             }
 959 
 960             /**
 961              * Return the zero-based offset of the caret.
 962              *
 963              * Note: That to the right of the caret will have the same index
 964              * value as the offset (the caret is between two characters).
 965              * @return the zero-based offset of the caret.
 966              */
 967             public int getCaretPosition() {
 968                 View v = getView();
 969                 if (v == null) {
 970                     return -1;
 971                 }
 972                 Container c = v.getContainer();
 973                 if (c == null) {
 974                     return -1;
 975                 }
 976                 if (c instanceof JTextComponent) {
 977                     return ((JTextComponent)c).getCaretPosition();
 978                 } else {
 979                     return -1;
 980                 }
 981             }
 982 
 983             /**
 984              * IndexedSegment extends Segment adding the offset into the
 985              * the model the <code>Segment</code> was asked for.
 986              */
 987             private class IndexedSegment extends Segment {
 988                 /**
 989                  * Offset into the model that the position represents.
 990                  */
 991                 public int modelOffset;
 992             }
 993 
 994             public String getAtIndex(int part, int index) {
 995                 return getAtIndex(part, index, 0);
 996             }
 997 
 998 
 999             public String getAfterIndex(int part, int index) {
1000                 return getAtIndex(part, index, 1);
1001             }
1002 
1003             public String getBeforeIndex(int part, int index) {
1004                 return getAtIndex(part, index, -1);
1005             }
1006 
1007             /**
1008              * Gets the word, sentence, or character at <code>index</code>.
1009              * If <code>direction</code> is non-null this will find the
1010              * next/previous word/sentence/character.
1011              */
1012             private String getAtIndex(int part, int index, int direction) {
1013                 if (model instanceof AbstractDocument) {
1014                     ((AbstractDocument)model).readLock();
1015                 }
1016                 try {
1017                     if (index < 0 || index >= model.getLength()) {
1018                         return null;
1019                     }
1020                     switch (part) {
1021                     case AccessibleText.CHARACTER:
1022                         if (index + direction < model.getLength() &&
1023                             index + direction >= 0) {
1024                             return model.getText(index + direction, 1);
1025                         }
1026                         break;
1027 
1028 
1029                     case AccessibleText.WORD:
1030                     case AccessibleText.SENTENCE:
1031                         IndexedSegment seg = getSegmentAt(part, index);
1032                         if (seg != null) {
1033                             if (direction != 0) {
1034                                 int next;
1035 
1036 
1037                                 if (direction < 0) {
1038                                     next = seg.modelOffset - 1;
1039                                 }
1040                                 else {
1041                                     next = seg.modelOffset + direction * seg.count;
1042                                 }
1043                                 if (next >= 0 && next <= model.getLength()) {
1044                                     seg = getSegmentAt(part, next);
1045                                 }
1046                                 else {
1047                                     seg = null;
1048                                 }
1049                             }
1050                             if (seg != null) {
1051                                 return new String(seg.array, seg.offset,
1052                                                   seg.count);
1053                             }
1054                         }
1055                         break;
1056 
1057                     default:
1058                         break;
1059                     }
1060                 } catch (BadLocationException e) {
1061                 } finally {
1062                     if (model instanceof AbstractDocument) {
1063                         ((AbstractDocument)model).readUnlock();
1064                     }
1065                 }
1066                 return null;
1067             }
1068 
1069             /*
1070              * Returns the paragraph element for the specified index.
1071              */
1072             private Element getParagraphElement(int index) {
1073                 if (model instanceof PlainDocument ) {
1074                     PlainDocument sdoc = (PlainDocument)model;
1075                     return sdoc.getParagraphElement(index);
1076                 } else if (model instanceof StyledDocument) {
1077                     StyledDocument sdoc = (StyledDocument)model;
1078                     return sdoc.getParagraphElement(index);
1079                 } else {
1080                     Element para;
1081                     for (para = model.getDefaultRootElement(); ! para.isLeaf(); ) {
1082                         int pos = para.getElementIndex(index);
1083                         para = para.getElement(pos);
1084                     }
1085                     if (para == null) {
1086                         return null;
1087                     }
1088                     return para.getParentElement();
1089                 }
1090             }
1091 
1092             /*
1093              * Returns a <code>Segment</code> containing the paragraph text
1094              * at <code>index</code>, or null if <code>index</code> isn't
1095              * valid.
1096              */
1097             private IndexedSegment getParagraphElementText(int index)
1098                 throws BadLocationException {
1099                 Element para = getParagraphElement(index);
1100 
1101 
1102                 if (para != null) {
1103                     IndexedSegment segment = new IndexedSegment();
1104                     try {
1105                         int length = para.getEndOffset() - para.getStartOffset();
1106                         model.getText(para.getStartOffset(), length, segment);
1107                     } catch (BadLocationException e) {
1108                         return null;
1109                     }
1110                     segment.modelOffset = para.getStartOffset();
1111                     return segment;
1112                 }
1113                 return null;
1114             }
1115 
1116 
1117             /**
1118              * Returns the Segment at <code>index</code> representing either
1119              * the paragraph or sentence as identified by <code>part</code>, or
1120              * null if a valid paragraph/sentence can't be found. The offset
1121              * will point to the start of the word/sentence in the array, and
1122              * the modelOffset will point to the location of the word/sentence
1123              * in the model.
1124              */
1125             private IndexedSegment getSegmentAt(int part, int index)
1126                 throws BadLocationException {
1127 
1128                 IndexedSegment seg = getParagraphElementText(index);
1129                 if (seg == null) {
1130                     return null;
1131                 }
1132                 BreakIterator iterator;
1133                 switch (part) {
1134                 case AccessibleText.WORD:
1135                     iterator = BreakIterator.getWordInstance(getLocale());
1136                     break;
1137                 case AccessibleText.SENTENCE:
1138                     iterator = BreakIterator.getSentenceInstance(getLocale());
1139                     break;
1140                 default:
1141                     return null;
1142                 }
1143                 seg.first();
1144                 iterator.setText(seg);
1145                 int end = iterator.following(index - seg.modelOffset + seg.offset);
1146                 if (end == BreakIterator.DONE) {
1147                     return null;
1148                 }
1149                 if (end > seg.offset + seg.count) {
1150                     return null;
1151                 }
1152                 int begin = iterator.previous();
1153                 if (begin == BreakIterator.DONE ||
1154                     begin >= seg.offset + seg.count) {
1155                     return null;
1156                 }
1157                 seg.modelOffset = seg.modelOffset + begin - seg.offset;
1158                 seg.offset = begin;
1159                 seg.count = end - begin;
1160                 return seg;
1161             }
1162 
1163             /**
1164              * Return the AttributeSet for a given character at a given index
1165              *
1166              * @param i the zero-based index into the text
1167              * @return the AttributeSet of the character
1168              */
1169             public AttributeSet getCharacterAttribute(int i) {
1170                 if (model instanceof StyledDocument) {
1171                     StyledDocument doc = (StyledDocument)model;
1172                     Element elem = doc.getCharacterElement(i);
1173                     if (elem != null) {
1174                         return elem.getAttributes();
1175                     }
1176                 }
1177                 return null;
1178             }
1179 
1180             /**
1181              * Returns the start offset within the selected text.
1182              * If there is no selection, but there is
1183              * a caret, the start and end offsets will be the same.
1184              *
1185              * @return the index into the text of the start of the selection
1186              */
1187             public int getSelectionStart() {
1188                 return editor.getSelectionStart();
1189             }
1190 
1191             /**
1192              * Returns the end offset within the selected text.
1193              * If there is no selection, but there is
1194              * a caret, the start and end offsets will be the same.
1195              *
1196              * @return the index into the text of the end of the selection
1197              */
1198             public int getSelectionEnd() {
1199                 return editor.getSelectionEnd();
1200             }
1201 
1202             /**
1203              * Returns the portion of the text that is selected.
1204              *
1205              * @return the String portion of the text that is selected
1206              */
1207             public String getSelectedText() {
1208                 return editor.getSelectedText();
1209             }
1210 
1211             /*
1212              * Returns the text substring starting at the specified
1213              * offset with the specified length.
1214              */
1215             private String getText(int offset, int length)
1216                 throws BadLocationException {
1217 
1218                 if (model != null && model instanceof StyledDocument) {
1219                     StyledDocument doc = (StyledDocument)model;
1220                     return model.getText(offset, length);
1221                 } else {
1222                     return null;
1223                 }
1224             }
1225         }
1226     }
1227 
1228     /*
1229      * ElementInfo for images
1230      */
1231     private class IconElementInfo extends ElementInfo implements Accessible {
1232 
1233         private int width = -1;
1234         private int height = -1;
1235 
1236         IconElementInfo(Element element, ElementInfo parent) {
1237             super(element, parent);
1238         }
1239 
1240         protected void invalidate(boolean first) {
1241             super.invalidate(first);
1242             width = height = -1;
1243         }
1244 
1245         private int getImageSize(Object key) {
1246             if (validateIfNecessary()) {
1247                 int size = getIntAttr(getAttributes(), key, -1);
1248 
1249                 if (size == -1) {
1250                     View v = getView();
1251 
1252                     size = 0;
1253                     if (v instanceof ImageView) {
1254                         Image img = ((ImageView)v).getImage();
1255                         if (img != null) {
1256                             if (key == HTML.Attribute.WIDTH) {
1257                                 size = img.getWidth(null);
1258                             }
1259                             else {
1260                                 size = img.getHeight(null);
1261                             }
1262                         }
1263                     }
1264                 }
1265                 return size;
1266             }
1267             return 0;
1268         }
1269 
1270         // begin AccessibleIcon implementation ...
1271         private AccessibleContext accessibleContext;
1272 
1273         public AccessibleContext getAccessibleContext() {
1274             if (accessibleContext == null) {
1275                 accessibleContext = new IconAccessibleContext(this);
1276             }
1277             return accessibleContext;
1278         }
1279 
1280         /*
1281          * AccessibleContext for images
1282          */
1283         protected class IconAccessibleContext extends HTMLAccessibleContext
1284             implements AccessibleIcon  {
1285 
1286             public IconAccessibleContext(ElementInfo elementInfo) {
1287                 super(elementInfo);
1288             }
1289 
1290             /**
1291              * Gets the accessibleName property of this object.  The accessibleName
1292              * property of an object is a localized String that designates the purpose
1293              * of the object.  For example, the accessibleName property of a label
1294              * or button might be the text of the label or button itself.  In the
1295              * case of an object that doesn't display its name, the accessibleName
1296              * should still be set.  For example, in the case of a text field used
1297              * to enter the name of a city, the accessibleName for the en_US locale
1298              * could be 'city.'
1299              *
1300              * @return the localized name of the object; null if this
1301              * object does not have a name
1302              *
1303              * @see #setAccessibleName
1304              */
1305             public String getAccessibleName() {
1306                 return getAccessibleIconDescription();
1307             }
1308 
1309             /**
1310              * Gets the accessibleDescription property of this object.  If this
1311              * property isn't set, returns the content type of this
1312              * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1313              *
1314              * @return the localized description of the object; <code>null</code>
1315              *  if this object does not have a description
1316              *
1317              * @see #setAccessibleName
1318              */
1319             public String getAccessibleDescription() {
1320                 return editor.getContentType();
1321             }
1322 
1323             /**
1324              * Gets the role of this object.  The role of the object is the generic
1325              * purpose or use of the class of this object.  For example, the role
1326              * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
1327              * AccessibleRole are provided so component developers can pick from
1328              * a set of predefined roles.  This enables assistive technologies to
1329              * provide a consistent interface to various tweaked subclasses of
1330              * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1331              * that act like a push button) as well as distinguish between subclasses
1332              * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1333              * and AccessibleRole.RADIO_BUTTON for radio buttons).
1334              * <p>Note that the AccessibleRole class is also extensible, so
1335              * custom component developers can define their own AccessibleRole's
1336              * if the set of predefined roles is inadequate.
1337              *
1338              * @return an instance of AccessibleRole describing the role of the object
1339              * @see AccessibleRole
1340              */
1341             public AccessibleRole getAccessibleRole() {
1342                 return AccessibleRole.ICON;
1343             }
1344 
1345             public AccessibleIcon [] getAccessibleIcon() {
1346                 AccessibleIcon [] icons = new AccessibleIcon[1];
1347                 icons[0] = this;
1348                 return icons;
1349             }
1350 
1351             /**
1352              * Gets the description of the icon.  This is meant to be a brief
1353              * textual description of the object.  For example, it might be
1354              * presented to a blind user to give an indication of the purpose
1355              * of the icon.
1356              *
1357              * @return the description of the icon
1358              */
1359             public String getAccessibleIconDescription() {
1360                 return ((ImageView)getView()).getAltText();
1361             }
1362 
1363             /**
1364              * Sets the description of the icon.  This is meant to be a brief
1365              * textual description of the object.  For example, it might be
1366              * presented to a blind user to give an indication of the purpose
1367              * of the icon.
1368              *
1369              * @param description the description of the icon
1370              */
1371             public void setAccessibleIconDescription(String description) {
1372             }
1373 
1374             /**
1375              * Gets the width of the icon
1376              *
1377              * @return the width of the icon.
1378              */
1379             public int getAccessibleIconWidth() {
1380                 if (width == -1) {
1381                     width = getImageSize(HTML.Attribute.WIDTH);
1382                 }
1383                 return width;
1384             }
1385 
1386             /**
1387              * Gets the height of the icon
1388              *
1389              * @return the height of the icon.
1390              */
1391             public int getAccessibleIconHeight() {
1392                 if (height == -1) {
1393                     height = getImageSize(HTML.Attribute.HEIGHT);
1394                 }
1395                 return height;
1396             }
1397         }
1398         // ... end AccessibleIconImplementation
1399     }
1400 
1401 
1402     /**
1403      * TableElementInfo encapsulates information about a HTML.Tag.TABLE.
1404      * To make access fast it crates a grid containing the children to
1405      * allow for access by row, column. TableElementInfo will contain
1406      * TableRowElementInfos, which will contain TableCellElementInfos.
1407      * Any time one of the rows or columns becomes invalid the table is
1408      * invalidated.  This is because any time one of the child attributes
1409      * changes the size of the grid may have changed.
1410      */
1411     private class TableElementInfo extends ElementInfo
1412         implements Accessible {
1413 
1414         protected ElementInfo caption;
1415 
1416         /**
1417          * Allocation of the table by row x column. There may be holes (eg
1418          * nulls) depending upon the html, any cell that has a rowspan/colspan
1419          * > 1 will be contained multiple times in the grid.
1420          */
1421         private TableCellElementInfo[][] grid;
1422 
1423 
1424         TableElementInfo(Element e, ElementInfo parent) {
1425             super(e, parent);
1426         }
1427 
1428         public ElementInfo getCaptionInfo() {
1429             return caption;
1430         }
1431 
1432         /**
1433          * Overriden to update the grid when validating.
1434          */
1435         protected void validate() {
1436             super.validate();
1437             updateGrid();
1438         }
1439 
1440         /**
1441          * Overriden to only alloc instances of TableRowElementInfos.
1442          */
1443         protected void loadChildren(Element e) {
1444 
1445             for (int counter = 0; counter < e.getElementCount(); counter++) {
1446                 Element child = e.getElement(counter);
1447                 AttributeSet attrs = child.getAttributes();
1448 
1449                 if (attrs.getAttribute(StyleConstants.NameAttribute) ==
1450                                        HTML.Tag.TR) {
1451                     addChild(new TableRowElementInfo(child, this, counter));
1452 
1453                 } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
1454                                        HTML.Tag.CAPTION) {
1455                     // Handle captions as a special case since all other
1456                     // children are table rows.
1457                     caption = createElementInfo(child, this);
1458                 }
1459             }
1460         }
1461 
1462         /**
1463          * Updates the grid.
1464          */
1465         private void updateGrid() {
1466             // Determine the max row/col count.
1467             int delta = 0;
1468             int maxCols = 0;
1469             int rows;
1470             for (int counter = 0; counter < getChildCount(); counter++) {
1471                 TableRowElementInfo row = getRow(counter);
1472                 int prev = 0;
1473                 for (int y = 0; y < delta; y++) {
1474                     prev = Math.max(prev, getRow(counter - y - 1).
1475                                     getColumnCount(y + 2));
1476                 }
1477                 delta = Math.max(row.getRowCount(), delta);
1478                 delta--;
1479                 maxCols = Math.max(maxCols, row.getColumnCount() + prev);
1480             }
1481             rows = getChildCount() + delta;
1482 
1483             // Alloc
1484             grid = new TableCellElementInfo[rows][];
1485             for (int counter = 0; counter < rows; counter++) {
1486                 grid[counter] = new TableCellElementInfo[maxCols];
1487             }
1488             // Update
1489             for (int counter = 0; counter < rows; counter++) {
1490                 getRow(counter).updateGrid(counter);
1491             }
1492         }
1493 
1494         /**
1495          * Returns the TableCellElementInfo at the specified index.
1496          */
1497         public TableRowElementInfo getRow(int index) {
1498             return (TableRowElementInfo)getChild(index);
1499         }
1500 
1501         /**
1502          * Returns the TableCellElementInfo by row and column.
1503          */
1504         public TableCellElementInfo getCell(int r, int c) {
1505             if (validateIfNecessary() && r < grid.length &&
1506                                          c < grid[0].length) {
1507                 return grid[r][c];
1508             }
1509             return null;
1510         }
1511 
1512         /**
1513          * Returns the rowspan of the specified entry.
1514          */
1515         public int getRowExtentAt(int r, int c) {
1516             TableCellElementInfo cell = getCell(r, c);
1517 
1518             if (cell != null) {
1519                 int rows = cell.getRowCount();
1520                 int delta = 1;
1521 
1522                 while ((r - delta) >= 0 && grid[r - delta][c] == cell) {
1523                     delta++;
1524                 }
1525                 return rows - delta + 1;
1526             }
1527             return 0;
1528         }
1529 
1530         /**
1531          * Returns the colspan of the specified entry.
1532          */
1533         public int getColumnExtentAt(int r, int c) {
1534             TableCellElementInfo cell = getCell(r, c);
1535 
1536             if (cell != null) {
1537                 int cols = cell.getColumnCount();
1538                 int delta = 1;
1539 
1540                 while ((c - delta) >= 0 && grid[r][c - delta] == cell) {
1541                     delta++;
1542                 }
1543                 return cols - delta + 1;
1544             }
1545             return 0;
1546         }
1547 
1548         /**
1549          * Returns the number of rows in the table.
1550          */
1551         public int getRowCount() {
1552             if (validateIfNecessary()) {
1553                 return grid.length;
1554             }
1555             return 0;
1556         }
1557 
1558         /**
1559          * Returns the number of columns in the table.
1560          */
1561         public int getColumnCount() {
1562             if (validateIfNecessary() && grid.length > 0) {
1563                 return grid[0].length;
1564             }
1565             return 0;
1566         }
1567 
1568         // begin AccessibleTable implementation ...
1569         private AccessibleContext accessibleContext;
1570 
1571         public AccessibleContext getAccessibleContext() {
1572             if (accessibleContext == null) {
1573                 accessibleContext = new TableAccessibleContext(this);
1574             }
1575             return accessibleContext;
1576         }
1577 
1578         /*
1579          * AccessibleContext for tables
1580          */
1581         public class TableAccessibleContext extends HTMLAccessibleContext
1582             implements AccessibleTable {
1583 
1584             private AccessibleHeadersTable rowHeadersTable;
1585 
1586             public TableAccessibleContext(ElementInfo elementInfo) {
1587                 super(elementInfo);
1588             }
1589 
1590             /**
1591              * Gets the accessibleName property of this object.  The accessibleName
1592              * property of an object is a localized String that designates the purpose
1593              * of the object.  For example, the accessibleName property of a label
1594              * or button might be the text of the label or button itself.  In the
1595              * case of an object that doesn't display its name, the accessibleName
1596              * should still be set.  For example, in the case of a text field used
1597              * to enter the name of a city, the accessibleName for the en_US locale
1598              * could be 'city.'
1599              *
1600              * @return the localized name of the object; null if this
1601              * object does not have a name
1602              *
1603              * @see #setAccessibleName
1604              */
1605             public String getAccessibleName() {
1606                 // return the role of the object
1607                 return getAccessibleRole().toString();
1608             }
1609 
1610             /**
1611              * Gets the accessibleDescription property of this object.  If this
1612              * property isn't set, returns the content type of this
1613              * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1614              *
1615              * @return the localized description of the object; <code>null</code>
1616              *  if this object does not have a description
1617              *
1618              * @see #setAccessibleName
1619              */
1620             public String getAccessibleDescription() {
1621                 return editor.getContentType();
1622             }
1623 
1624             /**
1625              * Gets the role of this object.  The role of the object is the generic
1626              * purpose or use of the class of this object.  For example, the role
1627              * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
1628              * AccessibleRole are provided so component developers can pick from
1629              * a set of predefined roles.  This enables assistive technologies to
1630              * provide a consistent interface to various tweaked subclasses of
1631              * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
1632              * that act like a push button) as well as distinguish between subclasses
1633              * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
1634              * and AccessibleRole.RADIO_BUTTON for radio buttons).
1635              * <p>Note that the AccessibleRole class is also extensible, so
1636              * custom component developers can define their own AccessibleRole's
1637              * if the set of predefined roles is inadequate.
1638              *
1639              * @return an instance of AccessibleRole describing the role of the object
1640              * @see AccessibleRole
1641              */
1642             public AccessibleRole getAccessibleRole() {
1643                 return AccessibleRole.TABLE;
1644             }
1645 
1646             /**
1647              * Gets the 0-based index of this object in its accessible parent.
1648              *
1649              * @return the 0-based index of this object in its parent; -1 if this
1650              * object does not have an accessible parent.
1651              *
1652              * @see #getAccessibleParent
1653              * @see #getAccessibleChildrenCount
1654              * @gsee #getAccessibleChild
1655              */
1656             public int getAccessibleIndexInParent() {
1657                 return elementInfo.getIndexInParent();
1658             }
1659 
1660             /**
1661              * Returns the number of accessible children of the object.
1662              *
1663              * @return the number of accessible children of the object.
1664              */
1665             public int getAccessibleChildrenCount() {
1666                 return ((TableElementInfo)elementInfo).getRowCount() *
1667                     ((TableElementInfo)elementInfo).getColumnCount();
1668             }
1669 
1670             /**
1671              * Returns the specified Accessible child of the object.  The Accessible
1672              * children of an Accessible object are zero-based, so the first child
1673              * of an Accessible child is at index 0, the second child is at index 1,
1674              * and so on.
1675              *
1676              * @param i zero-based index of child
1677              * @return the Accessible child of the object
1678              * @see #getAccessibleChildrenCount
1679              */
1680             public Accessible getAccessibleChild(int i) {
1681                 int rowCount = ((TableElementInfo)elementInfo).getRowCount();
1682                 int columnCount = ((TableElementInfo)elementInfo).getColumnCount();
1683                 int r = i / rowCount;
1684                 int c = i % columnCount;
1685                 if (r < 0 || r >= rowCount || c < 0 || c >= columnCount) {
1686                     return null;
1687                 } else {
1688                     return getAccessibleAt(r, c);
1689                 }
1690             }
1691 
1692             public AccessibleTable getAccessibleTable() {
1693                 return this;
1694             }
1695 
1696             /**
1697              * Returns the caption for the table.
1698              *
1699              * @return the caption for the table
1700              */
1701             public Accessible getAccessibleCaption() {
1702                 ElementInfo captionInfo = getCaptionInfo();
1703                 if (captionInfo instanceof Accessible) {
1704                     return (Accessible)caption;
1705                 } else {
1706                     return null;
1707                 }
1708             }
1709 
1710             /**
1711              * Sets the caption for the table.
1712              *
1713              * @param a the caption for the table
1714              */
1715             public void setAccessibleCaption(Accessible a) {
1716             }
1717 
1718             /**
1719              * Returns the summary description of the table.
1720              *
1721              * @return the summary description of the table
1722              */
1723             public Accessible getAccessibleSummary() {
1724                 return null;
1725             }
1726 
1727             /**
1728              * Sets the summary description of the table
1729              *
1730              * @param a the summary description of the table
1731              */
1732             public void setAccessibleSummary(Accessible a) {
1733             }
1734 
1735             /**
1736              * Returns the number of rows in the table.
1737              *
1738              * @return the number of rows in the table
1739              */
1740             public int getAccessibleRowCount() {
1741                 return ((TableElementInfo)elementInfo).getRowCount();
1742             }
1743 
1744             /**
1745              * Returns the number of columns in the table.
1746              *
1747              * @return the number of columns in the table
1748              */
1749             public int getAccessibleColumnCount() {
1750                 return ((TableElementInfo)elementInfo).getColumnCount();
1751             }
1752 
1753             /**
1754              * Returns the Accessible at a specified row and column
1755              * in the table.
1756              *
1757              * @param r zero-based row of the table
1758              * @param c zero-based column of the table
1759              * @return the Accessible at the specified row and column
1760              */
1761             public Accessible getAccessibleAt(int r, int c) {
1762                 TableCellElementInfo cellInfo = getCell(r, c);
1763                 if (cellInfo != null) {
1764                     return cellInfo.getAccessible();
1765                 } else {
1766                     return null;
1767                 }
1768             }
1769 
1770             /**
1771              * Returns the number of rows occupied by the Accessible at
1772              * a specified row and column in the table.
1773              *
1774              * @return the number of rows occupied by the Accessible at a
1775              * given specified (row, column)
1776              */
1777             public int getAccessibleRowExtentAt(int r, int c) {
1778                 return ((TableElementInfo)elementInfo).getRowExtentAt(r, c);
1779             }
1780 
1781             /**
1782              * Returns the number of columns occupied by the Accessible at
1783              * a specified row and column in the table.
1784              *
1785              * @return the number of columns occupied by the Accessible at a
1786              * given specified row and column
1787              */
1788             public int getAccessibleColumnExtentAt(int r, int c) {
1789                 return ((TableElementInfo)elementInfo).getColumnExtentAt(r, c);
1790             }
1791 
1792             /**
1793              * Returns the row headers as an AccessibleTable.
1794              *
1795              * @return an AccessibleTable representing the row
1796              * headers
1797              */
1798             public AccessibleTable getAccessibleRowHeader() {
1799                 return rowHeadersTable;
1800             }
1801 
1802             /**
1803              * Sets the row headers.
1804              *
1805              * @param table an AccessibleTable representing the
1806              * row headers
1807              */
1808             public void setAccessibleRowHeader(AccessibleTable table) {
1809             }
1810 
1811             /**
1812              * Returns the column headers as an AccessibleTable.
1813              *
1814              * @return an AccessibleTable representing the column
1815              * headers
1816              */
1817             public AccessibleTable getAccessibleColumnHeader() {
1818                 return null;
1819             }
1820 
1821             /**
1822              * Sets the column headers.
1823              *
1824              * @param table an AccessibleTable representing the
1825              * column headers
1826              */
1827             public void setAccessibleColumnHeader(AccessibleTable table) {
1828             }
1829 
1830             /**
1831              * Returns the description of the specified row in the table.
1832              *
1833              * @param r zero-based row of the table
1834              * @return the description of the row
1835              */
1836             public Accessible getAccessibleRowDescription(int r) {
1837                 return null;
1838             }
1839 
1840             /**
1841              * Sets the description text of the specified row of the table.
1842              *
1843              * @param r zero-based row of the table
1844              * @param a the description of the row
1845              */
1846             public void setAccessibleRowDescription(int r, Accessible a) {
1847             }
1848 
1849             /**
1850              * Returns the description text of the specified column in the table.
1851              *
1852              * @param c zero-based column of the table
1853              * @return the text description of the column
1854              */
1855             public Accessible getAccessibleColumnDescription(int c) {
1856                 return null;
1857             }
1858 
1859             /**
1860              * Sets the description text of the specified column in the table.
1861              *
1862              * @param c zero-based column of the table
1863              * @param a the text description of the column
1864              */
1865             public void setAccessibleColumnDescription(int c, Accessible a) {
1866             }
1867 
1868             /**
1869              * Returns a boolean value indicating whether the accessible at
1870              * a specified row and column is selected.
1871              *
1872              * @param r zero-based row of the table
1873              * @param c zero-based column of the table
1874              * @return the boolean value true if the accessible at the
1875              * row and column is selected. Otherwise, the boolean value
1876              * false
1877              */
1878             public boolean isAccessibleSelected(int r, int c) {
1879                 if (validateIfNecessary()) {
1880                     if (r < 0 || r >= getAccessibleRowCount() ||
1881                         c < 0 || c >= getAccessibleColumnCount()) {
1882                         return false;
1883                     }
1884                     TableCellElementInfo cell = getCell(r, c);
1885                     if (cell != null) {
1886                         Element elem = cell.getElement();
1887                         int start = elem.getStartOffset();
1888                         int end = elem.getEndOffset();
1889                         return start >= editor.getSelectionStart() &&
1890                             end <= editor.getSelectionEnd();
1891                     }
1892                 }
1893                 return false;
1894             }
1895 
1896             /**
1897              * Returns a boolean value indicating whether the specified row
1898              * is selected.
1899              *
1900              * @param r zero-based row of the table
1901              * @return the boolean value true if the specified row is selected.
1902              * Otherwise, false.
1903              */
1904             public boolean isAccessibleRowSelected(int r) {
1905                 if (validateIfNecessary()) {
1906                     if (r < 0 || r >= getAccessibleRowCount()) {
1907                         return false;
1908                     }
1909                     int nColumns = getAccessibleColumnCount();
1910 
1911                     TableCellElementInfo startCell = getCell(r, 0);
1912                     if (startCell == null) {
1913                         return false;
1914                     }
1915                     int start = startCell.getElement().getStartOffset();
1916 
1917                     TableCellElementInfo endCell = getCell(r, nColumns-1);
1918                     if (endCell == null) {
1919                         return false;
1920                     }
1921                     int end = endCell.getElement().getEndOffset();
1922 
1923                     return start >= editor.getSelectionStart() &&
1924                         end <= editor.getSelectionEnd();
1925                 }
1926                 return false;
1927             }
1928 
1929             /**
1930              * Returns a boolean value indicating whether the specified column
1931              * is selected.
1932              *
1933              * @param c zero-based column of the table
1934              * @return the boolean value true if the specified column is selected.
1935              * Otherwise, false.
1936              */
1937             public boolean isAccessibleColumnSelected(int c) {
1938                 if (validateIfNecessary()) {
1939                     if (c < 0 || c >= getAccessibleColumnCount()) {
1940                         return false;
1941                     }
1942                     int nRows = getAccessibleRowCount();
1943 
1944                     TableCellElementInfo startCell = getCell(0, c);
1945                     if (startCell == null) {
1946                         return false;
1947                     }
1948                     int start = startCell.getElement().getStartOffset();
1949 
1950                     TableCellElementInfo endCell = getCell(nRows-1, c);
1951                     if (endCell == null) {
1952                         return false;
1953                     }
1954                     int end = endCell.getElement().getEndOffset();
1955                     return start >= editor.getSelectionStart() &&
1956                         end <= editor.getSelectionEnd();
1957                 }
1958                 return false;
1959             }
1960 
1961             /**
1962              * Returns the selected rows in a table.
1963              *
1964              * @return an array of selected rows where each element is a
1965              * zero-based row of the table
1966              */
1967             public int [] getSelectedAccessibleRows() {
1968                 if (validateIfNecessary()) {
1969                     int nRows = getAccessibleRowCount();
1970                     Vector<Integer> vec = new Vector<Integer>();
1971 
1972                     for (int i = 0; i < nRows; i++) {
1973                         if (isAccessibleRowSelected(i)) {
1974                             vec.addElement(Integer.valueOf(i));
1975                         }
1976                     }
1977                     int retval[] = new int[vec.size()];
1978                     for (int i = 0; i < retval.length; i++) {
1979                         retval[i] = vec.elementAt(i).intValue();
1980                     }
1981                     return retval;
1982                 }
1983                 return new int[0];
1984             }
1985 
1986             /**
1987              * Returns the selected columns in a table.
1988              *
1989              * @return an array of selected columns where each element is a
1990              * zero-based column of the table
1991              */
1992             public int [] getSelectedAccessibleColumns() {
1993                 if (validateIfNecessary()) {
1994                     int nColumns = getAccessibleRowCount();
1995                     Vector<Integer> vec = new Vector<Integer>();
1996 
1997                     for (int i = 0; i < nColumns; i++) {
1998                         if (isAccessibleColumnSelected(i)) {
1999                             vec.addElement(Integer.valueOf(i));
2000                         }
2001                     }
2002                     int retval[] = new int[vec.size()];
2003                     for (int i = 0; i < retval.length; i++) {
2004                         retval[i] = vec.elementAt(i).intValue();
2005                     }
2006                     return retval;
2007                 }
2008                 return new int[0];
2009             }
2010 
2011             // begin AccessibleExtendedTable implementation -------------
2012 
2013             /**
2014              * Returns the row number of an index in the table.
2015              *
2016              * @param index the zero-based index in the table
2017              * @return the zero-based row of the table if one exists;
2018              * otherwise -1.
2019              */
2020             public int getAccessibleRow(int index) {
2021                 if (validateIfNecessary()) {
2022                     int numCells = getAccessibleColumnCount() *
2023                         getAccessibleRowCount();
2024                     if (index >= numCells) {
2025                         return -1;
2026                     } else {
2027                         return index / getAccessibleColumnCount();
2028                     }
2029                 }
2030                 return -1;
2031             }
2032 
2033             /**
2034              * Returns the column number of an index in the table.
2035              *
2036              * @param index the zero-based index in the table
2037              * @return the zero-based column of the table if one exists;
2038              * otherwise -1.
2039              */
2040             public int getAccessibleColumn(int index) {
2041                 if (validateIfNecessary()) {
2042                     int numCells = getAccessibleColumnCount() *
2043                         getAccessibleRowCount();
2044                     if (index >= numCells) {
2045                         return -1;
2046                     } else {
2047                         return index % getAccessibleColumnCount();
2048                     }
2049                 }
2050                 return -1;
2051             }
2052 
2053             /**
2054              * Returns the index at a row and column in the table.
2055              *
2056              * @param r zero-based row of the table
2057              * @param c zero-based column of the table
2058              * @return the zero-based index in the table if one exists;
2059              * otherwise -1.
2060              */
2061             public int getAccessibleIndex(int r, int c) {
2062                 if (validateIfNecessary()) {
2063                     if (r >= getAccessibleRowCount() ||
2064                         c >= getAccessibleColumnCount()) {
2065                         return -1;
2066                     } else {
2067                         return r * getAccessibleColumnCount() + c;
2068                     }
2069                 }
2070                 return -1;
2071             }
2072 
2073             /**
2074              * Returns the row header at a row in a table.
2075              * @param r zero-based row of the table
2076              *
2077              * @return a String representing the row header
2078              * if one exists; otherwise null.
2079              */
2080             public String getAccessibleRowHeader(int r) {
2081                 if (validateIfNecessary()) {
2082                     TableCellElementInfo cellInfo = getCell(r, 0);
2083                     if (cellInfo.isHeaderCell()) {
2084                         View v = cellInfo.getView();
2085                         if (v != null && model != null) {
2086                             try {
2087                                 return model.getText(v.getStartOffset(),
2088                                                      v.getEndOffset() -
2089                                                      v.getStartOffset());
2090                             } catch (BadLocationException e) {
2091                                 return null;
2092                             }
2093                         }
2094                     }
2095                 }
2096                 return null;
2097             }
2098 
2099             /**
2100              * Returns the column header at a column in a table.
2101              * @param c zero-based column of the table
2102              *
2103              * @return a String representing the column header
2104              * if one exists; otherwise null.
2105              */
2106             public String getAccessibleColumnHeader(int c) {
2107                 if (validateIfNecessary()) {
2108                     TableCellElementInfo cellInfo = getCell(0, c);
2109                     if (cellInfo.isHeaderCell()) {
2110                         View v = cellInfo.getView();
2111                         if (v != null && model != null) {
2112                             try {
2113                                 return model.getText(v.getStartOffset(),
2114                                                      v.getEndOffset() -
2115                                                      v.getStartOffset());
2116                             } catch (BadLocationException e) {
2117                                 return null;
2118                             }
2119                         }
2120                     }
2121                 }
2122                 return null;
2123             }
2124 
2125             public void addRowHeader(TableCellElementInfo cellInfo, int rowNumber) {
2126                 if (rowHeadersTable == null) {
2127                     rowHeadersTable = new AccessibleHeadersTable();
2128                 }
2129                 rowHeadersTable.addHeader(cellInfo, rowNumber);
2130             }
2131             // end of AccessibleExtendedTable implementation ------------
2132 
2133             protected class AccessibleHeadersTable implements AccessibleTable {
2134 
2135                 // Header information is modeled as a Hashtable of
2136                 // ArrayLists where each Hashtable entry represents
2137                 // a row containing one or more headers.
2138                 private Hashtable<Integer, ArrayList<TableCellElementInfo>> headers =
2139                         new Hashtable<Integer, ArrayList<TableCellElementInfo>>();
2140                 private int rowCount = 0;
2141                 private int columnCount = 0;
2142 
2143                 public void addHeader(TableCellElementInfo cellInfo, int rowNumber) {
2144                     Integer rowInteger = Integer.valueOf(rowNumber);
2145                     ArrayList<TableCellElementInfo> list = headers.get(rowInteger);
2146                     if (list == null) {
2147                         list = new ArrayList<TableCellElementInfo>();
2148                         headers.put(rowInteger, list);
2149                     }
2150                     list.add(cellInfo);
2151                 }
2152 
2153                 /**
2154                  * Returns the caption for the table.
2155                  *
2156                  * @return the caption for the table
2157                  */
2158                 public Accessible getAccessibleCaption() {
2159                     return null;
2160                 }
2161 
2162                 /**
2163                  * Sets the caption for the table.
2164                  *
2165                  * @param a the caption for the table
2166                  */
2167                 public void setAccessibleCaption(Accessible a) {
2168                 }
2169 
2170                 /**
2171                  * Returns the summary description of the table.
2172                  *
2173                  * @return the summary description of the table
2174                  */
2175                 public Accessible getAccessibleSummary() {
2176                     return null;
2177                 }
2178 
2179                 /**
2180                  * Sets the summary description of the table
2181                  *
2182                  * @param a the summary description of the table
2183                  */
2184                 public void setAccessibleSummary(Accessible a) {
2185                 }
2186 
2187                 /**
2188                  * Returns the number of rows in the table.
2189                  *
2190                  * @return the number of rows in the table
2191                  */
2192                 public int getAccessibleRowCount() {
2193                     return rowCount;
2194                 }
2195 
2196                 /**
2197                  * Returns the number of columns in the table.
2198                  *
2199                  * @return the number of columns in the table
2200                  */
2201                 public int getAccessibleColumnCount() {
2202                     return columnCount;
2203                 }
2204 
2205                 private TableCellElementInfo getElementInfoAt(int r, int c) {
2206                     ArrayList<TableCellElementInfo> list = headers.get(Integer.valueOf(r));
2207                     if (list != null) {
2208                         return list.get(c);
2209                     } else {
2210                         return null;
2211                     }
2212                 }
2213 
2214                 /**
2215                  * Returns the Accessible at a specified row and column
2216                  * in the table.
2217                  *
2218                  * @param r zero-based row of the table
2219                  * @param c zero-based column of the table
2220                  * @return the Accessible at the specified row and column
2221                  */
2222                 public Accessible getAccessibleAt(int r, int c) {
2223                     ElementInfo elementInfo = getElementInfoAt(r, c);
2224                     if (elementInfo instanceof Accessible) {
2225                         return (Accessible)elementInfo;
2226                     } else {
2227                         return null;
2228                     }
2229                 }
2230 
2231                 /**
2232                  * Returns the number of rows occupied by the Accessible at
2233                  * a specified row and column in the table.
2234                  *
2235                  * @return the number of rows occupied by the Accessible at a
2236                  * given specified (row, column)
2237                  */
2238                 public int getAccessibleRowExtentAt(int r, int c) {
2239                     TableCellElementInfo elementInfo = getElementInfoAt(r, c);
2240                     if (elementInfo != null) {
2241                         return elementInfo.getRowCount();
2242                     } else {
2243                         return 0;
2244                     }
2245                 }
2246 
2247                 /**
2248                  * Returns the number of columns occupied by the Accessible at
2249                  * a specified row and column in the table.
2250                  *
2251                  * @return the number of columns occupied by the Accessible at a
2252                  * given specified row and column
2253                  */
2254                 public int getAccessibleColumnExtentAt(int r, int c) {
2255                     TableCellElementInfo elementInfo = getElementInfoAt(r, c);
2256                     if (elementInfo != null) {
2257                         return elementInfo.getRowCount();
2258                     } else {
2259                         return 0;
2260                     }
2261                 }
2262 
2263                 /**
2264                  * Returns the row headers as an AccessibleTable.
2265                  *
2266                  * @return an AccessibleTable representing the row
2267                  * headers
2268                  */
2269                 public AccessibleTable getAccessibleRowHeader() {
2270                     return null;
2271                 }
2272 
2273                 /**
2274                  * Sets the row headers.
2275                  *
2276                  * @param table an AccessibleTable representing the
2277                  * row headers
2278                  */
2279                 public void setAccessibleRowHeader(AccessibleTable table) {
2280                 }
2281 
2282                 /**
2283                  * Returns the column headers as an AccessibleTable.
2284                  *
2285                  * @return an AccessibleTable representing the column
2286                  * headers
2287                  */
2288                 public AccessibleTable getAccessibleColumnHeader() {
2289                     return null;
2290                 }
2291 
2292                 /**
2293                  * Sets the column headers.
2294                  *
2295                  * @param table an AccessibleTable representing the
2296                  * column headers
2297                  */
2298                 public void setAccessibleColumnHeader(AccessibleTable table) {
2299                 }
2300 
2301                 /**
2302                  * Returns the description of the specified row in the table.
2303                  *
2304                  * @param r zero-based row of the table
2305                  * @return the description of the row
2306                  */
2307                 public Accessible getAccessibleRowDescription(int r) {
2308                     return null;
2309                 }
2310 
2311                 /**
2312                  * Sets the description text of the specified row of the table.
2313                  *
2314                  * @param r zero-based row of the table
2315                  * @param a the description of the row
2316                  */
2317                 public void setAccessibleRowDescription(int r, Accessible a) {
2318                 }
2319 
2320                 /**
2321                  * Returns the description text of the specified column in the table.
2322                  *
2323                  * @param c zero-based column of the table
2324                  * @return the text description of the column
2325                  */
2326                 public Accessible getAccessibleColumnDescription(int c) {
2327                     return null;
2328                 }
2329 
2330                 /**
2331                  * Sets the description text of the specified column in the table.
2332                  *
2333                  * @param c zero-based column of the table
2334                  * @param a the text description of the column
2335                  */
2336                 public void setAccessibleColumnDescription(int c, Accessible a) {
2337                 }
2338 
2339                 /**
2340                  * Returns a boolean value indicating whether the accessible at
2341                  * a specified row and column is selected.
2342                  *
2343                  * @param r zero-based row of the table
2344                  * @param c zero-based column of the table
2345                  * @return the boolean value true if the accessible at the
2346                  * row and column is selected. Otherwise, the boolean value
2347                  * false
2348                  */
2349                 public boolean isAccessibleSelected(int r, int c) {
2350                     return false;
2351                 }
2352 
2353                 /**
2354                  * Returns a boolean value indicating whether the specified row
2355                  * is selected.
2356                  *
2357                  * @param r zero-based row of the table
2358                  * @return the boolean value true if the specified row is selected.
2359                  * Otherwise, false.
2360                  */
2361                 public boolean isAccessibleRowSelected(int r) {
2362                     return false;
2363                 }
2364 
2365                 /**
2366                  * Returns a boolean value indicating whether the specified column
2367                  * is selected.
2368                  *
2369                  * @param c zero-based column of the table
2370                  * @return the boolean value true if the specified column is selected.
2371                  * Otherwise, false.
2372                  */
2373                 public boolean isAccessibleColumnSelected(int c) {
2374                     return false;
2375                 }
2376 
2377                 /**
2378                  * Returns the selected rows in a table.
2379                  *
2380                  * @return an array of selected rows where each element is a
2381                  * zero-based row of the table
2382                  */
2383                 public int [] getSelectedAccessibleRows() {
2384                     return new int [0];
2385                 }
2386 
2387                 /**
2388                  * Returns the selected columns in a table.
2389                  *
2390                  * @return an array of selected columns where each element is a
2391                  * zero-based column of the table
2392                  */
2393                 public int [] getSelectedAccessibleColumns() {
2394                     return new int [0];
2395                 }
2396             }
2397         } // ... end AccessibleHeadersTable
2398 
2399         /*
2400          * ElementInfo for table rows
2401          */
2402         private class TableRowElementInfo extends ElementInfo {
2403 
2404             private TableElementInfo parent;
2405             private int rowNumber;
2406 
2407             TableRowElementInfo(Element e, TableElementInfo parent, int rowNumber) {
2408                 super(e, parent);
2409                 this.parent = parent;
2410                 this.rowNumber = rowNumber;
2411             }
2412 
2413             protected void loadChildren(Element e) {
2414                 for (int x = 0; x < e.getElementCount(); x++) {
2415                     AttributeSet attrs = e.getElement(x).getAttributes();
2416 
2417                     if (attrs.getAttribute(StyleConstants.NameAttribute) ==
2418                             HTML.Tag.TH) {
2419                         TableCellElementInfo headerElementInfo =
2420                             new TableCellElementInfo(e.getElement(x), this, true);
2421                         addChild(headerElementInfo);
2422 
2423                         AccessibleTable at =
2424                             parent.getAccessibleContext().getAccessibleTable();
2425                         TableAccessibleContext tableElement =
2426                             (TableAccessibleContext)at;
2427                         tableElement.addRowHeader(headerElementInfo, rowNumber);
2428 
2429                     } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
2430                             HTML.Tag.TD) {
2431                         addChild(new TableCellElementInfo(e.getElement(x), this,
2432                                                           false));
2433                     }
2434                 }
2435             }
2436 
2437             /**
2438              * Returns the max of the rowspans of the cells in this row.
2439              */
2440             public int getRowCount() {
2441                 int rowCount = 1;
2442                 if (validateIfNecessary()) {
2443                     for (int counter = 0; counter < getChildCount();
2444                          counter++) {
2445 
2446                         TableCellElementInfo cell = (TableCellElementInfo)
2447                                                     getChild(counter);
2448 
2449                         if (cell.validateIfNecessary()) {
2450                             rowCount = Math.max(rowCount, cell.getRowCount());
2451                         }
2452                     }
2453                 }
2454                 return rowCount;
2455             }
2456 
2457             /**
2458              * Returns the sum of the column spans of the individual
2459              * cells in this row.
2460              */
2461             public int getColumnCount() {
2462                 int colCount = 0;
2463                 if (validateIfNecessary()) {
2464                     for (int counter = 0; counter < getChildCount();
2465                          counter++) {
2466                         TableCellElementInfo cell = (TableCellElementInfo)
2467                                                     getChild(counter);
2468 
2469                         if (cell.validateIfNecessary()) {
2470                             colCount += cell.getColumnCount();
2471                         }
2472                     }
2473                 }
2474                 return colCount;
2475             }
2476 
2477             /**
2478              * Overriden to invalidate the table as well as
2479              * TableRowElementInfo.
2480              */
2481             protected void invalidate(boolean first) {
2482                 super.invalidate(first);
2483                 getParent().invalidate(true);
2484             }
2485 
2486             /**
2487              * Places the TableCellElementInfos for this element in
2488              * the grid.
2489              */
2490             private void updateGrid(int row) {
2491                 if (validateIfNecessary()) {
2492                     boolean emptyRow = false;
2493 
2494                     while (!emptyRow) {
2495                         for (int counter = 0; counter < grid[row].length;
2496                                  counter++) {
2497                             if (grid[row][counter] == null) {
2498                                 emptyRow = true;
2499                                 break;
2500                             }
2501                         }
2502                         if (!emptyRow) {
2503                             row++;
2504                         }
2505                     }
2506                     for (int col = 0, counter = 0; counter < getChildCount();
2507                              counter++) {
2508                         TableCellElementInfo cell = (TableCellElementInfo)
2509                                                     getChild(counter);
2510 
2511                         while (grid[row][col] != null) {
2512                             col++;
2513                         }
2514                         for (int rowCount = cell.getRowCount() - 1;
2515                              rowCount >= 0; rowCount--) {
2516                             for (int colCount = cell.getColumnCount() - 1;
2517                                  colCount >= 0; colCount--) {
2518                                 grid[row + rowCount][col + colCount] = cell;
2519                             }
2520                         }
2521                         col += cell.getColumnCount();
2522                     }
2523                 }
2524             }
2525 
2526             /**
2527              * Returns the column count of the number of columns that have
2528              * a rowcount >= rowspan.
2529              */
2530             private int getColumnCount(int rowspan) {
2531                 if (validateIfNecessary()) {
2532                     int cols = 0;
2533                     for (int counter = 0; counter < getChildCount();
2534                          counter++) {
2535                         TableCellElementInfo cell = (TableCellElementInfo)
2536                                                     getChild(counter);
2537 
2538                         if (cell.getRowCount() >= rowspan) {
2539                             cols += cell.getColumnCount();
2540                         }
2541                     }
2542                     return cols;
2543                 }
2544                 return 0;
2545             }
2546         }
2547 
2548         /**
2549          * TableCellElementInfo is used to represents the cells of
2550          * the table.
2551          */
2552         private class TableCellElementInfo extends ElementInfo {
2553 
2554             private Accessible accessible;
2555             private boolean isHeaderCell;
2556 
2557             TableCellElementInfo(Element e, ElementInfo parent) {
2558                 super(e, parent);
2559                 this.isHeaderCell = false;
2560             }
2561 
2562             TableCellElementInfo(Element e, ElementInfo parent,
2563                                  boolean isHeaderCell) {
2564                 super(e, parent);
2565                 this.isHeaderCell = isHeaderCell;
2566             }
2567 
2568             /*
2569              * Returns whether this table cell is a header
2570              */
2571             public boolean isHeaderCell() {
2572                 return this.isHeaderCell;
2573             }
2574 
2575             /*
2576              * Returns the Accessible representing this table cell
2577              */
2578             public Accessible getAccessible() {
2579                 accessible = null;
2580                 getAccessible(this);
2581                 return accessible;
2582             }
2583 
2584             /*
2585              * Gets the outermost Accessible in the table cell
2586              */
2587             private void getAccessible(ElementInfo elementInfo) {
2588                 if (elementInfo instanceof Accessible) {
2589                     accessible = (Accessible)elementInfo;
2590                 } else {
2591                     for (int i = 0; i < elementInfo.getChildCount(); i++) {
2592                         getAccessible(elementInfo.getChild(i));
2593                     }
2594                 }
2595             }
2596 
2597             /**
2598              * Returns the rowspan attribute.
2599              */
2600             public int getRowCount() {
2601                 if (validateIfNecessary()) {
2602                     return Math.max(1, getIntAttr(getAttributes(),
2603                                                   HTML.Attribute.ROWSPAN, 1));
2604                 }
2605                 return 0;
2606             }
2607 
2608             /**
2609              * Returns the colspan attribute.
2610              */
2611             public int getColumnCount() {
2612                 if (validateIfNecessary()) {
2613                     return Math.max(1, getIntAttr(getAttributes(),
2614                                                   HTML.Attribute.COLSPAN, 1));
2615                 }
2616                 return 0;
2617             }
2618 
2619             /**
2620              * Overriden to invalidate the TableRowElementInfo as well as
2621              * the TableCellElementInfo.
2622              */
2623             protected void invalidate(boolean first) {
2624                 super.invalidate(first);
2625                 getParent().invalidate(true);
2626             }
2627         }
2628     }
2629 
2630 
2631     /**
2632      * ElementInfo provides a slim down view of an Element.  Each ElementInfo
2633      * can have any number of child ElementInfos that are not necessarily
2634      * direct children of the Element. As the Document changes various
2635      * ElementInfos become invalidated. Before accessing a particular portion
2636      * of an ElementInfo you should make sure it is valid by invoking
2637      * <code>validateIfNecessary</code>, this will return true if
2638      * successful, on the other hand a false return value indicates the
2639      * ElementInfo is not valid and can never become valid again (usually
2640      * the result of the Element the ElementInfo encapsulates being removed).
2641      */
2642     private class ElementInfo {
2643 
2644         /**
2645          * The children of this ElementInfo.
2646          */
2647         private ArrayList<ElementInfo> children;
2648         /**
2649          * The Element this ElementInfo is providing information for.
2650          */
2651         private Element element;
2652         /**
2653          * The parent ElementInfo, will be null for the root.
2654          */
2655         private ElementInfo parent;
2656         /**
2657          * Indicates the validity of the ElementInfo.
2658          */
2659         private boolean isValid;
2660         /**
2661          * Indicates if the ElementInfo can become valid.
2662          */
2663         private boolean canBeValid;
2664 
2665 
2666         /**
2667          * Creates the root ElementInfo.
2668          */
2669         ElementInfo(Element element) {
2670             this(element, null);
2671         }
2672 
2673         /**
2674          * Creates an ElementInfo representing <code>element</code> with
2675          * the specified parent.
2676          */
2677         ElementInfo(Element element, ElementInfo parent) {
2678             this.element = element;
2679             this.parent = parent;
2680             isValid = false;
2681             canBeValid = true;
2682         }
2683 
2684         /**
2685          * Validates the receiver. This recreates the children as well. This
2686          * will be invoked within a <code>readLock</code>. If this is overriden
2687          * it MUST invoke supers implementation first!
2688          */
2689         protected void validate() {
2690             isValid = true;
2691             loadChildren(getElement());
2692         }
2693 
2694         /**
2695          * Recreates the direct children of <code>info</code>.
2696          */
2697         protected void loadChildren(Element parent) {
2698             if (!parent.isLeaf()) {
2699                 for (int counter = 0, maxCounter = parent.getElementCount();
2700                     counter < maxCounter; counter++) {
2701                     Element e = parent.getElement(counter);
2702                     ElementInfo childInfo = createElementInfo(e, this);
2703 
2704                     if (childInfo != null) {
2705                         addChild(childInfo);
2706                     }
2707                     else {
2708                         loadChildren(e);
2709                     }
2710                 }
2711             }
2712         }
2713 
2714         /**
2715          * Returns the index of the child in the parent, or -1 for the
2716          * root or if the parent isn't valid.
2717          */
2718         public int getIndexInParent() {
2719             if (parent == null || !parent.isValid()) {
2720                 return -1;
2721             }
2722             return parent.indexOf(this);
2723         }
2724 
2725         /**
2726          * Returns the Element this <code>ElementInfo</code> represents.
2727          */
2728         public Element getElement() {
2729             return element;
2730         }
2731 
2732         /**
2733          * Returns the parent of this Element, or null for the root.
2734          */
2735         public ElementInfo getParent() {
2736             return parent;
2737         }
2738 
2739         /**
2740          * Returns the index of the specified child, or -1 if
2741          * <code>child</code> isn't a valid child.
2742          */
2743         public int indexOf(ElementInfo child) {
2744             ArrayList<ElementInfo> children = this.children;
2745 
2746             if (children != null) {
2747                 return children.indexOf(child);
2748             }
2749             return -1;
2750         }
2751 
2752         /**
2753          * Returns the child ElementInfo at <code>index</code>, or null
2754          * if <code>index</code> isn't a valid index.
2755          */
2756         public ElementInfo getChild(int index) {
2757             if (validateIfNecessary()) {
2758                 ArrayList<ElementInfo> children = this.children;
2759 
2760                 if (children != null && index >= 0 &&
2761                                         index < children.size()) {
2762                     return children.get(index);
2763                 }
2764             }
2765             return null;
2766         }
2767 
2768         /**
2769          * Returns the number of children the ElementInfo contains.
2770          */
2771         public int getChildCount() {
2772             validateIfNecessary();
2773             return (children == null) ? 0 : children.size();
2774         }
2775 
2776         /**
2777          * Adds a new child to this ElementInfo.
2778          */
2779         protected void addChild(ElementInfo child) {
2780             if (children == null) {
2781                 children = new ArrayList<ElementInfo>();
2782             }
2783             children.add(child);
2784         }
2785 
2786         /**
2787          * Returns the View corresponding to this ElementInfo, or null
2788          * if the ElementInfo can't be validated.
2789          */
2790         protected View getView() {
2791             if (!validateIfNecessary()) {
2792                 return null;
2793             }
2794             Object lock = lock();
2795             try {
2796                 View rootView = getRootView();
2797                 Element e = getElement();
2798                 int start = e.getStartOffset();
2799 
2800                 if (rootView != null) {
2801                     return getView(rootView, e, start);
2802                 }
2803                 return null;
2804             } finally {
2805                 unlock(lock);
2806             }
2807         }
2808 
2809         /**
2810          * Returns the Bounds for this ElementInfo, or null
2811          * if the ElementInfo can't be validated.
2812          */
2813         public Rectangle getBounds() {
2814             if (!validateIfNecessary()) {
2815                 return null;
2816             }
2817             Object lock = lock();
2818             try {
2819                 Rectangle bounds = getRootEditorRect();
2820                 View rootView = getRootView();
2821                 Element e = getElement();
2822 
2823                 if (bounds != null && rootView != null) {
2824                     try {
2825                         return rootView.modelToView(e.getStartOffset(),
2826                                                     Position.Bias.Forward,
2827                                                     e.getEndOffset(),
2828                                                     Position.Bias.Backward,
2829                                                     bounds).getBounds();
2830                     } catch (BadLocationException ble) { }
2831                 }
2832             } finally {
2833                 unlock(lock);
2834             }
2835             return null;
2836         }
2837 
2838         /**
2839          * Returns true if this ElementInfo is valid.
2840          */
2841         protected boolean isValid() {
2842             return isValid;
2843         }
2844 
2845         /**
2846          * Returns the AttributeSet associated with the Element, this will
2847          * return null if the ElementInfo can't be validated.
2848          */
2849         protected AttributeSet getAttributes() {
2850             if (validateIfNecessary()) {
2851                 return getElement().getAttributes();
2852             }
2853             return null;
2854         }
2855 
2856         /**
2857          * Returns the AttributeSet associated with the View that is
2858          * representing this Element, this will
2859          * return null if the ElementInfo can't be validated.
2860          */
2861         protected AttributeSet getViewAttributes() {
2862             if (validateIfNecessary()) {
2863                 View view = getView();
2864 
2865                 if (view != null) {
2866                     return view.getElement().getAttributes();
2867                 }
2868                 return getElement().getAttributes();
2869             }
2870             return null;
2871         }
2872 
2873         /**
2874          * Convenience method for getting an integer attribute from the passed
2875          * in AttributeSet.
2876          */
2877         protected int getIntAttr(AttributeSet attrs, Object key, int deflt) {
2878             if (attrs != null && attrs.isDefined(key)) {
2879                 int i;
2880                 String val = (String)attrs.getAttribute(key);
2881                 if (val == null) {
2882                     i = deflt;
2883                 }
2884                 else {
2885                     try {
2886                         i = Math.max(0, Integer.parseInt(val));
2887                     } catch (NumberFormatException x) {
2888                         i = deflt;
2889                     }
2890                 }
2891                 return i;
2892             }
2893             return deflt;
2894         }
2895 
2896         /**
2897          * Validates the ElementInfo if necessary.  Some ElementInfos may
2898          * never be valid again.  You should check <code>isValid</code> before
2899          * using one.  This will reload the children and invoke
2900          * <code>validate</code> if the ElementInfo is invalid and can become
2901          * valid again. This will return true if the receiver is valid.
2902          */
2903         protected boolean validateIfNecessary() {
2904             if (!isValid() && canBeValid) {
2905                 children = null;
2906                 Object lock = lock();
2907 
2908                 try {
2909                     validate();
2910                 } finally {
2911                     unlock(lock);
2912                 }
2913             }
2914             return isValid();
2915         }
2916 
2917         /**
2918          * Invalidates the ElementInfo. Subclasses should override this
2919          * if they need to reset state once invalid.
2920          */
2921         protected void invalidate(boolean first) {
2922             if (!isValid()) {
2923                 if (canBeValid && !first) {
2924                     canBeValid = false;
2925                 }
2926                 return;
2927             }
2928             isValid = false;
2929             canBeValid = first;
2930             if (children != null) {
2931                 for (ElementInfo child : children) {
2932                     child.invalidate(false);
2933                 }
2934                 children = null;
2935             }
2936         }
2937 
2938         private View getView(View parent, Element e, int start) {
2939             if (parent.getElement() == e) {
2940                 return parent;
2941             }
2942             int index = parent.getViewIndex(start, Position.Bias.Forward);
2943 
2944             if (index != -1 && index < parent.getViewCount()) {
2945                 return getView(parent.getView(index), e, start);
2946             }
2947             return null;
2948         }
2949 
2950         private int getClosestInfoIndex(int index) {
2951             for (int counter = 0; counter < getChildCount(); counter++) {
2952                 ElementInfo info = getChild(counter);
2953 
2954                 if (index < info.getElement().getEndOffset() ||
2955                     index == info.getElement().getStartOffset()) {
2956                     return counter;
2957                 }
2958             }
2959             return -1;
2960         }
2961 
2962         private void update(DocumentEvent e) {
2963             if (!isValid()) {
2964                 return;
2965             }
2966             ElementInfo parent = getParent();
2967             Element element = getElement();
2968 
2969             do {
2970                 DocumentEvent.ElementChange ec = e.getChange(element);
2971                 if (ec != null) {
2972                     if (element == getElement()) {
2973                         // One of our children changed.
2974                         invalidate(true);
2975                     }
2976                     else if (parent != null) {
2977                         parent.invalidate(parent == getRootInfo());
2978                     }
2979                     return;
2980                 }
2981                 element = element.getParentElement();
2982             } while (parent != null && element != null &&
2983                      element != parent.getElement());
2984 
2985             if (getChildCount() > 0) {
2986                 Element elem = getElement();
2987                 int pos = e.getOffset();
2988                 int index0 = getClosestInfoIndex(pos);
2989                 if (index0 == -1 &&
2990                     e.getType() == DocumentEvent.EventType.REMOVE &&
2991                     pos >= elem.getEndOffset()) {
2992                     // Event beyond our offsets. We may have represented this,
2993                     // that is the remove may have removed one of our child
2994                     // Elements that represented this, so, we should foward
2995                     // to last element.
2996                     index0 = getChildCount() - 1;
2997                 }
2998                 ElementInfo info = (index0 >= 0) ? getChild(index0) : null;
2999                 if (info != null &&
3000                     (info.getElement().getStartOffset() == pos) && (pos > 0)) {
3001                     // If at a boundary, forward the event to the previous
3002                     // ElementInfo too.
3003                     index0 = Math.max(index0 - 1, 0);
3004                 }
3005                 int index1;
3006                 if (e.getType() != DocumentEvent.EventType.REMOVE) {
3007                     index1 = getClosestInfoIndex(pos + e.getLength());
3008                     if (index1 < 0) {
3009                         index1 = getChildCount() - 1;
3010                     }
3011                 }
3012                 else {
3013                     index1 = index0;
3014                     // A remove may result in empty elements.
3015                     while ((index1 + 1) < getChildCount() &&
3016                            getChild(index1 + 1).getElement().getEndOffset() ==
3017                            getChild(index1 + 1).getElement().getStartOffset()){
3018                         index1++;
3019                     }
3020                 }
3021                 index0 = Math.max(index0, 0);
3022                 // The check for isValid is here as in the process of
3023                 // forwarding update our child may invalidate us.
3024                 for (int i = index0; i <= index1 && isValid(); i++) {
3025                     getChild(i).update(e);
3026                 }
3027             }
3028         }
3029     }
3030 
3031     /**
3032      * DocumentListener installed on the current Document.  Will invoke
3033      * <code>update</code> on the <code>RootInfo</code> in response to
3034      * any event.
3035      */
3036     private class DocumentHandler implements DocumentListener {
3037         public void insertUpdate(DocumentEvent e) {
3038             getRootInfo().update(e);
3039         }
3040         public void removeUpdate(DocumentEvent e) {
3041             getRootInfo().update(e);
3042         }
3043         public void changedUpdate(DocumentEvent e) {
3044             getRootInfo().update(e);
3045         }
3046     }
3047 
3048     /*
3049      * PropertyChangeListener installed on the editor.
3050      */
3051     private class PropertyChangeHandler implements PropertyChangeListener {
3052         public void propertyChange(PropertyChangeEvent evt) {
3053             if (evt.getPropertyName().equals("document")) {
3054                 // handle the document change
3055                 setDocument(editor.getDocument());
3056             }
3057         }
3058     }
3059 }