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