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