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         }
988         EditorKit k = (EditorKit) typeHandlers.get(type);
989         if (k == null) {
990             k = createEditorKitForContentType(type);
991             if (k != null) {
992                 setEditorKitForContentType(type, k);
993             }
994         }
995         if (k == null) {
996             k = createDefaultEditorKit();
997         }
998         return k;
999     }
1000
1001    /**
1002     * Directly sets the editor kit to use for the given type. A
1003     * look-and-feel implementation might use this in conjunction
1004     * with <code>createEditorKitForContentType</code> to install handlers for
1005     * content types with a look-and-feel bias.
1006     *
1007     * @param type the non-<code>null</code> content type
1008     * @param k the editor kit to be set
1009     */

1010    public void setEditorKitForContentType(String JavaDoc type, EditorKit k) {
1011        if (typeHandlers == null) {
1012            typeHandlers = new Hashtable(3);
1013        }
1014        typeHandlers.put(type, k);
1015    }
1016
1017    /**
1018     * Replaces the currently selected content with new content
1019     * represented by the given string. If there is no selection
1020     * this amounts to an insert of the given text. If there
1021     * is no replacement text (i.e. the content string is empty
1022     * or <code>null</code>) this amounts to a removal of the
1023     * current selection. The replacement text will have the
1024     * attributes currently defined for input. If the component is not
1025     * editable, beep and return.
1026     * <p>
1027     * This method is thread safe, although most Swing methods
1028     * are not. Please see
1029     * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
1030     * and Swing</A> for more information.
1031     *
1032     * @param content the content to replace the selection with. This
1033     * value can be <code>null</code>
1034     */

1035    public void replaceSelection(String JavaDoc content) {
1036        if (! isEditable()) {
1037        UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1038            return;
1039        }
1040        EditorKit kit = getEditorKit();
1041    if(kit instanceof StyledEditorKit) {
1042            try {
1043        Document doc = getDocument();
1044                Caret caret = getCaret();
1045                int p0 = Math.min(caret.getDot(), caret.getMark());
1046                int p1 = Math.max(caret.getDot(), caret.getMark());
1047                if (doc instanceof AbstractDocument) {
1048                    ((AbstractDocument)doc).replace(p0, p1 - p0, content,
1049                              ((StyledEditorKit)kit).getInputAttributes());
1050                }
1051                else {
1052                    if (p0 != p1) {
1053                        doc.remove(p0, p1 - p0);
1054                    }
1055                    if (content != null && content.length() > 0) {
1056                        doc.insertString(p0, content, ((StyledEditorKit)kit).
1057                                         getInputAttributes());
1058                    }
1059                }
1060            } catch (BadLocationException e) {
1061            UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1062            }
1063        }
1064        else {
1065        super.replaceSelection(content);
1066    }
1067    }
1068
1069    /**
1070     * Creates a handler for the given type from the default registry
1071     * of editor kits. The registry is created if necessary. If the
1072     * registered class has not yet been loaded, an attempt
1073     * is made to dynamically load the prototype of the kit for the
1074     * given type. If the type was registered with a <code>ClassLoader</code>,
1075     * that <code>ClassLoader</code> will be used to load the prototype.
1076     * If there was no registered <code>ClassLoader</code>,
1077     * <code>Class.forName</code> will be used to load the prototype.
1078     * <p>
1079     * Once a prototype <code>EditorKit</code> instance is successfully
1080     * located, it is cloned and the clone is returned.
1081     *
1082     * @param type the content type
1083     * @return the editor kit, or <code>null</code> if there is nothing
1084     * registered for the given type
1085     */

1086    public static EditorKit createEditorKitForContentType(String JavaDoc type) {
1087        EditorKit k = null;
1088        Hashtable kitRegistry = getKitRegisty();
1089    k = (EditorKit) kitRegistry.get(type);
1090        if (k == null) {
1091            // try to dynamically load the support
1092
String JavaDoc classname = (String JavaDoc) getKitTypeRegistry().get(type);
1093        ClassLoader JavaDoc loader = (ClassLoader JavaDoc) getKitLoaderRegistry().get(type);
1094            try {
1095        Class JavaDoc c;
1096        if (loader != null) {
1097            c = loader.loadClass(classname);
1098        } else {
1099                    // Will only happen if developer has invoked
1100
// registerEditorKitForContentType(type, class, null).
1101
c = Class.forName(classname, true, Thread.currentThread().
1102                                      getContextClassLoader());
1103        }
1104                k = (EditorKit) c.newInstance();
1105                kitRegistry.put(type, k);
1106            } catch (Throwable JavaDoc e) {
1107                k = null;
1108            }
1109        }
1110
1111        // create a copy of the prototype or null if there
1112
// is no prototype.
1113
if (k != null) {
1114            return (EditorKit) k.clone();
1115        }
1116        return null;
1117    }
1118
1119    /**
1120     * Establishes the default bindings of <code>type</code> to
1121     * <code>classname</code>.
1122     * The class will be dynamically loaded later when actually
1123     * needed, and can be safely changed before attempted uses
1124     * to avoid loading unwanted classes. The prototype
1125     * <code>EditorKit</code> will be loaded with <code>Class.forName</code>
1126     * when registered with this method.
1127     *
1128     * @param type the non-<code>null</code> content type
1129     * @param classname the class to load later
1130     */

1131    public static void registerEditorKitForContentType(String JavaDoc type, String JavaDoc classname) {
1132        registerEditorKitForContentType(type, classname,Thread.currentThread().
1133                                        getContextClassLoader());
1134    }
1135
1136    /**
1137     * Establishes the default bindings of <code>type</code> to
1138     * <code>classname</code>.
1139     * The class will be dynamically loaded later when actually
1140     * needed using the given <code>ClassLoader</code>,
1141     * and can be safely changed
1142     * before attempted uses to avoid loading unwanted classes.
1143     *
1144     * @param type the non-</code>null</code> content type
1145     * @param classname the class to load later
1146     * @param loader the <code>ClassLoader</code> to use to load the name
1147     */

