1 /*
2  * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package javax.swing.text.html;
26 
27 import java.net.*;
28 import java.io.*;
29 import java.awt.*;
30 import java.awt.event.*;
31 import java.util.*;
32 import javax.swing.*;
33 import javax.swing.event.*;
34 import javax.swing.text.*;
35 
36 /**
37  * Component decorator that implements the view interface
38  * for form elements, <input>, <textarea>,
39  * and <select>.  The model for the component is stored
40  * as an attribute of the element (using StyleConstants.ModelAttribute),
41  * and is used to build the component of the view.  The type
42  * of the model is assumed to of the type that would be set by
43  * <code>HTMLDocument.HTMLReader.FormAction</code>.  If there are
44  * multiple views mapped over the document, they will share the
45  * embedded component models.
46  * <p>
47  * The following table shows what components get built
48  * by this view.
49  *
50  * <table class="striped">
51  * <caption>Shows what components get built by this view</caption>
52  * <thead>
53  *   <tr>
54  *     <th scope="col">Element Type
55  *     <th scope="col">Component built
56  * </thead>
57  * <tbody>
58  *   <tr>
59  *     <th scope="row">input, type button
60  *     <td>JButton
61  *   <tr>
62  *     <th scope="row">input, type checkbox
63  *     <td>JCheckBox
64  *   <tr>
65  *     <th scope="row">input, type image
66  *     <td>JButton
67  *   <tr>
68  *     <th scope="row">input, type password
69  *     <td>JPasswordField
70  *   <tr>
71  *     <th scope="row">input, type radio
72  *     <td>JRadioButton
73  *   <tr>
74  *     <th scope="row">input, type reset
75  *     <td>JButton
76  *   <tr>
77  *     <th scope="row">input, type submit
78  *     <td>JButton
79  *   <tr>
80  *     <th scope="row">input, type text
81  *     <td>JTextField
82  *   <tr>
83  *     <th scope="row">select, size &gt; 1 or multiple attribute defined
84  *     <td>JList in a JScrollPane
85  *   <tr>
86  *     <th scope="row">select, size unspecified or 1
87  *     <td>JComboBox
88  *   <tr>
89  *     <th scope="row">textarea
90  *     <td>JTextArea in a JScrollPane
91  *   <tr>
92  *     <th scope="row">input, type file
93  *     <td>JTextField
94  * </tbody>
95  * </table>
96  *
97  * @author Timothy Prinzing
98  * @author Sunita Mani
99  */
100 public class FormView extends ComponentView implements ActionListener {
101 
102     /**
103      * If a value attribute is not specified for a FORM input element
104      * of type "submit", then this default string is used.
105      *
106      * @deprecated As of 1.3, value now comes from UIManager property
107      *             FormView.submitButtonText
108      */
109     @Deprecated
110     public static final String SUBMIT = new String("Submit Query");
111     /**
112      * If a value attribute is not specified for a FORM input element
113      * of type "reset", then this default string is used.
114      *
115      * @deprecated As of 1.3, value comes from UIManager UIManager property
116      *             FormView.resetButtonText
117      */
118     @Deprecated
119     public static final String RESET = new String("Reset");
120 
121     /**
122      * Document attribute name for storing POST data. JEditorPane.getPostData()
123      * uses the same name, should be kept in sync.
124      */
125     static final String PostDataProperty = "javax.swing.JEditorPane.postdata";
126 
127     /**
128      * Used to indicate if the maximum span should be the same as the
129      * preferred span. This is used so that the Component's size doesn't
130      * change if there is extra room on a line. The first bit is used for
131      * the X direction, and the second for the y direction.
132      */
133     private short maxIsPreferred;
134 
135     /**
136      * Creates a new FormView object.
137      *
138      * @param elem the element to decorate
139      */
FormView(Element elem)140     public FormView(Element elem) {
141         super(elem);
142     }
143 
144     /**
145      * Create the component.  This is basically a
146      * big switch statement based upon the tag type
147      * and html attributes of the associated element.
148      */
createComponent()149     protected Component createComponent() {
150         AttributeSet attr = getElement().getAttributes();
151         HTML.Tag t = (HTML.Tag)
152             attr.getAttribute(StyleConstants.NameAttribute);
153         JComponent c = null;
154         Object model = attr.getAttribute(StyleConstants.ModelAttribute);
155 
156         // Remove listeners previously registered in shared model
157         // when a new UI component is replaced.  See bug 7189299.
158         removeStaleListenerForModel(model);
159         if (t == HTML.Tag.INPUT) {
160             c = createInputComponent(attr, model);
161         } else if (t == HTML.Tag.SELECT) {
162 
163             if (model instanceof OptionListModel) {
164                 @SuppressWarnings("unchecked")
165                 JList<?> list = new JList<>((ListModel) model);
166                 int size = HTML.getIntegerAttributeValue(attr,
167                                                          HTML.Attribute.SIZE,
168                                                          1);
169                 list.setVisibleRowCount(size);
170                 list.setSelectionModel((ListSelectionModel)model);
171                 c = new JScrollPane(list);
172             } else {
173                 @SuppressWarnings("unchecked")
174                 JComboBox<?> tmp = new JComboBox<>((ComboBoxModel) model);
175                 c = tmp;
176                 maxIsPreferred = 3;
177             }
178         } else if (t == HTML.Tag.TEXTAREA) {
179             JTextArea area = new JTextArea((Document) model);
180             int rows = HTML.getIntegerAttributeValue(attr,
181                                                      HTML.Attribute.ROWS,
182                                                      1);
183             area.setRows(rows);
184             int cols = HTML.getIntegerAttributeValue(attr,
185                                                      HTML.Attribute.COLS,
186                                                      20);
187             maxIsPreferred = 3;
188             area.setColumns(cols);
189             c = new JScrollPane(area,
190                                 JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
191                                 JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
192         }
193 
194         if (c != null) {
195             c.setAlignmentY(1.0f);
196         }
197         return c;
198     }
199 
200 
201     /**
202      * Creates a component for an &lt;INPUT&gt; element based on the
203      * value of the "type" attribute.
204      *
205      * @param attr set of attributes associated with the &lt;INPUT&gt; element.
206      * @param model the value of the StyleConstants.ModelAttribute
207      * @return the component.
208      */
createInputComponent(AttributeSet attr, Object model)209     private JComponent createInputComponent(AttributeSet attr, Object model) {
210         JComponent c = null;
211         String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
212 
213         if (type.equals("submit") || type.equals("reset")) {
214             String value = (String)
215                 attr.getAttribute(HTML.Attribute.VALUE);
216             if (value == null) {
217                 if (type.equals("submit")) {
218                     value = UIManager.getString("FormView.submitButtonText");
219                 } else {
220                     value = UIManager.getString("FormView.resetButtonText");
221                 }
222             }
223             JButton button = new JButton(value);
224             if (model != null) {
225                 button.setModel((ButtonModel)model);
226                 button.addActionListener(this);
227             }
228             c = button;
229             maxIsPreferred = 3;
230         } else if (type.equals("image")) {
231             String srcAtt = (String) attr.getAttribute(HTML.Attribute.SRC);
232             JButton button;
233             try {
234                 URL base = ((HTMLDocument)getElement().getDocument()).getBase();
235                 URL srcURL = new URL(base, srcAtt);
236                 Icon icon = new ImageIcon(srcURL);
237                 button  = new JButton(icon);
238             } catch (MalformedURLException e) {
239                 button = new JButton(srcAtt);
240             }
241             if (model != null) {
242                 button.setModel((ButtonModel)model);
243                 button.addMouseListener(new MouseEventListener());
244             }
245             c = button;
246             maxIsPreferred = 3;
247         } else if (type.equals("checkbox")) {
248             c = new JCheckBox();
249             if (model != null) {
250                 ((JCheckBox)c).setModel((JToggleButton.ToggleButtonModel) model);
251             }
252             maxIsPreferred = 3;
253         } else if (type.equals("radio")) {
254             c = new JRadioButton();
255             if (model != null) {
256                 ((JRadioButton)c).setModel((JToggleButton.ToggleButtonModel)model);
257             }
258             maxIsPreferred = 3;
259         } else if (type.equals("text")) {
260             int size = HTML.getIntegerAttributeValue(attr,
261                                                      HTML.Attribute.SIZE,
262                                                      -1);
263             JTextField field;
264             if (size > 0) {
265                 field = new JTextField();
266                 field.setColumns(size);
267             }
268             else {
269                 field = new JTextField();
270                 field.setColumns(20);
271             }
272             c = field;
273             if (model != null) {
274                 field.setDocument((Document) model);
275             }
276             field.addActionListener(this);
277             maxIsPreferred = 3;
278         } else if (type.equals("password")) {
279             JPasswordField field = new JPasswordField();
280             c = field;
281             if (model != null) {
282                 field.setDocument((Document) model);
283             }
284             int size = HTML.getIntegerAttributeValue(attr,
285                                                      HTML.Attribute.SIZE,
286                                                      -1);
287             field.setColumns((size > 0) ? size : 20);
288             field.addActionListener(this);
289             maxIsPreferred = 3;
290         } else if (type.equals("file")) {
291             JTextField field = new JTextField();
292             if (model != null) {
293                 field.setDocument((Document)model);
294             }
295             int size = HTML.getIntegerAttributeValue(attr, HTML.Attribute.SIZE,
296                                                      -1);
297             field.setColumns((size > 0) ? size : 20);
298             JButton browseButton = new JButton(UIManager.getString
299                                            ("FormView.browseFileButtonText"));
300             Box box = Box.createHorizontalBox();
301             box.add(field);
302             box.add(Box.createHorizontalStrut(5));
303             box.add(browseButton);
304             browseButton.addActionListener(new BrowseFileAction(
305                                            attr, (Document)model));
306             c = box;
307             maxIsPreferred = 3;
308         }
309         return c;
310     }
311 
removeStaleListenerForModel(Object model)312     private void removeStaleListenerForModel(Object model) {
313         if (model instanceof DefaultButtonModel) {
314             // case of JButton whose model is DefaultButtonModel
315             // Need to remove stale ActionListener, ChangeListener and
316             // ItemListener that are instance of AbstractButton$Handler.
317             DefaultButtonModel buttonModel = (DefaultButtonModel) model;
318             String listenerClass = "javax.swing.AbstractButton$Handler";
319             for (ActionListener listener : buttonModel.getActionListeners()) {
320                 if (listenerClass.equals(listener.getClass().getName())) {
321                     buttonModel.removeActionListener(listener);
322                 }
323             }
324             for (ChangeListener listener : buttonModel.getChangeListeners()) {
325                 if (listenerClass.equals(listener.getClass().getName())) {
326                     buttonModel.removeChangeListener(listener);
327                 }
328             }
329             for (ItemListener listener : buttonModel.getItemListeners()) {
330                 if (listenerClass.equals(listener.getClass().getName())) {
331                     buttonModel.removeItemListener(listener);
332                 }
333             }
334         } else if (model instanceof AbstractListModel) {
335             // case of JComboBox and JList
336             // For JList, the stale ListDataListener is instance
337             // BasicListUI$Handler.
338             // For JComboBox, there are 2 stale ListDataListeners, which are
339             // BasicListUI$Handler and BasicComboBoxUI$Handler.
340             @SuppressWarnings("unchecked")
341             AbstractListModel<?> listModel = (AbstractListModel) model;
342             String listenerClass1 =
343                     "javax.swing.plaf.basic.BasicListUI$Handler";
344             String listenerClass2 =
345                     "javax.swing.plaf.basic.BasicComboBoxUI$Handler";
346             for (ListDataListener listener : listModel.getListDataListeners()) {
347                 if (listenerClass1.equals(listener.getClass().getName())
348                         || listenerClass2.equals(listener.getClass().getName()))
349                 {
350                     listModel.removeListDataListener(listener);
351                 }
352             }
353         } else if (model instanceof AbstractDocument) {
354             // case of JPasswordField, JTextField and JTextArea
355             // All have 2 stale DocumentListeners.
356             String listenerClass1 =
357                     "javax.swing.plaf.basic.BasicTextUI$UpdateHandler";
358             String listenerClass2 =
359                     "javax.swing.text.DefaultCaret$Handler";
360             AbstractDocument docModel = (AbstractDocument) model;
361             for (DocumentListener listener : docModel.getDocumentListeners()) {
362                 if (listenerClass1.equals(listener.getClass().getName())
363                         || listenerClass2.equals(listener.getClass().getName()))
364                 {
365                     docModel.removeDocumentListener(listener);
366                 }
367             }
368         }
369     }
370 
371     /**
372      * Determines the maximum span for this view along an
373      * axis. For certain components, the maximum and preferred span are the
374      * same. For others this will return the value
375      * returned by Component.getMaximumSize along the
376      * axis of interest.
377      *
378      * @param axis may be either View.X_AXIS or View.Y_AXIS
379      * @return   the span the view would like to be rendered into &gt;= 0.
380      *           Typically the view is told to render into the span
381      *           that is returned, although there is no guarantee.
382      *           The parent may choose to resize or break the view.
383      * @exception IllegalArgumentException for an invalid axis
384      */
getMaximumSpan(int axis)385     public float getMaximumSpan(int axis) {
386         switch (axis) {
387         case View.X_AXIS:
388             if ((maxIsPreferred & 1) == 1) {
389                 super.getMaximumSpan(axis);
390                 return getPreferredSpan(axis);
391             }
392             return super.getMaximumSpan(axis);
393         case View.Y_AXIS:
394             if ((maxIsPreferred & 2) == 2) {
395                 super.getMaximumSpan(axis);
396                 return getPreferredSpan(axis);
397             }
398             return super.getMaximumSpan(axis);
399         default:
400             break;
401         }
402         return super.getMaximumSpan(axis);
403     }
404 
405 
406     /**
407      * Responsible for processing the ActionEvent.
408      * If the element associated with the FormView,
409      * has a type of "submit", "reset", "text" or "password"
410      * then the action is processed.  In the case of a "submit"
411      * the form is submitted.  In the case of a "reset"
412      * the form is reset to its original state.
413      * In the case of "text" or "password", if the
414      * element is the last one of type "text" or "password",
415      * the form is submitted.  Otherwise, focus is transferred
416      * to the next component in the form.
417      *
418      * @param evt the ActionEvent.
419      */
actionPerformed(ActionEvent evt)420     public void actionPerformed(ActionEvent evt) {
421         Element element = getElement();
422         StringBuilder dataBuffer = new StringBuilder();
423         HTMLDocument doc = (HTMLDocument)getDocument();
424         AttributeSet attr = element.getAttributes();
425 
426         String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
427 
428         if (type.equals("submit")) {
429             getFormData(dataBuffer);
430             submitData(dataBuffer.toString());
431         } else if (type.equals("reset")) {
432             resetForm();
433         } else if (type.equals("text") || type.equals("password")) {
434             if (isLastTextOrPasswordField()) {
435                 getFormData(dataBuffer);
436                 submitData(dataBuffer.toString());
437             } else {
438                 getComponent().transferFocus();
439             }
440         }
441     }
442 
443 
444     /**
445      * This method is responsible for submitting the form data.
446      * A thread is forked to undertake the submission.
447      *
448      * @param data data to submit
449      */
submitData(String data)450     protected void submitData(String data) {
451         Element form = getFormElement();
452         AttributeSet attrs = form.getAttributes();
453         HTMLDocument doc = (HTMLDocument) form.getDocument();
454         URL base = doc.getBase();
455 
456         String target = (String) attrs.getAttribute(HTML.Attribute.TARGET);
457         if (target == null) {
458             target = "_self";
459         }
460 
461         String method = (String) attrs.getAttribute(HTML.Attribute.METHOD);
462         if (method == null) {
463             method = "GET";
464         }
465         method = method.toLowerCase();
466         boolean isPostMethod = method.equals("post");
467         if (isPostMethod) {
468             storePostData(doc, target, data);
469         }
470 
471         String action = (String) attrs.getAttribute(HTML.Attribute.ACTION);
472         URL actionURL;
473         try {
474             actionURL = (action == null)
475                 ? new URL(base.getProtocol(), base.getHost(),
476                                         base.getPort(), base.getFile())
477                 : new URL(base, action);
478             if (!isPostMethod) {
479                 String query = data.toString();
480                 actionURL = new URL(actionURL + "?" + query);
481             }
482         } catch (MalformedURLException e) {
483             actionURL = null;
484         }
485         final JEditorPane c = (JEditorPane) getContainer();
486         HTMLEditorKit kit = (HTMLEditorKit) c.getEditorKit();
487 
488         FormSubmitEvent formEvent = null;
489         if (!kit.isAutoFormSubmission() || doc.isFrameDocument()) {
490             FormSubmitEvent.MethodType methodType = isPostMethod
491                     ? FormSubmitEvent.MethodType.POST
492                     : FormSubmitEvent.MethodType.GET;
493             formEvent = new FormSubmitEvent(
494                     FormView.this, HyperlinkEvent.EventType.ACTIVATED,
495                     actionURL, form, target, methodType, data);
496 
497         }
498         // setPage() may take significant time so schedule it to run later.
499         final FormSubmitEvent fse = formEvent;
500         final URL url = actionURL;
501         SwingUtilities.invokeLater(new Runnable() {
502             public void run() {
503                 if (fse != null) {
504                     c.fireHyperlinkUpdate(fse);
505                 } else {
506                     try {
507                         c.setPage(url);
508                     } catch (IOException e) {
509                         UIManager.getLookAndFeel().provideErrorFeedback(c);
510                     }
511                 }
512             }
513         });
514     }
515 
storePostData(HTMLDocument doc, String target, String data)516     private void storePostData(HTMLDocument doc, String target, String data) {
517 
518         /* POST data is stored into the document property named by constant
519          * PostDataProperty from where it is later retrieved by method
520          * JEditorPane.getPostData().  If the current document is in a frame,
521          * the data is initially put into the toplevel (frameset) document
522          * property (named <PostDataProperty>.<Target frame name>).  It is the
523          * responsibility of FrameView which updates the target frame
524          * to move data from the frameset document property into the frame
525          * document property.
526          */
527 
528         Document propDoc = doc;
529         String propName = PostDataProperty;
530 
531         if (doc.isFrameDocument()) {
532             // find the top-most JEditorPane holding the frameset view.
533             FrameView.FrameEditorPane p =
534                     (FrameView.FrameEditorPane) getContainer();
535             FrameView v = p.getFrameView();
536             JEditorPane c = v.getOutermostJEditorPane();
537             if (c != null) {
538                 propDoc = c.getDocument();
539                 propName += ("." + target);
540             }
541         }
542 
543         propDoc.putProperty(propName, data);
544     }
545 
546     /**
547      * MouseEventListener class to handle form submissions when
548      * an input with type equal to image is clicked on.
549      * A MouseListener is necessary since along with the image
550      * data the coordinates associated with the mouse click
551      * need to be submitted.
552      */
553     protected class MouseEventListener extends MouseAdapter {
554 
mouseReleased(MouseEvent evt)555         public void mouseReleased(MouseEvent evt) {
556             String imageData = getImageData(evt.getPoint());
557             imageSubmit(imageData);
558         }
559     }
560 
561     /**
562      * This method is called to submit a form in response
563      * to a click on an image -- an &lt;INPUT&gt; form
564      * element of type "image".
565      *
566      * @param imageData the mouse click coordinates.
567      */
imageSubmit(String imageData)568     protected void imageSubmit(String imageData) {
569 
570         StringBuilder dataBuffer = new StringBuilder();
571         Element elem = getElement();
572         HTMLDocument hdoc = (HTMLDocument)elem.getDocument();
573         getFormData(dataBuffer);
574         if (dataBuffer.length() > 0) {
575             dataBuffer.append('&');
576         }
577         dataBuffer.append(imageData);
578         submitData(dataBuffer.toString());
579         return;
580     }
581 
582     /**
583      * Extracts the value of the name attribute
584      * associated with the input element of type
585      * image.  If name is defined it is encoded using
586      * the URLEncoder.encode() method and the
587      * image data is returned in the following format:
588      *      name + ".x" +"="+ x +"&"+ name +".y"+"="+ y
589      * otherwise,
590      *      "x="+ x +"&y="+ y
591      *
592      * @param point associated with the mouse click.
593      * @return the image data.
594      */
595     @SuppressWarnings("deprecation")
getImageData(Point point)596     private String getImageData(Point point) {
597 
598         String mouseCoords = point.x + ":" + point.y;
599         int sep = mouseCoords.indexOf(':');
600         String x = mouseCoords.substring(0, sep);
601         String y = mouseCoords.substring(++sep);
602         String name = (String) getElement().getAttributes().getAttribute(HTML.Attribute.NAME);
603 
604         String data;
605         if (name == null || name.isEmpty()) {
606             data = "x="+ x +"&y="+ y;
607         } else {
608             name = URLEncoder.encode(name);
609             data = name + ".x" +"="+ x +"&"+ name +".y"+"="+ y;
610         }
611         return data;
612     }
613 
614 
615     /**
616      * The following methods provide functionality required to
617      * iterate over a the elements of the form and in the case
618      * of a form submission, extract the data from each model
619      * that is associated with each form element, and in the
620      * case of reset, reinitialize the each model to its
621      * initial state.
622      */
623 
624 
625     /**
626      * Returns the Element representing the <code>FORM</code>.
627      */
getFormElement()628     private Element getFormElement() {
629         Element elem = getElement();
630         while (elem != null) {
631             if (elem.getAttributes().getAttribute
632                 (StyleConstants.NameAttribute) == HTML.Tag.FORM) {
633                 return elem;
634             }
635             elem = elem.getParentElement();
636         }
637         return null;
638     }
639 
640     /**
641      * Iterates over the
642      * element hierarchy, extracting data from the
643      * models associated with the relevant form elements.
644      * "Relevant" means the form elements that are part
645      * of the same form whose element triggered the submit
646      * action.
647      *
648      * @param buffer        the buffer that contains that data to submit
649      */
getFormData(StringBuilder buffer)650     private void getFormData(StringBuilder buffer) {
651         Element formE = getFormElement();
652         if (formE != null) {
653             ElementIterator it = new ElementIterator(formE);
654             Element next;
655 
656             while ((next = it.next()) != null) {
657                 if (isControl(next)) {
658                     String type = (String)next.getAttributes().getAttribute
659                                        (HTML.Attribute.TYPE);
660 
661                     if (type != null && type.equals("submit") &&
662                         next != getElement()) {
663                         // do nothing - this submit is not the trigger
664                     } else if (type == null || !type.equals("image")) {
665                         // images only result in data if they triggered
666                         // the submit and they require that the mouse click
667                         // coords be appended to the data.  Hence its
668                         // processing is handled by the view.
669                         loadElementDataIntoBuffer(next, buffer);
670                     }
671                 }
672             }
673         }
674     }
675 
676     /**
677      * Loads the data
678      * associated with the element into the buffer.
679      * The format in which data is appended depends
680      * on the type of the form element.  Essentially
681      * data is loaded in name/value pairs.
682      *
683      */
loadElementDataIntoBuffer(Element elem, StringBuilder buffer)684     private void loadElementDataIntoBuffer(Element elem, StringBuilder buffer) {
685 
686         AttributeSet attr = elem.getAttributes();
687         String name = (String)attr.getAttribute(HTML.Attribute.NAME);
688         if (name == null) {
689             return;
690         }
691         String value = null;
692         HTML.Tag tag = (HTML.Tag)elem.getAttributes().getAttribute
693                                   (StyleConstants.NameAttribute);
694 
695         if (tag == HTML.Tag.INPUT) {
696             value = getInputElementData(attr);
697         } else if (tag ==  HTML.Tag.TEXTAREA) {
698             value = getTextAreaData(attr);
699         } else if (tag == HTML.Tag.SELECT) {
700             loadSelectData(attr, buffer);
701         }
702 
703         if (name != null && value != null) {
704             appendBuffer(buffer, name, value);
705         }
706     }
707 
708 
709     /**
710      * Returns the data associated with an &lt;INPUT&gt; form
711      * element.  The value of "type" attributes is
712      * used to determine the type of the model associated
713      * with the element and then the relevant data is
714      * extracted.
715      */
getInputElementData(AttributeSet attr)716     private String getInputElementData(AttributeSet attr) {
717 
718         Object model = attr.getAttribute(StyleConstants.ModelAttribute);
719         String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
720         String value = null;
721 
722         if (type.equals("text") || type.equals("password")) {
723             Document doc = (Document)model;
724             try {
725                 value = doc.getText(0, doc.getLength());
726             } catch (BadLocationException e) {
727                 value = null;
728             }
729         } else if (type.equals("submit") || type.equals("hidden")) {
730             value = (String) attr.getAttribute(HTML.Attribute.VALUE);
731             if (value == null) {
732                 value = "";
733             }
734         } else if (type.equals("radio") || type.equals("checkbox")) {
735             ButtonModel m = (ButtonModel)model;
736             if (m.isSelected()) {
737                 value = (String) attr.getAttribute(HTML.Attribute.VALUE);
738                 if (value == null) {
739                     value = "on";
740                 }
741             }
742         } else if (type.equals("file")) {
743             Document doc = (Document)model;
744             String path;
745 
746             try {
747                 path = doc.getText(0, doc.getLength());
748             } catch (BadLocationException e) {
749                 path = null;
750             }
751             if (path != null && path.length() > 0) {
752                 value = path;
753             }
754         }
755         return value;
756     }
757 
758     /**
759      * Returns the data associated with the &lt;TEXTAREA&gt; form
760      * element.  This is done by getting the text stored in the
761      * Document model.
762      */
getTextAreaData(AttributeSet attr)763     private String getTextAreaData(AttributeSet attr) {
764         Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
765         try {
766             return doc.getText(0, doc.getLength());
767         } catch (BadLocationException e) {
768             return null;
769         }
770     }
771 
772 
773     /**
774      * Loads the buffer with the data associated with the Select
775      * form element.  Basically, only items that are selected
776      * and have their name attribute set are added to the buffer.
777      */
loadSelectData(AttributeSet attr, StringBuilder buffer)778     private void loadSelectData(AttributeSet attr, StringBuilder buffer) {
779 
780         String name = (String)attr.getAttribute(HTML.Attribute.NAME);
781         if (name == null) {
782             return;
783         }
784         Object m = attr.getAttribute(StyleConstants.ModelAttribute);
785         if (m instanceof OptionListModel) {
786             @SuppressWarnings("unchecked")
787             OptionListModel<Option> model = (OptionListModel<Option>) m;
788 
789             for (int i = 0; i < model.getSize(); i++) {
790                 if (model.isSelectedIndex(i)) {
791                     Option option = model.getElementAt(i);
792                     appendBuffer(buffer, name, option.getValue());
793                 }
794             }
795         } else if (m instanceof ComboBoxModel) {
796             @SuppressWarnings("unchecked")
797             ComboBoxModel<?> model = (ComboBoxModel)m;
798             Option option = (Option)model.getSelectedItem();
799             if (option != null) {
800                 appendBuffer(buffer, name, option.getValue());
801             }
802         }
803     }
804 
805     /**
806      * Appends name / value pairs into the
807      * buffer.  Both names and values are encoded using the
808      * URLEncoder.encode() method before being added to the
809      * buffer.
810      */
811     @SuppressWarnings("deprecation")
appendBuffer(StringBuilder buffer, String name, String value)812     private void appendBuffer(StringBuilder buffer, String name, String value) {
813         if (buffer.length() > 0) {
814             buffer.append('&');
815         }
816         String encodedName = URLEncoder.encode(name);
817         buffer.append(encodedName);
818         buffer.append('=');
819         String encodedValue = URLEncoder.encode(value);
820         buffer.append(encodedValue);
821     }
822 
823     /**
824      * Returns true if the Element <code>elem</code> represents a control.
825      */
isControl(Element elem)826     private boolean isControl(Element elem) {
827         return elem.isLeaf();
828     }
829 
830     /**
831      * Iterates over the element hierarchy to determine if
832      * the element parameter, which is assumed to be an
833      * &lt;INPUT&gt; element of type password or text, is the last
834      * one of either kind, in the form to which it belongs.
835      */
isLastTextOrPasswordField()836     boolean isLastTextOrPasswordField() {
837         Element parent = getFormElement();
838         Element elem = getElement();
839 
840         if (parent != null) {
841             ElementIterator it = new ElementIterator(parent);
842             Element next;
843             boolean found = false;
844 
845             while ((next = it.next()) != null) {
846                 if (next == elem) {
847                     found = true;
848                 }
849                 else if (found && isControl(next)) {
850                     AttributeSet elemAttr = next.getAttributes();
851 
852                     if (HTMLDocument.matchNameAttribute
853                                      (elemAttr, HTML.Tag.INPUT)) {
854                         String type = (String)elemAttr.getAttribute
855                                                   (HTML.Attribute.TYPE);
856 
857                         if ("text".equals(type) || "password".equals(type)) {
858                             return false;
859                         }
860                     }
861                 }
862             }
863         }
864         return true;
865     }
866 
867     /**
868      * Resets the form
869      * to its initial state by reinitializing the models
870      * associated with each form element to their initial
871      * values.
872      *
873      * param elem the element that triggered the reset
874      */
resetForm()875     void resetForm() {
876         Element parent = getFormElement();
877 
878         if (parent != null) {
879             ElementIterator it = new ElementIterator(parent);
880             Element next;
881 
882             while((next = it.next()) != null) {
883                 if (isControl(next)) {
884                     AttributeSet elemAttr = next.getAttributes();
885                     Object m = elemAttr.getAttribute(StyleConstants.
886                                                      ModelAttribute);
887                     if (m instanceof TextAreaDocument) {
888                         TextAreaDocument doc = (TextAreaDocument)m;
889                         doc.reset();
890                     } else if (m instanceof PlainDocument) {
891                         try {
892                             PlainDocument doc =  (PlainDocument)m;
893                             doc.remove(0, doc.getLength());
894                             if (HTMLDocument.matchNameAttribute
895                                              (elemAttr, HTML.Tag.INPUT)) {
896                                 String value = (String)elemAttr.
897                                            getAttribute(HTML.Attribute.VALUE);
898                                 if (value != null) {
899                                     doc.insertString(0, value, null);
900                                 }
901                             }
902                         } catch (BadLocationException e) {
903                         }
904                     } else if (m instanceof OptionListModel) {
905                         @SuppressWarnings("unchecked")
906                         OptionListModel<?> model = (OptionListModel) m;
907                         int size = model.getSize();
908                         for (int i = 0; i < size; i++) {
909                             model.removeIndexInterval(i, i);
910                         }
911                         BitSet selectionRange = model.getInitialSelection();
912                         for (int i = 0; i < selectionRange.size(); i++) {
913                             if (selectionRange.get(i)) {
914                                 model.addSelectionInterval(i, i);
915                             }
916                         }
917                     } else if (m instanceof OptionComboBoxModel) {
918                         @SuppressWarnings("unchecked")
919                         OptionComboBoxModel<?> model = (OptionComboBoxModel) m;
920                         Option option = model.getInitialSelection();
921                         if (option != null) {
922                             model.setSelectedItem(option);
923                         }
924                     } else if (m instanceof JToggleButton.ToggleButtonModel) {
925                         boolean checked = ((String)elemAttr.getAttribute
926                                            (HTML.Attribute.CHECKED) != null);
927                         JToggleButton.ToggleButtonModel model =
928                                         (JToggleButton.ToggleButtonModel)m;
929                         model.setSelected(checked);
930                     }
931                 }
932             }
933         }
934     }
935 
936 
937     /**
938      * BrowseFileAction is used for input type == file. When the user
939      * clicks the button a JFileChooser is brought up allowing the user
940      * to select a file in the file system. The resulting path to the selected
941      * file is set in the text field (actually an instance of Document).
942      */
943     private class BrowseFileAction implements ActionListener {
944         private AttributeSet attrs;
945         private Document model;
946 
BrowseFileAction(AttributeSet attrs, Document model)947         BrowseFileAction(AttributeSet attrs, Document model) {
948             this.attrs = attrs;
949             this.model = model;
950         }
951 
actionPerformed(ActionEvent ae)952         public void actionPerformed(ActionEvent ae) {
953             // PENDING: When mime support is added to JFileChooser use the
954             // accept value of attrs.
955             JFileChooser fc = new JFileChooser();
956             fc.setMultiSelectionEnabled(false);
957             if (fc.showOpenDialog(getContainer()) ==
958                   JFileChooser.APPROVE_OPTION) {
959                 File selected = fc.getSelectedFile();
960 
961                 if (selected != null) {
962                     try {
963                         if (model.getLength() > 0) {
964                             model.remove(0, model.getLength());
965                         }
966                         model.insertString(0, selected.getPath(), null);
967                     } catch (BadLocationException ble) {}
968                 }
969             }
970         }
971     }
972 }
973