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  package org.apache.rat.config.parameters;
20  
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.util.Collection;
24  import java.util.Map;
25  import java.util.TreeMap;
26  import java.util.function.Predicate;
27  import java.util.stream.Collectors;
28  
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.rat.BuilderParams;
31  import org.apache.rat.ConfigurationException;
32  import org.apache.rat.analysis.IHeaderMatcher;
33  import org.apache.rat.utils.DefaultLog;
34  
35  import static java.lang.String.format;
36  
37  /**
38   * A description of a component.
39   */
40  public class Description {
41      /** The type of component this describes */
42      private final ComponentType type;
43      /**
44       * The common name for the component. Set by ConfigComponent.name() or
45       * class/field name.
46       */
47      private final String name;
48      /** The description for the component */
49      private final String desc;
50      /** The class of the getter/setter parameter */
51      private final Class<?> childClass;
52      /** True if the getter/setter expects a collection of childClass objects */
53      private final boolean isCollection;
54      /** True if this component is required. */
55      private final boolean required;
56      /**
57       * A map of name to Description for all the components that are children of the
58       * described component.
59       */
60      private final Map<String, Description> children;
61  
62      /**
63       * Constructor.
64       * @param type the type of the component.
65       * @param name the name of the component.
66       * @param desc the description of the component.
67       * @param isCollection true if the getter/setter expects a collection
68       * @param childClass the class for expected for the getter/setter.
69       * @param children the collection of descriptions for all the components that
70       * are children of the described component.
71       * @param required If {@code true} the component is required.
72       */
73      public Description(final ComponentType type, final String name, final String desc, final boolean isCollection,
74                         final Class<?> childClass, final Collection<Description> children, final boolean required) {
75          this.type = type;
76          this.name = name;
77          this.desc = desc;
78          this.isCollection = isCollection;
79          this.required = required;
80          if (type == ComponentType.BUILD_PARAMETER) {
81              Method m;
82              try {
83                  m = BuilderParams.class.getMethod(name);
84              } catch (NoSuchMethodException | SecurityException e) {
85                  throw new ConfigurationException(format("'%s' is not a valid BuildParams method", name));
86              }
87              this.childClass = m.getReturnType();
88          } else {
89              this.childClass = childClass;
90          }
91          this.children = new TreeMap<>();
92          if (children != null) {
93              children.forEach(d -> this.children.put(d.name, d));
94          }
95      }
96  
97      /**
98       * Constructor
99       * @param configComponent the configuration component.
100      * @param isCollection the collection flag.
101      * @param childClass the type of object that the method getter/setter expects.
102      * @param children the collection of descriptions for all the components that
103      * are children the described component.
104      */
105     public Description(final ConfigComponent configComponent, final boolean isCollection, final Class<?> childClass,
106                        final Collection<Description> children) {
107         this(configComponent.type(), configComponent.name(), configComponent.desc(), isCollection, childClass, children,
108                 configComponent.required());
109     }
110 
111     /**
112      * Get the canBeChild flag.
113      * @return {@code true} if this item can be a child of the containing item.
114      */
115     public boolean isRequired() {
116         return required;
117     }
118 
119     /**
120      * Gets the type of the component.
121      * @return the component type.
122      */
123     public ComponentType getType() {
124         return type;
125     }
126 
127     /**
128      * Get the isCollection flag.
129      * @return true if this is a collection.
130      */
131     public boolean isCollection() {
132         return isCollection;
133     }
134 
135     /**
136      * Get the class of the objects for the getter/setter methods.
137      * @return the getter/setter param class.
138      */
139     public Class<?> getChildType() {
140         return childClass;
141     }
142 
143     /**
144      * Gets the common name for the matcher. (e.g. 'text', 'spdx', etc.) May not be
145      * null.
146      * @return The common name for the item being inspected.
147      */
148     public String getCommonName() {
149         return name;
150     }
151 
152     /**
153      * Gets the description of descriptive text for the component. May be an empty
154      * string or null.
155      * @return the descriptive text;
156      */
157     public String getDescription() {
158         return desc;
159     }
160 
161     /**
162      * Retrieve the value of the described parameter from the specified object.
163      * If the parameter is a collection return {@code null}.
164      * @param object the object that contains the value.
165      * @return the string value.
166      */
167     public String getParamValue(final Object object) {
168         if (isCollection) {
169             return null;
170         }
171         try {
172             Object val = getter(object.getClass()).invoke(object);
173             return val == null ? null : val.toString();
174         } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException | NoSuchMethodException
175                 | SecurityException e) {
176             DefaultLog.getInstance().error(format("Can not retrieve value for '%s' from %s%n", name, object.getClass().getName()),
177                     e);
178             return null;
179         }
180     }
181 
182     /**
183      * Gets a map of the parameters that the object contains. For example Copyright
184      * has 'start', 'stop', and 'owner' parameters. Some IHeaderMatchers have simple
185      * text values (e.g. 'regex' or 'text' types) these should list an unnamed
186      * parameter (empty string) with the text value.
187      * @return the map of parameters to the objects that represent them.
188      */
189     public Map<String, Description> getChildren() {
190         return children;
191     }
192 
193     /**
194      * Get all the children of a specific type
195      * @param type the type to return
196      * @return the collection of children of the specified type.
197      */
198     public Collection<Description> childrenOfType(final ComponentType type) {
199         return filterChildren(d -> d.getType() == type);
200     }
201 
202     /**
203      * Gets a filtered collection of the child descriptions.
204      * @param filter the filter to apply to the child descriptions.
205      * @return the collection of children that matches the filter.
206      */
207     public Collection<Description> filterChildren(final Predicate<Description> filter) {
208         return children.values().stream().filter(filter).collect(Collectors.toList());
209     }
210 
211     /**
212      * Generate a method name for this description.
213      * @param prefix the start of the method name (e.g. "set", "get" )
214      * @return the method name.
215      */
216     public String methodName(final String prefix) {
217         return prefix + StringUtils.capitalize(name);
218     }
219 
220     /**
221      * Returns the getter for the component in the specified class.
222      * @param clazz the Class to get the getter from.
223      * @return the getter Method.
224      * @throws NoSuchMethodException if the class does not have the getter.
225      * @throws SecurityException if the getter can not be accessed.
226      */
227     public Method getter(final Class<?> clazz) throws NoSuchMethodException, SecurityException {
228         return clazz.getMethod(methodName("get"));
229     }
230 
231     /**
232      * Returns the setter for the component in the specified class. Notes:
233      * <ul>
234      * <li>License can not be set in components. They are top level components.</li>
235      * <li>Matcher expects an "add" method that accepts an
236      * IHeaderMatcher.Builder.</li>
237      * <li>Parameter expects a {@code set(String)} method.</li>
238      * <li>Unlabeled expects a {@code set(String)} method.</li>
239      * <li>BuilderParam expects a {@code set} method that takes a
240      * {@code childClass} argument.</li>
241      * </ul>
242      * @param clazz the Class to get the getter from, generally a Builder class.
243      * @return the getter Method.
244      * @throws NoSuchMethodException if the class does not have the getter.
245      * @throws SecurityException if the getter can not be accessed.
246      */
247     public Method setter(final Class<?> clazz) throws NoSuchMethodException, SecurityException {
248         String methodName = methodName(isCollection ? "add" : "set");
249         switch (type) {
250         case LICENSE:
251             throw new NoSuchMethodException("Can not set a License as a child");
252         case MATCHER:
253             return clazz.getMethod(methodName, IHeaderMatcher.Builder.class);
254         case PARAMETER:
255             return clazz.getMethod(methodName,
256                     IHeaderMatcher.class.isAssignableFrom(childClass) ? IHeaderMatcher.Builder.class : childClass);
257         case BUILD_PARAMETER:
258             return clazz.getMethod(methodName, childClass);
259         }
260         // should not happen
261         throw new IllegalStateException("Type " + type + " not valid.");
262     }
263 
264     private void callSetter(final Description description, final IHeaderMatcher.Builder builder, final String value) {
265         try {
266             description.setter(builder.getClass()).invoke(builder, value);
267         } catch (NoSuchMethodException e) {
268             String msg = format("No setter for '%s' on %s", description.getCommonName(),
269                     builder.getClass().getCanonicalName());
270             DefaultLog.getInstance().error(msg);
271             throw new ConfigurationException(msg);
272         } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) {
273             String msg = format("Unable to call setter for '%s' on %s", description.getCommonName(),
274                     builder.getClass().getCanonicalName());
275             DefaultLog.getInstance().error(msg, e);
276             throw new ConfigurationException(msg, e);
277         }
278     }
279 
280     /**
281      * Sets the children of values in the builder. Sets the parameters to the values
282      * specified in the map. Only children that accept string arguments should be
283      * specified.
284      * @param builder The Matcher builder to set the values in.
285      * @param attributes a Map of parameter names to values.
286      */
287     public void setChildren(final IHeaderMatcher.Builder builder, final Map<String, String> attributes) {
288         attributes.forEach((key, value) -> setChild(builder, key, value));
289     }
290 
291     /**
292      * Sets the child value in the builder.
293      * @param builder The Matcher builder to set the values in.
294      * @param name the name of the child to set
295      * @param value the value of the parameter.
296      */
297     public void setChild(final IHeaderMatcher.Builder builder, final String name, final String value) {
298         Description d = getChildren().get(name);
299         if (d == null) {
300             DefaultLog.getInstance().error(format("%s does not define a ConfigComponent for a member %s.",
301                     builder.getClass().getCanonicalName(), name));
302         } else {
303             callSetter(d, builder, value);
304         }
305     }
306 
307     @Override
308     public String toString() {
309         String childList = children.isEmpty() ? ""
310                 : children.values().stream().map(Description::getCommonName).collect(Collectors.joining(", "));
311 
312         return format("Description[%s t:%s c:%s %s children: [%s]] ", name, type, isCollection, childClass,
313                 childList);
314     }
315 }