1148    public static void registerEditorKitForContentType(String JavaDoc type, String JavaDoc classname, ClassLoader JavaDoc loader) {
1149        getKitTypeRegistry().put(type, classname);
1150    getKitLoaderRegistry().put(type, loader);
1151    getKitRegisty().remove(type);
1152    }
1153
1154    /**
1155     * Returns the currently registered <code>EditorKit</code>
1156     * class name for the type <code>type</code>.
1157     *
1158     * @param type the non-<code>null</code> content type
1159     *
1160     * @since 1.3
1161     */

1162    public static String JavaDoc getEditorKitClassNameForContentType(String JavaDoc type) {
1163    return (String JavaDoc)getKitTypeRegistry().get(type);
1164    }
1165
1166    private static Hashtable getKitTypeRegistry() {
1167    loadDefaultKitsIfNecessary();
1168    return (Hashtable)SwingUtilities.appContextGet(kitTypeRegistryKey);
1169    }
1170
1171    private static Hashtable getKitLoaderRegistry() {
1172    loadDefaultKitsIfNecessary();
1173    return (Hashtable)SwingUtilities.appContextGet(kitLoaderRegistryKey);
1174    }
1175
1176    private static Hashtable getKitRegisty() {
1177    Hashtable ht = (Hashtable)SwingUtilities.appContextGet(kitRegistryKey);
1178    if (ht == null) {
1179        ht = new Hashtable(3);
1180        SwingUtilities.appContextPut(kitRegistryKey, ht);
1181    }
1182    return ht;
1183    }
1184
1185    /**
1186     * This is invoked every time the registries are accessed. Loading
1187     * is done this way instead of via a static as the static is only
1188     * called once when running in plugin resulting in the entries only
1189     * appearing in the first applet.
1190     */

1191    private static void loadDefaultKitsIfNecessary() {
1192    if (SwingUtilities.appContextGet(kitTypeRegistryKey) == null) {
1193        Hashtable ht = new Hashtable();
1194        SwingUtilities.appContextPut(kitTypeRegistryKey, ht);
1195        ht = new Hashtable();
1196        SwingUtilities.appContextPut(kitLoaderRegistryKey, ht);
1197        registerEditorKitForContentType("text/plain",
1198                  "javax.swing.JEditorPane$PlainEditorKit");
1199        registerEditorKitForContentType("text/html",
1200                  "javax.swing.text.html.HTMLEditorKit");
1201        registerEditorKitForContentType("text/rtf",
1202                  "javax.swing.text.rtf.RTFEditorKit");
1203        registerEditorKitForContentType("application/rtf",
1204                  "javax.swing.text.rtf.RTFEditorKit");
1205    }
1206    }
1207
1208    // --- java.awt.Component methods --------------------------
1209

1210    /**
1211     * Returns the preferred size for the <code>JEditorPane</code>.
1212     * The preferred size for <code>JEditorPane</code> is slightly altered
1213     * from the preferred size of the superclass. If the size
1214     * of the viewport has become smaller than the minimum size
1215     * of the component, the scrollable definition for tracking
1216     * width or height will turn to false. The default viewport
1217     * layout will give the preferred size, and that is not desired
1218     * in the case where the scrollable is tracking. In that case
1219     * the <em>normal</em> preferred size is adjusted to the
1220     * minimum size. This allows things like HTML tables to
1221     * shrink down to their minimum size and then be laid out at
1222     * their minimum size, refusing to shrink any further.
1223     *
1224     * @return a <code>Dimension</code> containing the preferred size
1225     */

1226    public Dimension getPreferredSize() {
1227    Dimension d = super.getPreferredSize();
1228    if (getParent() instanceof JViewport JavaDoc) {
1229        JViewport JavaDoc port = (JViewport JavaDoc)getParent();
1230        TextUI ui = getUI();
1231            int prefWidth = d.width;
1232            int prefHeight = d.height;
1233        if (! getScrollableTracksViewportWidth()) {
1234        int w = port.getWidth();
1235        Dimension min = ui.getMinimumSize(this);
1236        if (w != 0 && w < min.width) {
1237                    // Only adjust to min if we have a valid size
1238
prefWidth = min.width;
1239        }
1240        }
1241        if (! getScrollableTracksViewportHeight()) {
1242        int h = port.getHeight();
1243        Dimension min = ui.getMinimumSize(this);
1244        if (h != 0 && h < min.height) {
1245                    // Only adjust to min if we have a valid size
1246
prefHeight = min.height;
1247        }
1248        }
1249            if (prefWidth != d.width || prefHeight != d.height) {
1250                d = new Dimension(prefWidth, prefHeight);
1251            }
1252    }
1253    return d;
1254    }
1255
1256    // --- JTextComponent methods -----------------------------
1257

