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