0001: package com.meterware.httpunit;
0002:
0003: /********************************************************************************************************************
0004: * $Id: FormControl.java,v 1.50 2004/12/26 20:33:34 russgold Exp $
0005: *
0006: * Copyright (c) 2001-2003, Russell Gold
0007: *
0008: * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
0009: * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
0010: * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
0011: * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
0012: *
0013: * The above copyright notice and this permission notice shall be included in all copies or substantial portions
0014: * of the Software.
0015: *
0016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
0017: * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
0018: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
0019: * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
0020: * DEALINGS IN THE SOFTWARE.
0021: *
0022: *******************************************************************************************************************/
0023: import com.meterware.httpunit.scripting.*;
0024:
0025: import org.w3c.dom.NamedNodeMap;
0026: import org.w3c.dom.Node;
0027: import org.w3c.dom.Element;
0028: import org.w3c.dom.NodeList;
0029: import org.xml.sax.SAXException;
0030:
0031: import java.util.Hashtable;
0032: import java.util.ArrayList;
0033: import java.util.List;
0034: import java.util.Arrays;
0035: import java.io.IOException;
0036:
0037: /**
0038: * Represents a control in an HTML form.
0039: *
0040: * @author <a href="mailto:russgold@httpunit.org">Russell Gold</a>
0041: **/
0042: abstract class FormControl extends HTMLElementBase {
0043:
0044: final static String[] NO_VALUE = new String[0];
0045:
0046: private String _valueAttribute;
0047: private final boolean _readOnly;
0048: private boolean _disabled;
0049: private final String _onChangeEvent;
0050: private final String _onClickEvent;
0051: private final WebForm _form;
0052:
0053: public static final String UNDEFINED_TYPE = "undefined";
0054: public static final String BUTTON_TYPE = "button";
0055: public static final String RESET_BUTTON_TYPE = "reset";
0056: public static final String SUBMIT_BUTTON_TYPE = "submit";
0057: public static final String IMAGE_BUTTON_TYPE = "image";
0058: public static final String RADIO_BUTTON_TYPE = "radio";
0059: public static final String CHECKBOX_TYPE = "checkbox";
0060: public static final String TEXT_TYPE = "text";
0061: public static final String PASSWORD_TYPE = "password";
0062: public static final String HIDDEN_TYPE = "hidden";
0063: public static final String TEXTAREA_TYPE = "textarea";
0064: public static final String FILE_TYPE = "file";
0065: public static final String SINGLE_TYPE = "select-one";
0066: public static final String MULTIPLE_TYPE = "select-multiple";
0067:
0068: /**
0069: * Return the type of the control, as seen from JavaScript.
0070: */
0071: abstract public String getType();
0072:
0073: static ScriptableDelegate newSelectionOption() {
0074: return new SelectionFormControl.Option();
0075: }
0076:
0077: FormControl(WebForm form) {
0078: super (newEmptyNode(form));
0079: _form = form;
0080: _valueAttribute = "";
0081: _readOnly = false;
0082: _disabled = false;
0083: _onChangeEvent = "";
0084: _onClickEvent = "";
0085: }
0086:
0087: private static Node newEmptyNode(WebForm form) {
0088: return form.getNode().getOwnerDocument().createElement(
0089: "httpunit-supplied");
0090: }
0091:
0092: FormControl(WebForm form, Node node) {
0093: super (node);
0094: _form = form;
0095: _valueAttribute = NodeUtils.getNodeAttribute(node, "value");
0096: _readOnly = NodeUtils.isNodeAttributePresent(node, "readonly");
0097: _disabled = NodeUtils.isNodeAttributePresent(node, "disabled");
0098: _onChangeEvent = NodeUtils.getNodeAttribute(node, "onchange");
0099: _onClickEvent = NodeUtils.getNodeAttribute(node, "onclick");
0100:
0101: supportAttribute("tabindex");
0102: supportAttribute("disabled");
0103: }
0104:
0105: /**
0106: * Returns the current value(s) associated with this control. These values will be transmitted to the server
0107: * if the control is 'successful'.
0108: **/
0109: abstract String[] getValues();
0110:
0111: /**
0112: * Returns either a single delegate object or potentially an array of delegates as needed, given the form control.
0113: * This default implementation returns the scriptable delegate for the control.
0114: */
0115: Object getDelegate() {
0116: return getScriptableDelegate();
0117: }
0118:
0119: final protected WebForm getForm() {
0120: return _form;
0121: }
0122:
0123: protected ScriptableDelegate getParentDelegate() {
0124: return getForm().getScriptableDelegate();
0125: }
0126:
0127: /**
0128: * Returns the values permitted in this control. Does not apply to text or file controls.
0129: **/
0130: public String[] getOptionValues() {
0131: return NO_VALUE;
0132: }
0133:
0134: /**
0135: * Returns the list of values displayed by this control, if any.
0136: **/
0137: String[] getDisplayedOptions() {
0138: return NO_VALUE;
0139: }
0140:
0141: /**
0142: * Returns true if this control is read-only.
0143: **/
0144: boolean isReadOnly() {
0145: return _readOnly || _disabled;
0146: }
0147:
0148: /**
0149: * Returns true if this control is hidden.
0150: **/
0151: boolean isHidden() {
0152: return false;
0153: }
0154:
0155: void setDisabled(boolean disabled) {
0156: _disabled = disabled;
0157: }
0158:
0159: /**
0160: * Returns true if this control is disabled, meaning that it will not send a value to the server as part of a request.
0161: **/
0162: boolean isDisabled() {
0163: return _disabled;
0164: }
0165:
0166: /**
0167: * Returns true if this control accepts free-form text.
0168: **/
0169: boolean isTextControl() {
0170: return false;
0171: }
0172:
0173: /**
0174: * Returns true if only one control of this kind with this name can have a value. This is true for radio buttons.
0175: **/
0176: boolean isExclusive() {
0177: return false;
0178: }
0179:
0180: /**
0181: * Returns true if a single control can have multiple values.
0182: **/
0183: boolean isMultiValued() {
0184: return false;
0185: }
0186:
0187: /**
0188: * Returns true if this control accepts a file for upload.
0189: **/
0190: boolean isFileParameter() {
0191: return false;
0192: }
0193:
0194: abstract void addValues(ParameterProcessor processor,
0195: String characterSet) throws IOException;
0196:
0197: /**
0198: * Remove any required values for this control from the list, throwing an exception if they are missing.
0199: **/
0200: void claimRequiredValues(List values) {
0201: }
0202:
0203: /**
0204: * Sets this control to the next compatible value from the list, removing it from the list.
0205: **/
0206: void claimValue(List values) {
0207: }
0208:
0209: /**
0210: * Sets this control to the next compatible value from the list, removing it from the list.
0211: **/
0212: void claimUniqueValue(List values) {
0213: }
0214:
0215: /**
0216: * Specifies a file to be uploaded via this control.
0217: **/
0218: void claimUploadSpecification(List files) {
0219: }
0220:
0221: /**
0222: * Resets this control to its initial value.
0223: **/
0224: void reset() {
0225: }
0226:
0227: /**
0228: * Toggles the value of this control.
0229: */
0230: public void toggle() {
0231: throw new FormParameter.IllegalCheckboxParameterException(
0232: getName(), "toggleCheckbox");
0233: }
0234:
0235: /**
0236: * Sets the state of this boolean control.
0237: */
0238: public void setState(boolean state) {
0239: throw new FormParameter.IllegalCheckboxParameterException(
0240: getName(), "setCheckbox");
0241: }
0242:
0243: /**
0244: * Performs the 'onChange' event defined for this control.
0245: */
0246: protected void sendOnChangeEvent() {
0247: if (_onChangeEvent.length() > 0)
0248: getScriptableDelegate().doEvent(_onChangeEvent);
0249: }
0250:
0251: /**
0252: * Performs the 'onClick' event defined for this control.
0253: */
0254: protected void sendOnClickEvent() {
0255: if (_onClickEvent.length() > 0)
0256: getScriptableDelegate().doEvent(_onClickEvent);
0257: }
0258:
0259: /**
0260: * Creates and returns a scriptable object for this control. Subclasses should override this if they use a different
0261: * implementation of Scriptable.
0262: */
0263: protected ScriptableDelegate newScriptable() {
0264: return new Scriptable();
0265: }
0266:
0267: /**
0268: * Returns the default value of this control in the form. If no value is specified, defaults to the empty string.
0269: **/
0270: protected String getValueAttribute() {
0271: return _valueAttribute;
0272: }
0273:
0274: /**
0275: * Removes the specified required value from the list of values, throwing an exception if it is missing.
0276: **/
0277: final protected void claimValueIsRequired(List values,
0278: final String value) {
0279: if (!values.contains(value))
0280: throw new MissingParameterValueException(getName(), value,
0281: (String[]) values
0282: .toArray(new String[values.size()]));
0283: values.remove(value);
0284: }
0285:
0286: static String[] getControlElementTags() {
0287: return new String[] { "textarea", "select", "button", "input" };
0288: }
0289:
0290: static FormControl newFormParameter(WebForm form, Node node) {
0291: if (node.getNodeType() != Node.ELEMENT_NODE) {
0292: return null;
0293: } else if (node.getNodeName().equalsIgnoreCase("textarea")) {
0294: return new TextAreaFormControl(form, node);
0295: } else if (node.getNodeName().equalsIgnoreCase("select")) {
0296: return new SelectionFormControl(form, node);
0297: } else if (node.getNodeName().equalsIgnoreCase("button")) {
0298: final String type = NodeUtils.getNodeAttribute(node,
0299: "type", SUBMIT_BUTTON_TYPE);
0300: if (type.equalsIgnoreCase(SUBMIT_BUTTON_TYPE)) {
0301: return new SubmitButton(form, node);
0302: } else if (type.equalsIgnoreCase(RESET_BUTTON_TYPE)) {
0303: return new ResetButton(form, node);
0304: } else {
0305: return new Button(form, node);
0306: }
0307: } else if (!node.getNodeName().equalsIgnoreCase("input")) {
0308: return null;
0309: } else {
0310: final String type = NodeUtils.getNodeAttribute(node,
0311: "type", TEXT_TYPE);
0312: if (type.equalsIgnoreCase(TEXT_TYPE)) {
0313: return new TextFieldFormControl(form, node);
0314: } else if (type.equalsIgnoreCase(PASSWORD_TYPE)) {
0315: return new PasswordFieldFormControl(form, node);
0316: } else if (type.equalsIgnoreCase(HIDDEN_TYPE)) {
0317: return new HiddenFieldFormControl(form, node);
0318: } else if (type.equalsIgnoreCase(RADIO_BUTTON_TYPE)) {
0319: return new RadioButtonFormControl(form, node);
0320: } else if (type.equalsIgnoreCase(CHECKBOX_TYPE)) {
0321: return new CheckboxFormControl(form, node);
0322: } else if (type.equalsIgnoreCase(SUBMIT_BUTTON_TYPE)
0323: || type.equalsIgnoreCase(IMAGE_BUTTON_TYPE)) {
0324: return new SubmitButton(form, node);
0325: } else if (type.equalsIgnoreCase(BUTTON_TYPE)) {
0326: return new Button(form, node);
0327: } else if (type.equalsIgnoreCase(RESET_BUTTON_TYPE)) {
0328: return new ResetButton(form, node);
0329: } else if (type.equalsIgnoreCase(FILE_TYPE)) {
0330: return new FileSubmitFormControl(form, node);
0331: } else {
0332: return new TextFieldFormControl(form, node);
0333: }
0334: }
0335: }
0336:
0337: class Scriptable extends HTMLElementScriptable implements Input {
0338:
0339: public String getName() {
0340: return FormControl.this .getName();
0341: }
0342:
0343: public String getID() {
0344: return FormControl.this .getID();
0345: }
0346:
0347: public Scriptable() {
0348: super (FormControl.this );
0349: }
0350:
0351: public Object get(String propertyName) {
0352: if (propertyName.equalsIgnoreCase("name")) {
0353: return FormControl.this .getName();
0354: } else if (propertyName.equalsIgnoreCase("type")) {
0355: return FormControl.this .getType();
0356: } else {
0357: return super .get(propertyName);
0358: }
0359: }
0360:
0361: public void set(String propertyName, Object value) {
0362: if (propertyName.equalsIgnoreCase("value")) {
0363: _valueAttribute = value.toString();
0364: } else if (propertyName.equalsIgnoreCase("disabled")) {
0365: setDisabled(value instanceof Boolean
0366: && ((Boolean) value).booleanValue());
0367: } else {
0368: super .set(propertyName, value);
0369: }
0370: }
0371:
0372: public void click() throws IOException, SAXException {
0373: }
0374: }
0375:
0376: }
0377:
0378: abstract class BooleanFormControl extends FormControl {
0379:
0380: private boolean _isChecked;
0381: private String[] _value = new String[1];
0382:
0383: private final boolean _isCheckedDefault;
0384: private String[] _displayedValue;
0385:
0386: protected ScriptableDelegate newScriptable() {
0387: return new Scriptable();
0388: }
0389:
0390: class Scriptable extends FormControl.Scriptable {
0391:
0392: public Object get(String propertyName) {
0393: if (propertyName.equalsIgnoreCase("value")) {
0394: return getQueryValue();
0395: } else if (propertyName.equalsIgnoreCase("checked")) {
0396: return isChecked() ? Boolean.TRUE : Boolean.FALSE;
0397: } else if (propertyName.equalsIgnoreCase("defaultchecked")) {
0398: return _isCheckedDefault ? Boolean.TRUE : Boolean.FALSE;
0399: } else {
0400: return super .get(propertyName);
0401: }
0402: }
0403:
0404: public void set(String propertyName, Object value) {
0405: if (propertyName.equalsIgnoreCase("checked")) {
0406: setChecked(value instanceof Boolean
0407: && ((Boolean) value).booleanValue());
0408: } else {
0409: super .set(propertyName, value);
0410: }
0411: }
0412: }
0413:
0414: public BooleanFormControl(WebForm form, Node node) {
0415: super (form, node);
0416: _displayedValue = new String[] { readDisplayedValue(node) };
0417: _isChecked = _isCheckedDefault = NodeUtils
0418: .isNodeAttributePresent(node, "checked");
0419: }
0420:
0421: private String readDisplayedValue(Node node) {
0422: Node nextSibling = node.getNextSibling();
0423: while (nextSibling != null
0424: && nextSibling.getNodeType() != Node.TEXT_NODE
0425: && nextSibling.getNodeType() != Node.ELEMENT_NODE)
0426: nextSibling = nextSibling.getNextSibling();
0427: if (nextSibling == null
0428: || nextSibling.getNodeType() != Node.TEXT_NODE)
0429: return "";
0430: return nextSibling.getNodeValue();
0431: }
0432:
0433: boolean isChecked() {
0434: return _isChecked;
0435: }
0436:
0437: public void setChecked(boolean checked) {
0438: _isChecked = checked;
0439: }
0440:
0441: void reset() {
0442: _isChecked = _isCheckedDefault;
0443: }
0444:
0445: /**
0446: * Returns the current value(s) associated with this control. These values will be transmitted to the server
0447: * if the control is 'successful'.
0448: **/
0449: public String[] getValues() {
0450: return isChecked() ? toArray(getQueryValue()) : NO_VALUE;
0451: }
0452:
0453: /**
0454: * Returns the values permitted in this control.
0455: **/
0456: public String[] getOptionValues() {
0457: return (isReadOnly() && !isChecked()) ? NO_VALUE
0458: : toArray(getQueryValue());
0459: }
0460:
0461: String[] getDisplayedOptions() {
0462: return _displayedValue;
0463: }
0464:
0465: void addValues(ParameterProcessor processor, String characterSet)
0466: throws IOException {
0467: if (isChecked() && !isDisabled())
0468: processor.addParameter(getName(), getQueryValue(),
0469: characterSet);
0470: }
0471:
0472: /**
0473: * Remove any required values for this control from the list, throwing an exception if they are missing.
0474: **/
0475: void claimRequiredValues(List values) {
0476: if (isValueRequired())
0477: claimValueIsRequired(values, getQueryValue());
0478: }
0479:
0480: protected boolean isValueRequired() {
0481: return isReadOnly() && isChecked();
0482: }
0483:
0484: abstract String getQueryValue();
0485:
0486: private String[] toArray(String value) {
0487: _value[0] = value;
0488: return _value;
0489: }
0490: }
0491:
0492: class RadioButtonFormControl extends BooleanFormControl {
0493:
0494: public String getType() {
0495: return RADIO_BUTTON_TYPE;
0496: }
0497:
0498: public RadioButtonFormControl(WebForm form, Node node) {
0499: super (form, node);
0500: }
0501:
0502: /**
0503: * Returns true if only one control of this kind can have a value.
0504: **/
0505: public boolean isExclusive() {
0506: return true;
0507: }
0508:
0509: String getQueryValue() {
0510: return getValueAttribute();
0511: }
0512: }
0513:
0514: class RadioGroupFormControl extends FormControl {
0515:
0516: private List _buttonList = new ArrayList();
0517: private RadioButtonFormControl[] _buttons;
0518: private String[] _allowedValues;
0519:
0520: public String getType() {
0521: return UNDEFINED_TYPE;
0522: }
0523:
0524: public RadioGroupFormControl(WebForm form) {
0525: super (form);
0526: }
0527:
0528: void addRadioButton(RadioButtonFormControl control) {
0529: _buttonList.add(control);
0530: _buttons = null;
0531: _allowedValues = null;
0532: }
0533:
0534: public String[] getValues() {
0535: for (int i = 0; i < getButtons().length; i++) {
0536: if (getButtons()[i].isChecked())
0537: return getButtons()[i].getValues();
0538: }
0539: return NO_VALUE;
0540: }
0541:
0542: /**
0543: * Returns the option values defined for this radio button group.
0544: **/
0545: public String[] getOptionValues() {
0546: ArrayList valueList = new ArrayList();
0547: FormControl[] buttons = getButtons();
0548: for (int i = 0; i < buttons.length; i++) {
0549: valueList.addAll(Arrays
0550: .asList(buttons[i].getOptionValues()));
0551: }
0552: return (String[]) valueList
0553: .toArray(new String[valueList.size()]);
0554: }
0555:
0556: /**
0557: * Returns the options displayed for this radio button group.
0558: */
0559: String[] getDisplayedOptions() {
0560: ArrayList valueList = new ArrayList();
0561: FormControl[] buttons = getButtons();
0562: for (int i = 0; i < buttons.length; i++) {
0563: valueList.addAll(Arrays.asList(buttons[i]
0564: .getDisplayedOptions()));
0565: }
0566: return (String[]) valueList
0567: .toArray(new String[valueList.size()]);
0568: }
0569:
0570: Object getDelegate() {
0571: ScriptableDelegate[] delegates = new ScriptableDelegate[getButtons().length];
0572: for (int i = 0; i < delegates.length; i++) {
0573: delegates[i] = getButtons()[i].getScriptableDelegate();
0574: }
0575: return delegates;
0576: }
0577:
0578: void addValues(ParameterProcessor processor, String characterSet)
0579: throws IOException {
0580: for (int i = 0; i < getButtons().length; i++)
0581: getButtons()[i].addValues(processor, characterSet);
0582: }
0583:
0584: /**
0585: * Remove any required values for this control from the list, throwing an exception if they are missing.
0586: **/
0587: void claimRequiredValues(List values) {
0588: for (int i = 0; i < getButtons().length; i++) {
0589: getButtons()[i].claimRequiredValues(values);
0590: }
0591: }
0592:
0593: void claimUniqueValue(List values) {
0594: int matchingButtonIndex = -1;
0595: for (int i = 0; i < getButtons().length
0596: && matchingButtonIndex < 0; i++) {
0597: if (!getButtons()[i].isReadOnly()
0598: && values.contains(getButtons()[i].getQueryValue()))
0599: matchingButtonIndex = i;
0600: }
0601: if (matchingButtonIndex < 0)
0602: throw new IllegalParameterValueException(getButtons()[0]
0603: .getName(), (String) values.get(0),
0604: getAllowedValues());
0605:
0606: boolean wasChecked = getButtons()[matchingButtonIndex]
0607: .isChecked();
0608: for (int i = 0; i < getButtons().length; i++) {
0609: if (!getButtons()[i].isReadOnly())
0610: getButtons()[i].setChecked(i == matchingButtonIndex);
0611: }
0612: values
0613: .remove(getButtons()[matchingButtonIndex]
0614: .getQueryValue());
0615: if (!wasChecked)
0616: getButtons()[matchingButtonIndex].sendOnClickEvent();
0617: }
0618:
0619: void reset() {
0620: for (int i = 0; i < getButtons().length; i++)
0621: getButtons()[i].reset();
0622: }
0623:
0624: private String[] getAllowedValues() {
0625: if (_allowedValues == null) {
0626: _allowedValues = new String[getButtons().length];
0627: for (int i = 0; i < _allowedValues.length; i++) {
0628: _allowedValues[i] = getButtons()[i].getQueryValue();
0629: }
0630: }
0631: return _allowedValues;
0632: }
0633:
0634: private RadioButtonFormControl[] getButtons() {
0635: if (_buttons == null)
0636: _buttons = (RadioButtonFormControl[]) _buttonList
0637: .toArray(new RadioButtonFormControl[_buttonList
0638: .size()]);
0639: return _buttons;
0640: }
0641: }
0642:
0643: class CheckboxFormControl extends BooleanFormControl {
0644:
0645: public String getType() {
0646: return CHECKBOX_TYPE;
0647: }
0648:
0649: public CheckboxFormControl(WebForm form, Node node) {
0650: super (form, node);
0651: }
0652:
0653: void claimUniqueValue(List values) {
0654: if (isValueRequired())
0655: return;
0656: setState(values.contains(getQueryValue()));
0657: if (isChecked())
0658: values.remove(getQueryValue());
0659: }
0660:
0661: String getQueryValue() {
0662: final String value = getValueAttribute();
0663: return value.length() == 0 ? "on" : value;
0664: }
0665:
0666: /**
0667: * Toggles the value of this control.
0668: */
0669: public void toggle() {
0670: setState(!isChecked());
0671: }
0672:
0673: /**
0674: * Sets the state of this boolean control. Triggers the 'onclick' event if the state has changed.
0675: */
0676: public void setState(boolean state) {
0677: boolean wasChecked = isChecked();
0678: setChecked(state);
0679: if (isChecked() != wasChecked)
0680: sendOnClickEvent();
0681: }
0682: }
0683:
0684: abstract class TextFormControl extends FormControl {
0685:
0686: private String[] _value = new String[1];
0687: private String[] _defaultValue;
0688:
0689: public TextFormControl(WebForm form, Node node, String defaultValue) {
0690: super (form, node);
0691: _defaultValue = new String[] { defaultValue };
0692: }
0693:
0694: /**
0695: * Returns the current value(s) associated with this control. These values will be transmitted to the server
0696: * if the control is 'successful'.
0697: **/
0698: public String[] getValues() {
0699: return (_value[0] != null) ? _value : _defaultValue;
0700: }
0701:
0702: /**
0703: * Returns true to indicate that this control accepts free-form text.
0704: **/
0705: public boolean isTextControl() {
0706: return true;
0707: }
0708:
0709: protected ScriptableDelegate newScriptable() {
0710: return new Scriptable();
0711: }
0712:
0713: void addValues(ParameterProcessor processor, String characterSet)
0714: throws IOException {
0715: if (!isDisabled() && getName().length() > 0)
0716: processor.addParameter(getName(), getValues()[0],
0717: characterSet);
0718: }
0719:
0720: void claimValue(List values) {
0721: if (isReadOnly())
0722: return;
0723:
0724: String oldValue = getValues()[0];
0725: if (values.isEmpty()) {
0726: _value[0] = "";
0727: } else {
0728: _value[0] = (String) values.get(0);
0729: values.remove(0);
0730: }
0731: if (!(oldValue.equals(_value[0])))
0732: sendOnChangeEvent();
0733: }
0734:
0735: void reset() {
0736: _value[0] = null;
0737: }
0738:
0739: void claimRequiredValues(List values) {
0740: if (isReadOnly())
0741: claimValueIsRequired(values);
0742: }
0743:
0744: protected void claimValueIsRequired(List values) {
0745: claimValueIsRequired(values, _defaultValue[0]);
0746: }
0747:
0748: class Scriptable extends FormControl.Scriptable {
0749:
0750: public Object get(String propertyName) {
0751: if (propertyName.equalsIgnoreCase("value")) {
0752: return getValues()[0];
0753: } else if (propertyName.equalsIgnoreCase("defaultValue")) {
0754: return _defaultValue[0];
0755: } else {
0756: return super .get(propertyName);
0757: }
0758: }
0759:
0760: public void set(String propertyName, Object value) {
0761: if (!propertyName.equalsIgnoreCase("value")) {
0762: super .set(propertyName, value);
0763: } else if (value instanceof Number) {
0764: _value[0] = HttpUnitUtils.trimmedValue((Number) value);
0765: } else {
0766: _value[0] = (value == null) ? null : value.toString();
0767: }
0768: }
0769: }
0770: }
0771:
0772: class TextFieldFormControl extends TextFormControl {
0773:
0774: public String getType() {
0775: return TEXT_TYPE;
0776: }
0777:
0778: public TextFieldFormControl(WebForm form, Node node) {
0779: super (form, node, NodeUtils.getNodeAttribute(node, "value"));
0780: supportAttribute("maxlength");
0781: }
0782:
0783: }
0784:
0785: class PasswordFieldFormControl extends TextFieldFormControl {
0786:
0787: public String getType() {
0788: return PASSWORD_TYPE;
0789: }
0790:
0791: public PasswordFieldFormControl(WebForm form, Node node) {
0792: super (form, node);
0793: }
0794: }
0795:
0796: class HiddenFieldFormControl extends TextFieldFormControl {
0797:
0798: public String getType() {
0799: return HIDDEN_TYPE;
0800: }
0801:
0802: public HiddenFieldFormControl(WebForm form, Node node) {
0803: super (form, node);
0804: }
0805:
0806: void claimRequiredValues(List values) {
0807: claimValueIsRequired(values);
0808: }
0809:
0810: void claimValue(List values) {
0811: }
0812:
0813: boolean isHidden() {
0814: return true;
0815: }
0816: }
0817:
0818: class TextAreaFormControl extends TextFormControl {
0819:
0820: public TextAreaFormControl(WebForm form, Node node) {
0821: super (form, node, getDefaultValue(node));
0822:
0823: if (!node.getNodeName().equalsIgnoreCase("textarea")) {
0824: throw new RuntimeException("Not a textarea element");
0825: }
0826: }
0827:
0828: private static String getDefaultValue(Node node) {
0829: return NodeUtils.asText(node.getChildNodes());
0830: }
0831:
0832: public String getType() {
0833: return TEXTAREA_TYPE;
0834: }
0835:
0836: }
0837:
0838: class FileSubmitFormControl extends FormControl {
0839:
0840: public String getType() {
0841: return FILE_TYPE;
0842: }
0843:
0844: private UploadFileSpec _fileToUpload;
0845:
0846: protected ScriptableDelegate newScriptable() {
0847: return new Scriptable();
0848: }
0849:
0850: class Scriptable extends FormControl.Scriptable {
0851:
0852: public Object get(String propertyName) {
0853: if (propertyName.equalsIgnoreCase("value")) {
0854: return getSelectedName();
0855: } else {
0856: return super .get(propertyName);
0857: }
0858: }
0859:
0860: }
0861:
0862: public FileSubmitFormControl(WebForm form, Node node) {
0863: super (form, node);
0864: }
0865:
0866: /**
0867: * Returns true if this control accepts a file for upload.
0868: **/
0869: public boolean isFileParameter() {
0870: return true;
0871: }
0872:
0873: /**
0874: * Returns the name of the selected file, if any.
0875: */
0876: public String[] getValues() {
0877: return new String[] { getSelectedName() };
0878: }
0879:
0880: private String getSelectedName() {
0881: return _fileToUpload == null ? "" : _fileToUpload.getFileName();
0882: }
0883:
0884: /**
0885: * Specifies a number of file upload specifications for this control.
0886: **/
0887: void claimUploadSpecification(List files) {
0888: if (files.isEmpty()) {
0889: _fileToUpload = null;
0890: } else {
0891: _fileToUpload = (UploadFileSpec) files.get(0);
0892: files.remove(0);
0893: }
0894: }
0895:
0896: void addValues(ParameterProcessor processor, String characterSet)
0897: throws IOException {
0898: if (!isDisabled() && _fileToUpload != null) {
0899: processor.addFile(getName(), _fileToUpload);
0900: }
0901: }
0902: }
0903:
0904: class SelectionFormControl extends FormControl {
0905:
0906: private final boolean _multiSelect;
0907: private final boolean _listBox;
0908:
0909: private Options _selectionOptions;
0910:
0911: public String getType() {
0912: return (isMultiValued() ? MULTIPLE_TYPE : SINGLE_TYPE);
0913: }
0914:
0915: SelectionFormControl(WebForm form, Node node) {
0916: super (form, node);
0917: if (!node.getNodeName().equalsIgnoreCase("select"))
0918: throw new RuntimeException("Not a select element");
0919:
0920: int size = NodeUtils.getAttributeValue(node, "size", 0);
0921: _multiSelect = NodeUtils.isNodeAttributePresent(node,
0922: "multiple");
0923: _listBox = size > 1 || (_multiSelect && size != 1);
0924:
0925: _selectionOptions = _listBox ? (Options) new MultiSelectOptions(
0926: node)
0927: : (Options) new SingleSelectOptions(node);
0928: }
0929:
0930: public String[] getValues() {
0931: return _selectionOptions.getSelectedValues();
0932: }
0933:
0934: public String[] getOptionValues() {
0935: return _selectionOptions.getValues();
0936: }
0937:
0938: public String[] getDisplayedOptions() {
0939: return _selectionOptions.getDisplayedText();
0940: }
0941:
0942: /**
0943: * Returns true if a single control can have multiple values.
0944: **/
0945: public boolean isMultiValued() {
0946: return _multiSelect;
0947: }
0948:
0949: class Scriptable extends FormControl.Scriptable {
0950:
0951: public Object get(String propertyName) {
0952: if (propertyName.equalsIgnoreCase("options")) {
0953: return _selectionOptions;
0954: } else if (propertyName.equalsIgnoreCase("length")) {
0955: return new Integer(getOptionValues().length);
0956: } else if (propertyName.equalsIgnoreCase("value")) {
0957: return getSelectedValue();
0958: } else if (propertyName.equalsIgnoreCase("selectedIndex")) {
0959: return new Integer(_selectionOptions
0960: .getFirstSelectedIndex());
0961: } else {
0962: return super .get(propertyName);
0963: }
0964: }
0965:
0966: public Object get(int index) {
0967: return _selectionOptions.get(index);
0968: }
0969:
0970: private String getSelectedValue() {
0971: String[] values = getValues();
0972: return (values.length == 0 ? "" : values[0]);
0973: }
0974:
0975: public void set(String propertyName, Object value) {
0976: if (propertyName.equalsIgnoreCase("value")) {
0977: ArrayList values = new ArrayList();
0978: values.add(value);
0979: _selectionOptions.claimUniqueValues(values);
0980: } else if (propertyName.equalsIgnoreCase("selectedIndex")) {
0981: if (!(value instanceof Number))
0982: throw new RuntimeException(
0983: "selectedIndex must be set to an integer");
0984: _selectionOptions.setSelectedIndex(((Number) value)
0985: .intValue());
0986: } else {
0987: super .set(propertyName, value);
0988: }
0989: }
0990: }
0991:
0992: protected ScriptableDelegate newScriptable() {
0993: return new Scriptable();
0994: }
0995:
0996: void updateRequiredParameters(Hashtable required) {
0997: if (isReadOnly())
0998: required.put(getName(), getValues());
0999: }
1000:
1001: void addValues(ParameterProcessor processor, String characterSet)
1002: throws IOException {
1003: if (isDisabled())
1004: return;
1005: for (int i = 0; i < getValues().length; i++) {
1006: processor.addParameter(getName(), getValues()[i],
1007: characterSet);
1008: }
1009: }
1010:
1011: void claimUniqueValue(List values) {
1012: boolean changed = _selectionOptions.claimUniqueValues(values);
1013: if (changed)
1014: sendOnChangeEvent();
1015: }
1016:
1017: final void reset() {
1018: _selectionOptions.reset();
1019: }
1020:
1021: static class Option extends ScriptableDelegate implements
1022: SelectionOption {
1023:
1024: private String _text;
1025: private String _value;
1026: private boolean _defaultSelected;
1027: private boolean _selected;
1028: private int _index;
1029: private Options _container;
1030:
1031: Option() {
1032: }
1033:
1034: Option(String text, String value, boolean selected) {
1035: _text = text;
1036: _value = value;
1037: _defaultSelected = _selected = selected;
1038: }
1039:
1040: void reset() {
1041: _selected = _defaultSelected;
1042: }
1043:
1044: void addValueIfSelected(List list) {
1045: if (_selected)
1046: list.add(_value);
1047: }
1048:
1049: void setIndex(Options container, int index) {
1050: _container = container;
1051: _index = index;
1052: }
1053:
1054: //------------------------- SelectionOption methods ------------------------------
1055:
1056: public void initialize(String text, String value,
1057: boolean defaultSelected, boolean selected) {
1058: _text = text;
1059: _value = value;
1060: _defaultSelected = defaultSelected;
1061: _selected = selected;
1062: }
1063:
1064: public int getIndex() {
1065: return _index;
1066: }
1067:
1068: public String getText() {
1069: return _text;
1070: }
1071:
1072: public void setText(String text) {
1073: _text = text;
1074: }
1075:
1076: public String getValue() {
1077: return _value;
1078: }
1079:
1080: public void setValue(String value) {
1081: _value = value;
1082: }
1083:
1084: public boolean isDefaultSelected() {
1085: return _defaultSelected;
1086: }
1087:
1088: public void setSelected(boolean selected) {
1089: _selected = selected;
1090: if (selected)
1091: _container.optionSet(_index);
1092: }
1093:
1094: public boolean isSelected() {
1095: return _selected;
1096: }
1097: }
1098:
1099: abstract class Options extends ScriptableDelegate implements
1100: SelectionOptions {
1101:
1102: private Option[] _options;
1103:
1104: Options(Node selectionNode) {
1105: NodeList nl = ((Element) selectionNode)
1106: .getElementsByTagName("option");
1107:
1108: _options = new Option[nl.getLength()];
1109: for (int i = 0; i < _options.length; i++) {
1110: final String displayedText = getValue(
1111: nl.item(i).getFirstChild()).trim();
1112: _options[i] = new Option(displayedText, getOptionValue(
1113: nl.item(i), displayedText),
1114: nl.item(i).getAttributes().getNamedItem(
1115: "selected") != null);
1116: _options[i].setIndex(this , i);
1117: }
1118: }
1119:
1120: boolean claimUniqueValues(List values) {
1121: return claimUniqueValues(values, _options);
1122: }
1123:
1124: protected abstract boolean claimUniqueValues(List values,
1125: Option[] options);
1126:
1127: final protected void reportNoMatches(List values) {
1128: if (!_listBox)
1129: throw new IllegalParameterValueException(getName(),
1130: (String) values.get(0), getOptionValues());
1131: }
1132:
1133: String[] getSelectedValues() {
1134: ArrayList list = new ArrayList();
1135: for (int i = 0; i < _options.length; i++) {
1136: _options[i].addValueIfSelected(list);
1137: }
1138: if (!_listBox && list.isEmpty() && _options.length > 0)
1139: list.add(_options[0].getValue());
1140: return (String[]) list.toArray(new String[list.size()]);
1141: }
1142:
1143: void reset() {
1144: for (int i = 0; i < _options.length; i++) {
1145: _options[i].reset();
1146: }
1147: }
1148:
1149: String[] getDisplayedText() {
1150: String[] displayedText = new String[_options.length];
1151: for (int i = 0; i < displayedText.length; i++)
1152: displayedText[i] = _options[i].getText();
1153: return displayedText;
1154: }
1155:
1156: String[] getValues() {
1157: String[] values = new String[_options.length];
1158: for (int i = 0; i < values.length; i++)
1159: values[i] = _options[i].getValue();
1160: return values;
1161: }
1162:
1163: /**
1164: * Selects the matching item and deselects the others.
1165: **/
1166: void setSelectedIndex(int index) {
1167: for (int i = 0; i < _options.length; i++) {
1168: _options[i]._selected = (i == index);
1169: }
1170: }
1171:
1172: /**
1173: * Returns the index of the first item selected, or -1 if none is selected.
1174: */
1175: int getFirstSelectedIndex() {
1176: for (int i = 0; i < _options.length; i++) {
1177: if (_options[i].isSelected())
1178: return i;
1179: }
1180: return noOptionSelectedIndex();
1181: }
1182:
1183: protected abstract int noOptionSelectedIndex();
1184:
1185: public int getLength() {
1186: return _options.length;
1187: }
1188:
1189: public void setLength(int length) {
1190: if (length < 0 || length >= _options.length)
1191: return;
1192: Option[] newArray = new Option[length];
1193: System.arraycopy(_options, 0, newArray, 0, length);
1194: _options = newArray;
1195: }
1196:
1197: public void put(int i, SelectionOption option) {
1198: if (i < 0)
1199: return;
1200:
1201: if (option == null) {
1202: if (i >= _options.length)
1203: return;
1204: deleteOptionsEntry(i);
1205: } else {
1206: if (i >= _options.length) {
1207: i = _options.length;
1208: expandOptionsArray();
1209: }
1210: _options[i] = (Option) option;
1211: _options[i].setIndex(this , i);
1212: if (option.isSelected())
1213: ensureUniqueOption(_options, i);
1214: }
1215: }
1216:
1217: protected abstract void ensureUniqueOption(Option[] options,
1218: int i);
1219:
1220: private void deleteOptionsEntry(int i) {
1221: Option[] newArray = new Option[_options.length - 1];
1222: System.arraycopy(_options, 0, newArray, 0, i);
1223: System.arraycopy(_options, i + 1, newArray, i,
1224: newArray.length - i);
1225: _options = newArray;
1226: }
1227:
1228: private void expandOptionsArray() {
1229: Option[] newArray = new Option[_options.length + 1];
1230: System.arraycopy(_options, 0, newArray, 0, _options.length);
1231: _options = newArray;
1232: }
1233:
1234: public Object get(int index) {
1235: return _options[index];
1236: }
1237:
1238: /** Invoked when an option is set true. **/
1239: void optionSet(int i) {
1240: ensureUniqueOption(_options, i);
1241: }
1242:
1243: private String getOptionValue(Node optionNode,
1244: String displayedText) {
1245: NamedNodeMap nnm = optionNode.getAttributes();
1246: if (nnm.getNamedItem("value") != null) {
1247: return getValue(nnm.getNamedItem("value"));
1248: } else {
1249: return displayedText;
1250: }
1251: }
1252:
1253: private String getValue(Node node) {
1254: return (node == null) ? "" : emptyIfNull(node
1255: .getNodeValue());
1256: }
1257:
1258: private String emptyIfNull(String value) {
1259: return (value == null) ? "" : value;
1260: }
1261:
1262: }
1263:
1264: class SingleSelectOptions extends Options {
1265:
1266: public SingleSelectOptions(Node selectionNode) {
1267: super (selectionNode);
1268: }
1269:
1270: protected void ensureUniqueOption(Option[] options, int i) {
1271: for (int j = 0; j < options.length; j++) {
1272: options[j]._selected = (i == j);
1273: }
1274: }
1275:
1276: protected int noOptionSelectedIndex() {
1277: return 0;
1278: }
1279:
1280: protected boolean claimUniqueValues(List values,
1281: Option[] options) {
1282: boolean changed = false;
1283: for (int i = 0; i < values.size(); i++) {
1284: String value = (String) values.get(i);
1285: for (int j = 0; j < options.length; j++) {
1286: boolean selected = value.equals(options[j]
1287: .getValue());
1288: if (selected != options[j].isSelected())
1289: changed = true;
1290: options[j].setSelected(selected);
1291: if (selected) {
1292: values.remove(value);
1293: for (++j; j < options.length; j++)
1294: options[j].setSelected(false);
1295: return changed;
1296: }
1297: }
1298: }
1299: reportNoMatches(values);
1300: return changed;
1301: }
1302: }
1303:
1304: class MultiSelectOptions extends Options {
1305:
1306: public MultiSelectOptions(Node selectionNode) {
1307: super (selectionNode);
1308: }
1309:
1310: protected void ensureUniqueOption(Option[] options, int i) {
1311: }
1312:
1313: protected int noOptionSelectedIndex() {
1314: return -1;
1315: }
1316:
1317: protected boolean claimUniqueValues(List values,
1318: Option[] options) {
1319: boolean changed = false;
1320: for (int i = 0; i < options.length; i++) {
1321: final boolean newValue = values.contains(options[i]
1322: .getValue());
1323: if (newValue != options[i].isSelected())
1324: changed = true;
1325: options[i].setSelected(newValue);
1326: if (newValue)
1327: values.remove(options[i].getValue());
1328: }
1329: return changed;
1330: }
1331: }
1332:
1333: }
1334:
1335: //============================= exception class IllegalParameterValueException ======================================
1336:
1337: /**
1338: * This exception is thrown on an attempt to set a parameter to a value not permitted to it by the form.
1339: **/
1340: class IllegalParameterValueException extends
1341: IllegalRequestParameterException {
1342:
1343: IllegalParameterValueException(String parameterName,
1344: String badValue, String[] allowed) {
1345: _parameterName = parameterName;
1346: _badValue = badValue;
1347: _allowedValues = allowed;
1348: }
1349:
1350: public String getMessage() {
1351: StringBuffer sb = new StringBuffer(
1352: HttpUnitUtils.DEFAULT_TEXT_BUFFER_SIZE);
1353: sb.append("May not set parameter '").append(_parameterName)
1354: .append("' to '");
1355: sb.append(_badValue).append("'. Value must be one of: { ");
1356: for (int i = 0; i < _allowedValues.length; i++) {
1357: if (i != 0)
1358: sb.append(", ");
1359: sb.append(_allowedValues[i]);
1360: }
1361: sb.append(" }");
1362: return sb.toString();
1363: }
1364:
1365: private String _parameterName;
1366: private String _badValue;
1367: private String[] _allowedValues;
1368: }
1369:
1370: //============================= exception class MissingParameterValueException ======================================
1371:
1372: /**
1373: * This exception is thrown on an attempt to remove a required value from a form parameter.
1374: **/
1375: class MissingParameterValueException extends
1376: IllegalRequestParameterException {
1377:
1378: MissingParameterValueException(String parameterName,
1379: String missingValue, String[] proposed) {
1380: _parameterName = parameterName;
1381: _missingValue = missingValue;
1382: _proposedValues = proposed;
1383: }
1384:
1385: public String getMessage() {
1386: StringBuffer sb = new StringBuffer(
1387: HttpUnitUtils.DEFAULT_TEXT_BUFFER_SIZE);
1388: sb.append("Parameter '").append(_parameterName).append(
1389: "' must have the value '");
1390: sb.append(_missingValue)
1391: .append("'. Attempted to set it to: { ");
1392: for (int i = 0; i < _proposedValues.length; i++) {
1393: if (i != 0)
1394: sb.append(", ");
1395: sb.append(_proposedValues[i]);
1396: }
1397: sb.append(" }");
1398: return sb.toString();
1399: }
1400:
1401: private String _parameterName;
1402: private String _missingValue;
1403: private String[] _proposedValues;
1404: }
|