001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.util;
019
020import static org.junit.Assert.assertTrue;
021
022import java.lang.reflect.Method;
023import java.lang.reflect.Modifier;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.Map;
027import java.util.Set;
028
029/**
030 * Utility class to check whether a given class conforms to builder-style:
031 * Foo foo =
032 *   new Foo()
033 *     .setBar(bar)
034 *     .setBaz(baz)
035 */
036public final class BuilderStyleTest {
037  private BuilderStyleTest() {}
038
039  /*
040   * If a base class Foo declares a method setFoo() returning Foo, then the subclass should
041   * re-declare the methods overriding the return class with the subclass:
042   *
043   * class Foo {
044   *   Foo setFoo() {
045   *     ..
046   *     return this;
047   *   }
048   * }
049   *
050   * class Bar {
051   *   Bar setFoo() {
052   *     return (Bar) super.setFoo();
053   *   }
054   * }
055   *
056   */
057  @SuppressWarnings("rawtypes")
058  public static void assertClassesAreBuilderStyle(Class... classes) {
059    for (Class clazz : classes) {
060      System.out.println("Checking " + clazz);
061      Method[] methods = clazz.getDeclaredMethods();
062      Map<String, Set<Method>> methodsBySignature = new HashMap<>();
063      for (Method method : methods) {
064        if (!Modifier.isPublic(method.getModifiers())) {
065          continue; // only public classes
066        }
067        Class<?> ret = method.getReturnType();
068        if (method.getName().startsWith("set") || method.getName().startsWith("add")) {
069          System.out.println("  " + clazz.getSimpleName() + "." + method.getName() + "() : "
070            + ret.getSimpleName());
071
072          // because of subclass / super class method overrides, we group the methods fitting the
073          // same signatures because we get two method definitions from java reflection:
074          // Mutation.setDurability() : Mutation
075          //   Delete.setDurability() : Mutation
076          // Delete.setDurability() : Delete
077          String sig = method.getName();
078          for (Class<?> param : method.getParameterTypes()) {
079            sig += param.getName();
080          }
081          Set<Method> sigMethods = methodsBySignature.get(sig);
082          if (sigMethods == null) {
083            sigMethods = new HashSet<>();
084            methodsBySignature.put(sig, sigMethods);
085          }
086          sigMethods.add(method);
087        }
088      }
089      // now iterate over the methods by signatures
090      for (Map.Entry<String, Set<Method>> e : methodsBySignature.entrySet()) {
091        // at least one of method sigs should return the declaring class
092        boolean found = false;
093        for (Method m : e.getValue()) {
094          found = clazz.isAssignableFrom(m.getReturnType());
095          if (found) {
096            break;
097          }
098        }
099        String errorMsg = "All setXXX()|addXX() methods in " + clazz.getSimpleName()
100            + " should return a " + clazz.getSimpleName() + " object in builder style. "
101            + "Offending method:" + e.getValue().iterator().next().getName();
102        assertTrue(errorMsg, found);
103      }
104    }
105  }
106}