View Javadoc
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   * Internal builder to create instance of {@link AnnotationParsingContext}
27   *
28   * @author <a href="mailto:jiaqi@cyclopsgroup.org">Jiaqi Guo</a>
29   * @param <T> Type of bean to parse
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     * Get annotation from either writer or reader of a Java property
50     *
51     * @param <A> Type of annotation
52     * @param descriptor Field descriptor from which annotation is searched
53     * @param type Type of annotation
54     * @return Annotation or null if it's not found
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 }