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 edu.umd.cs.findbugs.annotations.NonNull;
021import java.io.ByteArrayOutputStream;
022import java.io.PrintStream;
023import java.io.UnsupportedEncodingException;
024import java.lang.invoke.CallSite;
025import java.lang.invoke.LambdaMetafactory;
026import java.lang.invoke.MethodHandle;
027import java.lang.invoke.MethodHandles;
028import java.lang.invoke.MethodType;
029import java.lang.management.ManagementFactory;
030import java.lang.management.ThreadInfo;
031import java.lang.management.ThreadMXBean;
032import java.lang.reflect.Constructor;
033import java.lang.reflect.Field;
034import java.lang.reflect.InvocationTargetException;
035import java.lang.reflect.Method;
036import java.nio.charset.Charset;
037import java.util.function.Function;
038import org.apache.yetus.audience.InterfaceAudience;
039import org.slf4j.Logger;
040
041@InterfaceAudience.Private
042public class ReflectionUtils {
043  @SuppressWarnings({ "unchecked", "TypeParameterUnusedInFormals" })
044  public static <T> T instantiateWithCustomCtor(String className, Class<?>[] ctorArgTypes,
045    Object[] ctorArgs) {
046    try {
047      Class<? extends T> resultType = (Class<? extends T>) Class.forName(className);
048      Constructor<? extends T> ctor = resultType.getDeclaredConstructor(ctorArgTypes);
049      return instantiate(className, ctor, ctorArgs);
050    } catch (ClassNotFoundException e) {
051      throw new UnsupportedOperationException("Unable to find " + className, e);
052    } catch (NoSuchMethodException e) {
053      throw new UnsupportedOperationException(
054        "Unable to find suitable constructor for class " + className, e);
055    }
056  }
057
058  public static <T> T instantiate(final String className, Constructor<T> ctor, Object... ctorArgs) {
059    try {
060      ctor.setAccessible(true);
061      return ctor.newInstance(ctorArgs);
062    } catch (IllegalAccessException e) {
063      throw new UnsupportedOperationException("Unable to access specified class " + className, e);
064    } catch (InstantiationException e) {
065      throw new UnsupportedOperationException("Unable to instantiate specified class " + className,
066        e);
067    } catch (InvocationTargetException e) {
068      throw new UnsupportedOperationException("Constructor threw an exception for " + className, e);
069    }
070  }
071
072  @SuppressWarnings({ "unchecked", "TypeParameterUnusedInFormals" })
073  public static <T> T newInstance(String className, Object... params) {
074    Class<T> type;
075    try {
076      type = (Class<T>) getClassLoader().loadClass(className);
077    } catch (ClassNotFoundException | ClassCastException e) {
078      throw new UnsupportedOperationException("Unable to load specified class " + className, e);
079    }
080    return instantiate(type.getName(), findConstructor(type, params), params);
081  }
082
083  public static ClassLoader getClassLoader() {
084    ClassLoader cl = Thread.currentThread().getContextClassLoader();
085    if (cl == null) {
086      cl = ReflectionUtils.class.getClassLoader();
087    }
088    if (cl == null) {
089      cl = ClassLoader.getSystemClassLoader();
090    }
091    if (cl == null) {
092      throw new RuntimeException("A ClassLoader could not be found");
093    }
094    return cl;
095  }
096
097  public static <T> T newInstance(Class<T> type, Object... params) {
098    return instantiate(type.getName(), findConstructor(type, params), params);
099  }
100
101  @SuppressWarnings("unchecked")
102  public static <T> Constructor<T> findConstructor(Class<T> type, Object... paramTypes) {
103    Constructor<T>[] constructors = (Constructor<T>[]) type.getDeclaredConstructors();
104    for (Constructor<T> ctor : constructors) {
105      Class<?>[] ctorParamTypes = ctor.getParameterTypes();
106      if (ctorParamTypes.length != paramTypes.length) {
107        continue;
108      }
109
110      boolean match = true;
111      for (int i = 0; i < ctorParamTypes.length && match; ++i) {
112        if (paramTypes[i] == null) {
113          match = !ctorParamTypes[i].isPrimitive();
114        } else {
115          Class<?> paramType = paramTypes[i].getClass();
116          match = !ctorParamTypes[i].isPrimitive()
117            ? ctorParamTypes[i].isAssignableFrom(paramType)
118            : ((int.class.equals(ctorParamTypes[i]) && Integer.class.equals(paramType))
119              || (long.class.equals(ctorParamTypes[i]) && Long.class.equals(paramType))
120              || (double.class.equals(ctorParamTypes[i]) && Double.class.equals(paramType))
121              || (char.class.equals(ctorParamTypes[i]) && Character.class.equals(paramType))
122              || (short.class.equals(ctorParamTypes[i]) && Short.class.equals(paramType))
123              || (boolean.class.equals(ctorParamTypes[i]) && Boolean.class.equals(paramType))
124              || (byte.class.equals(ctorParamTypes[i]) && Byte.class.equals(paramType)));
125        }
126      }
127
128      if (match) {
129        return ctor;
130      }
131    }
132    throw new UnsupportedOperationException(
133      "Unable to find suitable constructor for class " + type.getName());
134  }
135
136  /* synchronized on ReflectionUtils.class */
137  private static long previousLogTime = 0;
138  private static final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
139
140  /**
141   * Log the current thread stacks at INFO level.
142   * @param log         the logger that logs the stack trace
143   * @param title       a descriptive title for the call stacks
144   * @param minInterval the minimum time from the last
145   */
146  public static void logThreadInfo(Logger log, String title, long minInterval) {
147    boolean dumpStack = false;
148    if (log.isInfoEnabled()) {
149      synchronized (ReflectionUtils.class) {
150        long now = EnvironmentEdgeManager.currentTime();
151        if (now - previousLogTime >= minInterval * 1000) {
152          previousLogTime = now;
153          dumpStack = true;
154        }
155      }
156      if (dumpStack) {
157        try {
158          ByteArrayOutputStream buffer = new ByteArrayOutputStream();
159          printThreadInfo(new PrintStream(buffer, false, "UTF-8"), title);
160          log.info(buffer.toString(Charset.defaultCharset().name()));
161        } catch (UnsupportedEncodingException ignored) {
162          log.warn(
163            "Could not write thread info about '" + title + "' due to a string encoding issue.");
164        }
165      }
166    }
167  }
168
169  /**
170   * Print all of the thread's information and stack traces.
171   * @param stream the stream to
172   * @param title  a string title for the stack trace
173   */
174  static void printThreadInfo(PrintStream stream, String title) {
175    final int STACK_DEPTH = 20;
176    boolean contention = threadBean.isThreadContentionMonitoringEnabled();
177    long[] threadIds = threadBean.getAllThreadIds();
178    stream.println("Process Thread Dump: " + title);
179    stream.println(threadIds.length + " active threads");
180    for (long tid : threadIds) {
181      ThreadInfo info = threadBean.getThreadInfo(tid, STACK_DEPTH);
182      if (info == null) {
183        stream.println("  Inactive");
184        continue;
185      }
186      stream.println("Thread " + getTaskName(info.getThreadId(), info.getThreadName()) + ":");
187      Thread.State state = info.getThreadState();
188      stream.println("  State: " + state);
189      stream.println("  Blocked count: " + info.getBlockedCount());
190      stream.println("  Waited count: " + info.getWaitedCount());
191      if (contention) {
192        stream.println("  Blocked time: " + info.getBlockedTime());
193        stream.println("  Waited time: " + info.getWaitedTime());
194      }
195      if (state == Thread.State.WAITING) {
196        stream.println("  Waiting on " + info.getLockName());
197      } else if (state == Thread.State.BLOCKED) {
198        stream.println("  Blocked on " + info.getLockName());
199        stream
200          .println("  Blocked by " + getTaskName(info.getLockOwnerId(), info.getLockOwnerName()));
201      }
202      stream.println("  Stack:");
203      for (StackTraceElement frame : info.getStackTrace()) {
204        stream.println("    " + frame.toString());
205      }
206    }
207    stream.flush();
208  }
209
210  private static String getTaskName(long id, String name) {
211    if (name == null) {
212      return Long.toString(id);
213    }
214    return id + " (" + name + ")";
215  }
216
217  /**
218   * Creates a Function which can be called to performantly execute a reflected static method. The
219   * creation of the Function itself may not be fast, but executing that method thereafter should be
220   * much faster than {@link #invokeMethod(Object, String, Object...)}.
221   * @param lookupClazz      the class to find the static method in
222   * @param methodName       the method name
223   * @param argumentClazz    the type of the argument
224   * @param returnValueClass the type of the return value
225   * @return a function which when called executes the requested static method.
226   * @throws Throwable exception types from the underlying reflection
227   */
228  public static <I, R> Function<I, R> getOneArgStaticMethodAsFunction(Class<?> lookupClazz,
229    String methodName, Class<I> argumentClazz, Class<R> returnValueClass) throws Throwable {
230    MethodHandles.Lookup lookup = MethodHandles.lookup();
231    MethodHandle methodHandle = lookup.findStatic(lookupClazz, methodName,
232      MethodType.methodType(returnValueClass, argumentClazz));
233    CallSite site =
234      LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Function.class),
235        methodHandle.type().generic(), methodHandle, methodHandle.type());
236
237    return (Function<I, R>) site.getTarget().invokeExact();
238
239  }
240
241  /**
242   * Get and invoke the target method from the given object with given parameters
243   * @param obj        the object to get and invoke method from
244   * @param methodName the name of the method to invoke
245   * @param params     the parameters for the method to invoke
246   * @return the return value of the method invocation
247   */
248  @NonNull
249  public static Object invokeMethod(Object obj, String methodName, Object... params) {
250    Method m;
251    try {
252      m = obj.getClass().getMethod(methodName, getParameterTypes(params));
253      m.setAccessible(true);
254      return m.invoke(obj, params);
255    } catch (NoSuchMethodException e) {
256      throw new UnsupportedOperationException("Cannot find specified method " + methodName, e);
257    } catch (IllegalAccessException e) {
258      throw new UnsupportedOperationException("Unable to access specified method " + methodName, e);
259    } catch (IllegalArgumentException e) {
260      throw new UnsupportedOperationException("Illegal arguments supplied for method " + methodName,
261        e);
262    } catch (InvocationTargetException e) {
263      throw new UnsupportedOperationException("Method threw an exception for " + methodName, e);
264    }
265  }
266
267  private static Class<?>[] getParameterTypes(Object[] params) {
268    Class<?>[] parameterTypes = new Class<?>[params.length];
269    for (int i = 0; i < params.length; i++) {
270      parameterTypes[i] = params[i].getClass();
271    }
272    return parameterTypes;
273  }
274
275  public static Field getModifiersField() throws IllegalAccessException, NoSuchFieldException {
276    // this is copied from https://github.com/powermock/powermock/pull/1010/files to work around
277    // JDK 12+
278    Field modifiersField = null;
279    try {
280      modifiersField = Field.class.getDeclaredField("modifiers");
281    } catch (NoSuchFieldException e) {
282      try {
283        Method getDeclaredFields0 =
284          Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
285        boolean accessibleBeforeSet = getDeclaredFields0.isAccessible();
286        getDeclaredFields0.setAccessible(true);
287        Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
288        getDeclaredFields0.setAccessible(accessibleBeforeSet);
289        for (Field field : fields) {
290          if ("modifiers".equals(field.getName())) {
291            modifiersField = field;
292            break;
293          }
294        }
295        if (modifiersField == null) {
296          throw e;
297        }
298      } catch (NoSuchMethodException ex) {
299        e.addSuppressed(ex);
300        throw e;
301      } catch (InvocationTargetException ex) {
302        e.addSuppressed(ex);
303        throw e;
304      }
305    }
306    return modifiersField;
307  }
308
309}