• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / httpunit / 656

06 Dec 2025 09:11PM UTC coverage: 80.452% (+0.02%) from 80.435%
656

push

github

hazendaz
[maven-release-plugin] prepare for next development iteration

3213 of 4105 branches covered (78.27%)

Branch coverage included in aggregate %.

8245 of 10137 relevant lines covered (81.34%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.74
/src/main/java/com/meterware/httpunit/controls/SelectionFormControl.java
1
/*
2
 * MIT License
3
 *
4
 * Copyright 2011-2025 Russell Gold
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
7
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
8
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
9
 * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions
12
 * of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
15
 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
17
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18
 * DEALINGS IN THE SOFTWARE.
19
 */
20
package com.meterware.httpunit.controls;
21

22
import com.meterware.httpunit.FormControl;
23
import com.meterware.httpunit.NodeUtils;
24
import com.meterware.httpunit.WebForm;
25
import com.meterware.httpunit.dom.HTMLSelectElementImpl;
26
import com.meterware.httpunit.protocol.ParameterProcessor;
27
import com.meterware.httpunit.scripting.ScriptableDelegate;
28
import com.meterware.httpunit.scripting.SelectionOption;
29
import com.meterware.httpunit.scripting.SelectionOptions;
30

31
import java.io.IOException;
32
import java.util.ArrayList;
33
import java.util.Hashtable;
34
import java.util.List;
35

36
import org.w3c.dom.Element;
37
import org.w3c.dom.NamedNodeMap;
38
import org.w3c.dom.Node;
39
import org.w3c.dom.NodeList;
40

41
/**
42
 * FormControl for "Select" moved here by wf for testability and visibility see bugreport [ 1124057 ] Out of Bounds
43
 * Exception should be avoided.
44
 */
45
public class SelectionFormControl extends FormControl {
46

47
    /** The multi select. */
48
    private final boolean _multiSelect;
49

50
    /** The list box. */
51
    private final boolean _listBox;
52

53
    /** The selection options. */
54
    private Options _selectionOptions;
55

56
    @Override
57
    public String getType() {
58
        return isMultiValued() ? MULTIPLE_TYPE : SINGLE_TYPE;
1✔
59
    }
60

61
    /**
62
     * Instantiates a new selection form control.
63
     *
64
     * @param form
65
     *            the form
66
     * @param element
67
     *            the element
68
     */
69
    public SelectionFormControl(WebForm form, HTMLSelectElementImpl element) {
70
        super(form, element);
1✔
71
        if (!element.getNodeName().equalsIgnoreCase("select")) {
1!
72
            throw new RuntimeException("Not a select element");
×
73
        }
74

75
        int size = NodeUtils.getAttributeValue(element, "size", 0);
1✔
76
        _multiSelect = NodeUtils.isNodeAttributePresent(element, "multiple");
1✔
77
        _listBox = size > 1 || _multiSelect && size != 1;
1✔
78

79
        _selectionOptions = _listBox ? (Options) new MultiSelectOptions(element)
1✔
80
                : (Options) new SingleSelectOptions(element);
1✔
81
    }
1✔
82

83
    @Override
84
    public String[] getValues() {
85
        return _selectionOptions.getSelectedValues();
1✔
86
    }
87

88
    @Override
89
    public String[] getOptionValues() {
90
        return _selectionOptions.getValues();
1✔
91
    }
92

93
    @Override
94
    public String[] getDisplayedOptions() {
95
        return _selectionOptions.getDisplayedText();
1✔
96
    }
97

98
    /**
99
     * Returns true if a single control can have multiple values.
100
     **/
101
    @Override
102
    public boolean isMultiValued() {
103
        return _multiSelect;
1✔
104
    }
105

106
    /**
107
     * The Class Scriptable.
108
     */
109
    class Scriptable extends FormControl.Scriptable {
1✔
110

111
        /**
112
         * get the Object with the given property name
113
         *
114
         * @param propertyName
115
         *            - the name of the property to get
116
         *
117
         * @return the Object for the property
118
         */
119
        @Override
120
        public Object get(String propertyName) {
121
            if (propertyName.equalsIgnoreCase("options")) {
1✔
122
                return _selectionOptions;
1✔
123
            }
124
            if (propertyName.equalsIgnoreCase("length")) {
1✔
125
                return Integer.valueOf(getOptionValues().length);
1✔
126
            }
127
            if (propertyName.equalsIgnoreCase("value")) {
1✔
128
                return getSelectedValue();
1✔
129
            }
130
            if (propertyName.equalsIgnoreCase("selectedIndex")) {
1✔
131
                return Integer.valueOf(_selectionOptions.getFirstSelectedIndex());
1✔
132
            }
133
            return super.get(propertyName);
1✔
134
        }
135

136
        /**
137
         * get the Object at the given index
138
         *
139
         * @param index
140
         *            - the index of the object to get
141
         *
142
         * @return the object at the given index
143
         */
144
        @Override
145
        public Object get(int index) {
146
            return _selectionOptions.get(index);
1✔
147
        }
148

149
        /**
150
         * Gets the selected value.
151
         *
152
         * @return the selected value
153
         */
154
        private String getSelectedValue() {
155
            String[] values = getValues();
1✔
156
            return values.length == 0 ? "" : values[0];
1!
157
        }
158

159
        /**
160
         * set the property with the given name to the given value
161
         *
162
         * @param propertyName
163
         *            - the name of the property to set
164
         * @param value
165
         *            - the value to assign to the property
166
         */
167
        @Override
168
        public void set(String propertyName, Object value) {
169
            if (propertyName.equalsIgnoreCase("value")) {
1✔
170
                ArrayList values = new ArrayList<>();
1✔
171
                values.add(value);
1✔
172
                _selectionOptions.claimUniqueValues(values);
1✔
173
            } else if (propertyName.equalsIgnoreCase("selectedIndex")) {
1✔
174
                if (!(value instanceof Number)) {
1!
175
                    throw new RuntimeException("selectedIndex must be set to an integer");
×
176
                }
177
                _selectionOptions.setSelectedIndex(((Number) value).intValue());
1✔
178
            } else if (propertyName.equalsIgnoreCase("length")) {
1!
179
                _selectionOptions.setLength(((Number) value).intValue());
1✔
180
            } else {
181
                super.set(propertyName, value);
×
182
            }
183
        }
1✔
184
    }
185

186
    @Override
187
    public ScriptableDelegate newScriptable() {
188
        return new Scriptable();
1✔
189
    }
190

191
    /**
192
     * Update required parameters.
193
     *
194
     * @param required
195
     *            the required
196
     */
197
    void updateRequiredParameters(Hashtable required) {
198
        if (isReadOnly()) {
×
199
            required.put(getName(), getValues());
×
200
        }
201
    }
×
202

203
    @Override
204
    protected void addValues(ParameterProcessor processor, String characterSet) throws IOException {
205
        if (isDisabled()) {
1!
206
            return;
×
207
        }
208
        for (int i = 0; i < getValues().length; i++) {
1✔
209
            processor.addParameter(getName(), getValues()[i], characterSet);
1✔
210
        }
211
    }
1✔
212

213
    @Override
214
    protected void claimUniqueValue(List values) {
215
        boolean changed = _selectionOptions.claimUniqueValues(values);
1✔
216
        if (changed) {
1✔
217
            sendOnChangeEvent();
1✔
218
        }
219
    }
1✔
220

221
    @Override
222
    protected void reset() {
223
        _selectionOptions.reset();
1✔
224
    }
1✔
225

226
    /**
227
     * The Class Option.
228
     */
229
    public static class Option extends ScriptableDelegate implements SelectionOption {
230

231
        /** The text. */
232
        private String _text = "";
1✔
233

234
        /** The value. */
235
        private String _value;
236

237
        /** The default selected. */
238
        private boolean _defaultSelected;
239

240
        /** The selected. */
241
        private boolean _selected;
242

243
        /** The index. */
244
        private int _index;
245

246
        /** The container. */
247
        private Options _container;
248

249
        /**
250
         * Instantiates a new option.
251
         */
252
        public Option() {
1✔
253
        }
1✔
254

255
        /**
256
         * Instantiates a new option.
257
         *
258
         * @param text
259
         *            the text
260
         * @param value
261
         *            the value
262
         * @param selected
263
         *            the selected
264
         */
265
        Option(String text, String value, boolean selected) {
1✔
266
            _text = text;
1✔
267
            _value = value;
1✔
268
            _defaultSelected = _selected = selected;
1✔
269
        }
1✔
270

271
        /**
272
         * Reset.
273
         */
274
        void reset() {
275
            _selected = _defaultSelected;
1✔
276
        }
1✔
277

278
        /**
279
         * Adds the value if selected.
280
         *
281
         * @param list
282
         *            the list
283
         */
284
        void addValueIfSelected(List list) {
285
            if (_selected) {
1✔
286
                list.add(_value);
1✔
287
            }
288
        }
1✔
289

290
        /**
291
         * Sets the index.
292
         *
293
         * @param container
294
         *            the container
295
         * @param index
296
         *            the index
297
         */
298
        void setIndex(Options container, int index) {
299
            _container = container;
1✔
300
            _index = index;
1✔
301
        }
1✔
302

303
        // ------------------------- SelectionOption methods ------------------------------
304

305
        @Override
306
        public void initialize(String text, String value, boolean defaultSelected, boolean selected) {
307
            _text = text;
1✔
308
            _value = value;
1✔
309
            _defaultSelected = defaultSelected;
1✔
310
            _selected = selected;
1✔
311
        }
1✔
312

313
        @Override
314
        public int getIndex() {
315
            return _index;
1✔
316
        }
317

318
        @Override
319
        public String getText() {
320
            return _text;
1✔
321
        }
322

323
        @Override
324
        public void setText(String text) {
325
            _text = text;
1✔
326
        }
1✔
327

328
        @Override
329
        public String getValue() {
330
            return _value;
1✔
331
        }
332

333
        @Override
334
        public void setValue(String value) {
335
            _value = value;
1✔
336
        }
1✔
337

338
        @Override
339
        public boolean isDefaultSelected() {
340
            return _defaultSelected;
×
341
        }
342

343
        @Override
344
        public void setSelected(boolean selected) {
345
            _selected = selected;
1✔
346
            if (selected) {
1✔
347
                _container.optionSet(_index);
1✔
348
            }
349
        }
1✔
350

351
        @Override
352
        public boolean isSelected() {
353
            return _selected;
1✔
354
        }
355
    }
356

357
    /**
358
     * The Class Options.
359
     */
360
    public abstract class Options extends ScriptableDelegate implements SelectionOptions {
361

362
        /** The options. */
363
        private Option[] _options;
364

365
        /**
366
         * Instantiates a new options.
367
         *
368
         * @param selectionNode
369
         *            the selection node
370
         */
371
        Options(Node selectionNode) {
1✔
372
            // BR [ 1843978 ] Accessing Options in a form is in lower case
373
            // calls for uppercase "option" here ... pending as of 2007-12-30
374
            NodeList nl = ((Element) selectionNode).getElementsByTagName("OPTION");
1✔
375

376
            _options = new Option[nl.getLength()];
1✔
377
            for (int i = 0; i < _options.length; i++) {
1✔
378
                final String displayedText = getValue(nl.item(i).getFirstChild()).trim();
1✔
379
                _options[i] = new Option(displayedText, getOptionValue(nl.item(i), displayedText),
1✔
380
                        nl.item(i).getAttributes().getNamedItem("selected") != null);
1✔
381
                _options[i].setIndex(this, i);
1✔
382
            }
383
        }
1✔
384

385
        /**
386
         * claim unique values from the given list of values.
387
         *
388
         * @param values
389
         *            - the list of values
390
         *
391
         * @return true, if successful
392
         */
393
        boolean claimUniqueValues(List values) {
394
            return claimUniqueValues(values, _options);
1✔
395
        }
396

397
        /**
398
         * Claim unique values.
399
         *
400
         * @param values
401
         *            the values
402
         * @param options
403
         *            the options
404
         *
405
         * @return true, if successful
406
         */
407
        protected abstract boolean claimUniqueValues(List values, Option[] options);
408

409
        /**
410
         * report if there are no matches be aware of [ 1100437 ] Patch for ClassCastException in FormControl TODO
411
         * implement patch if test get's available.
412
         *
413
         * @param values
414
         *            the values
415
         */
416
        protected final void reportNoMatches(List values) {
417
            if (!_listBox) {
1!
418
                throw new IllegalParameterValueException(getName(), values, getOptionValues());
1✔
419
            }
420
        }
×
421

422
        /**
423
         * Gets the selected values.
424
         *
425
         * @return the selected values
426
         */
427
        String[] getSelectedValues() {
428
            ArrayList list = new ArrayList<>();
1✔
429
            for (Option _option : _options) {
1✔
430
                _option.addValueIfSelected(list);
1✔
431
            }
432
            if (!_listBox && list.isEmpty() && _options.length > 0) {
1✔
433
                list.add(_options[0].getValue());
1✔
434
            }
435
            return (String[]) list.toArray(new String[list.size()]);
1✔
436
        }
437

438
        /**
439
         * Reset.
440
         */
441
        void reset() {
442
            for (Option _option : _options) {
1✔
443
                _option.reset();
1✔
444
            }
445
        }
1✔
446

447
        /**
448
         * Gets the displayed text.
449
         *
450
         * @return the displayed text
451
         */
452
        String[] getDisplayedText() {
453
            String[] displayedText = new String[_options.length];
1✔
454
            for (int i = 0; i < displayedText.length; i++) {
1✔
455
                displayedText[i] = _options[i].getText();
1✔
456
            }
457
            return displayedText;
1✔
458
        }
459

460
        /**
461
         * Gets the values.
462
         *
463
         * @return the values
464
         */
465
        String[] getValues() {
466
            String[] values = new String[_options.length];
1✔
467
            for (int i = 0; i < values.length; i++) {
1✔
468
                values[i] = _options[i].getValue();
1✔
469
            }
470
            return values;
1✔
471
        }
472

473
        /**
474
         * Selects the matching item and deselects the others.
475
         *
476
         * @param index
477
         *            the new selected index
478
         */
479
        void setSelectedIndex(int index) {
480
            for (int i = 0; i < _options.length; i++) {
1✔
481
                _options[i]._selected = i == index;
1✔
482
            }
483
        }
1✔
484

485
        /**
486
         * Returns the index of the first item selected, or -1 if none is selected.
487
         *
488
         * @return the first selected index
489
         */
490
        int getFirstSelectedIndex() {
491
            for (int i = 0; i < _options.length; i++) {
1✔
492
                if (_options[i].isSelected()) {
1✔
493
                    return i;
1✔
494
                }
495
            }
496
            return noOptionSelectedIndex();
1✔
497
        }
498

499
        /**
500
         * No option selected index.
501
         *
502
         * @return the int
503
         */
504
        protected abstract int noOptionSelectedIndex();
505

506
        @Override
507
        public int getLength() {
508
            return _options.length;
1✔
509
        }
510

511
        /**
512
         * Modified by gklopp - 12/19/2005 [ 1396835 ] Javascript : length of a select element cannot be increased Bug
513
         * corrected : The length can be greater than the original length
514
         */
515
        @Override
516
        public void setLength(int length) {
517
            if (length < 0) {
1!
518
                return;
×
519
            }
520
            Option[] newArray = new Option[length];
1✔
521
            System.arraycopy(_options, 0, newArray, 0, Math.min(length, _options.length));
1✔
522
            for (int i = _options.length; i < length; i++) {
1✔
523
                newArray[i] = new Option();
1✔
524
            }
525
            _options = newArray;
1✔
526
        }
1✔
527

528
        @Override
529
        public void put(int i, SelectionOption option) {
530
            if (i < 0) {
1!
531
                return;
×
532
            }
533

534
            if (option == null) {
1✔
535
                if (i >= _options.length) {
1!
536
                    return;
×
537
                }
538
                deleteOptionsEntry(i);
1✔
539
            } else {
540
                if (i >= _options.length) {
1✔
541
                    i = _options.length;
1✔
542
                    expandOptionsArray();
1✔
543
                }
544
                _options[i] = (Option) option;
1✔
545
                _options[i].setIndex(this, i);
1✔
546
                if (option.isSelected()) {
1!
547
                    ensureUniqueOption(_options, i);
×
548
                }
549
            }
550
        }
1✔
551

552
        /**
553
         * Ensure unique option.
554
         *
555
         * @param options
556
         *            the options
557
         * @param i
558
         *            the i
559
         */
560
        protected abstract void ensureUniqueOption(Option[] options, int i);
561

562
        /**
563
         * Delete options entry.
564
         *
565
         * @param i
566
         *            the i
567
         */
568
        private void deleteOptionsEntry(int i) {
569
            Option[] newArray = new Option[_options.length - 1];
1✔
570
            System.arraycopy(_options, 0, newArray, 0, i);
1✔
571
            System.arraycopy(_options, i + 1, newArray, i, newArray.length - i);
1✔
572
            _options = newArray;
1✔
573
        }
1✔
574

575
        /**
576
         * Expand options array.
577
         */
578
        private void expandOptionsArray() {
579
            Option[] newArray = new Option[_options.length + 1];
1✔
580
            System.arraycopy(_options, 0, newArray, 0, _options.length);
1✔
581
            _options = newArray;
1✔
582
        }
1✔
583

584
        /**
585
         * get the Object at the given index check that the index is not out of bounds
586
         *
587
         * @param index
588
         *            - the index of the object to get
589
         *
590
         * @throws RuntimeException
591
         *             if index is out of bounds
592
         */
593
        @Override
594
        public Object get(int index) {
595
            // if the index is out of bounds
596
            if (index < 0 || index >= _options.length) {
1!
597
                // create a user friendly error message
598
                StringBuilder msg = new StringBuilder("invalid index ").append(index).append(" for Options ");
1✔
599
                // by listing all possible options
600
                for (int i = 0; i < _options.length; i++) {
1✔
601
                    msg.append(_options[i]._text);
1✔
602
                    if (i < _options.length - 1) {
1✔
603
                        msg.append(",");
1✔
604
                    }
605
                } // for
606
                  // now throw a RunTimeException that would
607
                  // have happened anyways with a less friendly message
608
                throw new RuntimeException(msg.toString());
1✔
609
            } // if
610
            return _options[index];
1✔
611
        } // get
612

613
        /**
614
         * Invoked when an option is set true. *
615
         *
616
         * @param i
617
         *            the i
618
         */
619
        void optionSet(int i) {
620
            ensureUniqueOption(_options, i);
1✔
621
        }
1✔
622

623
        /**
624
         * Gets the option value.
625
         *
626
         * @param optionNode
627
         *            the option node
628
         * @param displayedText
629
         *            the displayed text
630
         *
631
         * @return the option value
632
         */
633
        private String getOptionValue(Node optionNode, String displayedText) {
634
            NamedNodeMap nnm = optionNode.getAttributes();
1✔
635
            if (nnm.getNamedItem("value") != null) {
1✔
636
                return getValue(nnm.getNamedItem("value"));
1✔
637
            }
638
            return displayedText;
1✔
639
        }
640

641
        /**
642
         * Gets the value.
643
         *
644
         * @param node
645
         *            the node
646
         *
647
         * @return the value
648
         */
649
        private String getValue(Node node) {
650
            return node == null ? "" : emptyIfNull(node.getNodeValue());
1!
651
        }
652
    }
653

654
    /**
655
     * The Class SingleSelectOptions.
656
     */
657
    class SingleSelectOptions extends Options {
658

659
        /**
660
         * Instantiates a new single select options.
661
         *
662
         * @param selectionNode
663
         *            the selection node
664
         */
665
        public SingleSelectOptions(Node selectionNode) {
1✔
666
            super(selectionNode);
1✔
667
        }
1✔
668

669
        @Override
670
        protected void ensureUniqueOption(Option[] options, int i) {
671
            for (int j = 0; j < options.length; j++) {
1✔
672
                options[j]._selected = i == j;
1✔
673
            }
674
        }
1✔
675

676
        @Override
677
        protected int noOptionSelectedIndex() {
678
            return 0;
1✔
679
        }
680

681
        /**
682
         * claim the values be aware of [ 1100437 ] Patch for ClassCastException in FormControl TODO implement patch if
683
         * test get's available - the (String) cast might fail
684
         */
685
        @Override
686
        protected boolean claimUniqueValues(List values, Option[] options) {
687
            boolean changed = false;
1✔
688
            for (int i = 0; i < values.size(); i++) {
1!
689
                String value = (String) values.get(i);
1✔
690
                for (int j = 0; j < options.length; j++) {
1✔
691
                    boolean selected = value.equals(options[j].getValue());
1✔
692
                    if (selected != options[j].isSelected()) {
1✔
693
                        changed = true;
1✔
694
                    }
695
                    options[j].setSelected(selected);
1✔
696
                    if (selected) {
1✔
697
                        values.remove(value);
1✔
698
                        for (++j; j < options.length; j++) {
1✔
699
                            options[j].setSelected(false);
1✔
700
                        }
701
                        return changed;
1✔
702
                    }
703
                }
704
            }
705
            reportNoMatches(values);
×
706
            return changed;
×
707
        }
708
    }
709

710
    /**
711
     * The Class MultiSelectOptions.
712
     */
713
    class MultiSelectOptions extends Options {
714

715
        /**
716
         * Instantiates a new multi select options.
717
         *
718
         * @param selectionNode
719
         *            the selection node
720
         */
721
        public MultiSelectOptions(Node selectionNode) {
1✔
722
            super(selectionNode);
1✔
723
        }
1✔
724

725
        @Override
726
        protected void ensureUniqueOption(Option[] options, int i) {
727
        }
1✔
728

729
        @Override
730
        protected int noOptionSelectedIndex() {
731
            return -1;
1✔
732
        }
733

734
        @Override
735
        protected boolean claimUniqueValues(List values, Option[] options) {
736
            boolean changed = false;
1✔
737
            for (Option option : options) {
1✔
738
                final boolean newValue = values.contains(option.getValue());
1✔
739
                if (newValue != option.isSelected()) {
1✔
740
                    changed = true;
1✔
741
                }
742
                option.setSelected(newValue);
1✔
743
                if (newValue) {
1✔
744
                    values.remove(option.getValue());
1✔
745
                }
746
            }
747
            return changed;
1✔
748
        }
749
    }
750

751
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc