Skip to content

Add the possibility of default selection to MultiItemSelector #468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.springframework.shell.component.support.Itemable;
import org.springframework.shell.component.support.Matchable;
import org.springframework.shell.component.support.Nameable;
import org.springframework.shell.component.support.Selectable;
import org.springframework.shell.component.support.AbstractSelectorComponent.SelectorComponentContext;
import org.springframework.shell.component.MultiItemSelector.MultiItemSelectorContext;

Expand All @@ -40,7 +41,7 @@
*
* @author Janne Valkealahti
*/
public class MultiItemSelector<T, I extends Nameable & Matchable & Enableable & Itemable<T>>
public class MultiItemSelector<T, I extends Nameable & Matchable & Enableable & Selectable & Itemable<T>>
extends AbstractSelectorComponent<T, MultiItemSelectorContext<T, I>, I> {

private MultiItemSelectorContext<T, I> currentContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.springframework.shell.component.support.Itemable;
import org.springframework.shell.component.support.Matchable;
import org.springframework.shell.component.support.Nameable;
import org.springframework.shell.component.support.Selectable;
import org.springframework.shell.component.support.AbstractSelectorComponent.SelectorComponentContext;
import org.springframework.shell.component.SingleItemSelector.SingleItemSelectorContext;

Expand All @@ -40,7 +41,7 @@
*
* @author Janne Valkealahti
*/
public class SingleItemSelector<T, I extends Nameable & Matchable & Enableable & Itemable<T>>
public class SingleItemSelector<T, I extends Nameable & Matchable & Enableable & Selectable & Itemable<T>>
extends AbstractSelectorComponent<T, SingleItemSelectorContext<T, I>, I> {

private SingleItemSelectorContext<T, I> currentContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ private Stream<OrderedInputOperation> singleItemSelectorsStream() {
private Stream<OrderedInputOperation> multiItemSelectorsStream() {
return multiInputs.stream().map(input -> {
List<SelectorItem<String>> selectorItems = input.getSelectItems().stream()
.map(si -> SelectorItem.of(si.name(), si.item(), si.enabled()))
.map(si -> SelectorItem.of(si.name(), si.item(), si.enabled(), si.selected()))
.collect(Collectors.toList());
MultiItemSelector<String, SelectorItem<String>> selector = new MultiItemSelector<>(terminal,
selectorItems, input.getName(), input.getComparator());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ public class DefaultSelectItem implements SelectItem {
private String name;
private String item;
private boolean enabled;
private boolean selected;

public DefaultSelectItem(String name, String item, boolean enabled) {
public DefaultSelectItem(String name, String item, boolean enabled, boolean selected) {
this.name = name;
this.item = item;
this.enabled = enabled;
this.selected = selected;
}

@Override
Expand All @@ -46,4 +48,9 @@ public String item() {
public boolean enabled() {
return enabled;
}

@Override
public boolean selected() {
return selected;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,18 @@ public interface SelectItem {
*/
boolean enabled();

/**
* Return if the item is selected.
*
* @return if item is selected
*/
boolean selected();

public static SelectItem of(String name, String item) {
return of(name, item, true);
return of(name, item, true, false);
}

public static SelectItem of(String name, String item, boolean enabled) {
return new DefaultSelectItem(name, item, enabled);
public static SelectItem of(String name, String item, boolean enabled, boolean selected) {
return new DefaultSelectItem(name, item, enabled, selected);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
*
* @author Janne Valkealahti
*/
public abstract class AbstractSelectorComponent<T, C extends SelectorComponentContext<T, I, C>, I extends Nameable & Matchable & Enableable & Itemable<T>>
public abstract class AbstractSelectorComponent<T, C extends SelectorComponentContext<T, I, C>, I extends Nameable & Matchable & Enableable & Selectable & Itemable<T>>
extends AbstractComponent<C> {

private final static Logger log = LoggerFactory.getLogger(AbstractSelectorComponent.class);
Expand Down Expand Up @@ -257,7 +257,7 @@ private void initialExpose(C context) {
AtomicInteger index = new AtomicInteger(0);
itemStates = context.getItems().stream()
.sorted(comparator)
.map(item -> ItemState.of(item, item.getName(), index.getAndIncrement(), item.isEnabled()))
.map(item -> ItemState.of(item, item.getName(), index.getAndIncrement(), item.isEnabled(), item.isSelected()))
.collect(Collectors.toList());
}
for (int i = 0; i < itemStates.size(); i++) {
Expand All @@ -280,7 +280,7 @@ private ItemStateViewProjection buildItemStateView(int skip, SelectorComponentCo
AtomicInteger index = new AtomicInteger(0);
itemStates = context.getItems().stream()
.sorted(comparator)
.map(item -> ItemState.of(item, item.getName(), index.getAndIncrement(), item.isEnabled()))
.map(item -> ItemState.of(item, item.getName(), index.getAndIncrement(), item.isEnabled(), item.isSelected()))
.collect(Collectors.toList());
context.setItemStates(itemStates);
}
Expand Down Expand Up @@ -547,11 +547,12 @@ public static class ItemState<I extends Matchable> implements Matchable {
boolean enabled;
int index;

ItemState(I item, String name, int index, boolean enabled) {
ItemState(I item, String name, int index, boolean enabled, boolean selected) {
this.item = item;
this.name = name;
this.index = index;
this.enabled = enabled;
this.selected = selected;
}

public boolean matches(String match) {
Expand All @@ -574,8 +575,8 @@ public boolean isEnabled() {
return enabled;
}

static <I extends Matchable> ItemState<I> of(I item, String name, int index, boolean enabled) {
return new ItemState<I>(item, name, index, enabled);
static <I extends Matchable> ItemState<I> of(I item, String name, int index, boolean enabled, boolean selected) {
return new ItemState<I>(item, name, index, enabled, selected);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.component.support;

public interface Selectable {

boolean isSelected();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,31 @@

import org.springframework.util.StringUtils;

public interface SelectorItem<T> extends Nameable, Matchable, Enableable, Itemable<T> {
public interface SelectorItem<T> extends Nameable, Matchable, Enableable, Selectable, Itemable<T> {

static <T> SelectorItem<T> of(String name, T item) {
return of(name, item, true);
return of(name, item, true, false);
}

static <T> SelectorItem<T> of(String name, T item, boolean enabled) {
return new SelectorItemWrapper<T>(name, item, enabled);
static <T> SelectorItem<T> of(String name, T item, boolean enabled, boolean selected) {
return new SelectorItemWrapper<T>(name, item, enabled, selected);
}

public static class SelectorItemWrapper<T> implements SelectorItem<T> {
private String name;
private boolean enabled;
private T item;
private boolean selected;

public SelectorItemWrapper(String name, T item) {
this(name, item, true);
this(name, item, true, false);
}

public SelectorItemWrapper(String name, T item, boolean enabled) {
public SelectorItemWrapper(String name, T item, boolean enabled, boolean selected) {
this.name = name;
this.item = item;
this.enabled = enabled;
this.selected = selected;
}

@Override
Expand All @@ -51,7 +53,7 @@ public String getName() {
public boolean matches(String match) {
if (!StringUtils.hasText(match)) {
return true;
};
}
return name.toLowerCase().contains(match.toLowerCase());
}

Expand All @@ -60,8 +62,14 @@ public boolean isEnabled() {
return enabled;
}

@Override
public T getItem() {
return item;
}

@Override
public boolean isSelected() {
return selected;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ select_item(item) ::= <%
<({<figures.checkboxOff> }); format=selected_style(item.selected)> <item.name>
<endif>
<else>
<({<figures.checkboxOff> }); format="style-item-disabled"> <item.name; format="style-item-disabled">
<if(item.selected)>
<({<figures.checkboxOn> }); format="style-item-disabled"> <item.name; format="style-item-disabled">
<else>
<({<figures.checkboxOff> }); format="style-item-disabled"> <item.name; format="style-item-disabled">
<endif>
<endif>
%>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ public class MultiItemSelectorTests extends AbstractShellTests {
private static SimplePojo SIMPLE_POJO_5 = SimplePojo.of("data5");
private static SimplePojo SIMPLE_POJO_6 = SimplePojo.of("data6");
private static SimplePojo SIMPLE_POJO_7 = SimplePojo.of("data7");
private static SimplePojo SIMPLE_POJO_8 = SimplePojo.of("data8");
private static SelectorItem<SimplePojo> SELECTOR_ITEM_1 = SelectorItem.of("simplePojo1", SIMPLE_POJO_1);
private static SelectorItem<SimplePojo> SELECTOR_ITEM_2 = SelectorItem.of("simplePojo2", SIMPLE_POJO_2);
private static SelectorItem<SimplePojo> SELECTOR_ITEM_3 = SelectorItem.of("simplePojo3", SIMPLE_POJO_3);
private static SelectorItem<SimplePojo> SELECTOR_ITEM_4 = SelectorItem.of("simplePojo4", SIMPLE_POJO_4);
private static SelectorItem<SimplePojo> SELECTOR_ITEM_5 = SelectorItem.of("simplePojo5", SIMPLE_POJO_5);
private static SelectorItem<SimplePojo> SELECTOR_ITEM_6 = SelectorItem.of("simplePojo6", SIMPLE_POJO_6);
private static SelectorItem<SimplePojo> SELECTOR_ITEM_7 = SelectorItem.of("simplePojo7", SIMPLE_POJO_7, false);
private static SelectorItem<SimplePojo> SELECTOR_ITEM_7 = SelectorItem.of("simplePojo7", SIMPLE_POJO_7, false, false);
private static SelectorItem<SimplePojo> SELECTOR_ITEM_8 = SelectorItem.of("simplePojo8", SIMPLE_POJO_8, false, true);

private ExecutorService service;
private CountDownLatch latch;
Expand Down Expand Up @@ -204,6 +206,22 @@ public void testSelectLastBackwards() throws InterruptedException {
Stream<String> datas = selected.stream().map(SelectorItem::getItem).map(SimplePojo::getData);
assertThat(datas).containsExactlyInAnyOrder("data4");
}

@Test
public void testDefaultSelection() throws InterruptedException {
scheduleSelect(Arrays.asList(SELECTOR_ITEM_1, SELECTOR_ITEM_2, SELECTOR_ITEM_7, SELECTOR_ITEM_8));

TestBuffer testBuffer = new TestBuffer().cr();
write(testBuffer.getBytes());

awaitLatch();

List<SelectorItem<SimplePojo>> selected = result.get();
assertThat(selected).hasSize(1);
Stream<String> datas = selected.stream().map(SelectorItem::getItem).map(SimplePojo::getData);
assertThat(datas).containsExactlyInAnyOrder("data8");
assertThat(consoleOut()).contains("testSimple data8");
}

private void scheduleSelect() {
scheduleSelect(Arrays.asList(SELECTOR_ITEM_1, SELECTOR_ITEM_2, SELECTOR_ITEM_3,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public class ComponentCommands extends AbstractShellComponent {
public String multiSelector() {
List<SelectorItem<String>> items = new ArrayList<>();
items.add(SelectorItem.of("key1", "value1"));
items.add(SelectorItem.of("key2", "value2", false));
items.add(SelectorItem.of("key2", "value2", false, true));
items.add(SelectorItem.of("key3", "value3"));
MultiItemSelector<String, SelectorItem<String>> component = new MultiItemSelector<>(getTerminal(),
items, "testSimple", null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public String singleSelector() {
public String multiSelector() {
List<SelectorItem<String>> items = new ArrayList<>();
items.add(SelectorItem.of("key1", "value1"));
items.add(SelectorItem.of("key2", "value2", false));
items.add(SelectorItem.of("key2", "value2", false, true));
items.add(SelectorItem.of("key3", "value3"));
MultiItemSelector<String, SelectorItem<String>> component = new MultiItemSelector<>(getTerminal(),
items, "testSimple", null);
Expand Down