KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > JEditorPane


1 /*
2  * @(#)JEditorPane.java 1.125 04/07/23
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing;
8
9 import java.awt.*;
10 import java.awt.event.*;
11 import java.net.*;
12 import java.util.*;
13 import java.io.*;
14 import java.util.*;
15
16 import javax.swing.plaf.*;
17 import javax.swing.text.*;
18 import javax.swing.event.*;
19 import javax.swing.text.html.*;
20 import javax.accessibility.*;
21
22 /**
23  * A text component to edit various kinds of content.
24  * You can find how-to information and examples of using editor panes in
25  * <a HREF="http://java.sun.com/docs/books/tutorial/uiswing/components/text.html">Using Text Components</a>,
26  * a section in <em>The Java Tutorial.</em>
27  *
28  * <p>
29  * This component uses implementations of the
30  * <code>EditorKit</code> to accomplish its behavior. It effectively
31  * morphs into the proper kind of text editor for the kind
32  * of content it is given. The content type that editor is bound
33  * to at any given time is determined by the <code>EditorKit</code> currently
34  * installed. If the content is set to a new URL, its type is used
35  * to determine the <code>EditorKit</code> that should be used to
36  * load the content.
37  * <p>
38  * By default, the following types of content are known:
39  * <dl>
40  * <dt><b>text/plain</b>
41  * <dd>Plain text, which is the default the type given isn't
42  * recognized. The kit used in this case is an extension of
43  * <code>DefaultEditorKit</code> that produces a wrapped plain text view.
44  * <dt><b>text/html</b>
45  * <dd>HTML text. The kit used in this case is the class
46  * <code>javax.swing.text.html.HTMLEditorKit</code>
47  * which provides HTML 3.2 support.
48  * <dt><b>text/rtf</b>
49  * <dd>RTF text. The kit used in this case is the class
50  * <code>javax.swing.text.rtf.RTFEditorKit</code>
51  * which provides a limited support of the Rich Text Format.
52  * </dl>
53  * <p>
54  * There are several ways to load content into this component.
55  * <ol>
56  * <li>
57  * The <a HREF="#setText">setText</a> method can be used to initialize
58  * the component from a string. In this case the current
59  * <code>EditorKit</code> will be used, and the content type will be
60  * expected to be of this type.
61  * <li>
62  * The <a HREF="#read">read</a> method can be used to initialize the
63  * component from a <code>Reader</code>. Note that if the content type is HTML,
64  * relative references (e.g. for things like images) can't be resolved
65  * unless the &lt;base&gt; tag is used or the <em>Base</em> property
66  * on <code>HTMLDocument</code> is set.
67  * In this case the current <code>EditorKit</code> will be used,
68  * and the content type will be expected to be of this type.
69  * <li>
70  * The <a HREF="#setPage">setPage</a> method can be used to initialize
71  * the component from a URL. In this case, the content type will be
72  * determined from the URL, and the registered <code>EditorKit</code>
73  * for that content type will be set.
74  * </ol>
75  * <p>
76  * Some kinds of content may provide hyperlink support by generating
77  * hyperlink events. The HTML <code>EditorKit</code> will generate
78  * hyperlink events if the <code>JEditorPane</code> is <em>not editable</em>
79  * (<code>JEditorPane.setEditable(false);</code> has been called).
80  * If HTML frames are embedded in the document, the typical response would be
81  * to change a portion of the current document. The following code
82  * fragment is a possible hyperlink listener implementation, that treats
83  * HTML frame events specially, and simply displays any other activated
84  * hyperlinks.
85  * <code><pre>
86
87 &nbsp; class Hyperactive implements HyperlinkListener {
88 &nbsp;
89 &nbsp; public void hyperlinkUpdate(HyperlinkEvent e) {
90 &nbsp; if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
91 &nbsp; JEditorPane pane = (JEditorPane) e.getSource();
92 &nbsp; if (e instanceof HTMLFrameHyperlinkEvent) {
93 &nbsp; HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e;
94 &nbsp; HTMLDocument doc = (HTMLDocument)pane.getDocument();
95 &nbsp; doc.processHTMLFrameHyperlinkEvent(evt);
96 &nbsp; } else {
97 &nbsp; try {
98 &nbsp; pane.setPage(e.getURL());
99 &nbsp; } catch (Throwable t) {
100 &nbsp; t.printStackTrace();
101 &nbsp; }
102 &nbsp; }
103 &nbsp; }
104 &nbsp; }
105 &nbsp; }
106
107  * </pre></code>
108  * <p>
109  * For information on customizing how <b>text/html</b> is rendered please see
110  * {@link #W3C_LENGTH_UNITS} and {@link #HONOR_DISPLAY_PROPERTIES}
111  * <p>
112  * Culturally dependent information in some documents is handled through
113  * a mechanism called character encoding. Character encoding is an
114  * unambiguous mapping of the members of a character set (letters, ideographs,
115  * digits, symbols, or control functions) to specific numeric code values. It
116  * represents the way the file is stored. Example character encodings are
117  * ISO-8859-1, ISO-8859-5, Shift-jis, Euc-jp, and UTF-8. When the file is
118  * passed to an user agent (<code>JEditorPane</code>) it is converted to
119  * the document character set (ISO-10646 aka Unicode).
120  * <p>
121  * There are multiple ways to get a character set mapping to happen
122  * with <code>JEditorPane</code>.
123  * <ol>
124  * <li>
125  * One way is to specify the character set as a parameter of the MIME
126  * type. This will be established by a call to the
127  * <a HREF="#setContentType">setContentType</a> method. If the content
128  * is loaded by the <a HREF="#setPage">setPage</a> method the content
129  * type will have been set according to the specification of the URL.
130  * It the file is loaded directly, the content type would be expected to
131  * have been set prior to loading.
132  * <li>
133  * Another way the character set can be specified is in the document itself.
134  * This requires reading the document prior to determining the character set
135  * that is desired. To handle this, it is expected that the
136  * <code>EditorKit</code>.read operation throw a
137  * <code>ChangedCharSetException</code> which will
138  * be caught. The read is then restarted with a new Reader that uses
139  * the character set specified in the <code>ChangedCharSetException</code>
140  * (which is an <code>IOException</code>).
141  * </ol>
142  * <p>
143  * <dt><b><font size=+1>Newlines</font></b>
144  * <dd>
145  * For a discussion on how newlines are handled, see
146  * <a HREF="text/DefaultEditorKit.html">DefaultEditorKit</a>.
147  * </dl>
148  *
149  * <p>
150  * <strong>Warning:</strong>
151  * Serialized objects of this class will not be compatible with
152  * future Swing releases. The current serialization support is
153  * appropriate for short term storage or RMI between applications running
154  * the same version of Swing. As of 1.4, support for long term storage
155  * of all JavaBeans<sup><font size="-2">TM</font></sup>
156  * has been added to the <code>java.beans</code> package.
157  * Please see {@link java.beans.XMLEncoder}.
158  *
159  * @beaninfo
160  * attribute: isContainer false
161  * description: A text component to edit various types of content.
162  *
163  * @author Timothy Prinzing
164  * @version 1.125 07/23/04
165  */