1258    /**
1259     * Sets the text of this <code>TextComponent</code> to the specified
1260     * content,
1261     * which is expected to be in the format of the content type of
1262     * this editor. For example, if the type is set to <code>text/html</code>
1263     * the string should be specified in terms of HTML.
1264     * <p>
1265     * This is implemented to remove the contents of the current document,
1266     * and replace them by parsing the given string using the current
1267     * <code>EditorKit</code>. This gives the semantics of the
1268     * superclass by not changing
1269     * out the model, while supporting the content type currently set on
1270     * this component. The assumption is that the previous content is
1271     * relatively
1272     * small, and that the previous content doesn't have side effects.
1273     * Both of those assumptions can be violated and cause undesirable results.
1274     * To avoid this, create a new document,
1275     * <code>getEditorKit().createDefaultDocument()</code>, and replace the
1276     * existing <code>Document</code> with the new one. You are then assured the
1277     * previous <code>Document</code> won't have any lingering state.
1278     * <ol>
1279     * <li>
1280     * Leaving the existing model in place means that the old view will be
1281     * torn down, and a new view created, where replacing the document would
1282     * avoid the tear down of the old view.
1283     * <li>
1284     * Some formats (such as HTML) can install things into the document that
1285     * can influence future contents. HTML can have style information embedded
1286     * that would influence the next content installed unexpectedly.
1287     * </ol>
1288     * <p>
1289     * An alternative way to load this component with a string would be to
1290     * create a StringReader and call the read method. In this case the model
1291     * would be replaced after it was initialized with the contents of the
1292     * string.
1293     * <p>
1294     * This method is thread safe, although most Swing methods
1295     * are not. Please see
1296     * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
1297     * and Swing</A> for more information.
1298     *
1299     * @param t the new text to be set; if <code>null</code> the old
1300     * text will be deleted
1301     * @see #getText
1302     * @beaninfo
1303     * description: the text of this component
1304     */

1305    public void setText(String JavaDoc t) {
1306        try {
1307        Document doc = getDocument();
1308        doc.remove(0, doc.getLength());
1309        if (t == null || t.equals("")) {
1310        return;
1311        }
1312        Reader r = new StringReader(t);
1313        EditorKit kit = getEditorKit();
1314            kit.read(r, doc, 0);
1315        } catch (IOException ioe) {
1316        UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1317        } catch (BadLocationException ble) {
1318        UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1319    }
1320    }
1321
1322    /**
1323     * Returns the text contained in this <code>TextComponent</code>
1324     * in terms of the
1325     * content type of this editor. If an exception is thrown while
1326     * attempting to retrieve the text, <code>null</code> will be returned.
1327     * This is implemented to call <code>JTextComponent.write</code> with
1328     * a <code>StringWriter</code>.
1329     *
1330     * @return the text
1331     * @see #setText
1332     */

1333    public String JavaDoc getText() {
1334    String JavaDoc txt;
1335    try {
1336        StringWriter buf = new StringWriter();
1337        write(buf);
1338        txt = buf.toString();
1339        } catch (IOException ioe) {
1340            txt = null;
1341        }
1342        return txt;
1343    }
1344
1345    // --- Scrollable ----------------------------------------
1346

1347    /**
1348     * Returns true if a viewport should always force the width of this
1349     * <code>Scrollable</code> to match the width of the viewport.
1350     *
1351     * @return true if a viewport should force the Scrollables width to
1352     * match its own, false otherwise
1353     */

1354    public boolean getScrollableTracksViewportWidth() {
1355    if (getParent() instanceof JViewport JavaDoc) {
1356        JViewport JavaDoc port = (JViewport JavaDoc)getParent();
1357        TextUI ui = getUI();
1358        int w = port.getWidth();
1359        Dimension min = ui.getMinimumSize(this);
1360        Dimension max = ui.getMaximumSize(this);
1361        if ((w >= min.width) && (w <= max.width)) {
1362        return true;
1363        }
1364    }
1365    return false;
1366    }
1367
1368    /**
1369     * Returns true if a viewport should always force the height of this
1370     * <code>Scrollable</code> to match the height of the viewport.
1371     *
1372     * @return true if a viewport should force the
1373     * <code>Scrollable</code>'s height to match its own,
1374     * false otherwise
1375     */

1376    public boolean getScrollableTracksViewportHeight() {
1377    if (getParent() instanceof JViewport JavaDoc) {
1378        JViewport JavaDoc port = (JViewport JavaDoc)getParent();
1379        TextUI ui = getUI();
1380        int h = port.getHeight();
1381        Dimension min = ui.getMinimumSize(this);
1382        if (h >= min.height) {
1383        Dimension max = ui.getMaximumSize(this);
1384        if (h <= max.height) {
1385            return true;
1386        }
1387        }
1388    }
1389    return false;
1390    }
1391
1392    // --- Serialization ------------------------------------
1393

1394    /**
1395     * See <code>readObject</code> and <code>writeObject</code> in
1396     * <code>JComponent</code> for more
1397     * information about serialization in Swing.
1398     */

1399    private void writeObject(ObjectOutputStream s) throws IOException {
1400        s.defaultWriteObject();
1401        if (getUIClassID().equals(uiClassID)) {
1402            byte count = JComponent.getWriteObjCounter(this);
1403            JComponent.setWriteObjCounter(this, --count);
1404            if (count == 0 && ui != null) {
1405                ui.installUI(this);
1406            }
1407        }
1408    }
1409
1410    // --- variables ---------------------------------------
1411

1412    /**
1413     * Stream currently loading asynchronously (potentially cancelable).
1414     * Access to this variable should be synchronized.
1415     */

1416    PageStream loading;
1417
1418    /**
1419     * Current content binding of the editor.
1420     */

1421    private EditorKit kit;
1422
1423    private Hashtable pageProperties;
1424
1425    /**
1426     * Table of registered type handlers for this editor.
1427     */

1428    private Hashtable typeHandlers;
1429
1430    /*
1431     * Private AppContext keys for this class's static variables.
1432     */

1433    private static final Object JavaDoc kitRegistryKey =
1434        new StringBuffer JavaDoc("JEditorPane.kitRegistry");
1435    private static final Object JavaDoc kitTypeRegistryKey =
1436        new StringBuffer JavaDoc("JEditorPane.kitTypeRegistry");
1437    private static final Object JavaDoc kitLoaderRegistryKey =
1438        new StringBuffer JavaDoc("JEditorPane.kitLoaderRegistry");
1439
1440    /**
1441     * @see #getUIClassID
1442     * @see #readObject
1443     */

