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