1 /*
   2  * Copyright (c) 1997, 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 package javax.swing;
  26 
  27 import sun.swing.SwingUtilities2;
  28 
  29 import java.awt.*;
  30 import java.awt.event.*;
  31 import java.lang.reflect.*;
  32 import java.net.*;
  33 import java.util.*;
  34 import java.io.*;
  35 import java.util.*;
  36 
  37 import javax.swing.plaf.*;
  38 import javax.swing.text.*;
  39 import javax.swing.event.*;
  40 import javax.swing.text.html.*;
  41 import javax.accessibility.*;
  42 
  43 /**
  44  * A text component to edit various kinds of content.
  45  * You can find how-to information and examples of using editor panes in
  46  * <a href="http://docs.oracle.com/javase/tutorial/uiswing/components/text.html">Using Text Components</a>,
  47  * a section in <em>The Java Tutorial.</em>
  48  *
  49  * <p>
  50  * This component uses implementations of the
  51  * <code>EditorKit</code> to accomplish its behavior. It effectively
  52  * morphs into the proper kind of text editor for the kind
  53  * of content it is given.  The content type that editor is bound
  54  * to at any given time is determined by the <code>EditorKit</code> currently
  55  * installed.  If the content is set to a new URL, its type is used
  56  * to determine the <code>EditorKit</code> that should be used to
  57  * load the content.
  58  * <p>
  59  * By default, the following types of content are known:
  60  * <dl>
  61  * <dt><b>text/plain</b>
  62  * <dd>Plain text, which is the default the type given isn't
  63  * recognized.  The kit used in this case is an extension of
  64  * <code>DefaultEditorKit</code> that produces a wrapped plain text view.
  65  * <dt><b>text/html</b>
  66  * <dd>HTML text.  The kit used in this case is the class
  67  * <code>javax.swing.text.html.HTMLEditorKit</code>
  68  * which provides HTML 3.2 support.
  69  * <dt><b>text/rtf</b>
  70  * <dd>RTF text.  The kit used in this case is the class
  71  * <code>javax.swing.text.rtf.RTFEditorKit</code>
  72  * which provides a limited support of the Rich Text Format.
  73  * </dl>
  74  * <p>
  75  * There are several ways to load content into this component.
  76  * <ol>
  77  * <li>
  78  * The {@link #setText setText} method can be used to initialize
  79  * the component from a string.  In this case the current
  80  * <code>EditorKit</code> will be used, and the content type will be
  81  * expected to be of this type.
  82  * <li>
  83  * The {@link #read read} method can be used to initialize the
  84  * component from a <code>Reader</code>.  Note that if the content type is HTML,
  85  * relative references (e.g. for things like images) can't be resolved
  86  * unless the &lt;base&gt; tag is used or the <em>Base</em> property
  87  * on <code>HTMLDocument</code> is set.
  88  * In this case the current <code>EditorKit</code> will be used,
  89  * and the content type will be expected to be of this type.
  90  * <li>
  91  * The {@link #setPage setPage} method can be used to initialize
  92  * the component from a URL.  In this case, the content type will be
  93  * determined from the URL, and the registered <code>EditorKit</code>
  94  * for that content type will be set.
  95  * </ol>
  96  * <p>
  97  * Some kinds of content may provide hyperlink support by generating
  98  * hyperlink events.  The HTML <code>EditorKit</code> will generate
  99  * hyperlink events if the <code>JEditorPane</code> is <em>not editable</em>
 100  * (<code>JEditorPane.setEditable(false);</code> has been called).
 101  * If HTML frames are embedded in the document, the typical response would be
 102  * to change a portion of the current document.  The following code
 103  * fragment is a possible hyperlink listener implementation, that treats
 104  * HTML frame events specially, and simply displays any other activated
 105  * hyperlinks.
 106  * <pre>
 107 
 108 &nbsp;    class Hyperactive implements HyperlinkListener {
 109 &nbsp;
 110 &nbsp;        public void hyperlinkUpdate(HyperlinkEvent e) {
 111 &nbsp;            if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
 112 &nbsp;                JEditorPane pane = (JEditorPane) e.getSource();
 113 &nbsp;                if (e instanceof HTMLFrameHyperlinkEvent) {
 114 &nbsp;                    HTMLFrameHyperlinkEvent  evt = (HTMLFrameHyperlinkEvent)e;
 115 &nbsp;                    HTMLDocument doc = (HTMLDocument)pane.getDocument();
 116 &nbsp;                    doc.processHTMLFrameHyperlinkEvent(evt);
 117 &nbsp;                } else {
 118 &nbsp;                    try {
 119 &nbsp;                        pane.setPage(e.getURL());
 120 &nbsp;                    } catch (Throwable t) {
 121 &nbsp;                        t.printStackTrace();
 122 &nbsp;                    }
 123 &nbsp;                }
 124 &nbsp;            }
 125 &nbsp;        }
 126 &nbsp;    }
 127 
 128  * </pre>
 129  * <p>
 130  * For information on customizing how <b>text/html</b> is rendered please see
 131  * {@link #W3C_LENGTH_UNITS} and {@link #HONOR_DISPLAY_PROPERTIES}
 132  * <p>
 133  * Culturally dependent information in some documents is handled through
 134  * a mechanism called character encoding.  Character encoding is an
 135  * unambiguous mapping of the members of a character set (letters, ideographs,
 136  * digits, symbols, or control functions) to specific numeric code values. It
 137  * represents the way the file is stored. Example character encodings are
 138  * ISO-8859-1, ISO-8859-5, Shift-jis, Euc-jp, and UTF-8. When the file is
 139  * passed to an user agent (<code>JEditorPane</code>) it is converted to
 140  * the document character set (ISO-10646 aka Unicode).
 141  * <p>
 142  * There are multiple ways to get a character set mapping to happen
 143  * with <code>JEditorPane</code>.
 144  * <ol>
 145  * <li>
 146  * One way is to specify the character set as a parameter of the MIME
 147  * type.  This will be established by a call to the
 148  * {@link #setContentType setContentType} method.  If the content
 149  * is loaded by the {@link #setPage setPage} method the content
 150  * type will have been set according to the specification of the URL.
 151  * It the file is loaded directly, the content type would be expected to
 152  * have been set prior to loading.
 153  * <li>
 154  * Another way the character set can be specified is in the document itself.
 155  * This requires reading the document prior to determining the character set
 156  * that is desired.  To handle this, it is expected that the
 157  * <code>EditorKit</code>.read operation throw a
 158  * <code>ChangedCharSetException</code> which will
 159  * be caught.  The read is then restarted with a new Reader that uses
 160  * the character set specified in the <code>ChangedCharSetException</code>
 161  * (which is an <code>IOException</code>).
 162  * </ol>
 163  *
 164  * <dl>
 165  * <dt><b>Newlines</b>
 166  * <dd>
 167  * For a discussion on how newlines are handled, see
 168  * <a href="text/DefaultEditorKit.html">DefaultEditorKit</a>.
 169  * </dl>
 170  *
 171  * <p>
 172  * <strong>Warning:</strong> Swing is not thread safe. For more
 173  * information see <a
 174  * href="package-summary.html#threading">Swing's Threading
 175  * Policy</a>.
 176  * <p>
 177  * <strong>Warning:</strong>
 178  * Serialized objects of this class will not be compatible with
 179  * future Swing releases. The current serialization support is
 180  * appropriate for short term storage or RMI between applications running
 181  * the same version of Swing.  As of 1.4, support for long term storage
 182  * of all JavaBeans&trade;
 183  * has been added to the <code>java.beans</code> package.
 184  * Please see {@link java.beans.XMLEncoder}.
 185  *
 186  * @beaninfo
 187  *   attribute: isContainer false
 188  * description: A text component to edit various types of content.
 189  *
 190  * @author  Timothy Prinzing
 191  */
 192 @SuppressWarnings("serial") // Same-version serialization only
 193 public class JEditorPane extends JTextComponent {
 194 
 195     /**
 196      * Creates a new <code>JEditorPane</code>.
 197      * The document model is set to <code>null</code>.
 198      */
 199     public JEditorPane() {
 200         super();
 201         setFocusCycleRoot(true);
 202         setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
 203                 public Component getComponentAfter(Container focusCycleRoot,
 204                                                    Component aComponent) {
 205                     if (focusCycleRoot != JEditorPane.this ||
 206                         (!isEditable() && getComponentCount() > 0)) {
 207                         return super.getComponentAfter(focusCycleRoot,
 208                                                        aComponent);
 209                     } else {
 210                         Container rootAncestor = getFocusCycleRootAncestor();
 211                         return (rootAncestor != null)
 212                             ? rootAncestor.getFocusTraversalPolicy().
 213                                   getComponentAfter(rootAncestor,
 214                                                     JEditorPane.this)
 215                             : null;
 216                     }
 217                 }
 218                 public Component getComponentBefore(Container focusCycleRoot,
 219                                                     Component aComponent) {
 220                     if (focusCycleRoot != JEditorPane.this ||
 221                         (!isEditable() && getComponentCount() > 0)) {
 222                         return super.getComponentBefore(focusCycleRoot,
 223                                                         aComponent);
 224                     } else {
 225                         Container rootAncestor = getFocusCycleRootAncestor();
 226                         return (rootAncestor != null)
 227                             ? rootAncestor.getFocusTraversalPolicy().
 228                                   getComponentBefore(rootAncestor,
 229                                                      JEditorPane.this)
 230                             : null;
 231                     }
 232                 }
 233                 public Component getDefaultComponent(Container focusCycleRoot)
 234                 {
 235                     return (focusCycleRoot != JEditorPane.this ||
 236                             (!isEditable() && getComponentCount() > 0))
 237                         ? super.getDefaultComponent(focusCycleRoot)
 238                         : null;
 239                 }
 240                 protected boolean accept(Component aComponent) {
 241                     return (aComponent != JEditorPane.this)
 242                         ? super.accept(aComponent)
 243                         : false;
 244                 }
 245             });
 246         LookAndFeel.installProperty(this,
 247                                     "focusTraversalKeysForward",
 248                                     JComponent.
 249                                     getManagingFocusForwardTraversalKeys());
 250         LookAndFeel.installProperty(this,
 251                                     "focusTraversalKeysBackward",
 252                                     JComponent.
 253                                     getManagingFocusBackwardTraversalKeys());
 254     }
 255 
 256     /**
 257      * Creates a <code>JEditorPane</code> based on a specified URL for input.
 258      *
 259      * @param initialPage the URL
 260      * @exception IOException if the URL is <code>null</code>
 261      *          or cannot be accessed
 262      */
 263     public JEditorPane(URL initialPage) throws IOException {
 264         this();
 265         setPage(initialPage);
 266     }
 267 
 268     /**
 269      * Creates a <code>JEditorPane</code> based on a string containing
 270      * a URL specification.
 271      *
 272      * @param url the URL
 273      * @exception IOException if the URL is <code>null</code> or
 274      *          cannot be accessed
 275      */
 276     public JEditorPane(String url) throws IOException {
 277         this();
 278         setPage(url);
 279     }
 280 
 281     /**
 282      * Creates a <code>JEditorPane</code> that has been initialized
 283      * to the given text.  This is a convenience constructor that calls the
 284      * <code>setContentType</code> and <code>setText</code> methods.
 285      *
 286      * @param type mime type of the given text
 287      * @param text the text to initialize with; may be <code>null</code>
 288      * @exception NullPointerException if the <code>type</code> parameter
 289      *          is <code>null</code>
 290      */
 291     public JEditorPane(String type, String text) {
 292         this();
 293         setContentType(type);
 294         setText(text);
 295     }
 296 
 297     /**
 298      * Adds a hyperlink listener for notification of any changes, for example
 299      * when a link is selected and entered.
 300      *
 301      * @param listener the listener
 302      */
 303     public synchronized void addHyperlinkListener(HyperlinkListener listener) {
 304         listenerList.add(HyperlinkListener.class, listener);
 305     }
 306 
 307     /**
 308      * Removes a hyperlink listener.
 309      *
 310      * @param listener the listener
 311      */
 312     public synchronized void removeHyperlinkListener(HyperlinkListener listener) {
 313         listenerList.remove(HyperlinkListener.class, listener);
 314     }
 315 
 316     /**
 317      * Returns an array of all the <code>HyperLinkListener</code>s added
 318      * to this JEditorPane with addHyperlinkListener().
 319      *
 320      * @return all of the <code>HyperLinkListener</code>s added or an empty
 321      *         array if no listeners have been added
 322      * @since 1.4
 323      */
 324     public synchronized HyperlinkListener[] getHyperlinkListeners() {
 325         return listenerList.getListeners(javax.swing.event.HyperlinkListener.class);
 326     }
 327 
 328     /**
 329      * Notifies all listeners that have registered interest for
 330      * notification on this event type.  This is normally called
 331      * by the currently installed <code>EditorKit</code> if a content type
 332      * that supports hyperlinks is currently active and there
 333      * was activity with a link.  The listener list is processed
 334      * last to first.
 335      *
 336      * @param e the event
 337      * @see EventListenerList
 338      */
 339     public void fireHyperlinkUpdate(HyperlinkEvent e) {
 340         // Guaranteed to return a non-null array
 341         Object[] listeners = listenerList.getListenerList();
 342         // Process the listeners last to first, notifying
 343         // those that are interested in this event
 344         for (int i = listeners.length-2; i>=0; i-=2) {
 345             if (listeners[i]==HyperlinkListener.class) {
 346                 ((HyperlinkListener)listeners[i+1]).hyperlinkUpdate(e);
 347             }
 348         }
 349     }
 350 
 351 
 352     /**
 353      * Sets the current URL being displayed.  The content type of the
 354      * pane is set, and if the editor kit for the pane is
 355      * non-<code>null</code>, then
 356      * a new default document is created and the URL is read into it.
 357      * If the URL contains and reference location, the location will
 358      * be scrolled to by calling the <code>scrollToReference</code>
 359      * method. If the desired URL is the one currently being displayed,
 360      * the document will not be reloaded. To force a document
 361      * reload it is necessary to clear the stream description property
 362      * of the document. The following code shows how this can be done:
 363      *
 364      * <pre>
 365      *   Document doc = jEditorPane.getDocument();
 366      *   doc.putProperty(Document.StreamDescriptionProperty, null);
 367      * </pre>
 368      *
 369      * If the desired URL is not the one currently being
 370      * displayed, the <code>getStream</code> method is called to
 371      * give subclasses control over the stream provided.
 372      * <p>
 373      * This may load either synchronously or asynchronously
 374      * depending upon the document returned by the <code>EditorKit</code>.
 375      * If the <code>Document</code> is of type
 376      * <code>AbstractDocument</code> and has a value returned by
 377      * <code>AbstractDocument.getAsynchronousLoadPriority</code>
 378      * that is greater than or equal to zero, the page will be
 379      * loaded on a separate thread using that priority.
 380      * <p>
 381      * If the document is loaded synchronously, it will be
 382      * filled in with the stream prior to being installed into
 383      * the editor with a call to <code>setDocument</code>, which
 384      * is bound and will fire a property change event.  If an
 385      * <code>IOException</code> is thrown the partially loaded
 386      * document will
 387      * be discarded and neither the document or page property
 388      * change events will be fired.  If the document is
 389      * successfully loaded and installed, a view will be
 390      * built for it by the UI which will then be scrolled if
 391      * necessary, and then the page property change event
 392      * will be fired.
 393      * <p>
 394      * If the document is loaded asynchronously, the document
 395      * will be installed into the editor immediately using a
 396      * call to <code>setDocument</code> which will fire a
 397      * document property change event, then a thread will be
 398      * created which will begin doing the actual loading.
 399      * In this case, the page property change event will not be
 400      * fired by the call to this method directly, but rather will be
 401      * fired when the thread doing the loading has finished.
 402      * It will also be fired on the event-dispatch thread.
 403      * Since the calling thread can not throw an <code>IOException</code>
 404      * in the event of failure on the other thread, the page
 405      * property change event will be fired when the other
 406      * thread is done whether the load was successful or not.
 407      *
 408      * @param page the URL of the page
 409      * @exception IOException for a <code>null</code> or invalid
 410      *          page specification, or exception from the stream being read
 411      * @see #getPage
 412      * @beaninfo
 413      *  description: the URL used to set content
 414      *        bound: true
 415      *       expert: true
 416      */
 417     public void setPage(URL page) throws IOException {
 418         if (page == null) {
 419             throw new IOException("invalid url");
 420         }
 421         URL loaded = getPage();
 422 
 423 
 424         // reset scrollbar
 425         if (!page.equals(loaded) && page.getRef() == null) {
 426             scrollRectToVisible(new Rectangle(0,0,1,1));
 427         }
 428         boolean reloaded = false;
 429         Object postData = getPostData();
 430         if ((loaded == null) || !loaded.sameFile(page) || (postData != null)) {
 431             // different url or POST method, load the new content
 432 
 433             int p = getAsynchronousLoadPriority(getDocument());
 434             if (p < 0) {
 435                 // open stream synchronously
 436                 InputStream in = getStream(page);
 437                 if (kit != null) {
 438                     Document doc = initializeModel(kit, page);
 439 
 440                     // At this point, one could either load up the model with no
 441                     // view notifications slowing it down (i.e. best synchronous
 442                     // behavior) or set the model and start to feed it on a separate
 443                     // thread (best asynchronous behavior).
 444                     p = getAsynchronousLoadPriority(doc);
 445                     if (p >= 0) {
 446                         // load asynchronously
 447                         setDocument(doc);
 448                         synchronized(this) {
 449                             pageLoader = new PageLoader(doc, in, loaded, page);
 450                             pageLoader.execute();
 451                         }
 452                         return;
 453                     }
 454                     read(in, doc);
 455                     setDocument(doc);
 456                     reloaded = true;
 457                 }
 458             } else {
 459                 // we may need to cancel background loading
 460                 if (pageLoader != null) {
 461                     pageLoader.cancel(true);
 462                 }
 463 
 464                 // Do everything in a background thread.
 465                 // Model initialization is deferred to that thread, too.
 466                 pageLoader = new PageLoader(null, null, loaded, page);
 467                 pageLoader.execute();
 468                 return;
 469             }
 470         }
 471         final String reference = page.getRef();
 472         if (reference != null) {
 473             if (!reloaded) {
 474                 scrollToReference(reference);
 475             }
 476             else {
 477                 // Have to scroll after painted.
 478                 SwingUtilities.invokeLater(new Runnable() {
 479                     public void run() {
 480                         scrollToReference(reference);
 481                     }
 482                 });
 483             }
 484             getDocument().putProperty(Document.StreamDescriptionProperty, page);
 485         }
 486         firePropertyChange("page", loaded, page);
 487     }
 488 
 489     /**
 490      * Create model and initialize document properties from page properties.
 491      */
 492     private Document initializeModel(EditorKit kit, URL page) {
 493         Document doc = kit.createDefaultDocument();
 494         if (pageProperties != null) {
 495             // transfer properties discovered in stream to the
 496             // document property collection.
 497             for (Enumeration<String> e = pageProperties.keys(); e.hasMoreElements() ;) {
 498                 String key = e.nextElement();
 499                 doc.putProperty(key, pageProperties.get(key));
 500             }
 501             pageProperties.clear();
 502         }
 503         if (doc.getProperty(Document.StreamDescriptionProperty) == null) {
 504             doc.putProperty(Document.StreamDescriptionProperty, page);
 505         }
 506         return doc;
 507     }
 508 
 509     /**
 510      * Return load priority for the document or -1 if priority not supported.
 511      */
 512     private int getAsynchronousLoadPriority(Document doc) {
 513         return (doc instanceof AbstractDocument ?
 514             ((AbstractDocument) doc).getAsynchronousLoadPriority() : -1);
 515     }
 516 
 517     /**
 518      * This method initializes from a stream.  If the kit is
 519      * set to be of type <code>HTMLEditorKit</code>, and the
 520      * <code>desc</code> parameter is an <code>HTMLDocument</code>,
 521      * then it invokes the <code>HTMLEditorKit</code> to initiate
 522      * the read. Otherwise it calls the superclass
 523      * method which loads the model as plain text.
 524      *
 525      * @param in the stream from which to read
 526      * @param desc an object describing the stream
 527      * @exception IOException as thrown by the stream being
 528      *          used to initialize
 529      * @see JTextComponent#read
 530      * @see #setDocument
 531      */
 532     public void read(InputStream in, Object desc) throws IOException {
 533 
 534         if (desc instanceof HTMLDocument &&
 535             kit instanceof HTMLEditorKit) {
 536             HTMLDocument hdoc = (HTMLDocument) desc;
 537             setDocument(hdoc);
 538             read(in, hdoc);
 539         } else {
 540             String charset = (String) getClientProperty("charset");
 541             Reader r = (charset != null) ? new InputStreamReader(in, charset) :
 542                 new InputStreamReader(in);
 543             super.read(r, desc);
 544         }
 545     }
 546 
 547 
 548     /**
 549      * This method invokes the <code>EditorKit</code> to initiate a
 550      * read.  In the case where a <code>ChangedCharSetException</code>
 551      * is thrown this exception will contain the new CharSet.
 552      * Therefore the <code>read</code> operation
 553      * is then restarted after building a new Reader with the new charset.
 554      *
 555      * @param in the inputstream to use
 556      * @param doc the document to load
 557      *
 558      */
 559     void read(InputStream in, Document doc) throws IOException {
 560         if (! Boolean.TRUE.equals(doc.getProperty("IgnoreCharsetDirective"))) {
 561             final int READ_LIMIT = 1024 * 10;
 562             in = new BufferedInputStream(in, READ_LIMIT);
 563             in.mark(READ_LIMIT);
 564         }
 565         try {
 566             String charset = (String) getClientProperty("charset");
 567             Reader r = (charset != null) ? new InputStreamReader(in, charset) :
 568                 new InputStreamReader(in);
 569             kit.read(r, doc, 0);
 570         } catch (BadLocationException e) {
 571             throw new IOException(e.getMessage());
 572         } catch (ChangedCharSetException changedCharSetException) {
 573             String charSetSpec = changedCharSetException.getCharSetSpec();
 574             if (changedCharSetException.keyEqualsCharSet()) {
 575                 putClientProperty("charset", charSetSpec);
 576             } else {
 577                 setCharsetFromContentTypeParameters(charSetSpec);
 578             }
 579             try {
 580                 in.reset();
 581             } catch (IOException exception) {
 582                 //mark was invalidated
 583                 in.close();
 584                 URL url = (URL)doc.getProperty(Document.StreamDescriptionProperty);
 585                 if (url != null) {
 586                     URLConnection conn = url.openConnection();
 587                     in = conn.getInputStream();
 588                 } else {
 589                     //there is nothing we can do to recover stream
 590                     throw changedCharSetException;
 591                 }
 592             }
 593             try {
 594                 doc.remove(0, doc.getLength());
 595             } catch (BadLocationException e) {}
 596             doc.putProperty("IgnoreCharsetDirective", Boolean.valueOf(true));
 597             read(in, doc);
 598         }
 599     }
 600 
 601 
 602     /**
 603      * Loads a stream into the text document model.
 604      */
 605     class PageLoader extends SwingWorker<URL, Object> {
 606 
 607         /**
 608          * Construct an asynchronous page loader.
 609          */
 610         PageLoader(Document doc, InputStream in, URL old, URL page) {
 611             this.in = in;
 612             this.old = old;
 613             this.page = page;
 614             this.doc = doc;
 615         }
 616 
 617         /**
 618          * Try to load the document, then scroll the view
 619          * to the reference (if specified).  When done, fire
 620          * a page property change event.
 621          */
 622         protected URL doInBackground() {
 623             boolean pageLoaded = false;
 624             try {
 625                 if (in == null) {
 626                     in = getStream(page);
 627                     if (kit == null) {
 628                         // We received document of unknown content type.
 629                         UIManager.getLookAndFeel().
 630                                 provideErrorFeedback(JEditorPane.this);
 631                         return old;
 632                     }
 633                 }
 634 
 635                 if (doc == null) {
 636                     try {
 637                         SwingUtilities.invokeAndWait(new Runnable() {
 638                             public void run() {
 639                                 doc = initializeModel(kit, page);
 640                                 setDocument(doc);
 641                             }
 642                         });
 643                     } catch (InvocationTargetException ex) {
 644                         UIManager.getLookAndFeel().provideErrorFeedback(
 645                                                             JEditorPane.this);
 646                         return old;
 647                     } catch (InterruptedException ex) {
 648                         UIManager.getLookAndFeel().provideErrorFeedback(
 649                                                             JEditorPane.this);
 650                         return old;
 651                     }
 652                 }
 653 
 654                 read(in, doc);
 655                 URL page = (URL) doc.getProperty(Document.StreamDescriptionProperty);
 656                 String reference = page.getRef();
 657                 if (reference != null) {
 658                     // scroll the page if necessary, but do it on the
 659                     // event thread... that is the only guarantee that
 660                     // modelToView can be safely called.
 661                     Runnable callScrollToReference = new Runnable() {
 662                         public void run() {
 663                             URL u = (URL) getDocument().getProperty
 664                                 (Document.StreamDescriptionProperty);
 665                             String ref = u.getRef();
 666                             scrollToReference(ref);
 667                         }
 668                     };
 669                     SwingUtilities.invokeLater(callScrollToReference);
 670                 }
 671                 pageLoaded = true;
 672             } catch (IOException ioe) {
 673                 UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
 674             } finally {
 675                 if (pageLoaded) {
 676                     SwingUtilities.invokeLater(new Runnable() {
 677                         public void run() {
 678                             JEditorPane.this.firePropertyChange("page", old, page);
 679                         }
 680                     });
 681                 }
 682                 return (pageLoaded ? page : old);
 683             }
 684         }
 685 
 686         /**
 687          * The stream to load the document with
 688          */
 689         InputStream in;
 690 
 691         /**
 692          * URL of the old page that was replaced (for the property change event)
 693          */
 694         URL old;
 695 
 696         /**
 697          * URL of the page being loaded (for the property change event)
 698          */
 699         URL page;
 700 
 701         /**
 702          * The Document instance to load into. This is cached in case a
 703          * new Document is created between the time the thread this is created
 704          * and run.
 705          */
 706         Document doc;
 707     }
 708 
 709     /**
 710      * Fetches a stream for the given URL, which is about to
 711      * be loaded by the <code>setPage</code> method.  By
 712      * default, this simply opens the URL and returns the
 713      * stream.  This can be reimplemented to do useful things
 714      * like fetch the stream from a cache, monitor the progress
 715      * of the stream, etc.
 716      * <p>
 717      * This method is expected to have the the side effect of
 718      * establishing the content type, and therefore setting the
 719      * appropriate <code>EditorKit</code> to use for loading the stream.
 720      * <p>
 721      * If this the stream was an http connection, redirects
 722      * will be followed and the resulting URL will be set as
 723      * the <code>Document.StreamDescriptionProperty</code> so that relative
 724      * URL's can be properly resolved.
 725      *
 726      * @param page  the URL of the page
 727      */
 728     protected InputStream getStream(URL page) throws IOException {
 729         final URLConnection conn = page.openConnection();
 730         if (conn instanceof HttpURLConnection) {
 731             HttpURLConnection hconn = (HttpURLConnection) conn;
 732             hconn.setInstanceFollowRedirects(false);
 733             Object postData = getPostData();
 734             if (postData != null) {
 735                 handlePostData(hconn, postData);
 736             }
 737             int response = hconn.getResponseCode();
 738             boolean redirect = (response >= 300 && response <= 399);
 739 
 740             /*
 741              * In the case of a redirect, we want to actually change the URL
 742              * that was input to the new, redirected URL
 743              */
 744             if (redirect) {
 745                 String loc = conn.getHeaderField("Location");
 746                 if (loc.startsWith("http", 0)) {
 747                     page = new URL(loc);
 748                 } else {
 749                     page = new URL(page, loc);
 750                 }
 751                 return getStream(page);
 752             }
 753         }
 754 
 755         // Connection properties handler should be forced to run on EDT,
 756         // as it instantiates the EditorKit.
 757         if (SwingUtilities.isEventDispatchThread()) {
 758             handleConnectionProperties(conn);
 759         } else {
 760             try {
 761                 SwingUtilities.invokeAndWait(new Runnable() {
 762                     public void run() {
 763                         handleConnectionProperties(conn);
 764                     }
 765                 });
 766             } catch (InterruptedException e) {
 767                 throw new RuntimeException(e);
 768             } catch (InvocationTargetException e) {
 769                 throw new RuntimeException(e);
 770             }
 771         }
 772         return conn.getInputStream();
 773     }
 774 
 775     /**
 776      * Handle URL connection properties (most notably, content type).
 777      */
 778     private void handleConnectionProperties(URLConnection conn) {
 779         if (pageProperties == null) {
 780             pageProperties = new Hashtable<String, Object>();
 781         }
 782         String type = conn.getContentType();
 783         if (type != null) {
 784             setContentType(type);
 785             pageProperties.put("content-type", type);
 786         }
 787         pageProperties.put(Document.StreamDescriptionProperty, conn.getURL());
 788         String enc = conn.getContentEncoding();
 789         if (enc != null) {
 790             pageProperties.put("content-encoding", enc);
 791         }
 792     }
 793 
 794     private Object getPostData() {
 795         return getDocument().getProperty(PostDataProperty);
 796     }
 797 
 798     private void handlePostData(HttpURLConnection conn, Object postData)
 799                                                             throws IOException {
 800         conn.setDoOutput(true);
 801         DataOutputStream os = null;
 802         try {
 803             conn.setRequestProperty("Content-Type",
 804                     "application/x-www-form-urlencoded");
 805             os = new DataOutputStream(conn.getOutputStream());
 806             os.writeBytes((String) postData);
 807         } finally {
 808             if (os != null) {
 809                 os.close();
 810             }
 811         }
 812     }
 813 
 814 
 815     /**
 816      * Scrolls the view to the given reference location
 817      * (that is, the value returned by the <code>UL.getRef</code>
 818      * method for the URL being displayed).  By default, this
 819      * method only knows how to locate a reference in an
 820      * HTMLDocument.  The implementation calls the
 821      * <code>scrollRectToVisible</code> method to
 822      * accomplish the actual scrolling.  If scrolling to a
 823      * reference location is needed for document types other
 824      * than HTML, this method should be reimplemented.
 825      * This method will have no effect if the component
 826      * is not visible.
 827      *
 828      * @param reference the named location to scroll to
 829      */
 830     public void scrollToReference(String reference) {
 831         Document d = getDocument();
 832         if (d instanceof HTMLDocument) {
 833             HTMLDocument doc = (HTMLDocument) d;
 834             HTMLDocument.Iterator iter = doc.getIterator(HTML.Tag.A);
 835             for (; iter.isValid(); iter.next()) {
 836                 AttributeSet a = iter.getAttributes();
 837                 String nm = (String) a.getAttribute(HTML.Attribute.NAME);
 838                 if ((nm != null) && nm.equals(reference)) {
 839                     // found a matching reference in the document.
 840                     try {
 841                         int pos = iter.getStartOffset();
 842                         Rectangle r = modelToView(pos);
 843                         if (r != null) {
 844                             // the view is visible, scroll it to the
 845                             // center of the current visible area.
 846                             Rectangle vis = getVisibleRect();
 847                             //r.y -= (vis.height / 2);
 848                             r.height = vis.height;
 849                             scrollRectToVisible(r);
 850                             setCaretPosition(pos);
 851                         }
 852                     } catch (BadLocationException ble) {
 853                         UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
 854                     }
 855                 }
 856             }
 857         }
 858     }
 859 
 860     /**
 861      * Gets the current URL being displayed.  If a URL was
 862      * not specified in the creation of the document, this
 863      * will return <code>null</code>, and relative URL's will not be
 864      * resolved.
 865      *
 866      * @return the URL, or <code>null</code> if none
 867      */
 868     public URL getPage() {
 869         return (URL) getDocument().getProperty(Document.StreamDescriptionProperty);
 870     }
 871 
 872     /**
 873      * Sets the current URL being displayed.
 874      *
 875      * @param url the URL for display
 876      * @exception IOException for a <code>null</code> or invalid URL
 877      *          specification
 878      */
 879     public void setPage(String url) throws IOException {
 880         if (url == null) {
 881             throw new IOException("invalid url");
 882         }
 883         URL page = new URL(url);
 884         setPage(page);
 885     }
 886 
 887     /**
 888      * Gets the class ID for the UI.
 889      *
 890      * @return the string "EditorPaneUI"
 891      * @see JComponent#getUIClassID
 892      * @see UIDefaults#getUI
 893      */
 894     public String getUIClassID() {
 895         return uiClassID;
 896     }
 897 
 898     /**
 899      * Creates the default editor kit (<code>PlainEditorKit</code>) for when
 900      * the component is first created.
 901      *
 902      * @return the editor kit
 903      */
 904     protected EditorKit createDefaultEditorKit() {
 905         return new PlainEditorKit();
 906     }
 907 
 908     /**
 909      * Fetches the currently installed kit for handling content.
 910      * <code>createDefaultEditorKit</code> is called to set up a default
 911      * if necessary.
 912      *
 913      * @return the editor kit
 914      */
 915     public EditorKit getEditorKit() {
 916         if (kit == null) {
 917             kit = createDefaultEditorKit();
 918             isUserSetEditorKit = false;
 919         }
 920         return kit;
 921     }
 922 
 923     /**
 924      * Gets the type of content that this editor
 925      * is currently set to deal with.  This is
 926      * defined to be the type associated with the
 927      * currently installed <code>EditorKit</code>.
 928      *
 929      * @return the content type, <code>null</code> if no editor kit set
 930      */
 931     public final String getContentType() {
 932         return (kit != null) ? kit.getContentType() : null;
 933     }
 934 
 935     /**
 936      * Sets the type of content that this editor
 937      * handles.  This calls <code>getEditorKitForContentType</code>,
 938      * and then <code>setEditorKit</code> if an editor kit can
 939      * be successfully located.  This is mostly convenience method
 940      * that can be used as an alternative to calling
 941      * <code>setEditorKit</code> directly.
 942      * <p>
 943      * If there is a charset definition specified as a parameter
 944      * of the content type specification, it will be used when
 945      * loading input streams using the associated <code>EditorKit</code>.
 946      * For example if the type is specified as
 947      * <code>text/html; charset=EUC-JP</code> the content
 948      * will be loaded using the <code>EditorKit</code> registered for
 949      * <code>text/html</code> and the Reader provided to
 950      * the <code>EditorKit</code> to load unicode into the document will
 951      * use the <code>EUC-JP</code> charset for translating
 952      * to unicode.  If the type is not recognized, the content
 953      * will be loaded using the <code>EditorKit</code> registered
 954      * for plain text, <code>text/plain</code>.
 955      *
 956      * @param type the non-<code>null</code> mime type for the content editing
 957      *   support
 958      * @see #getContentType
 959      * @beaninfo
 960      *  description: the type of content
 961      * @throws NullPointerException if the <code>type</code> parameter
 962      *          is <code>null</code>
 963      */
 964     public final void setContentType(String type) {
 965         // The type could have optional info is part of it,
 966         // for example some charset info.  We need to strip that
 967         // of and save it.
 968         int parm = type.indexOf(";");
 969         if (parm > -1) {
 970             // Save the paramList.
 971             String paramList = type.substring(parm);
 972             // update the content type string.
 973             type = type.substring(0, parm).trim();
 974             if (type.toLowerCase().startsWith("text/")) {
 975                 setCharsetFromContentTypeParameters(paramList);
 976             }
 977         }
 978         if ((kit == null) || (! type.equals(kit.getContentType()))
 979                 || !isUserSetEditorKit) {
 980             EditorKit k = getEditorKitForContentType(type);
 981             if (k != null && k != kit) {
 982                 setEditorKit(k);
 983                 isUserSetEditorKit = false;
 984             }
 985         }
 986 
 987     }
 988 
 989     /**
 990      * This method gets the charset information specified as part
 991      * of the content type in the http header information.
 992      */
 993     private void setCharsetFromContentTypeParameters(String paramlist) {
 994         String charset;
 995         try {
 996             // paramlist is handed to us with a leading ';', strip it.
 997             int semi = paramlist.indexOf(';');
 998             if (semi > -1 && semi < paramlist.length()-1) {
 999                 paramlist = paramlist.substring(semi + 1);
1000             }
1001 
1002             if (paramlist.length() > 0) {
1003                 // parse the paramlist into attr-value pairs & get the
1004                 // charset pair's value
1005                 HeaderParser hdrParser = new HeaderParser(paramlist);
1006                 charset = hdrParser.findValue("charset");
1007                 if (charset != null) {
1008                     putClientProperty("charset", charset);
1009                 }
1010             }
1011         }
1012         catch (IndexOutOfBoundsException e) {
1013             // malformed parameter list, use charset we have
1014         }
1015         catch (NullPointerException e) {
1016             // malformed parameter list, use charset we have
1017         }
1018         catch (Exception e) {
1019             // malformed parameter list, use charset we have; but complain
1020             System.err.println("JEditorPane.getCharsetFromContentTypeParameters failed on: " + paramlist);
1021             e.printStackTrace();
1022         }
1023     }
1024 
1025 
1026     /**
1027      * Sets the currently installed kit for handling
1028      * content.  This is the bound property that
1029      * establishes the content type of the editor.
1030      * Any old kit is first deinstalled, then if kit is
1031      * non-<code>null</code>,
1032      * the new kit is installed, and a default document created for it.
1033      * A <code>PropertyChange</code> event ("editorKit") is always fired when
1034      * <code>setEditorKit</code> is called.
1035      * <p>
1036      * <em>NOTE: This has the side effect of changing the model,
1037      * because the <code>EditorKit</code> is the source of how a
1038      * particular type
1039      * of content is modeled.  This method will cause <code>setDocument</code>
1040      * to be called on behalf of the caller to ensure integrity
1041      * of the internal state.</em>
1042      *
1043      * @param kit the desired editor behavior
1044      * @see #getEditorKit
1045      * @beaninfo
1046      *  description: the currently installed kit for handling content
1047      *        bound: true
1048      *       expert: true
1049      */
1050     public void setEditorKit(EditorKit kit) {
1051         EditorKit old = this.kit;
1052         isUserSetEditorKit = true;
1053         if (old != null) {
1054             old.deinstall(this);
1055         }
1056         this.kit = kit;
1057         if (this.kit != null) {
1058             this.kit.install(this);
1059             setDocument(this.kit.createDefaultDocument());
1060         }
1061         firePropertyChange("editorKit", old, kit);
1062     }
1063 
1064     /**
1065      * Fetches the editor kit to use for the given type
1066      * of content.  This is called when a type is requested
1067      * that doesn't match the currently installed type.
1068      * If the component doesn't have an <code>EditorKit</code> registered
1069      * for the given type, it will try to create an
1070      * <code>EditorKit</code> from the default <code>EditorKit</code> registry.
1071      * If that fails, a <code>PlainEditorKit</code> is used on the
1072      * assumption that all text documents can be represented
1073      * as plain text.
1074      * <p>
1075      * This method can be reimplemented to use some
1076      * other kind of type registry.  This can
1077      * be reimplemented to use the Java Activation
1078      * Framework, for example.
1079      *
1080      * @param type the non-<code>null</code> content type
1081      * @return the editor kit
1082      */
1083     public EditorKit getEditorKitForContentType(String type) {
1084         if (typeHandlers == null) {
1085             typeHandlers = new Hashtable<String, EditorKit>(3);
1086         }
1087         EditorKit k = typeHandlers.get(type);
1088         if (k == null) {
1089             k = createEditorKitForContentType(type);
1090             if (k != null) {
1091                 setEditorKitForContentType(type, k);
1092             }
1093         }
1094         if (k == null) {
1095             k = createDefaultEditorKit();
1096         }
1097         return k;
1098     }
1099 
1100     /**
1101      * Directly sets the editor kit to use for the given type.  A
1102      * look-and-feel implementation might use this in conjunction
1103      * with <code>createEditorKitForContentType</code> to install handlers for
1104      * content types with a look-and-feel bias.
1105      *
1106      * @param type the non-<code>null</code> content type
1107      * @param k the editor kit to be set
1108      */
1109     public void setEditorKitForContentType(String type, EditorKit k) {
1110         if (typeHandlers == null) {
1111             typeHandlers = new Hashtable<String, EditorKit>(3);
1112         }
1113         typeHandlers.put(type, k);
1114     }
1115 
1116     /**
1117      * Replaces the currently selected content with new content
1118      * represented by the given string.  If there is no selection
1119      * this amounts to an insert of the given text.  If there
1120      * is no replacement text (i.e. the content string is empty
1121      * or <code>null</code>) this amounts to a removal of the
1122      * current selection.  The replacement text will have the
1123      * attributes currently defined for input.  If the component is not
1124      * editable, beep and return.
1125      *
1126      * @param content  the content to replace the selection with.  This
1127      *   value can be <code>null</code>
1128      */
1129     @Override
1130     public void replaceSelection(String content) {
1131         if (! isEditable()) {
1132             UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1133             return;
1134         }
1135         EditorKit kit = getEditorKit();
1136         if(kit instanceof StyledEditorKit) {
1137             try {
1138                 Document doc = getDocument();
1139                 Caret caret = getCaret();
1140                 boolean composedTextSaved = saveComposedText(caret.getDot());
1141                 int p0 = Math.min(caret.getDot(), caret.getMark());
1142                 int p1 = Math.max(caret.getDot(), caret.getMark());
1143                 if (doc instanceof AbstractDocument) {
1144                     ((AbstractDocument)doc).replace(p0, p1 - p0, content,
1145                               ((StyledEditorKit)kit).getInputAttributes());
1146                 }
1147                 else {
1148                     if (p0 != p1) {
1149                         doc.remove(p0, p1 - p0);
1150                     }
1151                     if (content != null && content.length() > 0) {
1152                         doc.insertString(p0, content, ((StyledEditorKit)kit).
1153                                          getInputAttributes());
1154                     }
1155                 }
1156                 if (composedTextSaved) {
1157                     restoreComposedText();
1158                 }
1159             } catch (BadLocationException e) {
1160                 UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1161             }
1162         }
1163         else {
1164             super.replaceSelection(content);
1165         }
1166     }
1167 
1168     /**
1169      * Creates a handler for the given type from the default registry
1170      * of editor kits.  The registry is created if necessary.  If the
1171      * registered class has not yet been loaded, an attempt
1172      * is made to dynamically load the prototype of the kit for the
1173      * given type.  If the type was registered with a <code>ClassLoader</code>,
1174      * that <code>ClassLoader</code> will be used to load the prototype.
1175      * If there was no registered <code>ClassLoader</code>,
1176      * <code>Class.forName</code> will be used to load the prototype.
1177      * <p>
1178      * Once a prototype <code>EditorKit</code> instance is successfully
1179      * located, it is cloned and the clone is returned.
1180      *
1181      * @param type the content type
1182      * @return the editor kit, or <code>null</code> if there is nothing
1183      *   registered for the given type
1184      */
1185     public static EditorKit createEditorKitForContentType(String type) {
1186         Hashtable<String, EditorKit> kitRegistry = getKitRegisty();
1187         EditorKit k = kitRegistry.get(type);
1188         if (k == null) {
1189             // try to dynamically load the support
1190             String classname = getKitTypeRegistry().get(type);
1191             ClassLoader loader = getKitLoaderRegistry().get(type);
1192             try {
1193                 Class c;
1194                 if (loader != null) {
1195                     c = loader.loadClass(classname);
1196                 } else {
1197                     // Will only happen if developer has invoked
1198                     // registerEditorKitForContentType(type, class, null).
1199                     c = Class.forName(classname, true, Thread.currentThread().
1200                                       getContextClassLoader());
1201                 }
1202                 k = (EditorKit) c.newInstance();
1203                 kitRegistry.put(type, k);
1204             } catch (Throwable e) {
1205                 k = null;
1206             }
1207         }
1208 
1209         // create a copy of the prototype or null if there
1210         // is no prototype.
1211         if (k != null) {
1212             return (EditorKit) k.clone();
1213         }
1214         return null;
1215     }
1216 
1217     /**
1218      * Establishes the default bindings of <code>type</code> to
1219      * <code>classname</code>.
1220      * The class will be dynamically loaded later when actually
1221      * needed, and can be safely changed before attempted uses
1222      * to avoid loading unwanted classes.  The prototype
1223      * <code>EditorKit</code> will be loaded with <code>Class.forName</code>
1224      * when registered with this method.
1225      *
1226      * @param type the non-<code>null</code> content type
1227      * @param classname the class to load later
1228      */
1229     public static void registerEditorKitForContentType(String type, String classname) {
1230         registerEditorKitForContentType(type, classname,Thread.currentThread().
1231                                         getContextClassLoader());
1232     }
1233 
1234     /**
1235      * Establishes the default bindings of <code>type</code> to
1236      * <code>classname</code>.
1237      * The class will be dynamically loaded later when actually
1238      * needed using the given <code>ClassLoader</code>,
1239      * and can be safely changed
1240      * before attempted uses to avoid loading unwanted classes.
1241      *
1242      * @param type the non-<code>null</code> content type
1243      * @param classname the class to load later
1244      * @param loader the <code>ClassLoader</code> to use to load the name
1245      */
1246     public static void registerEditorKitForContentType(String type, String classname, ClassLoader loader) {
1247         getKitTypeRegistry().put(type, classname);
1248         getKitLoaderRegistry().put(type, loader);
1249         getKitRegisty().remove(type);
1250     }
1251 
1252     /**
1253      * Returns the currently registered <code>EditorKit</code>
1254      * class name for the type <code>type</code>.
1255      *
1256      * @param type  the non-<code>null</code> content type
1257      *
1258      * @since 1.3
1259      */
1260     public static String getEditorKitClassNameForContentType(String type) {
1261         return getKitTypeRegistry().get(type);
1262     }
1263 
1264     private static Hashtable<String, String> getKitTypeRegistry() {
1265         loadDefaultKitsIfNecessary();
1266         return (Hashtable)SwingUtilities.appContextGet(kitTypeRegistryKey);
1267     }
1268 
1269     private static Hashtable<String, ClassLoader> getKitLoaderRegistry() {
1270         loadDefaultKitsIfNecessary();
1271         return (Hashtable)SwingUtilities.appContextGet(kitLoaderRegistryKey);
1272     }
1273 
1274     private static Hashtable<String, EditorKit> getKitRegisty() {
1275         Hashtable ht = (Hashtable)SwingUtilities.appContextGet(kitRegistryKey);
1276         if (ht == null) {
1277             ht = new Hashtable(3);
1278             SwingUtilities.appContextPut(kitRegistryKey, ht);
1279         }
1280         return ht;
1281     }
1282 
1283     /**
1284      * This is invoked every time the registries are accessed. Loading
1285      * is done this way instead of via a static as the static is only
1286      * called once when running in plugin resulting in the entries only
1287      * appearing in the first applet.
1288      */
1289     private static void loadDefaultKitsIfNecessary() {
1290         if (SwingUtilities.appContextGet(kitTypeRegistryKey) == null) {
1291             synchronized(defaultEditorKitMap) {
1292                 if (defaultEditorKitMap.size() == 0) {
1293                     defaultEditorKitMap.put("text/plain",
1294                                             "javax.swing.JEditorPane$PlainEditorKit");
1295                     defaultEditorKitMap.put("text/html",
1296                                             "javax.swing.text.html.HTMLEditorKit");
1297                     defaultEditorKitMap.put("text/rtf",
1298                                             "javax.swing.text.rtf.RTFEditorKit");
1299                     defaultEditorKitMap.put("application/rtf",
1300                                             "javax.swing.text.rtf.RTFEditorKit");
1301                 }
1302             }
1303             Hashtable ht = new Hashtable();
1304             SwingUtilities.appContextPut(kitTypeRegistryKey, ht);
1305             ht = new Hashtable();
1306             SwingUtilities.appContextPut(kitLoaderRegistryKey, ht);
1307             for (String key : defaultEditorKitMap.keySet()) {
1308                 registerEditorKitForContentType(key,defaultEditorKitMap.get(key));
1309             }
1310 
1311         }
1312     }
1313 
1314     // --- java.awt.Component methods --------------------------
1315 
1316     /**
1317      * Returns the preferred size for the <code>JEditorPane</code>.
1318      * The preferred size for <code>JEditorPane</code> is slightly altered
1319      * from the preferred size of the superclass.  If the size
1320      * of the viewport has become smaller than the minimum size
1321      * of the component, the scrollable definition for tracking
1322      * width or height will turn to false.  The default viewport
1323      * layout will give the preferred size, and that is not desired
1324      * in the case where the scrollable is tracking.  In that case
1325      * the <em>normal</em> preferred size is adjusted to the
1326      * minimum size.  This allows things like HTML tables to
1327      * shrink down to their minimum size and then be laid out at
1328      * their minimum size, refusing to shrink any further.
1329      *
1330      * @return a <code>Dimension</code> containing the preferred size
1331      */
1332     public Dimension getPreferredSize() {
1333         Dimension d = super.getPreferredSize();
1334         Container parent = SwingUtilities.getUnwrappedParent(this);
1335         if (parent instanceof JViewport) {
1336             JViewport port = (JViewport) parent;
1337             TextUI ui = getUI();
1338             int prefWidth = d.width;
1339             int prefHeight = d.height;
1340             if (! getScrollableTracksViewportWidth()) {
1341                 int w = port.getWidth();
1342                 Dimension min = ui.getMinimumSize(this);
1343                 if (w != 0 && w < min.width) {
1344                     // Only adjust to min if we have a valid size
1345                     prefWidth = min.width;
1346                 }
1347             }
1348             if (! getScrollableTracksViewportHeight()) {
1349                 int h = port.getHeight();
1350                 Dimension min = ui.getMinimumSize(this);
1351                 if (h != 0 && h < min.height) {
1352                     // Only adjust to min if we have a valid size
1353                     prefHeight = min.height;
1354                 }
1355             }
1356             if (prefWidth != d.width || prefHeight != d.height) {
1357                 d = new Dimension(prefWidth, prefHeight);
1358             }
1359         }
1360         return d;
1361     }
1362 
1363     // --- JTextComponent methods -----------------------------
1364 
1365     /**
1366      * Sets the text of this <code>TextComponent</code> to the specified
1367      * content,
1368      * which is expected to be in the format of the content type of
1369      * this editor.  For example, if the type is set to <code>text/html</code>
1370      * the string should be specified in terms of HTML.
1371      * <p>
1372      * This is implemented to remove the contents of the current document,
1373      * and replace them by parsing the given string using the current
1374      * <code>EditorKit</code>.  This gives the semantics of the
1375      * superclass by not changing
1376      * out the model, while supporting the content type currently set on
1377      * this component.  The assumption is that the previous content is
1378      * relatively
1379      * small, and that the previous content doesn't have side effects.
1380      * Both of those assumptions can be violated and cause undesirable results.
1381      * To avoid this, create a new document,
1382      * <code>getEditorKit().createDefaultDocument()</code>, and replace the
1383      * existing <code>Document</code> with the new one. You are then assured the
1384      * previous <code>Document</code> won't have any lingering state.
1385      * <ol>
1386      * <li>
1387      * Leaving the existing model in place means that the old view will be
1388      * torn down, and a new view created, where replacing the document would
1389      * avoid the tear down of the old view.
1390      * <li>
1391      * Some formats (such as HTML) can install things into the document that
1392      * can influence future contents.  HTML can have style information embedded
1393      * that would influence the next content installed unexpectedly.
1394      * </ol>
1395      * <p>
1396      * An alternative way to load this component with a string would be to
1397      * create a StringReader and call the read method.  In this case the model
1398      * would be replaced after it was initialized with the contents of the
1399      * string.
1400      *
1401      * @param t the new text to be set; if <code>null</code> the old
1402      *    text will be deleted
1403      * @see #getText
1404      * @beaninfo
1405      * description: the text of this component
1406      */
1407     public void setText(String t) {
1408         try {
1409             Document doc = getDocument();
1410             doc.remove(0, doc.getLength());
1411             if (t == null || t.equals("")) {
1412                 return;
1413             }
1414             Reader r = new StringReader(t);
1415             EditorKit kit = getEditorKit();
1416             kit.read(r, doc, 0);
1417         } catch (IOException ioe) {
1418             UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1419         } catch (BadLocationException ble) {
1420             UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1421         }
1422     }
1423 
1424     /**
1425      * Returns the text contained in this <code>TextComponent</code>
1426      * in terms of the
1427      * content type of this editor.  If an exception is thrown while
1428      * attempting to retrieve the text, <code>null</code> will be returned.
1429      * This is implemented to call <code>JTextComponent.write</code> with
1430      * a <code>StringWriter</code>.
1431      *
1432      * @return the text
1433      * @see #setText
1434      */
1435     public String getText() {
1436         String txt;
1437         try {
1438             StringWriter buf = new StringWriter();
1439             write(buf);
1440             txt = buf.toString();
1441         } catch (IOException ioe) {
1442             txt = null;
1443         }
1444         return txt;
1445     }
1446 
1447     // --- Scrollable  ----------------------------------------
1448 
1449     /**
1450      * Returns true if a viewport should always force the width of this
1451      * <code>Scrollable</code> to match the width of the viewport.
1452      *
1453      * @return true if a viewport should force the Scrollables width to
1454      * match its own, false otherwise
1455      */
1456     public boolean getScrollableTracksViewportWidth() {
1457         Container parent = SwingUtilities.getUnwrappedParent(this);
1458         if (parent instanceof JViewport) {
1459             JViewport port = (JViewport) parent;
1460             TextUI ui = getUI();
1461             int w = port.getWidth();
1462             Dimension min = ui.getMinimumSize(this);
1463             Dimension max = ui.getMaximumSize(this);
1464             if ((w >= min.width) && (w <= max.width)) {
1465                 return true;
1466             }
1467         }
1468         return false;
1469     }
1470 
1471     /**
1472      * Returns true if a viewport should always force the height of this
1473      * <code>Scrollable</code> to match the height of the viewport.
1474      *
1475      * @return true if a viewport should force the
1476      *          <code>Scrollable</code>'s height to match its own,
1477      *          false otherwise
1478      */
1479     public boolean getScrollableTracksViewportHeight() {
1480         Container parent = SwingUtilities.getUnwrappedParent(this);
1481         if (parent instanceof JViewport) {
1482             JViewport port = (JViewport) parent;
1483             TextUI ui = getUI();
1484             int h = port.getHeight();
1485             Dimension min = ui.getMinimumSize(this);
1486             if (h >= min.height) {
1487                 Dimension max = ui.getMaximumSize(this);
1488                 if (h <= max.height) {
1489                     return true;
1490                 }
1491             }
1492         }
1493         return false;
1494     }
1495 
1496     // --- Serialization ------------------------------------
1497 
1498     /**
1499      * See <code>readObject</code> and <code>writeObject</code> in
1500      * <code>JComponent</code> for more
1501      * information about serialization in Swing.
1502      */
1503     private void writeObject(ObjectOutputStream s) throws IOException {
1504         s.defaultWriteObject();
1505         if (getUIClassID().equals(uiClassID)) {
1506             byte count = JComponent.getWriteObjCounter(this);
1507             JComponent.setWriteObjCounter(this, --count);
1508             if (count == 0 && ui != null) {
1509                 ui.installUI(this);
1510             }
1511         }
1512     }
1513 
1514     // --- variables ---------------------------------------
1515 
1516     private SwingWorker<URL, Object> pageLoader;
1517 
1518     /**
1519      * Current content binding of the editor.
1520      */
1521     private EditorKit kit;
1522     private boolean isUserSetEditorKit;
1523 
1524     private Hashtable<String, Object> pageProperties;
1525 
1526     /** Should be kept in sync with javax.swing.text.html.FormView counterpart. */
1527     final static String PostDataProperty = "javax.swing.JEditorPane.postdata";
1528 
1529     /**
1530      * Table of registered type handlers for this editor.
1531      */
1532     private Hashtable<String, EditorKit> typeHandlers;
1533 
1534     /*
1535      * Private AppContext keys for this class's static variables.
1536      */
1537     private static final Object kitRegistryKey =
1538         new StringBuffer("JEditorPane.kitRegistry");
1539     private static final Object kitTypeRegistryKey =
1540         new StringBuffer("JEditorPane.kitTypeRegistry");
1541     private static final Object kitLoaderRegistryKey =
1542         new StringBuffer("JEditorPane.kitLoaderRegistry");
1543 
1544     /**
1545      * @see #getUIClassID
1546      * @see #readObject
1547      */
1548     private static final String uiClassID = "EditorPaneUI";
1549 
1550 
1551     /**
1552      * Key for a client property used to indicate whether
1553      * <a href="http://www.w3.org/TR/CSS21/syndata.html#length-units">
1554      * w3c compliant</a> length units are used for html rendering.
1555      * <p>
1556      * By default this is not enabled; to enable
1557      * it set the client {@link #putClientProperty property} with this name
1558      * to <code>Boolean.TRUE</code>.
1559      *
1560      * @since 1.5
1561      */
1562     public static final String W3C_LENGTH_UNITS = "JEditorPane.w3cLengthUnits";
1563 
1564     /**
1565      * Key for a client property used to indicate whether
1566      * the default font and foreground color from the component are
1567      * used if a font or foreground color is not specified in the styled
1568      * text.
1569      * <p>
1570      * The default varies based on the look and feel;
1571      * to enable it set the client {@link #putClientProperty property} with
1572      * this name to <code>Boolean.TRUE</code>.
1573      *
1574      * @since 1.5
1575      */
1576     public static final String HONOR_DISPLAY_PROPERTIES = "JEditorPane.honorDisplayProperties";
1577 
1578     static final Map<String, String> defaultEditorKitMap = new HashMap<String, String>(0);
1579 
1580     /**
1581      * Returns a string representation of this <code>JEditorPane</code>.
1582      * This method
1583      * is intended to be used only for debugging purposes, and the
1584      * content and format of the returned string may vary between
1585      * implementations. The returned string may be empty but may not
1586      * be <code>null</code>.
1587      *
1588      * @return  a string representation of this <code>JEditorPane</code>
1589      */
1590     protected String paramString() {
1591         String kitString = (kit != null ?
1592                             kit.toString() : "");
1593         String typeHandlersString = (typeHandlers != null ?
1594                                      typeHandlers.toString() : "");
1595 
1596         return super.paramString() +
1597         ",kit=" + kitString +
1598         ",typeHandlers=" + typeHandlersString;
1599     }
1600 
1601 
1602 /////////////////
1603 // Accessibility support
1604 ////////////////
1605 
1606 
1607     /**
1608      * Gets the AccessibleContext associated with this JEditorPane.
1609      * For editor panes, the AccessibleContext takes the form of an
1610      * AccessibleJEditorPane.
1611      * A new AccessibleJEditorPane instance is created if necessary.
1612      *
1613      * @return an AccessibleJEditorPane that serves as the
1614      *         AccessibleContext of this JEditorPane
1615      */
1616     public AccessibleContext getAccessibleContext() {
1617         if (getEditorKit() instanceof HTMLEditorKit) {
1618             if (accessibleContext == null || accessibleContext.getClass() !=
1619                     AccessibleJEditorPaneHTML.class) {
1620                 accessibleContext = new AccessibleJEditorPaneHTML();
1621             }
1622         } else if (accessibleContext == null || accessibleContext.getClass() !=
1623                        AccessibleJEditorPane.class) {
1624             accessibleContext = new AccessibleJEditorPane();
1625         }
1626         return accessibleContext;
1627     }
1628 
1629     /**
1630      * This class implements accessibility support for the
1631      * <code>JEditorPane</code> class.  It provides an implementation of the
1632      * Java Accessibility API appropriate to editor pane user-interface
1633      * elements.
1634      * <p>
1635      * <strong>Warning:</strong>
1636      * Serialized objects of this class will not be compatible with
1637      * future Swing releases. The current serialization support is
1638      * appropriate for short term storage or RMI between applications running
1639      * the same version of Swing.  As of 1.4, support for long term storage
1640      * of all JavaBeans&trade;
1641      * has been added to the <code>java.beans</code> package.
1642      * Please see {@link java.beans.XMLEncoder}.
1643      */
1644     @SuppressWarnings("serial") // Same-version serialization only
1645     protected class AccessibleJEditorPane extends AccessibleJTextComponent {
1646 
1647         /**
1648          * Gets the accessibleDescription property of this object.  If this
1649          * property isn't set, returns the content type of this
1650          * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1651          *
1652          * @return the localized description of the object; <code>null</code>
1653          *      if this object does not have a description
1654          *
1655          * @see #setAccessibleName
1656          */
1657         public String getAccessibleDescription() {
1658             String description = accessibleDescription;
1659 
1660             // fallback to client property
1661             if (description == null) {
1662                 description = (String)getClientProperty(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY);
1663             }
1664             if (description == null) {
1665                 description = JEditorPane.this.getContentType();
1666             }
1667             return description;
1668         }
1669 
1670         /**
1671          * Gets the state set of this object.
1672          *
1673          * @return an instance of AccessibleStateSet describing the states
1674          * of the object
1675          * @see AccessibleStateSet
1676          */
1677         public AccessibleStateSet getAccessibleStateSet() {
1678             AccessibleStateSet states = super.getAccessibleStateSet();
1679             states.add(AccessibleState.MULTI_LINE);
1680             return states;
1681         }
1682     }
1683 
1684     /**
1685      * This class provides support for <code>AccessibleHypertext</code>,
1686      * and is used in instances where the <code>EditorKit</code>
1687      * installed in this <code>JEditorPane</code> is an instance of
1688      * <code>HTMLEditorKit</code>.
1689      * <p>
1690      * <strong>Warning:</strong>
1691      * Serialized objects of this class will not be compatible with
1692      * future Swing releases. The current serialization support is
1693      * appropriate for short term storage or RMI between applications running
1694      * the same version of Swing.  As of 1.4, support for long term storage
1695      * of all JavaBeans&trade;
1696      * has been added to the <code>java.beans</code> package.
1697      * Please see {@link java.beans.XMLEncoder}.
1698      */
1699     @SuppressWarnings("serial") // Same-version serialization only
1700     protected class AccessibleJEditorPaneHTML extends AccessibleJEditorPane {
1701 
1702         private AccessibleContext accessibleContext;
1703 
1704         public AccessibleText getAccessibleText() {
1705             return new JEditorPaneAccessibleHypertextSupport();
1706         }
1707 
1708         protected AccessibleJEditorPaneHTML () {
1709             HTMLEditorKit kit = (HTMLEditorKit)JEditorPane.this.getEditorKit();
1710             accessibleContext = kit.getAccessibleContext();
1711         }
1712 
1713         /**
1714          * Returns the number of accessible children of the object.
1715          *
1716          * @return the number of accessible children of the object.
1717          */
1718         public int getAccessibleChildrenCount() {
1719             if (accessibleContext != null) {
1720                 return accessibleContext.getAccessibleChildrenCount();
1721             } else {
1722                 return 0;
1723             }
1724         }
1725 
1726         /**
1727          * Returns the specified Accessible child of the object.  The Accessible
1728          * children of an Accessible object are zero-based, so the first child
1729          * of an Accessible child is at index 0, the second child is at index 1,
1730          * and so on.
1731          *
1732          * @param i zero-based index of child
1733          * @return the Accessible child of the object
1734          * @see #getAccessibleChildrenCount
1735          */
1736         public Accessible getAccessibleChild(int i) {
1737             if (accessibleContext != null) {
1738                 return accessibleContext.getAccessibleChild(i);
1739             } else {
1740                 return null;
1741             }
1742         }
1743 
1744         /**
1745          * Returns the Accessible child, if one exists, contained at the local
1746          * coordinate Point.
1747          *
1748          * @param p The point relative to the coordinate system of this object.
1749          * @return the Accessible, if it exists, at the specified location;
1750          * otherwise null
1751          */
1752         public Accessible getAccessibleAt(Point p) {
1753             if (accessibleContext != null && p != null) {
1754                 try {
1755                     AccessibleComponent acomp =
1756                         accessibleContext.getAccessibleComponent();
1757                     if (acomp != null) {
1758                         return acomp.getAccessibleAt(p);
1759                     } else {
1760                         return null;
1761                     }
1762                 } catch (IllegalComponentStateException e) {
1763                     return null;
1764                 }
1765             } else {
1766                 return null;
1767             }
1768         }
1769     }
1770 
1771     /**
1772      * What's returned by
1773      * <code>AccessibleJEditorPaneHTML.getAccessibleText</code>.
1774      *
1775      * Provides support for <code>AccessibleHypertext</code> in case
1776      * there is an HTML document being displayed in this
1777      * <code>JEditorPane</code>.
1778      *
1779      */
1780     protected class JEditorPaneAccessibleHypertextSupport
1781     extends AccessibleJEditorPane implements AccessibleHypertext {
1782 
1783         public class HTMLLink extends AccessibleHyperlink {
1784             Element element;
1785 
1786             public HTMLLink(Element e) {
1787                 element = e;
1788             }
1789 
1790             /**
1791              * Since the document a link is associated with may have
1792              * changed, this method returns whether this Link is valid
1793              * anymore (with respect to the document it references).
1794              *
1795              * @return a flag indicating whether this link is still valid with
1796              *         respect to the AccessibleHypertext it belongs to
1797              */
1798             public boolean isValid() {
1799                 return JEditorPaneAccessibleHypertextSupport.this.linksValid;
1800             }
1801 
1802             /**
1803              * Returns the number of accessible actions available in this Link
1804              * If there are more than one, the first one is NOT considered the
1805              * "default" action of this LINK object (e.g. in an HTML imagemap).
1806              * In general, links will have only one AccessibleAction in them.
1807              *
1808              * @return the zero-based number of Actions in this object
1809              */
1810             public int getAccessibleActionCount() {
1811                 return 1;
1812             }
1813 
1814             /**
1815              * Perform the specified Action on the object
1816              *
1817              * @param i zero-based index of actions
1818              * @return true if the the action was performed; else false.
1819              * @see #getAccessibleActionCount
1820              */
1821             public boolean doAccessibleAction(int i) {
1822                 if (i == 0 && isValid() == true) {
1823                     URL u = (URL) getAccessibleActionObject(i);
1824                     if (u != null) {
1825                         HyperlinkEvent linkEvent =
1826                             new HyperlinkEvent(JEditorPane.this, HyperlinkEvent.EventType.ACTIVATED, u);
1827                         JEditorPane.this.fireHyperlinkUpdate(linkEvent);
1828                         return true;
1829                     }
1830                 }
1831                 return false;  // link invalid or i != 0
1832             }
1833 
1834             /**
1835              * Return a String description of this particular
1836              * link action.  The string returned is the text
1837              * within the document associated with the element
1838              * which contains this link.
1839              *
1840              * @param i zero-based index of the actions
1841              * @return a String description of the action
1842              * @see #getAccessibleActionCount
1843              */
1844             public String getAccessibleActionDescription(int i) {
1845                 if (i == 0 && isValid() == true) {
1846                     Document d = JEditorPane.this.getDocument();
1847                     if (d != null) {
1848                         try {
1849                             return d.getText(getStartIndex(),
1850                                              getEndIndex() - getStartIndex());
1851                         } catch (BadLocationException exception) {
1852                             return null;
1853                         }
1854                     }
1855                 }
1856                 return null;
1857             }
1858 
1859             /**
1860              * Returns a URL object that represents the link.
1861              *
1862              * @param i zero-based index of the actions
1863              * @return an URL representing the HTML link itself
1864              * @see #getAccessibleActionCount
1865              */
1866             public Object getAccessibleActionObject(int i) {
1867                 if (i == 0 && isValid() == true) {
1868                     AttributeSet as = element.getAttributes();
1869                     AttributeSet anchor =
1870                         (AttributeSet) as.getAttribute(HTML.Tag.A);
1871                     String href = (anchor != null) ?
1872                         (String) anchor.getAttribute(HTML.Attribute.HREF) : null;
1873                     if (href != null) {
1874                         URL u;
1875                         try {
1876                             u = new URL(JEditorPane.this.getPage(), href);
1877                         } catch (MalformedURLException m) {
1878                             u = null;
1879                         }
1880                         return u;
1881                     }
1882                 }
1883                 return null;  // link invalid or i != 0
1884             }
1885 
1886             /**
1887              * Return an object that represents the link anchor,
1888              * as appropriate for that link.  E.g. from HTML:
1889              *   <a href="http://www.sun.com/access">Accessibility</a>
1890              * this method would return a String containing the text:
1891              * 'Accessibility'.
1892              *
1893              * Similarly, from this HTML:
1894              *   &lt;a HREF="#top"&gt;&lt;img src="top-hat.gif" alt="top hat"&gt;&lt;/a&gt;
1895              * this might return the object ImageIcon("top-hat.gif", "top hat");
1896              *
1897              * @param i zero-based index of the actions
1898              * @return an Object representing the hypertext anchor
1899              * @see #getAccessibleActionCount
1900              */
1901             public Object getAccessibleActionAnchor(int i) {
1902                 return getAccessibleActionDescription(i);
1903             }
1904 
1905 
1906             /**
1907              * Get the index with the hypertext document at which this
1908              * link begins
1909              *
1910              * @return index of start of link
1911              */
1912             public int getStartIndex() {
1913                 return element.getStartOffset();
1914             }
1915 
1916             /**
1917              * Get the index with the hypertext document at which this
1918              * link ends
1919              *
1920              * @return index of end of link
1921              */
1922             public int getEndIndex() {
1923                 return element.getEndOffset();
1924             }
1925         }
1926 
1927         private class LinkVector extends Vector<HTMLLink> {
1928             public int baseElementIndex(Element e) {
1929                 HTMLLink l;
1930                 for (int i = 0; i < elementCount; i++) {
1931                     l = elementAt(i);
1932                     if (l.element == e) {
1933                         return i;
1934                     }
1935                 }
1936                 return -1;
1937             }
1938         }
1939 
1940         LinkVector hyperlinks;
1941         boolean linksValid = false;
1942 
1943         /**
1944          * Build the private table mapping links to locations in the text
1945          */
1946         private void buildLinkTable() {
1947             hyperlinks.removeAllElements();
1948             Document d = JEditorPane.this.getDocument();
1949             if (d != null) {
1950                 ElementIterator ei = new ElementIterator(d);
1951                 Element e;
1952                 AttributeSet as;
1953                 AttributeSet anchor;
1954                 String href;
1955                 while ((e = ei.next()) != null) {
1956                     if (e.isLeaf()) {
1957                         as = e.getAttributes();
1958                     anchor = (AttributeSet) as.getAttribute(HTML.Tag.A);
1959                     href = (anchor != null) ?
1960                         (String) anchor.getAttribute(HTML.Attribute.HREF) : null;
1961                         if (href != null) {
1962                             hyperlinks.addElement(new HTMLLink(e));
1963                         }
1964                     }
1965                 }
1966             }
1967             linksValid = true;
1968         }
1969 
1970         /**
1971          * Make one of these puppies
1972          */
1973         public JEditorPaneAccessibleHypertextSupport() {
1974             hyperlinks = new LinkVector();
1975             Document d = JEditorPane.this.getDocument();
1976             if (d != null) {
1977                 d.addDocumentListener(new DocumentListener() {
1978                     public void changedUpdate(DocumentEvent theEvent) {
1979                         linksValid = false;
1980                     }
1981                     public void insertUpdate(DocumentEvent theEvent) {
1982                         linksValid = false;
1983                     }
1984                     public void removeUpdate(DocumentEvent theEvent) {
1985                         linksValid = false;
1986                     }
1987                 });
1988             }
1989         }
1990 
1991         /**
1992          * Returns the number of links within this hypertext doc.
1993          *
1994          * @return number of links in this hypertext doc.
1995          */
1996         public int getLinkCount() {
1997             if (linksValid == false) {
1998                 buildLinkTable();
1999             }
2000             return hyperlinks.size();
2001         }
2002 
2003         /**
2004          * Returns the index into an array of hyperlinks that
2005          * is associated with this character index, or -1 if there
2006          * is no hyperlink associated with this index.
2007          *
2008          * @param  charIndex index within the text
2009          * @return index into the set of hyperlinks for this hypertext doc.
2010          */
2011         public int getLinkIndex(int charIndex) {
2012             if (linksValid == false) {
2013                 buildLinkTable();
2014             }
2015             Element e = null;
2016             Document doc = JEditorPane.this.getDocument();
2017             if (doc != null) {
2018                 for (e = doc.getDefaultRootElement(); ! e.isLeaf(); ) {
2019                     int index = e.getElementIndex(charIndex);
2020                     e = e.getElement(index);
2021                 }
2022             }
2023 
2024             // don't need to verify that it's an HREF element; if
2025             // not, then it won't be in the hyperlinks Vector, and
2026             // so indexOf will return -1 in any case
2027             return hyperlinks.baseElementIndex(e);
2028         }
2029 
2030         /**
2031          * Returns the index into an array of hyperlinks that
2032          * index.  If there is no hyperlink at this index, it returns
2033          * null.
2034          *
2035          * @param linkIndex into the set of hyperlinks for this hypertext doc.
2036          * @return string representation of the hyperlink
2037          */
2038         public AccessibleHyperlink getLink(int linkIndex) {
2039             if (linksValid == false) {
2040                 buildLinkTable();
2041             }
2042             if (linkIndex >= 0 && linkIndex < hyperlinks.size()) {
2043                 return hyperlinks.elementAt(linkIndex);
2044             } else {
2045                 return null;
2046             }
2047         }
2048 
2049         /**
2050          * Returns the contiguous text within the document that
2051          * is associated with this hyperlink.
2052          *
2053          * @param linkIndex into the set of hyperlinks for this hypertext doc.
2054          * @return the contiguous text sharing the link at this index
2055          */
2056         public String getLinkText(int linkIndex) {
2057             if (linksValid == false) {
2058                 buildLinkTable();
2059             }
2060             Element e = (Element) hyperlinks.elementAt(linkIndex);
2061             if (e != null) {
2062                 Document d = JEditorPane.this.getDocument();
2063                 if (d != null) {
2064                     try {
2065                         return d.getText(e.getStartOffset(),
2066                                          e.getEndOffset() - e.getStartOffset());
2067                     } catch (BadLocationException exception) {
2068                         return null;
2069                     }
2070                 }
2071             }
2072             return null;
2073         }
2074     }
2075 
2076     static class PlainEditorKit extends DefaultEditorKit implements ViewFactory {
2077 
2078         /**
2079          * Fetches a factory that is suitable for producing
2080          * views of any models that are produced by this
2081          * kit.  The default is to have the UI produce the
2082          * factory, so this method has no implementation.
2083          *
2084          * @return the view factory
2085          */
2086         public ViewFactory getViewFactory() {
2087             return this;
2088         }
2089 
2090         /**
2091          * Creates a view from the given structural element of a
2092          * document.
2093          *
2094          * @param elem  the piece of the document to build a view of
2095          * @return the view
2096          * @see View
2097          */
2098         public View create(Element elem) {
2099             Document doc = elem.getDocument();
2100             Object i18nFlag
2101                 = doc.getProperty("i18n"/*AbstractDocument.I18NProperty*/);
2102             if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
2103                 // build a view that support bidi
2104                 return createI18N(elem);
2105             } else {
2106                 return new WrappedPlainView(elem);
2107             }
2108         }
2109 
2110         View createI18N(Element elem) {
2111             String kind = elem.getName();
2112             if (kind != null) {
2113                 if (kind.equals(AbstractDocument.ContentElementName)) {
2114                     return new PlainParagraph(elem);
2115                 } else if (kind.equals(AbstractDocument.ParagraphElementName)){
2116                     return new BoxView(elem, View.Y_AXIS);
2117                 }
2118             }
2119             return null;
2120         }
2121 
2122         /**
2123          * Paragraph for representing plain-text lines that support
2124          * bidirectional text.
2125          */
2126         static class PlainParagraph extends javax.swing.text.ParagraphView {
2127 
2128             PlainParagraph(Element elem) {
2129                 super(elem);
2130                 layoutPool = new LogicalView(elem);
2131                 layoutPool.setParent(this);
2132             }
2133 
2134             protected void setPropertiesFromAttributes() {
2135                 Component c = getContainer();
2136                 if ((c != null)
2137                     && (! c.getComponentOrientation().isLeftToRight()))
2138                 {
2139                     setJustification(StyleConstants.ALIGN_RIGHT);
2140                 } else {
2141                     setJustification(StyleConstants.ALIGN_LEFT);
2142                 }
2143             }
2144 
2145             /**
2146              * Fetch the constraining span to flow against for
2147              * the given child index.
2148              */
2149             public int getFlowSpan(int index) {
2150                 Component c = getContainer();
2151                 if (c instanceof JTextArea) {
2152                     JTextArea area = (JTextArea) c;
2153                     if (! area.getLineWrap()) {
2154                         // no limit if unwrapped
2155                         return Integer.MAX_VALUE;
2156                     }
2157                 }
2158                 return super.getFlowSpan(index);
2159             }
2160 
2161             protected SizeRequirements calculateMinorAxisRequirements(int axis,
2162                                                             SizeRequirements r)
2163             {
2164                 SizeRequirements req
2165                     = super.calculateMinorAxisRequirements(axis, r);
2166                 Component c = getContainer();
2167                 if (c instanceof JTextArea) {
2168                     JTextArea area = (JTextArea) c;
2169                     if (! area.getLineWrap()) {
2170                         // min is pref if unwrapped
2171                         req.minimum = req.preferred;
2172                     }
2173                 }
2174                 return req;
2175             }
2176 
2177             /**
2178              * This class can be used to represent a logical view for
2179              * a flow.  It keeps the children updated to reflect the state
2180              * of the model, gives the logical child views access to the
2181              * view hierarchy, and calculates a preferred span.  It doesn't
2182              * do any rendering, layout, or model/view translation.
2183              */
2184             static class LogicalView extends CompositeView {
2185 
2186                 LogicalView(Element elem) {
2187                     super(elem);
2188                 }
2189 
2190                 protected int getViewIndexAtPosition(int pos) {
2191                     Element elem = getElement();
2192                     if (elem.getElementCount() > 0) {
2193                         return elem.getElementIndex(pos);
2194                     }
2195                     return 0;
2196                 }
2197 
2198                 protected boolean
2199                 updateChildren(DocumentEvent.ElementChange ec,
2200                                DocumentEvent e, ViewFactory f)
2201                 {
2202                     return false;
2203                 }
2204 
2205                 protected void loadChildren(ViewFactory f) {
2206                     Element elem = getElement();
2207                     if (elem.getElementCount() > 0) {
2208                         super.loadChildren(f);
2209                     } else {
2210                         View v = new GlyphView(elem);
2211                         append(v);
2212                     }
2213                 }
2214 
2215                 public float getPreferredSpan(int axis) {
2216                     if( getViewCount() != 1 )
2217                         throw new Error("One child view is assumed.");
2218 
2219                     View v = getView(0);
2220                     //((GlyphView)v).setGlyphPainter(null);
2221                     return v.getPreferredSpan(axis);
2222                 }
2223 
2224                 /**
2225                  * Forward the DocumentEvent to the given child view.  This
2226                  * is implemented to reparent the child to the logical view
2227                  * (the children may have been parented by a row in the flow
2228                  * if they fit without breaking) and then execute the
2229                  * superclass behavior.
2230                  *
2231                  * @param v the child view to forward the event to.
2232                  * @param e the change information from the associated document
2233                  * @param a the current allocation of the view
2234                  * @param f the factory to use to rebuild if the view has
2235                  *          children
2236                  * @see #forwardUpdate
2237                  * @since 1.3
2238                  */
2239                 protected void forwardUpdateToView(View v, DocumentEvent e,
2240                                                    Shape a, ViewFactory f) {
2241                     v.setParent(this);
2242                     super.forwardUpdateToView(v, e, a, f);
2243                 }
2244 
2245                 // The following methods don't do anything useful, they
2246                 // simply keep the class from being abstract.
2247 
2248                 public void paint(Graphics g, Shape allocation) {
2249                 }
2250 
2251                 protected boolean isBefore(int x, int y, Rectangle alloc) {
2252                     return false;
2253                 }
2254 
2255                 protected boolean isAfter(int x, int y, Rectangle alloc) {
2256                     return false;
2257                 }
2258 
2259                 protected View getViewAtPoint(int x, int y, Rectangle alloc) {
2260                     return null;
2261                 }
2262 
2263                 protected void childAllocation(int index, Rectangle a) {
2264                 }
2265             }
2266         }
2267     }
2268 
2269 /* This is useful for the nightmare of parsing multi-part HTTP/RFC822 headers
2270  * sensibly:
2271  * From a String like: 'timeout=15, max=5'
2272  * create an array of Strings:
2273  * { {"timeout", "15"},
2274  *   {"max", "5"}
2275  * }
2276  * From one like: 'Basic Realm="FuzzFace" Foo="Biz Bar Baz"'
2277  * create one like (no quotes in literal):
2278  * { {"basic", null},
2279  *   {"realm", "FuzzFace"}
2280  *   {"foo", "Biz Bar Baz"}
2281  * }
2282  * keys are converted to lower case, vals are left as is....
2283  *
2284  * author Dave Brown
2285  */
2286 
2287 
2288 static class HeaderParser {
2289 
2290     /* table of key/val pairs - maxes out at 10!!!!*/
2291     String raw;
2292     String[][] tab;
2293 
2294     public HeaderParser(String raw) {
2295         this.raw = raw;
2296         tab = new String[10][2];
2297         parse();
2298     }
2299 
2300     private void parse() {
2301 
2302         if (raw != null) {
2303             raw = raw.trim();
2304             char[] ca = raw.toCharArray();
2305             int beg = 0, end = 0, i = 0;
2306             boolean inKey = true;
2307             boolean inQuote = false;
2308             int len = ca.length;
2309             while (end < len) {
2310                 char c = ca[end];
2311                 if (c == '=') { // end of a key
2312                     tab[i][0] = new String(ca, beg, end-beg).toLowerCase();
2313                     inKey = false;
2314                     end++;
2315                     beg = end;
2316                 } else if (c == '\"') {
2317                     if (inQuote) {
2318                         tab[i++][1]= new String(ca, beg, end-beg);
2319                         inQuote=false;
2320                         do {
2321                             end++;
2322                         } while (end < len && (ca[end] == ' ' || ca[end] == ','));
2323                         inKey=true;
2324                         beg=end;
2325                     } else {
2326                         inQuote=true;
2327                         end++;
2328                         beg=end;
2329                     }
2330                 } else if (c == ' ' || c == ',') { // end key/val, of whatever we're in
2331                     if (inQuote) {
2332                         end++;
2333                         continue;
2334                     } else if (inKey) {
2335                         tab[i++][0] = (new String(ca, beg, end-beg)).toLowerCase();
2336                     } else {
2337                         tab[i++][1] = (new String(ca, beg, end-beg));
2338                     }
2339                     while (end < len && (ca[end] == ' ' || ca[end] == ',')) {
2340                         end++;
2341                     }
2342                     inKey = true;
2343                     beg = end;
2344                 } else {
2345                     end++;
2346                 }
2347             }
2348             // get last key/val, if any
2349             if (--end > beg) {
2350                 if (!inKey) {
2351                     if (ca[end] == '\"') {
2352                         tab[i++][1] = (new String(ca, beg, end-beg));
2353                     } else {
2354                         tab[i++][1] = (new String(ca, beg, end-beg+1));
2355                     }
2356                 } else {
2357                     tab[i][0] = (new String(ca, beg, end-beg+1)).toLowerCase();
2358                 }
2359             } else if (end == beg) {
2360                 if (!inKey) {
2361                     if (ca[end] == '\"') {
2362                         tab[i++][1] = String.valueOf(ca[end-1]);
2363                     } else {
2364                         tab[i++][1] = String.valueOf(ca[end]);
2365                     }
2366                 } else {
2367                     tab[i][0] = String.valueOf(ca[end]).toLowerCase();
2368                 }
2369             }
2370         }
2371 
2372     }
2373 
2374     public String findKey(int i) {
2375         if (i < 0 || i > 10)
2376             return null;
2377         return tab[i][0];
2378     }
2379 
2380     public String findValue(int i) {
2381         if (i < 0 || i > 10)
2382             return null;
2383         return tab[i][1];
2384     }
2385 
2386     public String findValue(String key) {
2387         return findValue(key, null);
2388     }
2389 
2390     public String findValue(String k, String Default) {
2391         if (k == null)
2392             return Default;
2393         k = k.toLowerCase();
2394         for (int i = 0; i < 10; ++i) {
2395             if (tab[i][0] == null) {
2396                 return Default;
2397             } else if (k.equals(tab[i][0])) {
2398                 return tab[i][1];
2399             }
2400         }
2401         return Default;
2402     }
2403 
2404     public int findInt(String k, int Default) {
2405         try {
2406             return Integer.parseInt(findValue(k, String.valueOf(Default)));
2407         } catch (Throwable t) {
2408             return Default;
2409         }
2410     }
2411  }
2412 
2413 }