Source code: com/meterware/httpunit/WebForm.java
1 package com.meterware.httpunit;
2 /********************************************************************************************************************
3 * $Id: WebForm.java,v 1.101 2004/09/29 17:15:25 russgold Exp $
4 *
5 * Copyright (c) 2000-2004, Russell Gold
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
10 * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in all copies or substantial portions
13 * of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
16 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
18 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 * DEALINGS IN THE SOFTWARE.
20 *
21 *******************************************************************************************************************/
22 import com.meterware.httpunit.scripting.NamedDelegate;
23 import com.meterware.httpunit.scripting.ScriptableDelegate;
24
25 import java.io.IOException;
26 import java.io.File;
27 import java.net.URL;
28 import java.net.UnknownServiceException;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Vector;
34
35 import org.w3c.dom.Node;
36 import org.xml.sax.SAXException;
37
38
39 /**
40 * This class represents a form in an HTML page. Users of this class may examine the parameters
41 * defined for the form, the structure of the form (as a DOM), or the text of the form. They
42 * may also create a {@link WebRequest} to simulate the submission of the form.
43 *
44 * @author <a href="mailto:russgold@httpunit.org">Russell Gold</a>
45 **/
46 public class WebForm extends WebRequestSource {
47 private static final FormParameter UNKNOWN_PARAMETER = new FormParameter();
48 private Button[] _buttons;
49
50
51 /** Predicate to match a link's name. **/
52 public final static HTMLElementPredicate MATCH_NAME;
53
54 /**
55 * Submits this form using the web client from which it was originally obtained.
56 **/
57 public WebResponse submit() throws IOException, SAXException {
58 return submit( getDefaultButton() );
59 }
60
61
62 /**
63 * Submits this form using the web client from which it was originally obtained.
64 * Will usually return the result of that submission; however, if the submit button's 'onclick'
65 * or the form's 'onsubmit' event is triggered and
66 * inhibits the submission, will return the updated contents of the frame containing this form.
67 **/
68 public WebResponse submit( SubmitButton button ) throws IOException, SAXException {
69 return (button == null || button.doOnClickEvent()) ? doFormSubmit( button ) : getCurrentFrameContents();
70 }
71
72
73 /**
74 * Submits this form using the web client from which it was originally obtained.
75 * Will usually return the result of that submission; however, if the submit button's 'onclick'
76 * or the form's 'onsubmit' event is triggered and
77 * inhibits the submission, will return the updated contents of the frame containing this form.
78 * @since 1.6
79 **/
80 public WebResponse submit( SubmitButton button, int x, int y ) throws IOException, SAXException {
81 return button.doOnClickEvent() ? doFormSubmit( button, x, y ) : getCurrentFrameContents();
82 }
83
84
85 /**
86 * Submits this form using the web client from which it was originally obtained, ignoring any buttons defined for the form.
87 * @since 1.6
88 **/
89 public WebResponse submitNoButton() throws SAXException, IOException {
90 return submit( SubmitButton.createFakeSubmitButton( this /* fake */ ) );
91 }
92
93
94 protected WebResponse submitRequest( String event, WebRequest request ) throws IOException, SAXException {
95 try {
96 return super.submitRequest( event, request );
97 } catch (UnknownServiceException e) {
98 throw new UnsupportedActionException( "HttpUnit does not support " + request.getURL().getProtocol() + " URLs in form submissions" );
99 }
100 }
101
102
103 /**
104 * Submits the form without also invoking the button's "onclick" event.
105 */
106 WebResponse doFormSubmit( SubmitButton button ) throws IOException, SAXException {
107 return submitRequest( getAttribute( "onsubmit" ), getRequest( button ) );
108 }
109
110
111 WebResponse doFormSubmit( SubmitButton button, int x, int y ) throws IOException, SAXException {
112 return submitRequest( getAttribute( "onsubmit" ), getRequest( button, x, y ) );
113 }
114
115
116 /**
117 * Returns the method defined for this form.
118 **/
119 public String getMethod() {
120 return getAttribute( "method", "GET" );
121 }
122
123
124 /**
125 * Returns the action defined for this form.
126 **/
127 public String getAction() {
128 return getDestination();
129 }
130
131
132 /**
133 * Returns true if a parameter with given name exists in this form.
134 **/
135 public boolean hasParameterNamed( String soughtName ) {
136 return getFormParameters().containsKey( soughtName );
137 }
138
139
140 /**
141 * Returns true if a parameter starting with a given name exists,
142 **/
143 public boolean hasParameterStartingWithPrefix( String prefix ) {
144 String[] names = getParameterNames();
145 for (int i = 0; i < names.length; i++) {
146 if (names[i].startsWith( prefix )) return true;
147 }
148 return false;
149 }
150
151
152 /**
153 * Returns an array containing all of the buttons defined for this form.
154 **/
155 public Button[] getButtons() {
156 if (_buttons == null) {
157 FormControl[] controls = getFormControls();
158 ArrayList buttonList = new ArrayList();
159 for (int i = 0; i < controls.length; i++) {
160 FormControl control = controls[ i ];
161 if (control instanceof Button) buttonList.add( control );
162 }
163 _buttons = (Button[]) buttonList.toArray( new Button[ buttonList.size() ] );
164 }
165 return _buttons;
166 }
167
168
169 public Button getButton( HTMLElementPredicate predicate, Object criteria ) {
170 Button[] buttons = getButtons();
171 for (int i = 0; i < buttons.length; i++) {
172 if (predicate.matchesCriteria( buttons[i], criteria )) return buttons[i];
173 }
174 return null;
175 }
176
177
178 /**
179 * Convenience method which returns the button with the specified ID.
180 */
181 public Button getButtonWithID( String buttonID ) {
182 return getButton( Button.WITH_ID, buttonID );
183 }
184
185
186 /**
187 * Returns an array containing the submit buttons defined for this form.
188 **/
189 public SubmitButton[] getSubmitButtons() {
190 if (_submitButtons == null) {
191 Vector buttons = getSubmitButtonVector();
192 _submitButtons = new SubmitButton[ buttons.size() ];
193 buttons.copyInto( _submitButtons );
194 }
195 return _submitButtons;
196 }
197
198
199 /**
200 * Returns the submit button defined in this form with the specified name.
201 * If more than one such button exists, will return the first found.
202 * If no such button is found, will return null.
203 **/
204 public SubmitButton getSubmitButton( String name ) {
205 SubmitButton[] buttons = getSubmitButtons();
206 for (int i = 0; i < buttons.length; i++) {
207 if (buttons[i].getName().equals( name )) {
208 return buttons[i];
209 }
210 }
211 return null;
212 }
213
214
215 /**
216 * Returns the submit button defined in this form with the specified name and value.
217 * If more than one such button exists, will return the first found.
218 * If no such button is found, will return null.
219 **/
220 public SubmitButton getSubmitButton( String name, String value ) {
221 SubmitButton[] buttons = getSubmitButtons();
222 for (int i = 0; i < buttons.length; i++) {
223 if (buttons[i].getName().equals( name ) && buttons[i].getValue().equals( value )) {
224 return buttons[i];
225 }
226 }
227 return null;
228 }
229
230
231 /**
232 * Returns the submit button defined in this form with the specified ID.
233 * If more than one such button exists, will return the first found.
234 * If no such button is found, will return null.
235 **/
236 public SubmitButton getSubmitButtonWithID( String ID ) {
237 SubmitButton[] buttons = getSubmitButtons();
238 for (int i = 0; i < buttons.length; i++) {
239 if (buttons[i].getID().equals( ID )) {
240 return buttons[i];
241 }
242 }
243 return null;
244 }
245
246
247 /**
248 * Creates and returns a web request which will simulate the submission of this form with a button with the specified name and value.
249 **/
250 public WebRequest getRequest( String submitButtonName, String submitButtonValue ) {
251 SubmitButton sb = getSubmitButton( submitButtonName, submitButtonValue );
252 if (sb == null) throw new IllegalSubmitButtonException( submitButtonName, submitButtonValue );
253 return getRequest( sb );
254 }
255
256
257 /**
258 * Creates and returns a web request which will simulate the submission of this form with a button with the specified name.
259 **/
260 public WebRequest getRequest( String submitButtonName ) {
261 SubmitButton sb = getSubmitButton( submitButtonName );
262 if (sb == null) throw new IllegalSubmitButtonException( submitButtonName, "" );
263 return getRequest( sb );
264 }
265
266
267 /**
268 * Creates and returns a web request which will simulate the submission of this form by pressing the specified button.
269 * If the button is null, simulates the pressing of the default button.
270 **/
271 public WebRequest getRequest( SubmitButton button ) {
272 return getRequest( button, 0, 0 );
273 }
274
275
276 /**
277 * Creates and returns a web request which will simulate the submission of this form by pressing the specified button.
278 * If the button is null, simulates the pressing of the default button.
279 **/
280 public WebRequest getRequest( SubmitButton button, int x, int y ) {
281 if (button == null) button = getDefaultButton();
282
283 if (HttpUnitOptions.getParameterValuesValidated()) {
284 if (button == null) {
285 throw new IllegalUnnamedSubmitButtonException();
286 } else if (button.isFake()) {
287 // bypass checks
288 } else if (!getSubmitButtonVector().contains( button )) {
289 throw new IllegalSubmitButtonException( button );
290 } else if (button.isDisabled()) {
291 throw new DisabledSubmitButtonException( button );
292 }
293 }
294
295 SubmitButton[] buttons = getSubmitButtons();
296 for (int i = 0; i < buttons.length; i++) {
297 buttons[i].setPressed( false );
298 }
299 button.setPressed( true );
300
301 if (getMethod().equalsIgnoreCase( "post" )) {
302 return new PostMethodWebRequest( this, button, x, y );
303 } else {
304 return new GetMethodWebRequest( this, WebRequest.newParameterHolder( this ), button, x, y );
305 }
306 }
307
308
309 /**
310 * Creates and returns a web request which includes the specified button. If no button is specified, will include
311 * the default button, if any. No parameter validation will be done on the returned request and no scripts
312 * will be run when it is submitted.
313 **/
314 public WebRequest newUnvalidatedRequest( SubmitButton button ) {
315 return newUnvalidatedRequest( button, 0, 0 );
316 }
317
318
319 /**
320 * Creates and returns a web request which includes the specified button and position. If no button is specified,
321 * will include the default button, if any. No parameter validation will be done on the returned request
322 * and no scripts will be run when it is submitted.
323 **/
324 public WebRequest newUnvalidatedRequest( SubmitButton button, int x, int y ) {
325 if (button == null) button = getDefaultButton();
326
327 SubmitButton[] buttons = getSubmitButtons();
328 for (int i = 0; i < buttons.length; i++) {
329 buttons[i].setPressed( false );
330 }
331 button.setPressed( true );
332
333 if (getMethod().equalsIgnoreCase( "post" )) {
334 return new PostMethodWebRequest( this, new UncheckedParameterHolder( this ), button, x, y );
335 } else {
336 return new GetMethodWebRequest( this, new UncheckedParameterHolder( this ), button, x, y );
337 }
338 }
339
340
341 private WebRequest getScriptedSubmitRequest() {
342 SubmitButton[] buttons = getSubmitButtons();
343 for (int i = 0; i < buttons.length; i++) {
344 buttons[i].setPressed( false );
345 }
346
347 if (getMethod().equalsIgnoreCase( "post" )) {
348 return new PostMethodWebRequest( this );
349 } else {
350 return new GetMethodWebRequest( this );
351 }
352
353 }
354
355
356 /**
357 * Returns the default value of the named parameter. If the parameter does not exist returns null.
358 **/
359 public String getParameterValue( String name ) {
360 String[] values = getParameterValues( name );
361 return values.length == 0 ? null : values[0];
362 }
363
364
365 /**
366 * Returns the displayed options defined for the specified parameter name.
367 **/
368 public String[] getOptions( String name ) {
369 return getParameter( name ).getOptions();
370 }
371
372
373 /**
374 * Returns the option values defined for the specified parameter name.
375 **/
376 public String[] getOptionValues( String name ) {
377 return getParameter( name ).getOptionValues();
378 }
379
380
381 /**
382 * Returns true if the named parameter accepts multiple values.
383 **/
384 public boolean isMultiValuedParameter( String name ) {
385 return getParameter( name ).isMultiValuedParameter();
386 }
387
388
389 /**
390 * Returns the number of text parameters in this form with the specified name.
391 **/
392 public int getNumTextParameters( String name ) {
393 return getParameter( name ).getNumTextParameters();
394 }
395
396
397 /**
398 * Returns true if the named parameter accepts free-form text.
399 **/
400 public boolean isTextParameter( String name ) {
401 return getParameter( name ).isTextParameter();
402 }
403
404
405 void setSubmitAsMime( boolean mimeEncoded ) {
406 throw new IllegalStateException( "May not change the encoding for a validated request created from a form" );
407 }
408
409
410 /**
411 * Returns true if this form is to be submitted using mime encoding (the default is URL encoding).
412 **/
413 public boolean isSubmitAsMime() {
414 return "multipart/form-data".equalsIgnoreCase( getAttribute( "enctype" ) );
415 }
416
417
418 /**
419 * Resets all parameters to their initial values.
420 */
421 public void reset() {
422 String event = getAttribute( "onreset" );
423 if (event.length() == 0 || getScriptableObject().doEvent( event )) resetControls();
424 }
425
426
427 private void resetControls() {
428 FormControl[] controls = getFormControls();
429 for (int i = 0; i < controls.length; i++) {
430 controls[i].reset();
431 }
432 }
433
434
435 /**
436 * Returns an object which provides scripting access to this form.
437 **/
438 public Scriptable getScriptableObject() {
439 if (_scriptable == null) {
440 _scriptable = new Scriptable();
441 _scriptable.setScriptEngine( getBaseResponse().getScriptableObject().getDocument().getScriptEngine( _scriptable ) );
442 }
443 return _scriptable;
444 }
445
446
447 //---------------------------------- WebRequestSource methods --------------------------------
448
449 /**
450 * Returns the character set encoding for this form.
451 **/
452 public String getCharacterSet() {
453 return _characterSet;
454 }
455
456
457 /**
458 * Returns true if the named parameter accepts files for upload.
459 **/
460 public boolean isFileParameter( String name ) {
461 return getParameter( name ).isFileParameter();
462 }
463
464
465 /**
466 * Returns an array containing the names of the parameters defined for this form.
467 **/
468 public String[] getParameterNames() {
469 ArrayList parameterNames = new ArrayList( getFormParameters().keySet() );
470 return (String[]) parameterNames.toArray( new String[ parameterNames.size() ] );
471 }
472
473
474 /**
475 * Returns the multiple default values of the named parameter.
476 **/
477 public String[] getParameterValues( String name ) {
478 final FormParameter parameter = getParameter( name );
479 return parameter.getValues();
480 }
481
482
483 /**
484 * Returns true if the named parameter is read-only. If more than one control exists with the same name,
485 * will return true only if all such controls are read-only.
486 **/
487 public boolean isReadOnlyParameter( String name ) {
488 return getParameter( name ).isReadOnlyParameter();
489 }
490
491
492 /**
493 * Returns true if the named parameter is disabled. If more than one control exists with the same name,
494 * will return true only if all such controls are read-only.
495 **/
496 public boolean isDisabledParameter( String name ) {
497 return getParameter( name ).isDisabledParameter();
498 }
499
500
501 /**
502 * Returns true if the named parameter is hidden. If more than one control exists with the same name,
503 * will return true only if all such controls are hidden.
504 **/
505 public boolean isHiddenParameter( String name ) {
506 return getParameter( name ).isHiddenParameter();
507 }
508
509
510 /**
511 * Creates and returns a web request which will simulate the submission of this form with an unnamed submit button.
512 **/
513 public WebRequest getRequest() {
514 return getRequest( (SubmitButton) null );
515 }
516
517
518 /**
519 * Creates and returns a web request based on the current state of this form. No parameter validation will be done
520 * and there is no guarantee over the order of parameters transmitted.
521 */
522 public WebRequest newUnvalidatedRequest() {
523 return newUnvalidatedRequest( null );
524 }
525
526
527 /**
528 * Returns the scriptable delegate.
529 */
530
531 public ScriptableDelegate getScriptableDelegate() {
532 return getScriptableObject();
533 }
534
535
536 /**
537 * Records a parameter defined by including it in the destination URL. Ignores any parameters whose name matches
538 * a form control.
539 **/
540 protected void addPresetParameter( String name, String value ) {
541 FormControl[] formControls = getFormControls();
542 for (int i = 0; i < formControls.length; i++) {
543 if (formControls[i].getName().equals( name)) return;
544 }
545 _presets.add( new PresetFormParameter( this, name, value ) );
546 }
547
548
549 protected String getEmptyParameterValue() {
550 return null;
551 }
552
553
554 //---------------------------------- ParameterHolder methods --------------------------------
555
556
557 /**
558 * Specifies the position at which an image button (if any) was clicked.
559 **/
560 void selectImageButtonPosition( SubmitButton imageButton, int x, int y ) {
561 imageButton.setLocation( x, y );
562 }
563
564
565 /**
566 * Iterates through the fixed, predefined parameters in this holder, recording them in the supplied parameter processor.\
567 * These parameters always go on the URL, no matter what encoding method is used.
568 **/
569
570 void recordPredefinedParameters( ParameterProcessor processor ) throws IOException {
571 FormControl[] controls = getPresetParameters();
572 for (int i = 0; i < controls.length; i++) {
573 controls[i].addValues( processor, getCharacterSet() );
574 }
575 }
576
577
578 /**
579 * Iterates through the parameters in this holder, recording them in the supplied parameter processor.
580 **/
581 void recordParameters( ParameterProcessor processor ) throws IOException {
582 FormControl[] controls = getFormControls();
583 for (int i = 0; i < controls.length; i++) {
584 controls[i].addValues( processor, getCharacterSet() );
585 }
586 }
587
588
589 /**
590 * Removes a parameter name from this collection.
591 **/
592 public void removeParameter( String name ) {
593 setParameter( name, NO_VALUES );
594 }
595
596
597 /**
598 * Sets the value of a parameter in this form.
599 **/
600 public void setParameter( String name, String value ) {
601 setParameter( name, new String[] { value } );
602 }
603
604
605 /**
606 * Sets the multiple values of a parameter in this form. This is generally used when there are multiple
607 * controls with the same name in the form.
608 */
609 public void setParameter( String name, final String[] values ) {
610 FormParameter parameter = getParameter( name );
611 if (parameter == UNKNOWN_PARAMETER) throw new NoSuchParameterException( name );
612 parameter.setValues( values );
613 }
614
615
616 /**
617 * Sets the multiple values of a file upload parameter in a web request.
618 **/
619 public void setParameter( String name, UploadFileSpec[] files ) {
620 FormParameter parameter = getParameter( name );
621 if (parameter == null) throw new NoSuchParameterException( name );
622 parameter.setFiles( files );
623 }
624
625
626 /**
627 * Sets the single value of a file upload parameter in this form.
628 * A more convenient way to do this than using {@link #setParameter(String,UploadFileSpec[])}
629 * @since 1.6
630 */
631 public void setParameter( String name, File file ) {
632 setParameter( name, new UploadFileSpec[] { new UploadFileSpec( file ) } );
633 }
634
635
636 /**
637 * Toggles the value of the specified checkbox parameter.
638 * @param name the name of the checkbox parameter
639 * @throws IllegalArgumentException if the specified parameter is not a checkbox or there is more than one
640 * control with that name.
641 * @since 1.5.4
642 */
643 public void toggleCheckbox( String name ) {
644 FormParameter parameter = getParameter( name );
645 if (parameter == null) throw new NoSuchParameterException( name );
646 parameter.toggleCheckbox();
647 }
648
649
650 /**
651 * Toggles the value of the specified checkbox parameter.
652 * @param name the name of the checkbox parameter
653 * @param value of the checkbox parameter
654 * @throws IllegalArgumentException if the specified parameter is not a checkbox or if there is no checkbox
655 * with the specified name and value.
656 * @since 1.6
657 */
658 public void toggleCheckbox( String name, String value ) {
659 FormParameter parameter = getParameter( name );
660 if (parameter == null) throw new NoSuchParameterException( name );
661 parameter.toggleCheckbox( value );
662 }
663
664
665 /**
666 * Sets the value of the specified checkbox parameter.
667 * @param name the name of the checkbox parameter
668 * @param state the new state of the checkbox
669 * @throws IllegalArgumentException if the specified parameter is not a checkbox or there is more than one
670 * control with that name.
671 * @since 1.5.4
672 */
673 public void setCheckbox( String name, boolean state ) {
674 FormParameter parameter = getParameter( name );
675 if (parameter == null) throw new NoSuchParameterException( name );
676 parameter.setValue( state );
677 }
678
679
680 /**
681 * Sets the value of the specified checkbox parameter.
682 * @param name the name of the checkbox parameter
683 * @param value of the checkbox parameter
684 * @param state the new state of the checkbox
685 * @throws IllegalArgumentException if the specified parameter is not a checkbox or if there is no checkbox
686 * with the specified name and value.
687 * @since 1.6
688 */
689 public void setCheckbox( String name, String value, boolean state ) {
690 FormParameter parameter = getParameter( name );
691 if (parameter == null) throw new NoSuchParameterException( name );
692 parameter.setValue( value, state );
693 }
694
695
696 public class Scriptable extends HTMLElementScriptable implements NamedDelegate {
697 public String getAction() { return WebForm.this.getAction(); }
698 public void setAction( String newAction ) { setDestination( newAction ); _presetParameters = null; }
699
700
701 public void submit() throws IOException, SAXException {
702 submitRequest( getScriptedSubmitRequest() );
703 }
704
705
706 public void reset() throws IOException, SAXException {
707 resetControls();
708 }
709
710
711 public String getName() {
712 return WebForm.this.getID().length() != 0 ? WebForm.this.getID() : WebForm.this.getName();
713 }
714
715
716 public Object get( String propertyName ) {
717 if (propertyName.equals( "target" )) {
718 return getTarget();
719 } else if (propertyName.equals( "length" )) {
720 return new Integer(getFormControls().length);
721 } else {
722 final FormParameter parameter = getParameter( propertyName );
723 if (parameter != UNKNOWN_PARAMETER) return parameter.getScriptableObject();
724 FormControl control = getControlWithID( propertyName );
725 return control == null ? super.get( propertyName ) : control.getScriptableDelegate();
726 }
727 }
728
729
730 /**
731 * Sets the value of the named property. Will throw a runtime exception if the property does not exist or
732 * cannot accept the specified value.
733 **/
734 public void set( String propertyName, Object value ) {
735 if (propertyName.equals( "target" )) {
736 setTargetAttribute( value.toString() );
737 } else if (value instanceof String) {
738 setParameterValue( propertyName, (String) value );
739 } else if (value instanceof Number) {
740 setParameterValue( propertyName, roundedValue( (Number) value ) );
741 } else {
742 super.set( propertyName, value );
743 }
744 }
745
746
747 /**
748 * Trim off any trailing zeros (and the decimal point if the result is an integer).
749 */
750 private String roundedValue( Number number ) {
751 String rawNumber = number.toString();
752 if (rawNumber.indexOf('.') == -1) return rawNumber;
753
754 int index = rawNumber.length();
755 while (rawNumber.charAt( index-1 ) == '0') index--;
756 if (rawNumber.charAt( index-1 ) == '.') index--;
757 return rawNumber.substring( 0, index );
758 }
759
760
761 public void setParameterValue( String name, String value ) {
762 final Object scriptableObject = getParameter( name ).getScriptableObject();
763 if (scriptableObject instanceof ScriptableDelegate) {
764 ((ScriptableDelegate) scriptableObject).set( "value", value );
765 } else if (scriptableObject instanceof ScriptableDelegate[]) {
766 ((ScriptableDelegate[]) scriptableObject)[0].set( "value", value );
767 }
768 }
769
770
771 public ScriptableDelegate[] getElementDelegates() {
772 FormControl[] controls = getFormControls();
773 ScriptableDelegate[] result = new ScriptableDelegate[ controls.length ];
774 for (int i = 0; i < result.length; i++) {
775 result[i] = controls[i].getScriptableDelegate();
776 }
777 return result;
778 }
779
780
781 public ScriptableDelegate[] getElementsByTagName( String name ) throws SAXException {
782 return getDelegates( getHTMLPage().getElementsByTagName( getNode(), name ) );
783 }
784
785
786 Scriptable() {
787 super( WebForm.this );
788 }
789 }
790
791
792 //---------------------------------- package members --------------------------------
793
794 /**
795 * Contructs a web form given the URL of its source page and the DOM extracted
796 * from that page.
797 **/
798 WebForm( WebResponse response, URL baseURL, Node node, FrameSelector frame, String defaultTarget, String characterSet ) {
799 super( response, node, baseURL, NodeUtils.getNodeAttribute( node, "action" ), frame, defaultTarget );
800 _characterSet = characterSet;
801 }
802
803
804 /**
805 * Returns the form control which is part of this form with the specified ID.
806 */
807 FormControl getControlWithID( String id ) {
808 FormControl[] controls = getFormControls();
809 for (int i = 0; i < controls.length; i++) {
810 FormControl control = controls[i];
811 if (control.getID().equals(id)) return control;
812 }
813 return null;
814 }
815
816
817 //---------------------------------- private members --------------------------------
818
819 private final static String[] NO_VALUES = new String[0];
820
821 /** The attributes of the form parameters. **/
822 private FormControl[] _formControls;
823
824 /** The submit buttons in this form. **/
825 private SubmitButton[] _submitButtons;
826
827 /** The character set in which the form will be submitted. **/
828 private String _characterSet;
829
830 /** A map of parameter names to form parameter objects. **/
831 private Map _formParameters;
832
833 /** The Scriptable object associated with this form. **/
834 private Scriptable _scriptable;
835
836 private Vector _buttonVector;
837
838 private FormControl[] _presetParameters;
839 private ArrayList _presets;
840
841
842 private SubmitButton getDefaultButton() {
843 if (getSubmitButtons().length == 1) {
844 return getSubmitButtons()[0];
845 } else {
846 return getSubmitButton( "" );
847 }
848 }
849
850
851 private Vector getSubmitButtonVector() {
852 if (_buttonVector == null) {
853 _buttonVector = new Vector();
854 FormControl[] controls = getFormControls();
855 for (int i = 0; i < controls.length; i++) {
856 FormControl control = controls[ i ];
857 if (control instanceof SubmitButton) _buttonVector.add( control );
858 }
859
860 if (_buttonVector.isEmpty()) _buttonVector.addElement( new SubmitButton( this ) );
861 }
862 return _buttonVector;
863 }
864
865
866 private FormControl[] getPresetParameters() {
867 if (_presetParameters == null) {
868 _presets = new ArrayList();
869 loadDestinationParameters();
870 _presetParameters = (FormControl[]) _presets.toArray( new FormControl[ _presets.size() ] );
871 }
872 return _presetParameters;
873 }
874
875
876 private ArrayList _controlList = new ArrayList();
877
878 FormControl newFormControl( Node child ) {
879 return FormControl.newFormParameter( this, child );
880 }
881
882
883 void addFormControl( FormControl control ) {
884 _controlList.add( control );
885 _formControls = null;
886 _formParameters = null;
887 }
888
889
890 /**
891 * Returns an array of form parameter attributes for this form.
892 **/
893 private FormControl[] getFormControls() {
894 if (_formControls == null) {
895 _formControls = (FormControl[]) _controlList.toArray( new FormControl[ _controlList.size() ] );
896 }
897 return _formControls;
898 }
899
900
901 private FormParameter getParameter( String name ) {
902 final FormParameter parameter = ((FormParameter) getFormParameters().get( name ));
903 return parameter != null ? parameter : UNKNOWN_PARAMETER;
904 }
905
906
907 /**
908 * Returns a map of parameter name to form parameter objects. Each form parameter object represents the set of form
909 * controls with a particular name. Unnamed parameters are ignored.
910 */
911 private Map getFormParameters() {
912 if (_formParameters == null) {
913 _formParameters = new HashMap();
914 loadFormParameters( getPresetParameters() );
915 loadFormParameters( getFormControls() );
916 }
917 return _formParameters;
918 }
919
920
921 private void loadFormParameters( FormControl[] controls ) {
922 for (int i = 0; i < controls.length; i++) {
923 if (controls[i].getName().length() == 0) continue;
924 FormParameter parameter = (FormParameter) _formParameters.get( controls[i].getName() );
925 if (parameter == null) {
926 parameter = new FormParameter();
927 _formParameters.put( controls[i].getName(), parameter );
928 }
929 parameter.addControl( controls[i] );
930 }
931 }
932
933
934 static {
935 MATCH_NAME = new HTMLElementPredicate() {
936 public boolean matchesCriteria( Object htmlElement, Object criteria ) {
937 return HttpUnitUtils.matches( ((WebForm) htmlElement).getName(), (String) criteria );
938 };
939 };
940
941 }
942
943
944 //===========================---===== exception class NoSuchParameterException =========================================
945
946
947 /**
948 * This exception is thrown on an attempt to set a parameter to a value not permitted to it by the form.
949 **/
950 class NoSuchParameterException extends IllegalRequestParameterException {
951
952
953 NoSuchParameterException( String parameterName ) {
954 _parameterName = parameterName;
955 }
956
957
958 public String getMessage() {
959 return "No parameter named '" + _parameterName + "' is defined in the form";
960 }
961
962
963 private String _parameterName;
964
965 }
966
967
968 //============================= exception class IllegalUnnamedSubmitButtonException ======================================
969
970
971 /**
972 * This exception is thrown on an attempt to define a form request with a button not defined on that form.
973 **/
974 class IllegalUnnamedSubmitButtonException extends IllegalRequestParameterException {
975
976
977 IllegalUnnamedSubmitButtonException() {
978 }
979
980
981 public String getMessage() {
982 return "This form has more than one submit button, none unnamed. You must specify the button to be used.";
983 }
984
985 }
986
987
988 //============================= exception class IllegalSubmitButtonException ======================================
989
990
991 /**
992 * This exception is thrown on an attempt to define a form request with a button not defined on that form.
993 **/
994 class IllegalSubmitButtonException extends IllegalRequestParameterException {
995
996
997 IllegalSubmitButtonException( SubmitButton button ) {
998 _name = button.getName();
999 _value = button.getValue();
1000 }
1001
1002
1003 IllegalSubmitButtonException( String name, String value ) {
1004 _name = name;
1005 _value = value;
1006 }
1007
1008
1009 public String getMessage() {
1010 return "Specified submit button (name=\"" + _name + "\" value=\"" + _value + "\") not part of this form.";
1011 }
1012
1013
1014 private String _name;
1015 private String _value;
1016
1017 }
1018
1019//============================= exception class IllegalUnnamedSubmitButtonException ======================================
1020
1021
1022 /**
1023 * This exception is thrown on an attempt to define a form request with a button not defined on that form.
1024 **/
1025 class DisabledSubmitButtonException extends IllegalStateException {
1026
1027
1028 DisabledSubmitButtonException( SubmitButton button ) {
1029 _name = button.getName();
1030 _value = button.getValue();
1031 }
1032
1033
1034 public String getMessage() {
1035 return "The specified button (name='" + _name + "' value='" + _value
1036 + "' is disabled and may not be used to submit this form.";
1037 }
1038
1039
1040 private String _name;
1041 private String _value;
1042
1043 }
1044
1045}
1046
1047
1048
1049//========================================== class PresetFormParameter =================================================
1050
1051
1052 class PresetFormParameter extends FormControl {
1053
1054 PresetFormParameter( WebForm form, String name, String value ) {
1055 super( form );
1056 _name = name;
1057 _value = value;
1058 }
1059
1060
1061 /**
1062 * Returns the name of this control..
1063 **/
1064 public String getName() {
1065 return _name;
1066 }
1067
1068
1069 /**
1070 * Returns true if this control is read-only.
1071 **/
1072 public boolean isReadOnly() {
1073 return true;
1074 }
1075
1076
1077 /**
1078 * Returns true if this control accepts free-form text.
1079 **/
1080 public boolean isTextControl() {
1081 return true;
1082 }
1083
1084
1085 /**
1086 * Remove any required values for this control from the list, throwing an exception if they are missing.
1087 **/
1088 void claimRequiredValues( List values ) {
1089 if (_value != null) claimValueIsRequired( values, _value );
1090 }
1091
1092
1093 public String getType() {
1094 return UNDEFINED_TYPE;
1095 }
1096
1097 /**
1098 * Returns the current value(s) associated with this control. These values will be transmitted to the server
1099 * if the control is 'successful'.
1100 **/
1101 public String[] getValues() {
1102 if (_values == null) _values = new String[] { _value };
1103 return _values;
1104 }
1105
1106
1107 void addValues( ParameterProcessor processor, String characterSet ) throws IOException {
1108 processor.addParameter( _name, _value, characterSet );
1109 }
1110
1111
1112 private String _name;
1113 private String _value;
1114 private String[] _values;
1115 }
1116
1117
1118
1119
1120