content: /**
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.aliyun.odps.utils;
  
  import java.io.IOException;
  import java.lang.reflect.Array;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.Field;
  import java.lang.reflect.GenericArrayType;
  import java.lang.reflect.Member;
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  import java.lang.reflect.ParameterizedType;
  import java.lang.reflect.ReflectPermission;
  import java.lang.reflect.Type;
  import java.lang.reflect.TypeVariable;
  import java.security.AccessController;
  import java.security.PrivilegedAction;
  import java.security.PrivilegedActionException;
  import java.security.PrivilegedExceptionAction;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;
  
  import com.aliyun.odps.conf.Configurable;
  import com.aliyun.odps.conf.Configuration;
  import com.aliyun.odps.io.DataInputBuffer;
  import com.aliyun.odps.io.DataOutputBuffer;
  import com.aliyun.odps.io.NullWritable;
  import com.aliyun.odps.io.Writable;
  
  import sun.security.util.SecurityConstants;
  
  /**
   * General reflection utilities
   */
  public class ReflectionUtils {
  
    private static final Class<?>[] EMPTY_ARRAY = new Class[]{};
  
    /**
     * Cache of constructors for each class. Pins the classes so they can't be
     * garbage collected until ReflectionUtils can be collected.
     */
    private static final ConcurrentHashMap<Class<?>, Constructor<?>>
        CONSTRUCTOR_CACHE =
        new ConcurrentHashMap<Class<?>, Constructor<?>>();
  
    /**
     * Check and set 'configuration' if necessary.
     *
     * @param theObject
     *     object for which to set configuration
     * @param conf
     *     Configuration
     */
    public static void setConf(Object theObject, Configuration conf) {
      if (conf != null) {
        if (theObject instanceof Configurable) {
          ((Configurable) theObject).setConf(conf);
        }
      }
    }
    /**
     * Check the access to theMember that belongs to theClass
     * @param theClass
     * @param theMember
     * @exception
     *        java.security.AccessControlException
     */
    private static void checkMemberAccess(final Class<?> theClass,final Member theMember) {
      // 1) bypass the permission check when theMember is public or null.
      if(theMember == null || Modifier.isPublic(theMember.getModifiers())){
        return;
      }
      // 2) bypass the permission check when not in a security mode
      final SecurityManager s = System.getSecurityManager();
      if (s == null) {
        return;
      }
      // 3) bypass the permission check when theClass is an user defined class.
      if(AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
        public Boolean run() {
          return ReflectionUtils.class.getClassLoader() == theClass.getClassLoader().getParent();
        }
      })){
        return;
      }
      // do permission check on other conditions!!!
      s.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
      s.checkPermission(new ReflectPermission("suppressAccessChecks"));
      //this.checkPackageAccess(ccl, checkProxyInterfaces);
    }
    /**
     * Create an object for the given class and initialize it from conf
     *
     * @param theClass
     *     class of which an object is created
     * @param conf
     *     Configuration
     * @return a new object
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public static <T> T newInstance(final Class<T> theClass, Configuration conf) {
      T result;
      Constructor<T> retMethod = (Constructor<T>) CONSTRUCTOR_CACHE.get(theClass);
      if (retMethod == null) try {
        retMethod = AccessController.doPrivileged(new PrivilegedExceptionAction<Constructor<T>>() {
          public Constructor<T> run() throws NoSuchMethodException {
            Constructor<T> ret = theClass.getDeclaredConstructor(EMPTY_ARRAY);
            ret.setAccessible(true);
            return ret;
          }
        });
        checkMemberAccess(theClass, retMethod);
        CONSTRUCTOR_CACHE.putIfAbsent(theClass, retMethod);
      } catch (PrivilegedActionException e) {
        throw new RuntimeException(e);
      }
      try {
        result = retMethod.newInstance();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
      setConf(result, conf);
      return result;
    }
  
    /**
     * Instantiate classes that are ImmmutableClasssesGiraphConfigurable
     *
     * @param theClass
     *     Class to instantiate
     * @param configuration
     *     Graph configuration, may be null
     * @param <T>
     *     Type to instantiate
     * @return Newly instantiated object with configuration set if possible
     */
    @SuppressWarnings("unchecked")
    public static <T> T newInstanceFast(Class<T> theClass,
                                        Configuration configuration) {
      T result = null;
  
      if (theClass.equals(NullWritable.class)) {
        return (T) NullWritable.get();
      }
      try {
        result = theClass.newInstance();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
  
      setConf(result, configuration);
      return result;
    }
  
    // methods to support testing
    static void clearCache() {
      CONSTRUCTOR_CACHE.clear();
    }
  
    static int getCacheSize() {
      return CONSTRUCTOR_CACHE.size();
    }
  
    /**
     * Find a declared method in a class
     *
     * @param inClass
     *     Class to search for declared field.
     * @param methodName
     *     Method name to search for
     * @return Method or will return null.
     */
    @Deprecated
    public static Method findUserClassMethod(final Class<?> inClass, final String methodName) {
      Method retMethod = AccessController.doPrivileged(new PrivilegedAction<Method>() {
        public Method run() {
          for (final Method method : inClass.getDeclaredMethods()) {
            if (method.getName().equals(methodName)) {
              if (!method.isAccessible()) {
                method.setAccessible(true);
              }
              return method;
            }
          }
          return null;
        }
      });
      checkMemberAccess(inClass, retMethod);
      return retMethod;
    }
  
    /**
     * Find declared methods in a class by method name
     *
     * @param inClass
     *     Class to search for declared field.
     * @param methodName
     *     Method name to search for
     * @return list of Methods, empty if not found.
     */
    public static List<Method> findUserClassMethods(final Class<?> inClass, final String methodName) {
      List<Method> retMethod = AccessController.doPrivileged(new PrivilegedAction<List<Method>>() {
        public List<Method> run() {
          List<Method> methods = new ArrayList<Method>();
          for (final Method method : inClass.getDeclaredMethods()) {
            if (method.getName().equals(methodName)) {
              if (!method.isAccessible()) {
                method.setAccessible(true);
              }
              methods.add(method);
            }
          }
          return methods;
        }
      });
      for (final Method m : retMethod) {
        checkMemberAccess(inClass, m);
      }
      return retMethod;
    }
  
    /**
     * Get the underlying class for a type, or null if the type is a variable
     * type.
     *
     * @param type
     *     the type
     * @return the underlying class
     */
    public static Class<?> getClass(Type type) {
      if (type instanceof Class) {
        return (Class<?>) type;
      } else if (type instanceof ParameterizedType) {
        return getClass(((ParameterizedType) type).getRawType());
      } else if (type instanceof GenericArrayType) {
        Type componentType = ((GenericArrayType) type).getGenericComponentType();
        Class<?> componentClass = getClass(componentType);
        if (componentClass != null) {
          return Array.newInstance(componentClass, 0).getClass();
        } else {
          return null;
        }
      } else {
        return null;
      }
    }
  
    /**
     * Try to directly set a (possibly private) field on an Object.
     *
     * @param target
     *     Target to set the field on.
     * @param fieldname
     *     Name of field.
     * @param value
     *     Value to set on target.
     */
    public static void setField(Object target, String fieldname, Object value)
        throws NoSuchFieldException, IllegalAccessException {
      Field field = ReflectionUtils.findDeclaredField(target.getClass(),
                                                      fieldname);
      field.setAccessible(true);
      field.set(target, value);
    }
  
    /**
     * Find a declared field in a class or one of its super classes
     *
     * @param inClass
     *     Class to search for declared field.
     * @param fieldname
     *     Field name to search for
     * @return Field or will throw.
     * @throws NoSuchFieldException
     *     When field not found.
     */
    public static Field findDeclaredField(Class<?> inClass, String fieldname)
        throws NoSuchFieldException {
      while (!Object.class.equals(inClass)) {
        for (Field field : inClass.getDeclaredFields()) {
          if (field.getName().equals(fieldname)) {
            return field;
          }
        }
        inClass = inClass.getSuperclass();
      }
      String msg = "Class '" + inClass.getName() + "' has no field '" + fieldname
                   + "'";
      throw new NoSuchFieldException(msg);
    }
  
    /**
     * Find a declared method in a class or one of its super classes
     *
     * @param inClass
     *     Class to search for declared field.
     * @param methodName
     *     Method name to search for
     * @return Method or will throw.
     * @throws NoSuchMethodException
     *     When method not found.
     */
    public static Method findDeclaredMethodRecursive(Class<?> inClass,
                                                     String methodName) throws NoSuchMethodException {
      while (!Object.class.equals(inClass)) {
        for (Method method : inClass.getDeclaredMethods()) {
          if (method.getName().equals(methodName)) {
            return method;
          }
        }
        inClass = inClass.getSuperclass();
      }
      String msg = "ODPS-0730001: Class '" + inClass.getName()
                   + "' not implement method '" + methodName + "'";
      throw new NoSuchMethodException(msg);
    }
  
    /**
     * Find a declared method in a class
     *
     * @param inClass
     *     Class to search for declared field.
     * @param methodName
     *     Method name to search for
     * @return Method or will throw.
     * @throws NoSuchMethodException
     *     When method not found.
     */
    public static Method findDeclaredMethod(Class<?> inClass, String methodName) {
      if (!Object.class.equals(inClass)) {
        for (Method method : inClass.getDeclaredMethods()) {
          if (method.getName().equals(methodName)) {
            return method;
          }
        }
      }
      String msg = "ODPS-0730001: Class '" + inClass.getName()
                   + "' not implement method '" + methodName + "'";
      throw new RuntimeException(msg);
    }
  
    /**
     * Get the actual type arguments a child class has used to extend a generic
     * base class.
     *
     * @param <T>
     *     Type to evaluate.
     * @param baseClass
     *     the base class
     * @param childClass
     *     the child class
     * @return a list of the raw classes for the actual type arguments.
     */
    public static <T> List<Class<?>> getTypeArguments(Class<T> baseClass,
                                                      Class<? extends T> childClass) {
      Map<Type, Type> resolvedTypes = new HashMap<Type, Type>();
      Type type = childClass;
      // start walking up the inheritance hierarchy until we hit baseClass
      while (!getClass(type).equals(baseClass)) {
        if (type instanceof Class) {
          // there is no useful information for us in raw types,
          // so just keep going.
          type = ((Class<?>) type).getGenericSuperclass();
        } else {
          ParameterizedType parameterizedType = (ParameterizedType) type;
          Class<?> rawType = (Class<?>) parameterizedType.getRawType();
  
          Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
          TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
          for (int i = 0; i < actualTypeArguments.length; i++) {
            resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
          }
  
          if (!rawType.equals(baseClass)) {
            type = rawType.getGenericSuperclass();
          }
        }
      }
  
      // finally, for each actual type argument provided to baseClass,
      // determine (if possible) the raw class for that type argument.
      Type[] actualTypeArguments;
      if (type instanceof Class) {
        actualTypeArguments = ((Class<?>) type).getTypeParameters();
      } else {
        actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
      }
      List<Class<?>> typeArgumentsAsClasses = new ArrayList<Class<?>>();
      // resolve types by chasing down type variables.
      for (Type baseType : actualTypeArguments) {
        while (resolvedTypes.containsKey(baseType)) {
          baseType = resolvedTypes.get(baseType);
        }
        typeArgumentsAsClasses.add(getClass(baseType));
      }
      return typeArgumentsAsClasses;
    }
  
    public static void checkNonStaticField(Class<?> userImplClass,
                                           Class<?> parentClass) {
      while (!parentClass.equals(userImplClass)) {
        Field[] fields = userImplClass.getDeclaredFields();
        List<Field> nonStaticFields = new ArrayList<Field>();
        for (Field field : fields) {
          if (!Modifier.isStatic(field.getModifiers())) {
            nonStaticFields.add(field);
          }
        }
        if (!nonStaticFields.isEmpty()) {
          StringBuffer sb = new StringBuffer();
          sb.append("[" + nonStaticFields.get(0).getName());
          for (int i = 1; i < nonStaticFields.size(); ++i) {
            sb.append("," + nonStaticFields.get(i).getName());
          }
          sb.append("]");
          throw new IllegalStateException("ODPS-0730001: Subclass of "
                                          + parentClass.getSimpleName() + " '" + userImplClass
                                              .getName()
                                          + "' must not have non-static member variables " + sb
                                              .toString()
                                          + ", put them into vertex value if really need.");
        }
  
        userImplClass = userImplClass.getSuperclass();
      }
    }
  
    @Deprecated
    public static void cloneWritableInto(Writable dst, Writable src)
        throws IOException {
      CopyInCopyOutBuffer buffer = cloneBuffers.get();
      buffer.outBuffer.reset();
      src.write(buffer.outBuffer);
      buffer.moveData();
      dst.readFields(buffer.inBuffer);
    }
  
    /**
     * A pair of input/output buffers that we use to clone writables.
     */
    private static class CopyInCopyOutBuffer {
  
      DataOutputBuffer outBuffer = new DataOutputBuffer();
      DataInputBuffer inBuffer = new DataInputBuffer();
  
      /**
       * Move the data from the output buffer to the input buffer.
       */
      void moveData() {
        inBuffer.reset(outBuffer.getData(), outBuffer.getLength());
      }
    }
  
    /**
     * Allocate a buffer for each thread that tries to clone objects.
     */
    private static ThreadLocal<CopyInCopyOutBuffer>
        cloneBuffers =
        new ThreadLocal<CopyInCopyOutBuffer>() {
          protected synchronized CopyInCopyOutBuffer initialValue() {
            return new CopyInCopyOutBuffer();
          }
        };
  
  }
  