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: Foo foo = new Foo()
031 * .setBar(bar) .setBaz(baz)
032 */
033public final class BuilderStyleTest {
034  private BuilderStyleTest() {
035  }
036
037  /*
038   * If a base class Foo declares a method setFoo() returning Foo, then the subclass should
039   * re-declare the methods overriding the return class with the subclass: class Foo { Foo setFoo()
040   * { .. return this; } } class Bar { Bar setFoo() { return (Bar) super.setFoo(); } }
041   */
042  @SuppressWarnings("rawtypes")
043  public static void assertClassesAreBuilderStyle(Class... classes) {
044    for (Class clazz : classes) {
045      System.out.println("Checking " + clazz);
046      Method[] methods = clazz.getDeclaredMethods();
047      Map<String, Set<Method>> methodsBySignature = new HashMap<>();
048      for (Method method : methods) {
049        if (!Modifier.isPublic(method.getModifiers())) {
050          continue; // only public classes
051        }
052        Class<?> ret = method.getReturnType();
053        if (method.getName().startsWith("set") || method.getName().startsWith("add")) {
054          System.out.println(
055            "  " + clazz.getSimpleName() + "." + method.getName() + "() : " + ret.getSimpleName());
056
057          // because of subclass / super class method overrides, we group the methods fitting the
058          // same signatures because we get two method definitions from java reflection:
059          // Mutation.setDurability() : Mutation
060          // Delete.setDurability() : Mutation
061          // Delete.setDurability() : Delete
062          String sig = method.getName();
063          for (Class<?> param : method.getParameterTypes()) {
064            sig += param.getName();
065          }
066          Set<Method> sigMethods = methodsBySignature.get(sig);
067          if (sigMethods == null) {
068            sigMethods = new HashSet<>();
069            methodsBySignature.put(sig, sigMethods);
070          }
071          sigMethods.add(method);
072        }
073      }
074      // now iterate over the methods by signatures
075      for (Map.Entry<String, Set<Method>> e : methodsBySignature.entrySet()) {
076        // at least one of method sigs should return the declaring class
077        boolean found = false;
078        for (Method m : e.getValue()) {
079          found = clazz.isAssignableFrom(m.getReturnType());
080          if (found) {
081            break;
082          }
083        }
084        String errorMsg = "All setXXX()|addXX() methods in " + clazz.getSimpleName()
085          + " should return a " + clazz.getSimpleName() + " object in builder style. "
086          + "Offending method:" + e.getValue().iterator().next().getName();
087        assertTrue(errorMsg, found);
088      }
089    }
090  }
091}