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 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      */
 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      */
 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      */
 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 
 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      */
 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      */
 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      */
 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 
 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 
 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      */
 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")
 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.equals("")) {
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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")
 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      */
 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      */
 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      */
 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 
 947         BrowseFileAction(AttributeSet attrs, Document model) {
 948             this.attrs = attrs;
 949             this.model = model;
 950         }
 951 
 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 }