1444    private static final String JavaDoc uiClassID = "EditorPaneUI";
1445
1446
1447    /**
1448     * Key for a client property used to indicate whether
1449     * <a HREF="http://www.w3.org/TR/CSS21/syndata.html#length-units">
1450     * w3c compliant</a> length units are used for html rendering.
1451     * <p>
1452     * By default this is not enabled; to enable
1453     * it set the client {@link putClientProperty property} with this name
1454     * to <code>Boolean.TRUE</code>.
1455     *
1456     * @since 1.5
1457     */

1458    public static final String JavaDoc W3C_LENGTH_UNITS = "JEditorPane.w3cLengthUnits";
1459
1460    /**
1461     * Key for a client property used to indicate whether
1462     * the default font and foreground color from the component are
1463     * used if a font or foreground color is not specified in the styled
1464     * text.
1465     * <p>
1466     * The default varies based on the look and feel;
1467     * to enable it set the client {@link putClientProperty property} with
1468     * this name to <code>Boolean.TRUE</code>.
1469     *
1470     * @since 1.5
1471     */

1472    public static final String JavaDoc HONOR_DISPLAY_PROPERTIES = "JEditorPane.honorDisplayProperties";
1473
1474    /**
1475     * Returns a string representation of this <code>JEditorPane</code>.
1476     * This method
1477     * is intended to be used only for debugging purposes, and the
1478     * content and format of the returned string may vary between
1479     * implementations. The returned string may be empty but may not
1480     * be <code>null</code>.
1481     *
1482     * @return a string representation of this <code>JEditorPane</code>
1483     */

1484    protected String JavaDoc paramString() {
1485        String JavaDoc kitString = (kit != null ?
1486                kit.toString() : "");
1487        String JavaDoc typeHandlersString = (typeHandlers != null ?
1488                     typeHandlers.toString() : "");
1489
1490        return super.paramString() +
1491        ",kit=" + kitString +
1492        ",typeHandlers=" + typeHandlersString;
1493    }
1494
1495    
1496/////////////////
1497
// Accessibility support
1498
////////////////
1499

1500
1501    /**
1502     * Gets the AccessibleContext associated with this JEditorPane.
1503     * For editor panes, the AccessibleContext takes the form of an
1504     * AccessibleJEditorPane.
1505     * A new AccessibleJEditorPane instance is created if necessary.
1506     *
1507     * @return an AccessibleJEditorPane that serves as the
1508     * AccessibleContext of this JEditorPane
1509     */

1510    public AccessibleContext getAccessibleContext() {
1511        if (accessibleContext == null) {
1512        if (JEditorPane.this.getEditorKit() instanceof HTMLEditorKit) {
1513        accessibleContext = new AccessibleJEditorPaneHTML();
1514        } else {
1515                accessibleContext = new AccessibleJEditorPane();
1516        }
1517        }
1518        return accessibleContext;
1519    }
1520
1521    /**
1522     * This class implements accessibility support for the
1523     * <code>JEditorPane</code> class. It provides an implementation of the
1524     * Java Accessibility API appropriate to editor pane user-interface
1525     * elements.
1526     * <p>
1527     * <strong>Warning:</strong>
1528     * Serialized objects of this class will not be compatible with
1529     * future Swing releases. The current serialization support is
1530     * appropriate for short term storage or RMI between applications running
1531     * the same version of Swing. As of 1.4, support for long term storage
1532     * of all JavaBeans<sup><font size="-2">TM</font></sup>
1533     * has been added to the <code>java.beans</code> package.
1534     * Please see {@link java.beans.XMLEncoder}.
1535     */

1536    protected class AccessibleJEditorPane extends AccessibleJTextComponent {
1537
1538        /**
1539         * Gets the accessibleDescription property of this object. If this
1540         * property isn't set, returns the content type of this
1541         * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1542         *
1543         * @return the localized description of the object; <code>null</code>
1544         * if this object does not have a description
1545         *
1546         * @see #setAccessibleName
1547         */

1548        public String JavaDoc getAccessibleDescription() {
1549            if (accessibleDescription != null) {
1550                return accessibleDescription;
1551            } else {
1552                return JEditorPane.this.getContentType();
1553            }
1554        }
1555
1556        /**
1557         * Gets the state set of this object.
1558         *
1559         * @return an instance of AccessibleStateSet describing the states
1560         * of the object
1561         * @see AccessibleStateSet
1562         */

1563        public AccessibleStateSet getAccessibleStateSet() {
1564            AccessibleStateSet states = super.getAccessibleStateSet();
1565            states.add(AccessibleState.MULTI_LINE);
1566            return states;
1567        }
1568    }
1569
1570    /**
1571     * This class provides support for <code>AccessibleHypertext</code>,
1572     * and is used in instances where the <code>EditorKit</code>
1573     * installed in this <code>JEditorPane</code> is an instance of
1574     * <code>HTMLEditorKit</code>.
1575     * <p>
1576     * <strong>Warning:</strong>
1577     * Serialized objects of this class will not be compatible with
1578     * future Swing releases. The current serialization support is
1579     * appropriate for short term storage or RMI between applications running
1580     * the same version of Swing. As of 1.4, support for long term storage
1581     * of all JavaBeans<sup><font size="-2">TM</font></sup>
1582     * has been added to the <code>java.beans</code> package.
1583     * Please see {@link java.beans.XMLEncoder}.
1584     */

