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