166 public class JEditorPane extends JTextComponent {
167
168     /**
169      * Creates a new <code>JEditorPane</code>.
170      * The document model is set to <code>null</code>.
171      */

172     public JEditorPane() {
173         super();
174     setFocusCycleRoot(true);
175     setFocusTraversalPolicy(new LayoutFocusTraversalPolicy JavaDoc() {
176         public Component getComponentAfter(Container focusCycleRoot,
177                            Component aComponent) {
178             if (focusCycleRoot != JEditorPane.this ||
179             (!isEditable() && getComponentCount() > 0)) {
180             return super.getComponentAfter(focusCycleRoot,
181                                aComponent);
182             } else {
183             Container rootAncestor = getFocusCycleRootAncestor();
184             return (rootAncestor != null)
185                 ? rootAncestor.getFocusTraversalPolicy().
186                       getComponentAfter(rootAncestor,
187                             JEditorPane.this)
188                 : null;
189             }
190         }
191         public Component getComponentBefore(Container focusCycleRoot,
192                             Component aComponent) {
193             if (focusCycleRoot != JEditorPane.this ||
194             (!isEditable() && getComponentCount() > 0)) {
195             return super.getComponentBefore(focusCycleRoot,
196                             aComponent);
197             } else {
198             Container rootAncestor = getFocusCycleRootAncestor();
199             return (rootAncestor != null)
200                 ? rootAncestor.getFocusTraversalPolicy().
201                       getComponentBefore(rootAncestor,
202                              JEditorPane.this)
203                 : null;
204             }
205         }
206         public Component getDefaultComponent(Container focusCycleRoot)
207         {
208             return (focusCycleRoot != JEditorPane.this ||
209                 (!isEditable() && getComponentCount() > 0))
210             ? super.getDefaultComponent(focusCycleRoot)
211             : null;
212         }
213         protected boolean accept(Component aComponent) {
214             return (aComponent != JEditorPane.this)
215             ? super.accept(aComponent)
216             : false;
217         }
218         });
219         LookAndFeel.installProperty(this,
220                                     "focusTraversalKeysForward",
221                                     JComponent.
222                                     getManagingFocusForwardTraversalKeys());
223         LookAndFeel.installProperty(this,
224                                     "focusTraversalKeysBackward",
225                                     JComponent.
226                                     getManagingFocusBackwardTraversalKeys());
227     }
228
229     /**
230      * Creates a <code>JEditorPane</code> based on a specified URL for input.
231      *
232      * @param initialPage the URL
233      * @exception IOException if the URL is <code>null</code>
234      * or cannot be accessed
235      */

236     public JEditorPane(URL initialPage) throws IOException {
237         this();
238         setPage(initialPage);
239     }
240
241     /**
242      * Creates a <code>JEditorPane</code> based on a string containing
243      * a URL specification.
244      *
245      * @param url the URL
246      * @exception IOException if the URL is <code>null</code> or
247      * cannot be accessed
248      */

249     public JEditorPane(String JavaDoc url) throws IOException {
250         this();
251         setPage(url);
252     }
253
254     /**
255      * Creates a <code>JEditorPane</code> that has been initialized
256      * to the given text. This is a convenience constructor that calls the
257      * <code>setContentType</code> and <code>setText</code> methods.
258      *
259      * @param type mime type of the given text
260      * @param text the text to initialize with; may be <code>null</code>
261      * @exception NullPointerException if the <code>type</code> parameter
262      * is <code>null</code>
263      */

264     public JEditorPane(String JavaDoc type, String JavaDoc text) {
265     this();
266     setContentType(type);
267     setText(text);
268     }
269
270     /**
271      * Adds a hyperlink listener for notification of any changes, for example
272      * when a link is selected and entered.
273      *
274      * @param listener the listener
275      */

276     public synchronized void addHyperlinkListener(HyperlinkListener listener) {
277         listenerList.add(HyperlinkListener.class, listener);
278     }
279
280     /**
281      * Removes a hyperlink listener.
282      *
283      * @param listener the listener
284      */

285     public synchronized void removeHyperlinkListener(HyperlinkListener listener) {
286         listenerList.remove(HyperlinkListener.class, listener);
287     }
288
289     /**
290      * Returns an array of all the <code>HyperLinkListener</code>s added
291      * to this JEditorPane with addHyperlinkListener().
292      *
293      * @return all of the <code>HyperLinkListener</code>s added or an empty
294      * array if no listeners have been added
295      * @since 1.4
296      */

297     public synchronized HyperlinkListener[] getHyperlinkListeners() {
298         return (HyperlinkListener[])listenerList.getListeners(
299                 HyperlinkListener.class);
300     }
301
302     /**
303      * Notifies all listeners that have registered interest for
304      * notification on this event type. This is normally called
305      * by the currently installed <code>EditorKit</code> if a content type
306      * that supports hyperlinks is currently active and there
307      * was activity with a link. The listener list is processed
308      * last to first.
309      *
310      * @param e the event
311      * @see EventListenerList
312      */

313     public void fireHyperlinkUpdate(HyperlinkEvent e) {
314         // Guaranteed to return a non-null array
315
Object JavaDoc[] listeners = listenerList.getListenerList();
316         // Process the listeners last to first, notifying
317
// those that are interested in this event
318
for (int i = listeners.length-2; i>=0; i-=2) {
319             if (listeners[i]==HyperlinkListener.class) {
320                 ((HyperlinkListener)listeners[i+1]).hyperlinkUpdate(e);
321             }
322         }
323     }
324
325
326     /**
327      * Sets the current URL being displayed. The content type of the
328      * pane is set, and if the editor kit for the pane is
329      * non-<code>null</code>, then
330      * a new default document is created and the URL is read into it.
331      * If the URL contains and reference location, the location will
332      * be scrolled to by calling the <code>scrollToReference</code>
333      * method. If the desired URL is the one currently being displayed,
334      * the document will not be reloaded. To force a document
335      * reload it is necessary to clear the stream description property
336      * of the document. The following code shows how this can be done:
337      *
338      * <pre>
339      * Document doc = jEditorPane.getDocument();
340      * doc.putProperty(Document.StreamDescriptionProperty, null);
341      * </pre>
342      *
343      * If the desired URL is not the one currently being
344      * displayed, the <code>getStream</code> method is called to
345      * give subclasses control over the stream provided.
346      * <p>
347      * This may load either synchronously or asynchronously
348      * depending upon the document returned by the <code>EditorKit</code>.
349      * If the <code>Document</code> is of type
350      * <code>AbstractDocument</code> and has a value returned by
351      * <code>AbstractDocument.getAsynchronousLoadPriority</code>
352      * that is greater than or equal to zero, the page will be
353      * loaded on a separate thread using that priority.
354      * <p>
355      * If the document is loaded synchronously, it will be
356      * filled in with the stream prior to being installed into
357      * the editor with a call to <code>setDocument</code>, which
358      * is bound and will fire a property change event. If an
359      * <code>IOException</code> is thrown the partially loaded
360      * document will
361      * be discarded and neither the document or page property
362      * change events will be fired. If the document is
363      * successfully loaded and installed, a view will be
364      * built for it by the UI which will then be scrolled if
365      * necessary, and then the page property change event
366      * will be fired.
367      * <p>
368      * If the document is loaded asynchronously, the document
369      * will be installed into the editor immediately using a
370      * call to <code>setDocument</code> which will fire a
371      * document property change event, then a thread will be
372      * created which will begin doing the actual loading.
373      * In this case, the page property change event will not be
374      * fired by the call to this method directly, but rather will be
375      * fired when the thread doing the loading has finished.
376      * It will also be fired on the event-dispatch thread.
377      * Since the calling thread can not throw an <code>IOException</code>
378      * in the event of failure on the other thread, the page
379      * property change event will be fired when the other
380      * thread is done whether the load was successful or not.
381      *
382      * @param page the URL of the page
383      * @exception IOException for a <code>null</code> or invalid
384      * page specification, or exception from the stream being read
385      * @see #getPage
386      * @beaninfo
387      * description: the URL used to set content
388      * bound: true
389      * expert: true
390      */

391     public void setPage(URL page) throws IOException {
392         if (page == null) {
393             throw new IOException("invalid url");
394         }
395     URL loaded = getPage();
396
397
398     // reset scrollbar
399
if (!page.equals(loaded) && page.getRef() == null) {
400         scrollRectToVisible(new Rectangle(0,0,1,1));
401     }
402     boolean reloaded = false;
403     if ((loaded == null) || (! loaded.sameFile(page))) {
404
405         // different url, load the new content
406
InputStream in = getStream(page);
407         if (kit != null) {
408         Document doc = kit.createDefaultDocument();
409         if (pageProperties != null) {
410             // transfer properties discovered in stream to the
411
// document property collection.
412
for (Enumeration e = pageProperties.keys(); e.hasMoreElements() ;) {
413             Object JavaDoc key = e.nextElement();
414             doc.putProperty(key, pageProperties.get(key));
415             }
416             pageProperties.clear();
417         }
418         if (doc.getProperty(Document.StreamDescriptionProperty) == null) {
419             doc.putProperty(Document.StreamDescriptionProperty, page);
420         }
421
422         // At this point, one could either load up the model with no
423
// view notifications slowing it down (i.e. best synchronous
424
// behavior) or set the model and start to feed it on a separate
425
// thread (best asynchronous behavior).
426
synchronized(this) {
427             if (loading != null) {
428             // we are loading asynchronously, so we need to cancel
429
// the old stream.
430
loading.cancel();
431             loading = null;
432             }
433         }
434         if (doc instanceof AbstractDocument) {
435             AbstractDocument adoc = (AbstractDocument) doc;
436             int p = adoc.getAsynchronousLoadPriority();
437             if (p >= 0) {
438             // load asynchronously
439
setDocument(doc);
440             synchronized(this) {
441                 loading = new PageStream(in);
442                 Thread JavaDoc pl = new PageLoader(doc, loading, p, loaded, page);
443                 pl.start();
444             }
445             return;
446             }
447         }
448         read(in, doc);
449         setDocument(doc);
450         reloaded = true;
451         }
452     }
453     final String JavaDoc reference = page.getRef();
454     if (reference != null) {
455         if (!reloaded) {
456         scrollToReference(reference);
457         }
458         else {
459         // Have to scroll after painted.
460
SwingUtilities.invokeLater(new Runnable JavaDoc() {
461             public void run() {
462             scrollToReference(reference);
463             }
464         });
465         }
466         getDocument().putProperty(Document.StreamDescriptionProperty, page);
467     }
468         firePropertyChange("page", loaded, page);
469     }
470
471
472     /**
473      * This method initializes from a stream. If the kit is
474      * set to be of type <code>HTMLEditorKit</code>, and the
475      * <code>desc</code> parameter is an <code>HTMLDocument</code>,
476      * then it invokes the <code>HTMLEditorKit</code> to initiate
477      * the read. Otherwise it calls the superclass
478      * method which loads the model as plain text.
479      *
480      * @param in the stream from which to read
481      * @param desc an object describing the stream
482      * @exception IOException as thrown by the stream being
483      * used to initialize
484      * @see JTextComponent#read
485      * @see #setDocument
486      */

487     public void read(InputStream in, Object JavaDoc desc) throws IOException {
488
489     if (desc instanceof HTMLDocument &&
490         kit instanceof HTMLEditorKit) {
491         HTMLDocument hdoc = (HTMLDocument) desc;
492         setDocument(hdoc);
493         read(in, hdoc);
494     } else {
495         String JavaDoc charset = (String JavaDoc) getClientProperty("charset");
496         Reader r = (charset != null) ? new InputStreamReader(in, charset) :
497         new InputStreamReader(in);
498         super.read(r, desc);
499     }
500     }
501
502
503     /**
504      * This method invokes the <code>EditorKit</code> to initiate a
505      * read. In the case where a <code>ChangedCharSetException</code>
506      * is thrown this exception will contain the new CharSet.
507      * Therefore the <code>read</code> operation
508      * is then restarted after building a new Reader with the new charset.
509      *
510      * @param in the inputstream to use
511      * @param doc the document to load
512      *
513      */

514     void read(InputStream in, Document doc) throws IOException {
515     try {
516         String JavaDoc charset = (String JavaDoc) getClientProperty("charset");
517         Reader r = (charset != null) ? new InputStreamReader(in, charset) :
518         new InputStreamReader(in);
519         kit.read(r, doc, 0);
520     } catch (BadLocationException e) {
521         throw new IOException(e.getMessage());
522     } catch (ChangedCharSetException e1) {
523         String JavaDoc charSetSpec = e1.getCharSetSpec();
524         if (e1.keyEqualsCharSet()) {
525         putClientProperty("charset", charSetSpec);
526         } else {
527         setCharsetFromContentTypeParameters(charSetSpec);
528         }
529         in.close();
530         URL url = (URL)doc.getProperty(Document.StreamDescriptionProperty);
531         URLConnection conn = url.openConnection();
532         in = conn.getInputStream();
533         try {
534         doc.remove(0, doc.getLength());
535         } catch (BadLocationException e) {}
536         doc.putProperty("IgnoreCharsetDirective", Boolean.valueOf(true));
537         read(in, doc);
538     }
539     }
540
541
542     /**
543      * Thread to load a stream into the text document model.
544      */

545     class PageLoader extends Thread JavaDoc {
546
547     /**
548      * Construct an asynchronous page loader.
549      */

550     PageLoader(Document doc, InputStream in, int priority, URL old,
551            URL page) {
552         setPriority(priority);
553         this.in = in;
554         this.old = old;
555         this.page = page;
556         this.doc = doc;
557     }
558
559     /**
560      * Try to load the document, then scroll the view
561      * to the reference (if specified). When done, fire
562      * a page property change event.
563      */

564         public void run() {
565         try {
566         read(in, doc);
567         synchronized(JEditorPane.this) {
568             loading = null;
569         }
570         URL page = (URL) doc.getProperty(Document.StreamDescriptionProperty);
571         String JavaDoc reference = page.getRef();
572         if (reference != null) {
573             // scroll the page if necessary, but do it on the
574
// event thread... that is the only guarantee that
575
// modelToView can be safely called.
576
Runnable JavaDoc callScrollToReference = new Runnable JavaDoc() {
577                         public void run() {
578                 URL u = (URL) getDocument().getProperty
579                 (Document.StreamDescriptionProperty);
580                 String JavaDoc ref = u.getRef();
581                 scrollToReference(ref);
582             }
583             };
584             SwingUtilities.invokeLater(callScrollToReference);
585         }
586         } catch (IOException ioe) {
587         UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
588         } finally {
589                 SwingUtilities.invokeLater(new Runnable JavaDoc() {
590                     public void run() {
591                         firePropertyChange("page", old, page);
592                     }
593                 });
594         }
595     }
596
597     /**
598      * The stream to load the document with
599      */

600     InputStream in;
601     
602     /**
603      * URL of the old page that was replaced (for the property change event)
604      */

605     URL old;
606
607     /**
608      * URL of the page being loaded (for the property change event)
609      */

610     URL page;
611
612     /**
613      * The Document instance to load into. This is cached in case a
614      * new Document is created between the time the thread this is created
615      * and run.
616      */

617     Document doc;
618     }
619
620     static class PageStream extends FilterInputStream {
621
622     boolean canceled;
623     
624     public PageStream(InputStream i) {
625         super(i);
626         canceled = false;
627     }
628
629     /**
630      * Cancel the loading of the stream by throwing
631      * an IOException on the next request.
632      */

633         public synchronized void cancel() {
634         canceled = true;
635     }
636
637         protected synchronized void checkCanceled() throws IOException {
638         if (canceled) {
639         throw new IOException("page canceled");
640         }
641     }
642
643         public int read() throws IOException {
644         checkCanceled();
645         return super.read();
646     }
647
648         public long skip(long n) throws IOException {
649         checkCanceled();
650         return super.skip(n);
651     }
652
653         public int available() throws IOException {
654         checkCanceled();
655         return super.available();
656     }
657
658         public void reset() throws IOException {
659         checkCanceled();
660         super.reset();
661     }
662
663     }
664
665     /**
666      * Fetches a stream for the given URL, which is about to
667      * be loaded by the <code>setPage</code> method. By
668      * default, this simply opens the URL and returns the
669      * stream. This can be reimplemented to do useful things
670      * like fetch the stream from a cache, monitor the progress
671      * of the stream, etc.
672      * <p>
673      * This method is expected to have the the side effect of
674      * establishing the content type, and therefore setting the
675      * appropriate <code>EditorKit</code> to use for loading the stream.
676      * <p>
677      * If this the stream was an http connection, redirects
678      * will be followed and the resulting URL will be set as
679      * the <code>Document.StreamDescriptionProperty</code> so that relative
680      * URL's can be properly resolved.
681      *
682      * @param page the URL of the page
683      */

684     protected InputStream getStream(URL page) throws IOException {
685     URLConnection conn = page.openConnection();
686     if (conn instanceof HttpURLConnection) {
687         HttpURLConnection hconn = (HttpURLConnection) conn;
688         hconn.setInstanceFollowRedirects(false);
689         int response = hconn.getResponseCode();
690         boolean redirect = (response >= 300 && response <= 399);
691
692         /*
693          * In the case of a redirect, we want to actually change the URL
694          * that was input to the new, redirected URL
695          */

696         if (redirect) {
697         String JavaDoc loc = conn.getHeaderField("Location");
698         if (loc.startsWith("http", 0)) {
699             page = new URL(loc);
700         } else {
701             page = new URL(page, loc);
702         }
703         return getStream(page);
704         }
705     }
706     if (pageProperties == null) {
707         pageProperties = new Hashtable();
708     }
709     String JavaDoc type = conn.getContentType();
710     if (type != null) {
711         setContentType(type);
712         pageProperties.put("content-type", type);
713     }
714     pageProperties.put(Document.StreamDescriptionProperty, page);
715     String JavaDoc enc = conn.getContentEncoding();
716     if (enc != null) {
717         pageProperties.put("content-encoding", enc);
718     }
719     InputStream in = conn.getInputStream();
720     return in;
721     }
722
723     /**
724      * Scrolls the view to the given reference location
725      * (that is, the value returned by the <code>UL.getRef</code>
726      * method for the URL being displayed). By default, this
727      * method only knows how to locate a reference in an
728      * HTMLDocument. The implementation calls the
729      * <code>scrollRectToVisible</code> method to
730      * accomplish the actual scrolling. If scrolling to a
731      * reference location is needed for document types other
732      * than HTML, this method should be reimplemented.
733      * This method will have no effect if the component
734      * is not visible.
735      *
736      * @param reference the named location to scroll to
737      */

738     public void scrollToReference(String JavaDoc reference) {
739     Document d = getDocument();
740     if (d instanceof HTMLDocument) {
741         HTMLDocument doc = (HTMLDocument) d;
742         HTMLDocument.Iterator iter = doc.getIterator(HTML.Tag.A);
743         for (; iter.isValid(); iter.next()) {
744         AttributeSet a = iter.getAttributes();
745         String JavaDoc nm = (String JavaDoc) a.getAttribute(HTML.Attribute.NAME);
746         if ((nm != null) && nm.equals(reference)) {
747             // found a matching reference in the document.
748
try {
749             Rectangle r = modelToView(iter.getStartOffset());
750             if (r != null) {
751                 // the view is visible, scroll it to the
752
// center of the current visible area.
753
Rectangle vis = getVisibleRect();
754                 //r.y -= (vis.height / 2);
755
r.height = vis.height;
756                 scrollRectToVisible(r);
757             }
758             } catch (BadLocationException ble) {
759                 UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
760             }
761         }
762         }
763     }
764     }
765
766     /**
767      * Gets the current URL being displayed. If a URL was
768      * not specified in the creation of the document, this
769      * will return <code>null</code>, and relative URL's will not be
770      * resolved.
771      *
772      * @return the URL, or <code>null</code> if none
773      */

774     public URL getPage() {
775         return (URL) getDocument().getProperty(Document.StreamDescriptionProperty);
776     }
777
778     /**
779      * Sets the current URL being displayed.
780      *
781      * @param url the URL for display
782      * @exception IOException for a <code>null</code> or invalid URL
783      * specification
784      */

785     public void setPage(String JavaDoc url) throws IOException {
786         if (url == null) {
787             throw new IOException("invalid url");
788         }
789         URL page = new URL(url);
790         setPage(page);
791     }
792
793     /**
794      * Gets the class ID for the UI.
795      *
796      * @return the string "EditorPaneUI"
797      * @see JComponent#getUIClassID
798      * @see UIDefaults#getUI
799      */

800     public String JavaDoc getUIClassID() {
801         return uiClassID;
802     }
803
804     /**
805      * Creates the default editor kit (<code>PlainEditorKit</code>) for when
806      * the component is first created.
807      *
808      * @return the editor kit
809      */

810     protected EditorKit createDefaultEditorKit() {
811         return new PlainEditorKit();
812     }
813
814     /**
815      * Fetches the currently installed kit for handling content.
816      * <code>createDefaultEditorKit</code> is called to set up a default
817      * if necessary.
818      *
819      * @return the editor kit
820      */

821     public EditorKit getEditorKit() {
822         if (kit == null) {
823             kit = createDefaultEditorKit();
824         }
825         return kit;
826     }
827
828     /**
829      * Gets the type of content that this editor
830      * is currently set to deal with. This is
831      * defined to be the type associated with the
832      * currently installed <code>EditorKit</code>.
833      *
834      * @return the content type, <code>null</code> if no editor kit set
835      */

836     public final String JavaDoc getContentType() {
837         return (kit != null) ? kit.getContentType() : null;
838     }
839
840     /**
841      * Sets the type of content that this editor
842      * handles. This calls <code>getEditorKitForContentType</code>,
843      * and then <code>setEditorKit</code> if an editor kit can
844      * be successfully located. This is mostly convenience method
845      * that can be used as an alternative to calling
846      * <code>setEditorKit</code> directly.
847      * <p>
848      * If there is a charset definition specified as a parameter
849      * of the content type specification, it will be used when
850      * loading input streams using the associated <code>EditorKit</code>.
851      * For example if the type is specified as
852      * <code>text/html; charset=EUC-JP</code> the content
853      * will be loaded using the <code>EditorKit</code> registered for
854      * <code>text/html</code> and the Reader provided to
855      * the <code>EditorKit</code> to load unicode into the document will
856      * use the <code>EUC-JP</code> charset for translating
857      * to unicode. If the type is not recognized, the content
858      * will be loaded using the <code>EditorKit</code> registered
859      * for plain text, <code>text/plain</code>.
860      *
861      * @param type the non-<code>null</code> mime type for the content editing
862      * support
863      * @see #getContentType
864      * @beaninfo
865      * description: the type of content
866      * @throws NullPointerException if the <code>type</code> parameter
867      * is <code>null</code>
868      */

869     public final void setContentType(String JavaDoc type) {
870     // The type could have optional info is part of it,
871
// for example some charset info. We need to strip that
872
// of and save it.
873
int parm = type.indexOf(";");
874     if (parm > -1) {
875         // Save the paramList.
876
String JavaDoc paramList = type.substring(parm);
877         // update the content type string.
878
type = type.substring(0, parm).trim();
879         if (type.toLowerCase().startsWith("text/")) {
880         setCharsetFromContentTypeParameters(paramList);
881         }
882     }
883         if ((kit == null) || (! type.equals(kit.getContentType()))) {
884             EditorKit k = getEditorKitForContentType(type);
885             if (k != null) {
886                 setEditorKit(k);
887             }
888         }
889     }
890
891     /**
892      * This method gets the charset information specified as part
893      * of the content type in the http header information.
894      */

895     private void setCharsetFromContentTypeParameters(String JavaDoc paramlist) {
896     String JavaDoc charset = null;
897     try {
898         // paramlist is handed to us with a leading ';', strip it.
899
int semi = paramlist.indexOf(';');
900         if (semi > -1 && semi < paramlist.length()-1) {
901         paramlist = paramlist.substring(semi + 1);
902         }
903
904         if (paramlist.length() > 0) {
905         // parse the paramlist into attr-value pairs & get the
906
// charset pair's value
907
HeaderParser hdrParser = new HeaderParser(paramlist);
908         charset = hdrParser.findValue("charset");
909         if (charset != null) {
910             putClientProperty("charset", charset);
911         }
912         }
913     }
914     catch (IndexOutOfBoundsException JavaDoc e) {
915         // malformed parameter list, use charset we have
916
}
917     catch (NullPointerException JavaDoc e) {
918         // malformed parameter list, use charset we have
919
}
920     catch (Exception JavaDoc e) {
921         // malformed parameter list, use charset we have; but complain
922
System.err.println("JEditorPane.getCharsetFromContentTypeParameters failed on: " + paramlist);
923         e.printStackTrace();
924     }
925     }
926
927
928     /**
929      * Sets the currently installed kit for handling
930      * content. This is the bound property that
931      * establishes the content type of the editor.
932      * Any old kit is first deinstalled, then if kit is
933      * non-<code>null</code>,
934      * the new kit is installed, and a default document created for it.
935      * A <code>PropertyChange</code> event ("editorKit") is always fired when
936      * <code>setEditorKit</code> is called.
937      * <p>
938      * <em>NOTE: This has the side effect of changing the model,
939      * because the <code>EditorKit</code> is the source of how a
940      * particular type
941      * of content is modeled. This method will cause <code>setDocument</code>
942      * to be called on behalf of the caller to ensure integrity
943      * of the internal state.</em>
944      *
945      * @param kit the desired editor behavior
946      * @see #getEditorKit
947      * @beaninfo
948      * description: the currently installed kit for handling content
949      * bound: true
950      * expert: true
951      */

952     public void setEditorKit(EditorKit kit) {
953         EditorKit old = this.kit;
954         if (old != null) {
955             old.deinstall(this);
956         }
957         this.kit = kit;
958         if (this.kit != null) {
959             this.kit.install(this);
960             setDocument(this.kit.createDefaultDocument());
961         }
962         firePropertyChange("editorKit", old, kit);
963     }
964
965     /**
966      * Fetches the editor kit to use for the given type
967      * of content. This is called when a type is requested
968      * that doesn't match the currently installed type.
969      * If the component doesn't have an <code>EditorKit</code> registered
970      * for the given type, it will try to create an
971      * <code>EditorKit</code> from the default <code>EditorKit</code> registry.
972      * If that fails, a <code>PlainEditorKit</code> is used on the
973      * assumption that all text documents can be represented
974      * as plain text.
975      * <p>
976      * This method can be reimplemented to use some
977      * other kind of type registry. This can
978      * be reimplemented to use the Java Activation
979      * Framework, for example.
980      *
981      * @param type the non-</code>null</code> content type
982      * @return the editor kit
983      */

984     public EditorKit getEditorKitForContentType(String JavaDoc type) {
985         if (typeHandlers == null) {
986             typeHandlers = new Hashtable(3);
987