View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.rat.config.parameters;
21  
22  import java.lang.reflect.Field;
23  import java.lang.reflect.Method;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.List;
28  
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.commons.text.WordUtils;
31  import org.apache.rat.ConfigurationException;
32  import org.apache.rat.ImplementationException;
33  import org.apache.rat.analysis.IHeaderMatcher;
34  import org.apache.rat.license.ILicense;
35  
36  import static java.lang.String.format;
37  
38  /**
39   * Builds Description objects for the various component instances.
40   */
41  public final class DescriptionBuilder {
42      private DescriptionBuilder() {
43          // do not instantiate
44      }
45  
46      /**
47       * Create the description for the object.
48       * The object must have a ConfigComponent annotation or {@code null} will be returned.
49       * @param object the object to process.
50       * @return the Description of the object.
51       */
52      public static Description build(final Object object) {
53          if (object instanceof ILicense) {
54              ILicense license = (ILicense) object;
55              Class<?> clazz = object.getClass();
56              ConfigComponent configComponent = clazz.getAnnotation(ConfigComponent.class);
57              if (configComponent == null || configComponent.type() != ComponentType.LICENSE) {
58                  throw new ConfigurationException(
59                          format("Licenses must have License type specified in ConfigComponent annotation. Annotation missing or incorrect in %s", clazz));
60              }
61              List<Description> children = getConfigComponents(object.getClass());
62              return new Description(ComponentType.LICENSE, license.getId(), license.getName(), false, null, children, false);
63          }
64          return buildMap(object.getClass());
65      }
66  
67      private static String fixupMethodName(final Method method) {
68          String name = method.getName();
69          if (name.startsWith("get") || name.startsWith("set") || name.startsWith("add")) {
70              if (name.length() > 3) {
71                  return WordUtils.uncapitalize(name.substring(3));
72              }
73          }
74          throw new ImplementationException(format("'%s' is not a recognized method name", name));
75      }
76      /**
77       * Build the list of descriptions for children of the class.
78       * @param clazz source class.
79       * @return the Descriptions of the child elements.
80       */
81      static List<Description> getConfigComponents(final Class<?> clazz) {
82          if (clazz == null || clazz == String.class || clazz == Object.class) {
83              return Collections.emptyList();
84          }
85          List<Description> result = new ArrayList<>();
86          for (Field field : clazz.getDeclaredFields()) {
87              ConfigComponent configComponent = field.getAnnotation(ConfigComponent.class);
88              if (configComponent != null) {
89                  String name = StringUtils.isBlank(configComponent.name()) ? field.getName() : configComponent.name();
90                  Class<?> childClazz = configComponent.parameterType() == void.class ? field.getType()
91                          : configComponent.parameterType();
92                  boolean isCollection = Iterable.class.isAssignableFrom(field.getType());
93  
94                  Description desc = new Description(configComponent.type(), name, configComponent.desc(), isCollection,
95                          childClazz, getConfigComponents(childClazz), configComponent.required());
96                  result.add(desc);
97              }
98          }
99          for (Method method : clazz.getDeclaredMethods()) {
100             ConfigComponent configComponent = method.getAnnotation(ConfigComponent.class);
101             if (configComponent != null) {
102                 String name = StringUtils.isBlank(configComponent.name()) ? fixupMethodName(method) : configComponent.name();
103                 Class<?> childClazz = configComponent.parameterType() == void.class ? method.getReturnType()
104                         : configComponent.parameterType();
105                 boolean isCollection = Iterable.class.isAssignableFrom(method.getReturnType());
106 
107                 Description desc = new Description(configComponent.type(), name, configComponent.desc(), isCollection,
108                         childClazz, getConfigComponents(childClazz), configComponent.required());
109                 result.add(desc);
110             }
111         }
112         result.addAll(getConfigComponents(clazz.getSuperclass()));
113         Arrays.stream(clazz.getInterfaces()).forEach(c -> result.addAll(getConfigComponents(c)));
114         return result;
115     }
116 
117     private static ConfigComponent findConfigComponent(final Class<?> clazz) {
118         if (clazz == null || clazz == String.class || clazz == Object.class) {
119             return null;
120         }
121         ConfigComponent configComponent = clazz.getAnnotation(ConfigComponent.class);
122         return configComponent == null ? findConfigComponent(clazz.getSuperclass()) : configComponent;
123     }
124 
125     public static Class<?> getBuiltClass(final Class<? extends IHeaderMatcher.Builder> clazz) {
126         try {
127             MatcherBuilder matcherBuilder = clazz.getAnnotation(MatcherBuilder.class);
128             if (matcherBuilder == null) {
129                 return clazz.getMethod("build").getReturnType();
130             } else {
131                 return matcherBuilder.value();
132             }
133         } catch (NoSuchMethodException | SecurityException e) {
134             throw new IllegalStateException("The 'build' method of the Builder interface must always be public");
135         }
136     }
137 
138     /**
139      * Create a description for a class.
140      * @param clazz the class to build the description for.
141      * @return the Description of the class or null if no ConfigComponent annotation was found on the class.
142      */
143     public static Description buildMap(final Class<?> clazz) {
144         if (clazz == IHeaderMatcher.class) {
145             throw new ImplementationException("'clazz' parameter must not be IHeaderMatcher.class but may be a child of it");
146         }
147         Class<?> workingClass = IHeaderMatcher.Builder.class.isAssignableFrom(clazz)
148                     ? getBuiltClass((Class<IHeaderMatcher.Builder>) clazz)
149                     : clazz;
150 
151         ConfigComponent configComponent = findConfigComponent(workingClass);
152         if (configComponent == null) {
153             return null;
154         }
155         List<Description> children = getConfigComponents(workingClass);
156 
157         return new Description(configComponent, false, null, children);
158     }
159 }