1585    protected class AccessibleJEditorPaneHTML extends AccessibleJEditorPane {
1586
1587    private AccessibleContext accessibleContext;
1588
1589    public AccessibleText getAccessibleText() {
1590        return new JEditorPaneAccessibleHypertextSupport();
1591    }
1592
1593    protected AccessibleJEditorPaneHTML () {
1594        HTMLEditorKit kit = (HTMLEditorKit)JEditorPane.this.getEditorKit();
1595        accessibleContext = kit.getAccessibleContext();
1596    }
1597
1598    /**
1599     * Returns the number of accessible children of the object.
1600     *
1601     * @return the number of accessible children of the object.
1602     */

1603    public int getAccessibleChildrenCount() {
1604        if (accessibleContext != null) {
1605        return accessibleContext.getAccessibleChildrenCount();
1606        } else {
1607        return 0;
1608        }
1609    }
1610    
1611    /**
1612     * Returns the specified Accessible child of the object. The Accessible
1613     * children of an Accessible object are zero-based, so the first child
1614     * of an Accessible child is at index 0, the second child is at index 1,
1615     * and so on.
1616     *
1617     * @param i zero-based index of child
1618     * @return the Accessible child of the object
1619     * @see #getAccessibleChildrenCount
1620     */

1621    public Accessible getAccessibleChild(int i) {
1622        if (accessibleContext != null) {
1623        return accessibleContext.getAccessibleChild(i);
1624        } else {
1625        return null;
1626        }
1627    }
1628    
1629    /**
1630     * Returns the Accessible child, if one exists, contained at the local
1631     * coordinate Point.
1632     *
1633     * @param p The point relative to the coordinate system of this object.
1634     * @return the Accessible, if it exists, at the specified location;
1635     * otherwise null
1636     */

1637    public Accessible getAccessibleAt(Point p) {
1638        if (accessibleContext != null && p != null) {
1639        try {
1640            AccessibleComponent acomp =
1641            accessibleContext.getAccessibleComponent();
1642            if (acomp != null) {
1643            return acomp.getAccessibleAt(p);
1644            } else {
1645            return null;
1646            }
1647        } catch (IllegalComponentStateException e) {
1648            return null;
1649        }
1650        } else {
1651        return null;
1652        }
1653    }
1654    }
1655
1656    /**
1657     * What's returned by
1658     * <code>AccessibleJEditorPaneHTML.getAccessibleText</code>.
1659     *
1660     * Provides support for <code>AccessibleHypertext</code> in case
1661     * there is an HTML document being displayed in this
1662     * <code>JEditorPane</code>.
1663     *
1664     */

