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

Save This Page
Home » openjdk-7 » javax » swing » [javadoc | source]