1 package org.cyclopsgroup.jcli.impl;
2
3 import java.beans.BeanInfo;
4 import java.beans.IntrospectionException;
5 import java.beans.Introspector;
6 import java.beans.PropertyDescriptor;
7 import java.lang.annotation.Annotation;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.HashMap;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.stream.Collectors;
14 import javax.annotation.Nullable;
15 import org.cyclopsgroup.caff.conversion.AnnotatedConverter;
16 import org.cyclopsgroup.caff.conversion.Converter;
17 import org.cyclopsgroup.caff.ref.ValueReference;
18 import org.cyclopsgroup.jcli.annotation.Argument;
19 import org.cyclopsgroup.jcli.annotation.Cli;
20 import org.cyclopsgroup.jcli.annotation.MultiValue;
21 import org.cyclopsgroup.jcli.annotation.Option;
22 import com.google.common.collect.FluentIterable;
23 import com.google.common.collect.ImmutableList;
24
25
26
27
28
29
30
31 class ParsingContextBuilder<T> {
32 @SuppressWarnings("unchecked")
33 private static <B, P> Reference<B> createReference(Class<B> beanType, ValueReference<B> reference,
34 String longName) {
35 Class<P> valueType = (Class<P>) reference.getType();
36 MultiValue multiValue = reference.getAnnotation(MultiValue.class);
37 if (multiValue != null) {
38 valueType = (Class<P>) multiValue.valueType();
39 }
40 Converter<P> converter = new AnnotatedConverter<P>(valueType, reference.getAnontatedElements());
41 if (multiValue != null) {
42 return new MultiValueReference<B>(beanType, converter, reference, longName,
43 multiValue.listType());
44 }
45 return new SingleValueReference<B>(beanType, converter, reference, longName);
46 }
47
48
49
50
51
52
53
54
55
56 @Nullable
57 private static <A extends Annotation> A getAnnotation(PropertyDescriptor descriptor,
58 Class<A> type) {
59 A a = null;
60 if (descriptor.getWriteMethod() != null) {
61 a = descriptor.getWriteMethod().getAnnotation(type);
62 }
63 if (a == null && descriptor.getReadMethod() != null) {
64 a = descriptor.getReadMethod().getAnnotation(type);
65 }
66 return a;
67 }
68
69 private static <T> List<ValueReference<T>> referenceOfDescriptors(Class<T> beanType) {
70 BeanInfo beanInfo;
71 try {
72 beanInfo = Introspector.getBeanInfo(beanType);
73 } catch (IntrospectionException e) {
74 throw new RuntimeException("Bean " + beanType + " is not correctly defined", e);
75 }
76 return Arrays.asList(beanInfo.getPropertyDescriptors()).stream()
77 .map(d -> ValueReference.<T>instanceOf(d)).collect(Collectors.toList());
78 }
79
80 private static <T> ImmutableList<ValueReference<T>> referenceOfFields(Class<T> beanType) {
81 Map<String, ValueReference<T>> refMap = new HashMap<>();
82 FluentIterable.from(beanType.getFields()).append(beanType.getDeclaredFields()).toList().stream()
83 .map(f -> ValueReference.<T>instanceOf(f)).forEach(f -> refMap.put(f.getName(), f));
84 return ImmutableList.copyOf(refMap.values());
85 }
86
87 private final Class<T> beanType;
88
89 @SuppressWarnings("unchecked")
90 ParsingContextBuilder(Class<? extends T> beanType) {
91 this.beanType = (Class<T>) beanType;
92 }
93
94 AnnotationParsingContext<T> build() {
95 List<AnnotationOption> options = new ArrayList<AnnotationOption>();
96 Map<String, Reference<T>> references = new HashMap<String, Reference<T>>();
97 Argument argument = null;
98 List<ValueReference<T>> writableReferences =
99 FluentIterable.from(referenceOfDescriptors(beanType)).append(referenceOfFields(beanType))
100 .filter(r -> r.isWritable()).toList();
101
102 for (ValueReference<T> ref : writableReferences) {
103 Option option = ref.getAnnotation(Option.class);
104 if (option != null) {
105 boolean flag = (ref.getType() == Boolean.TYPE || ref.getType() == Boolean.class);
106 boolean multiValue = ref.getAnnotation(MultiValue.class) != null;
107 options.add(new AnnotationOption(option, flag, multiValue));
108 references.put(option.name(), createReference(beanType, ref, option.longName()));
109 continue;
110 }
111 Argument arg = ref.getAnnotation(Argument.class);
112 if (arg != null) {
113 argument = arg;
114 references.put(DefaultArgumentProcessor.ARGUMENT_REFERNCE_NAME,
115 createReference(beanType, ref, DefaultArgumentProcessor.ARGUMENT_REFERNCE_NAME));
116 continue;
117 }
118 }
119
120 return new AnnotationParsingContext<T>(references, options,
121 new AnnotationCli(beanType.getAnnotation(Cli.class)),
122 argument == null ? null : new AnnotationArgument(argument));
123 }
124 }