1665    protected class JEditorPaneAccessibleHypertextSupport
1666    extends AccessibleJEditorPane implements AccessibleHypertext {
1667
1668    public class HTMLLink extends AccessibleHyperlink {
1669        Element element;
1670
1671        public HTMLLink(Element e) {
1672        element = e;
1673        }
1674
1675        /**
1676         * Since the document a link is associated with may have
1677         * changed, this method returns whether this Link is valid
1678         * anymore (with respect to the document it references).
1679         *
1680         * @return a flag indicating whether this link is still valid with
1681         * respect to the AccessibleHypertext it belongs to
1682         */

1683        public boolean isValid() {
1684        return JEditorPaneAccessibleHypertextSupport.this.linksValid;
1685        }
1686
1687        /**
1688         * Returns the number of accessible actions available in this Link
1689         * If there are more than one, the first one is NOT considered the
1690         * "default" action of this LINK object (e.g. in an HTML imagemap).
1691         * In general, links will have only one AccessibleAction in them.
1692         *
1693         * @return the zero-based number of Actions in this object
1694         */

1695        public int getAccessibleActionCount() {
1696        return 1;
1697        }
1698
1699        /**
1700         * Perform the specified Action on the object
1701         *
1702         * @param i zero-based index of actions
1703         * @return true if the the action was performed; else false.
1704         * @see #getAccessibleActionCount
1705         */

1706        public boolean doAccessibleAction(int i) {
1707        if (i == 0 && isValid() == true) {
1708            URL u = (URL) getAccessibleActionObject(i);
1709            if (u != null) {
1710            HyperlinkEvent linkEvent =
1711                new HyperlinkEvent(JEditorPane.this, HyperlinkEvent.EventType.ACTIVATED, u);
1712            JEditorPane.this.fireHyperlinkUpdate(linkEvent);
1713            return true;
1714            }
1715        }
1716        return false; // link invalid or i != 0
1717
}
1718
1719        /**
1720         * Return a String description of this particular
1721         * link action. The string returned is the text
1722         * within the document associated with the element
1723         * which contains this link.
1724         *
1725         * @param i zero-based index of the actions
1726         * @return a String description of the action
1727         * @see #getAccessibleActionCount
1728         */

1729        public String JavaDoc getAccessibleActionDescription(int i) {
1730        if (i == 0 && isValid() == true) {
1731            Document d = JEditorPane.this.getDocument();
1732            if (d != null) {
1733            try {
1734                return d.getText(getStartIndex(),
1735                         getEndIndex() - getStartIndex());
1736            } catch (BadLocationException exception) {
1737                return null;
1738            }
1739            }
1740        }
1741        return null;
1742        }
1743
1744        /**
1745         * Returns a URL object that represents the link.
1746         *
1747         * @param i zero-based index of the actions
1748         * @return an URL representing the HTML link itself
1749         * @see #getAccessibleActionCount
1750         */

1751        public Object JavaDoc getAccessibleActionObject(int i) {
1752        if (i == 0 && isValid() == true) {
1753            AttributeSet as = element.getAttributes();
1754            AttributeSet anchor =
1755            (AttributeSet) as.getAttribute(HTML.Tag.A);
1756            String JavaDoc href = (anchor != null) ?
1757            (String JavaDoc) anchor.getAttribute(HTML.Attribute.HREF) : null;
1758            if (href != null) {
1759            URL u;
1760            try {
1761                u = new URL(JEditorPane.this.getPage(), href);
1762            } catch (MalformedURLException m) {
1763                u = null;
1764            }
1765            return u;
1766            }
1767        }
1768        return null; // link invalid or i != 0
1769
}
1770
1771        /**
1772         * Return an object that represents the link anchor,
1773         * as appropriate for that link. E.g. from HTML:
1774         * <a HREF="http://www.sun.com/access">Accessibility</a>
1775         * this method would return a String containing the text:
1776         * 'Accessibility'.
1777         *
1778         * Similarly, from this HTML:
1779         * &lt;a HREF="#top"&gt;&lt;img SRC="top-hat.gif" alt="top hat"&gt;&lt;/a&gt;
1780         * this might return the object ImageIcon("top-hat.gif", "top hat");
1781         *
1782         * @param i zero-based index of the actions
1783         * @return an Object representing the hypertext anchor
1784         * @see #getAccessibleActionCount
1785         */

1786        public Object JavaDoc getAccessibleActionAnchor(int i) {
1787        return getAccessibleActionDescription(i);
1788        }
1789
1790
1791        /**
1792         * Get the index with the hypertext document at which this
1793         * link begins
1794         *
1795         * @return index of start of link
1796         */

1797        public int getStartIndex() {
1798        return element.getStartOffset();
1799        }
1800
1801        /**
1802         * Get the index with the hypertext document at which this
1803         * link ends
1804         *
1805         * @return index of end of link
1806         */

1807        public int getEndIndex() {
1808        return element.getEndOffset();
1809        }
1810    }
1811
1812    private class LinkVector extends Vector {
1813        public int baseElementIndex(Element e) {
1814        HTMLLink l;
1815        for (int i = 0; i < elementCount; i++) {
1816            l = (HTMLLink) elementAt(i);
1817            if (l.element == e) {
1818            return i;
1819            }
1820        }
1821        return -1;
1822        }
1823    }
1824
1825        LinkVector hyperlinks;
1826    boolean linksValid = false;
1827
1828    /**
1829     * Build the private table mapping links to locations in the text
1830     */

1831    private void buildLinkTable() {
1832        hyperlinks.removeAllElements();
1833        Document d = JEditorPane.this.getDocument();
1834        if (d != null) {
1835        ElementIterator ei = new ElementIterator(d);
1836        Element e;
1837        AttributeSet as;
1838        AttributeSet anchor;
1839        String JavaDoc href;
1840        while ((e = ei.next()) != null) {
1841            if (e.isLeaf()) {
1842            as = e.getAttributes();
1843            anchor = (AttributeSet) as.getAttribute(HTML.Tag.A);
1844            href = (anchor != null) ?
1845            (String JavaDoc) anchor.getAttribute(HTML.Attribute.HREF) : null;
1846            if (href != null) {
1847                hyperlinks.addElement(new HTMLLink(e));
1848            }
1849            }
1850        }
1851        }
1852        linksValid = true;
1853    }
1854
1855    /**
1856     * Make one of these puppies
1857     */

1858    public JEditorPaneAccessibleHypertextSupport() {
1859        hyperlinks = new LinkVector();
1860        Document d = JEditorPane.this.getDocument();
1861        if (d != null) {
1862        d.addDocumentListener(new DocumentListener() {
1863            public void changedUpdate(DocumentEvent theEvent) {
1864            linksValid = false;
1865            }
1866            public void insertUpdate(DocumentEvent theEvent) {
1867            linksValid = false;
1868            }
1869            public void removeUpdate(DocumentEvent theEvent) {
1870            linksValid = false;
1871            }
1872        });
1873        }
1874    }
1875
1876    /**
1877     * Returns the number of links within this hypertext doc.
1878     *
1879     * @return number of links in this hypertext doc.
1880     */

1881    public int getLinkCount() {
1882        if (linksValid == false) {
1883        buildLinkTable();
1884        }
1885        return hyperlinks.size();
1886    }
1887
1888    /**
1889     * Returns the index into an array of hyperlinks that
1890     * is associated with this character index, or -1 if there
1891     * is no hyperlink associated with this index.
1892     *
1893     * @param charIndex index within the text
1894     * @return index into the set of hyperlinks for this hypertext doc.
1895     */

1896    public int getLinkIndex(int charIndex) {
1897        if (linksValid == false) {
1898        buildLinkTable();
1899        }
1900            Element e = null;
1901        Document doc = JEditorPane.this.getDocument();
1902        if (doc != null) {
1903        for (e = doc.getDefaultRootElement(); ! e.isLeaf(); ) {
1904            int index = e.getElementIndex(charIndex);
1905            e = e.getElement(index);
1906        }
1907        }
1908
1909        // don't need to verify that it's an HREF element; if
1910
// not, then it won't be in the hyperlinks Vector, and
1911
// so indexOf will return -1 in any case
1912
return hyperlinks.baseElementIndex(e);
1913    }
1914
1915    /**
1916     * Returns the index into an array of hyperlinks that
1917     * index. If there is no hyperlink at this index, it returns
1918     * null.
1919     *
1920     * @param linkIndex into the set of hyperlinks for this hypertext doc.
1921     * @return string representation of the hyperlink
1922     */

1923    public AccessibleHyperlink getLink(int linkIndex) {
1924        if (linksValid == false) {
1925        buildLinkTable();
1926        }
1927        if (linkIndex >= 0 && linkIndex < hyperlinks.size()) {
1928            return (AccessibleHyperlink) hyperlinks.elementAt(linkIndex);
1929        } else {
1930        return null;
1931        }
1932    }
1933
1934    /**
1935     * Returns the contiguous text within the document that
1936     * is associated with this hyperlink.
1937     *
1938     * @param linkIndex into the set of hyperlinks for this hypertext doc.
1939     * @return the contiguous text sharing the link at this index
1940     */

1941    public String JavaDoc getLinkText(int linkIndex) {
1942        if (linksValid == false) {
1943        buildLinkTable();
1944        }
1945        Element e = (Element) hyperlinks.elementAt(linkIndex);
1946        if (e != null) {
1947        Document d = JEditorPane.this.getDocument();
1948        if (d != null) {
1949            try {
1950            return d.getText(e.getStartOffset(),
1951                     e.getEndOffset() - e.getStartOffset());
1952            } catch (BadLocationException exception) {
1953            return null;
1954            }
1955        }
1956        }
1957        return null;
1958    }
1959    }
1960
1961    static class PlainEditorKit extends DefaultEditorKit implements ViewFactory {
1962
1963    /**
1964     * Fetches a factory that is suitable for producing
1965     * views of any models that are produced by this
1966     * kit. The default is to have the UI produce the
1967     * factory, so this method has no implementation.
1968     *
1969     * @return the view factory
1970     */

1971        public ViewFactory getViewFactory() {
1972        return this;
1973    }
1974
1975    /**
1976     * Creates a view from the given structural element of a
1977     * document.
1978     *
1979     * @param elem the piece of the document to build a view of
1980     * @return the view
1981     * @see View
1982     */

1983        public View create(Element elem) {
1984            Document doc = elem.getDocument();
1985            Object JavaDoc i18nFlag
1986                = doc.getProperty("i18n"/*AbstractDocument.I18NProperty*/);
1987            if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
1988                // build a view that support bidi
1989
return createI18N(elem);
1990            } else {
1991                return new WrappedPlainView(elem);
1992            }
1993        }
1994
1995        View createI18N(Element elem) {
1996            String JavaDoc kind = elem.getName();
1997            if (kind != null) {
1998                if (kind.equals(AbstractDocument.ContentElementName)) {
1999                    return new PlainParagraph(elem);
2000                } else if (kind.equals(AbstractDocument.ParagraphElementName)){
2001                    return new BoxView(elem, View.Y_AXIS);
2002                }
2003            }
2004            return null;
2005        }
2006
2007        /**
2008         * Paragraph for representing plain-text lines that support
2009         * bidirectional text.
2010         */

