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 }