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