2011        static class PlainParagraph extends javax.swing.text.ParagraphView JavaDoc {
2012
2013            PlainParagraph(Element elem) {
2014                super(elem);
2015                layoutPool = new LogicalView(elem);
2016                layoutPool.setParent(this);
2017            }
2018
2019            protected void setPropertiesFromAttributes() {
2020                Component c = getContainer();
2021                if ((c != null)
2022                    && (! c.getComponentOrientation().isLeftToRight()))
2023                {
2024                    setJustification(StyleConstants.ALIGN_RIGHT);
2025                } else {
2026                    setJustification(StyleConstants.ALIGN_LEFT);
2027                }
2028            }
2029
2030            /**
2031             * Fetch the constraining span to flow against for
2032             * the given child index.
2033             */

2034            public int getFlowSpan(int index) {
2035                Component c = getContainer();
2036                if (c instanceof JTextArea JavaDoc) {
2037                    JTextArea JavaDoc area = (JTextArea JavaDoc) c;
2038                    if (! area.getLineWrap()) {
2039                        // no limit if unwrapped
2040
return Integer.MAX_VALUE;
2041                    }
2042                }
2043                return super.getFlowSpan(index);
2044            }
2045
2046            protected SizeRequirements JavaDoc calculateMinorAxisRequirements(int axis,
2047                                                            SizeRequirements JavaDoc r)
2048            {
2049                SizeRequirements JavaDoc req
2050                    = super.calculateMinorAxisRequirements(axis, r);
2051                Component c = getContainer();
2052                if (c instanceof JTextArea JavaDoc) {
2053                    JTextArea JavaDoc area = (JTextArea JavaDoc) c;
2054                    if (! area.getLineWrap()) {
2055                        // min is pref if unwrapped
2056
req.minimum = req.preferred;
2057                    }
2058                }
2059                return req;
2060            }
2061
2062            /**
2063             * This class can be used to represent a logical view for
2064             * a flow. It keeps the children updated to reflect the state
2065             * of the model, gives the logical child views access to the
2066             * view hierarchy, and calculates a preferred span. It doesn't
2067             * do any rendering, layout, or model/view translation.
2068             */

2069            static class LogicalView extends CompositeView {
2070        
2071                LogicalView(Element elem) {
2072                    super(elem);
2073                }
2074
2075                protected int getViewIndexAtPosition(int pos) {
2076                    Element elem = getElement();
2077                    if (elem.getElementCount() > 0) {
2078                        return elem.getElementIndex(pos);
2079                    }
2080                    return 0;
2081                }
2082
2083                protected boolean
2084                updateChildren(DocumentEvent.ElementChange ec,
2085                               DocumentEvent e, ViewFactory f)
2086                {
2087                    return false;
2088                }
2089
2090                protected void loadChildren(ViewFactory f) {
2091                    Element elem = getElement();
2092                    if (elem.getElementCount() > 0) {
2093                        super.loadChildren(f);
2094                    } else {
2095                        View v = new GlyphView(elem);
2096                        append(v);
2097                    }
2098                }
2099
2100                public float getPreferredSpan(int axis) {
2101                    if( getViewCount() != 1 )
2102                        throw new Error JavaDoc("One child view is assumed.");
2103                
2104                    View v = getView(0);
2105                    //((GlyphView)v).setGlyphPainter(null);
2106
return v.getPreferredSpan(axis);
2107                }
2108
2109                /**
2110                 * Forward the DocumentEvent to the given child view. This
2111                 * is implemented to reparent the child to the logical view
2112                 * (the children may have been parented by a row in the flow
2113                 * if they fit without breaking) and then execute the
2114                 * superclass behavior.
2115                 *
2116                 * @param v the child view to forward the event to.
2117                 * @param e the change information from the associated document
2118                 * @param a the current allocation of the view
2119                 * @param f the factory to use to rebuild if the view has
2120                 * children
2121                 * @see #forwardUpdate
2122                 * @since 1.3
2123                 */

2124                protected void forwardUpdateToView(View v, DocumentEvent e,
2125                                                   Shape a, ViewFactory f) {
2126                    v.setParent(this);
2127                    super.forwardUpdateToView(v, e, a, f);
2128                }
2129
2130                // The following methods don't do anything useful, they
2131
// simply keep the class from being abstract.
2132

2133                public void paint(Graphics g, Shape allocation) {
2134                }
2135
2136                protected boolean isBefore(int x, int y, Rectangle alloc) {
2137                    return false;
2138                }
2139
2140                protected boolean isAfter(int x, int y, Rectangle alloc) {
2141                    return false;
2142                }
2143
2144                protected View getViewAtPoint(int x, int y, Rectangle alloc) {
2145                    return null;
2146                }
2147
2148                protected void childAllocation(int index, Rectangle a) {
2149                }
2150            }
2151        }
2152    }
2153
2154/* This is useful for the nightmare of parsing multi-part HTTP/RFC822 headers
2155 * sensibly:
2156 * From a String like: 'timeout=15, max=5'
2157 * create an array of Strings:
2158 * { {"timeout", "15"},
2159 * {"max", "5"}
2160 * }
2161 * From one like: 'Basic Realm="FuzzFace" Foo="Biz Bar Baz"'
2162 * create one like (no quotes in literal):
2163 * { {"basic", null},
2164 * {"realm", "FuzzFace"}
2165 * {"foo", "Biz Bar Baz"}
2166 * }
2167 * keys are converted to lower case, vals are left as is....
2168 *
2169 * author Dave Brown
2170 */

