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