Skip to content

Commit 3acb5bd

Browse files
committed
Support annotation.
1 parent f3e486d commit 3acb5bd

File tree

12 files changed

+261
-30
lines changed

12 files changed

+261
-30
lines changed

src/main/java/me/zhhe/cli/menu/BeanMenuBuilder.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@
2727
*/
2828
public class BeanMenuBuilder extends MenuBuilder {
2929

30-
public final BeanMenuBuilder bean(Object bean) {
30+
public final BeanMenuBuilder bean(final Object bean) {
3131
final Collection<? extends BeanItem> beanItems = BeanParser.getInstance().parse(bean);
3232
for (final BeanItem beanItem : beanItems) {
3333
final MenuItemBuilder itemBuilder = new MenuItemBuilder()
34-
.longArgName(beanItem.getName())
34+
.argName(beanItem.getArgName())
35+
.longArgName(beanItem.getLongArgName())
36+
.description(beanItem.getDescription())
3537
.value(beanItem.getValue())
3638
.inputChecker(beanItem.getExecutor());
3739
item(itemBuilder.build());

src/main/java/me/zhhe/cli/menu/CliWriter.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ START, no, getSafeValue(item.argName), getSafeValue(item.longArgName),
6464

6565
final String desc = getSafeValue(item.description);
6666
if (StringUtils.isNotBlank(desc))
67-
System.out.format(": %s%n", desc);
67+
System.out.format(": %s", desc);
68+
69+
System.out.println();
6870
}
6971

7072
private String getSafeValue(final String value) {

src/main/java/me/zhhe/cli/menu/MenuBuilder.java

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package me.zhhe.cli.menu;
1616

17+
import com.google.common.base.Joiner;
1718
import com.google.common.base.MoreObjects;
1819

1920
import org.apache.commons.cli.CommandLine;
@@ -138,6 +139,8 @@ private void injectArgumentToItems() {
138139
if (ArrayUtils.isEmpty(args))
139140
return;
140141

142+
logger.info(String.format("args: %s", Joiner.on(' ').join(args)));
143+
141144
final Options options = new Options();
142145
final Map<Option, MenuItem> itemsByOption = new HashMap<>();
143146
for (final MenuItem item : items) {

src/main/java/me/zhhe/cli/menu/MenuItem.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@
1919
import java.util.function.Consumer;
2020
import java.util.function.Supplier;
2121

22+
import javax.annotation.Nonnull;
23+
2224
/**
2325
* @author zhhe.me@gmail.com.
2426
* @since 7/8/2018
2527
*/
2628
public class MenuItem {
2729

28-
String argName;
29-
String longArgName;
30-
String description;
30+
@Nonnull String argName;
31+
@Nonnull String longArgName;
32+
@Nonnull String description;
3133
Supplier<?> value;
3234
Consumer<String> inputChecker;
3335

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (C) 2018 The java-cli-menu Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package me.zhhe.cli.menu;
16+
17+
import java.lang.annotation.Retention;
18+
import java.lang.annotation.Target;
19+
20+
import static java.lang.annotation.ElementType.FIELD;
21+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
22+
23+
/**
24+
* @author zhhe.me@gmail.com.
25+
* @since 12/8/2018
26+
*/
27+
@Retention(RUNTIME)
28+
@Target({FIELD})
29+
public @interface Option {
30+
/** argument name. */
31+
String name() default "";
32+
33+
/** long argument name.
34+
* when neither {@link #name()} nor long arg name is specified,
35+
* field name will be used as long arg name. */
36+
String longArgName() default "";
37+
38+
/** description. */
39+
String description() default "";
40+
}
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (C) 2018 The java-cli-menu Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package me.zhhe.cli.menu;
16+
17+
import java.lang.annotation.ElementType;
18+
import java.lang.annotation.Retention;
19+
import java.lang.annotation.RetentionPolicy;
20+
import java.lang.annotation.Target;
21+
22+
/**
23+
* @author zhhe.me@gmail.com.
24+
* @since 12/8/2018
25+
*/
26+
@Retention(RetentionPolicy.RUNTIME)
27+
@Target(ElementType.METHOD)
28+
public @interface Setter {
29+
/** linked field. */
30+
String value();
31+
}

src/main/java/me/zhhe/cli/menu/bean/BeanItem.java

+22-5
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,43 @@
1414

1515
package me.zhhe.cli.menu.bean;
1616

17+
import com.google.common.base.MoreObjects;
18+
1719
import java.util.function.Consumer;
1820
import java.util.function.Supplier;
1921

22+
import javax.annotation.Nonnull;
23+
2024
/**
2125
* @author zhhe.me@gmail.com.
2226
* @since 10/8/2018
2327
*/
2428
public class BeanItem {
25-
private final String name;
29+
@Nonnull private final String argName;
30+
@Nonnull private final String longArgName;
31+
@Nonnull private final String description;
2632
private final Supplier<String> value;
2733
private final Consumer<String> executor;
2834

29-
BeanItem(String name, Supplier<String> value, Consumer<String> executor) {
30-
this.name = name;
35+
BeanItem(String argName, String longArgName, String description,
36+
Supplier<String> value, Consumer<String> executor) {
37+
this.argName = MoreObjects.firstNonNull(argName, "").trim();
38+
this.longArgName = MoreObjects.firstNonNull(longArgName, "").trim();
39+
this.description = MoreObjects.firstNonNull(description, "").trim();
3140
this.value = value;
3241
this.executor = executor;
3342
}
3443

35-
public String getName() {
36-
return name;
44+
public String getLongArgName() {
45+
return longArgName;
46+
}
47+
48+
public String getArgName() {
49+
return argName;
50+
}
51+
52+
public String getDescription() {
53+
return description;
3754
}
3855

3956
public Supplier<String> getValue() {

src/main/java/me/zhhe/cli/menu/bean/BeanParser.java

+43-12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package me.zhhe.cli.menu.bean;
1616

17+
import org.apache.commons.lang3.StringUtils;
18+
1719
import java.lang.reflect.Field;
1820
import java.lang.reflect.InvocationTargetException;
1921
import java.lang.reflect.Method;
@@ -25,7 +27,11 @@
2527
import java.util.List;
2628
import java.util.Locale;
2729
import java.util.Map;
30+
import java.util.function.Function;
31+
import java.util.function.Predicate;
2832

33+
import me.zhhe.cli.menu.Setter;
34+
import me.zhhe.cli.menu.Option;
2935
import me.zhhe.cli.menu.util.ExceptionUtil;
3036

3137
/**
@@ -47,15 +53,40 @@ public static BeanParser getInstance() {
4753
}
4854

4955
public Collection<? extends BeanItem> parse(final Object bean) {
50-
final Field[] fields = bean.getClass().getDeclaredFields();
51-
final Map<String, Method> validSetters = extractValidSetters(bean.getClass());
56+
final Class<?> clazz = bean.getClass();
57+
final Field[] fields = clazz.getDeclaredFields();
58+
final Method[] methods = clazz.getMethods();
59+
60+
final Map<String, Method> settersWithAnnotation = extractMethods(methods,
61+
m -> m.isAnnotationPresent(Setter.class),
62+
m -> m.getAnnotation(Setter.class).value().trim());
63+
final Map<String, Method> settersViaNaming = extractMethods(methods,
64+
m -> m.getName().startsWith("set"), m -> m.getName());
65+
5266
final List<BeanItem> items = new ArrayList<>();
5367
Arrays.stream(fields).forEach(field -> {
54-
final String name = field.getName();
55-
final Method m = validSetters.get(formatSetterName(name));
56-
if (m!=null) {
68+
final String fieldName = field.getName();
69+
final Option option = field.getAnnotation(Option.class);
70+
final String argName = option!=null ? option.name() : "";
71+
72+
// in case of:
73+
// a) option is null
74+
// b) or both argName & longArgName is empty
75+
// fieldName will be used as longArgName.
76+
final String longArgName =
77+
(option == null ||
78+
(StringUtils.isBlank(option.longArgName())
79+
&& StringUtils.isBlank(option.name()))
80+
) ? fieldName : option.longArgName();
81+
82+
final String description = option!=null ? option.description() : "";
83+
// annotation has high priority than naming convention.
84+
final Method m = settersWithAnnotation.containsKey(fieldName) ?
85+
settersWithAnnotation.get(fieldName) : settersViaNaming.get(formatSetterName(fieldName));
86+
87+
if (m != null) {
5788
field.setAccessible(true);
58-
items.add(new BeanItem(field.getName(),
89+
items.add(new BeanItem(argName, longArgName, description,
5990
() -> {
6091
try {
6192
final Object o = field.get(bean);
@@ -81,11 +112,11 @@ public Collection<? extends BeanItem> parse(final Object bean) {
81112
return items;
82113
}
83114

84-
private Map<String, Method> extractValidSetters(final Class<?> clazz) {
85-
final Map<String, Method> validSetters = new HashMap<>();
86-
final Method[] methods = clazz.getMethods();
115+
private Map<String, Method> extractMethods(final Method[] methods,
116+
Predicate<Method> predict, Function<Method, String> key) {
117+
final Map<String, Method> ret = new HashMap<>();
87118
for (final Method m : methods) {
88-
if (!m.getName().startsWith("set"))
119+
if (!predict.test(m))
89120
continue;
90121

91122
if (void.class != m.getReturnType())
@@ -99,10 +130,10 @@ private Map<String, Method> extractValidSetters(final Class<?> clazz) {
99130
if (String.class != params[0].getType())
100131
continue;
101132

102-
validSetters.put(m.getName(), m);
133+
ret.put(key.apply(m), m);
103134
}
104135

105-
return validSetters;
136+
return ret;
106137
}
107138

108139
private String formatSetterName(final String fieldName) {

src/main/java/me/zhhe/cli/menu/util/Logger.java

+8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ public static final Logger getInstance() {
2929
return DEFAULT;
3030
}
3131

32+
public void debug(final String msg) {
33+
System.out.format("DEBUG %s%n", msg);
34+
}
35+
36+
public void info(final String msg) {
37+
System.out.format("INFO %s%n", msg);
38+
}
39+
3240
public void warning(final String msg) {
3341
System.out.format("WARN %s%n", msg);
3442
}

src/test/java/me/zhhe/cli/menu/bean/BeanParserTest.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import java.util.Collection;
2121

22+
import me.zhhe.cli.menu.bean.sub.MyBean;
23+
2224
import static org.assertj.core.api.Assertions.*;
2325

2426
/**
@@ -38,18 +40,23 @@ public void setUp() throws Exception {
3840
public void parse() {
3941
final MyBean bean = new MyBean();
4042
final Collection<? extends BeanItem> items = test.parse(bean);
41-
assertThat(items).extracting("name").containsOnly("firstName", "title");
43+
assertThat(items).hasSize(2)
44+
.extracting("argName", "longArgName", "description")
45+
.containsOnly(
46+
tuple("", "firstName", "First name"),
47+
tuple("t", "", "")
48+
);
4249

4350
final String firstName = "it's first name";
44-
final BeanItem biFirstName = items.stream().filter(item -> item.getName().equals("firstName"))
51+
final BeanItem biFirstName = items.stream().filter(item -> item.getLongArgName().equals("firstName"))
4552
.findFirst().get();
4653
biFirstName.getExecutor().accept(firstName);
47-
assertThat(bean.firstName).isEqualTo(firstName).isEqualTo(biFirstName.getValue().get());
54+
assertThat(bean.getFirstName()).isEqualTo("2-" + firstName).isEqualTo(biFirstName.getValue().get());
4855

4956
final String title = "it's my title";
50-
final BeanItem biTitle = items.stream().filter(item -> item.getName().equals("title"))
57+
final BeanItem biTitle = items.stream().filter(item -> item.getArgName().equals("t"))
5158
.findFirst().get();
5259
biTitle.getExecutor().accept(title);
53-
assertThat(bean.title).isEqualTo(title).isEqualTo(biTitle.getValue().get());
60+
assertThat(bean.getTitle()).isEqualTo(title).isEqualTo(biTitle.getValue().get());
5461
}
5562
}

0 commit comments

Comments
 (0)