2171
2172
2173static class HeaderParser {
2174
2175    /* table of key/val pairs - maxes out at 10!!!!*/
2176    String JavaDoc raw;
2177    String JavaDoc[][] tab;
2178    
2179    public HeaderParser(String JavaDoc raw) {
2180    this.raw = raw;
2181    tab = new String JavaDoc[10][2];
2182    parse();
2183    }
2184
2185    private void parse() {
2186    
2187    if (raw != null) {
2188        raw = raw.trim();
2189        char[] ca = raw.toCharArray();
2190        int beg = 0, end = 0, i = 0;
2191        boolean inKey = true;
2192        boolean inQuote = false;
2193        int len = ca.length;
2194        while (end < len) {
2195        char c = ca[end];
2196        if (c == '=') { // end of a key
2197
tab[i][0] = new String JavaDoc(ca, beg, end-beg).toLowerCase();
2198            inKey = false;
2199            end++;
2200            beg = end;
2201        } else if (c == '\"') {
2202            if (inQuote) {
2203            tab[i++][1]= new String JavaDoc(ca, beg, end-beg);
2204            inQuote=false;
2205            do {
2206                end++;
2207            } while (end < len && (ca[end] == ' ' || ca[end] == ','));
2208            inKey=true;
2209            beg=end;
2210            } else {
2211            inQuote=true;
2212            end++;
2213            beg=end;
2214            }
2215        } else if (c == ' ' || c == ',') { // end key/val, of whatever we're in
2216
if (inQuote) {
2217            end++;
2218            continue;
2219            } else if (inKey) {
2220            tab[i++][0] = (new String JavaDoc(ca, beg, end-beg)).toLowerCase();
2221            } else {
2222            tab[i++][1] = (new String JavaDoc(ca, beg, end-beg));
2223            }
2224            while (end < len && (ca[end] == ' ' || ca[end] == ',')) {
2225            end++;
2226            }
2227            inKey = true;
2228            beg = end;
2229        } else {
2230            end++;
2231        }
2232        }
2233        // get last key/val, if any
2234
if (--end > beg) {
2235        if (!inKey) {
2236            if (ca[end] == '\"') {
2237            tab[i++][1] = (new String JavaDoc(ca, beg, end-beg));
2238            } else {
2239            tab[i++][1] = (new String JavaDoc(ca, beg, end-beg+1));
2240            }
2241        } else {
2242            tab[i][0] = (new String JavaDoc(ca, beg, end-beg+1)).toLowerCase();
2243        }
2244        } else if (end == beg) {
2245        if (!inKey) {
2246            if (ca[end] == '\"') {
2247            tab[i++][1] = String.valueOf(ca[end-1]);
2248            } else {
2249            tab[i++][1] = String.valueOf(ca[end]);
2250            }
2251        } else {
2252            tab[i][0] = String.valueOf(ca[end]).toLowerCase();
2253        }
2254        }
2255    }
2256    
2257    }
2258
2259    public String JavaDoc findKey(int i) {
2260    if (i < 0 || i > 10)
2261        return null;
2262    return tab[i][0];
2263    }
2264
2265    public String JavaDoc findValue(int i) {
2266    if (i < 0 || i > 10)
2267        return null;
2268    return tab[i][1];
2269    }
2270
2271    public String JavaDoc findValue(String JavaDoc key) {
2272    return findValue(key, null);
2273    }
2274
2275    public String JavaDoc findValue(String JavaDoc k, String JavaDoc Default) {
2276    if (k == null)
2277        return Default;
2278    k = k.toLowerCase();
2279    for (int i = 0; i < 10; ++i) {
2280        if (tab[i][0] == null) {
2281        return Default;
2282        } else if (k.equals(tab[i][0])) {
2283        return tab[i][1];
2284        }
2285    }
2286    return Default;
2287    }
2288
2289    public int findInt(String JavaDoc k, int Default) {
2290    try {
2291        return Integer.parseInt(findValue(k, String.valueOf(Default)));
2292    } catch (Throwable JavaDoc t) {
2293        return Default;
2294    }
2295    }
2296 }
2297
2298}
2299
2300
Popular Tags