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, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.util;
20  
21  import static org.junit.Assert.assertTrue;
22  
23  import java.lang.reflect.Method;
24  import java.lang.reflect.Modifier;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Map;
28  import java.util.Set;
29  
30  /**
31   * Utility class to check whether a given class conforms to builder-style:
32   * Foo foo =
33   *   new Foo()
34   *     .setBar(bar)
35   *     .setBaz(baz)
36   */
37  public class BuilderStyleTest {
38  
39    /*
40     * If a base class Foo declares a method setFoo() returning Foo, then the subclass should
41     * re-declare the methods overriding the return class with the subclass:
42     *
43     * class Foo {
44     *   Foo setFoo() {
45     *     ..
46     *     return this;
47     *   }
48     * }
49     *
50     * class Bar {
51     *   Bar setFoo() {
52     *     return (Bar) super.setFoo();
53     *   }
54     * }
55     *
56     */
57    @SuppressWarnings("rawtypes")
58    public static void assertClassesAreBuilderStyle(Class... classes) {
59      for (Class clazz : classes) {
60        System.out.println("Checking " + clazz);
61        Method[] methods = clazz.getDeclaredMethods();
62        Map<String, Set<Method>> methodsBySignature = new HashMap<>();
63        for (Method method : methods) {
64          if (!Modifier.isPublic(method.getModifiers())) {
65            continue; // only public classes
66          }
67          Class<?> ret = method.getReturnType();
68          if (method.getName().startsWith("set") || method.getName().startsWith("add")) {
69            System.out.println("  " + clazz.getSimpleName() + "." + method.getName() + "() : "
70              + ret.getSimpleName());
71  
72            // because of subclass / super class method overrides, we group the methods fitting the
73            // same signatures because we get two method definitions from java reflection:
74            // Mutation.setDurability() : Mutation
75            //   Delete.setDurability() : Mutation
76            // Delete.setDurability() : Delete
77            String sig = method.getName();
78            for (Class<?> param : method.getParameterTypes()) {
79              sig += param.getName();
80            }
81            Set<Method> sigMethods = methodsBySignature.get(sig);
82            if (sigMethods == null) {
83              sigMethods = new HashSet<Method>();
84              methodsBySignature.put(sig, sigMethods);
85            }
86            sigMethods.add(method);
87          }
88        }
89        // now iterate over the methods by signatures
90        for (Map.Entry<String, Set<Method>> e : methodsBySignature.entrySet()) {
91          // at least one of method sigs should return the declaring class
92          boolean found = false;
93          for (Method m : e.getValue()) {
94            found = clazz.isAssignableFrom(m.getReturnType());
95            if (found) break;
96          }
97          String errorMsg = "All setXXX()|addXX() methods in " + clazz.getSimpleName()
98              + " should return a " + clazz.getSimpleName() + " object in builder style. "
99              + "Offending method:" + e.getValue().iterator().next().getName();
100         assertTrue(errorMsg, found);
101       }
102     }
103   }
104 }