All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hydraql.reflectasm.Reflector Maven / Gradle / Ivy

/*
 * 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.hydraql.reflectasm;

import com.hydraql.reflectasm.exceptions.ReflectionException;
import com.hydraql.reflectasm.invoker.AmbiguousMethodInvoker;
import com.hydraql.reflectasm.invoker.GetFieldInvoker;
import com.hydraql.reflectasm.invoker.Invoker;
import com.hydraql.reflectasm.invoker.MethodInvoker;
import com.hydraql.reflectasm.invoker.SetFieldInvoker;
import com.hydraql.reflectasm.property.PropertyNamer;
import com.hydraql.reflectasm.util.MapUtil;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
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.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * @author [email protected] 2024/8/11 13:54
 */
public class Reflector {
  private final Class clazz;
  private final MethodAccess methodAccess;
  private final FieldAccess fieldAccess;
  private final Map setMethods = new HashMap<>();
  private final Map getMethods = new HashMap<>();
  private final Map> setTypes = new HashMap<>();
  private final Map> getTypes = new HashMap<>();
  private Constructor defaultConstructor;

  public Reflector(Class clazz) {
    this.clazz = clazz;
    this.methodAccess = MethodAccess.get(clazz);
    this.fieldAccess = FieldAccess.get(clazz);
    this.addDefaultConstructor(clazz);
    Method[] classMethods = getClassMethods(clazz);
    addGetMethods(classMethods);
    addSetMethods(classMethods);
    addFields();
  }

  private void addDefaultConstructor(Class clazz) {
    Constructor[] constructors = clazz.getDeclaredConstructors();
    Arrays.stream(constructors).filter(constructor -> constructor.getParameterTypes().length == 0)
        .findAny().ifPresent(constructor -> this.defaultConstructor = constructor);
  }

  private void addFields() {
    Field[] fields = this.getFields();
    for (Field field : fields) {
      if (isNotGeneralProperty(field)) {
        continue;
      }
      if (!setMethods.containsKey(field.getName())) {
        addSetField(field);
      }
      if (!getMethods.containsKey(field.getName())) {
        addGetField(field);
      }
    }
  }

  private void addSetField(Field field) {
    if (isValidPropertyName(field.getName())) {
      setMethods.put(field.getName(), new SetFieldInvoker(field));
      Type fieldType = TypeParameterResolver.resolveFieldType(field, clazz);
      setTypes.put(field.getName(), typeToClass(fieldType));
    }
  }

  private void addGetField(Field field) {
    if (isValidPropertyName(field.getName())) {
      getMethods.put(field.getName(), new GetFieldInvoker(field));
      Type fieldType = TypeParameterResolver.resolveFieldType(field, clazz);
      getTypes.put(field.getName(), typeToClass(fieldType));
    }
  }

  private void addGetMethods(Method[] methods) {
    Map> conflictingGetters = new HashMap<>();
    Arrays.stream(methods).filter(
      m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName())).forEach(
        m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
    resolveGetterConflicts(conflictingGetters);
  }

  private void resolveGetterConflicts(Map> conflictingGetters) {
    for (Map.Entry> entry : conflictingGetters.entrySet()) {
      Method winner = null;
      String propName = entry.getKey();
      boolean isAmbiguous = false;
      for (Method candidate : entry.getValue()) {
        if (winner == null) {
          winner = candidate;
          continue;
        }
        Class winnerType = winner.getReturnType();
        Class candidateType = candidate.getReturnType();
        if (candidateType.equals(winnerType)) {
          if (!boolean.class.equals(candidateType)) {
            isAmbiguous = true;
            break;
          }
          if (candidate.getName().startsWith("is")) {
            winner = candidate;
          }
        } /*
           * else if (candidateType.isAssignableFrom(winnerType)) { // OK getter type is descendant
           * }
           */ else if (winnerType.isAssignableFrom(candidateType)) {
          winner = candidate;
        } else {
          isAmbiguous = true;
          break;
        }
      }
      addGetMethod(propName, winner, isAmbiguous);
    }
  }

  private void addGetMethod(String name, Method method, boolean isAmbiguous) {
    int methodIndex = methodAccess.getIndex(method);
    MethodInvoker invoker = isAmbiguous
        ? new AmbiguousMethodInvoker(methodAccess, methodIndex, MessageFormat.format(
          "Illegal overloaded getter method with ambiguous type for property ''{0}'' in class ''{1}''. This breaks the JavaBeans specification and can cause unpredictable results.",
          name, method.getDeclaringClass().getName()))
        : new MethodInvoker(methodAccess, methodIndex);
    getMethods.put(name, invoker);
    Type returnType = TypeParameterResolver.resolveReturnType(method, clazz);
    getTypes.put(name, typeToClass(returnType));
  }

  private void addSetMethods(Method[] methods) {
    Map> conflictingSetters = new HashMap<>();
    Arrays.stream(methods).filter(
      m -> m.getParameterTypes().length == 1 && PropertyNamer.isSetter(m.getName())).forEach(
        m -> addMethodConflict(conflictingSetters, PropertyNamer.methodToProperty(m.getName()), m));
    resolveSetterConflicts(conflictingSetters);
  }

  private void addMethodConflict(Map> conflictingMethods, String name,
      Method method) {
    if (isValidPropertyName(name)) {
      List list = MapUtil.computeIfAbsent(conflictingMethods, name, k -> new ArrayList<>());
      list.add(method);
    }
  }

  private void resolveSetterConflicts(Map> conflictingSetters) {
    for (Map.Entry> entry : conflictingSetters.entrySet()) {
      String propName = entry.getKey();
      List setters = entry.getValue();
      Class getterType = getTypes.get(propName);
      boolean isGetterAmbiguous = getMethods.get(propName) instanceof AmbiguousMethodInvoker;
      boolean isSetterAmbiguous = false;
      Method match = null;
      for (Method setter : setters) {
        if (!isGetterAmbiguous && setter.getParameterTypes()[0].equals(getterType)) {
          // should be the best match
          match = setter;
          break;
        }
        if (!isSetterAmbiguous) {
          match = pickBetterSetter(match, setter, propName);
          isSetterAmbiguous = match == null;
        }
      }
      if (match != null) {
        addSetMethod(propName, match);
      }
    }
  }

  private Method pickBetterSetter(Method setter1, Method setter2, String property) {
    if (setter1 == null) {
      return setter2;
    }
    Class paramType1 = setter1.getParameterTypes()[0];
    Class paramType2 = setter2.getParameterTypes()[0];
    if (paramType1.isAssignableFrom(paramType2)) {
      return setter2;
    }
    if (paramType2.isAssignableFrom(paramType1)) {
      return setter1;
    }
    int methodIndex = methodAccess.getIndex(setter1);
    MethodInvoker invoker = new AmbiguousMethodInvoker(methodAccess, methodIndex,
        MessageFormat.format(
          "Ambiguous setters defined for property ''{0}'' in class ''{1}'' with types ''{2}'' and ''{3}''.",
          property, setter2.getDeclaringClass().getName(), paramType1.getName(),
          paramType2.getName()));
    setMethods.put(property, invoker);
    Type[] paramTypes = TypeParameterResolver.resolveParamTypes(setter1, clazz);
    setTypes.put(property, typeToClass(paramTypes[0]));
    return null;
  }

  private void addSetMethod(String name, Method method) {
    int methodIndex = methodAccess.getIndex(method);
    MethodInvoker invoker = new MethodInvoker(methodAccess, methodIndex);
    setMethods.put(name, invoker);
    Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, clazz);
    setTypes.put(name, typeToClass(paramTypes[0]));
  }

  private boolean isValidPropertyName(String name) {
    return (!name.startsWith("$") && !"serialVersionUID".equals(name) && !"class".equals(name));
  }

  private Class typeToClass(Type src) {
    Class result = null;
    if (src instanceof Class) {
      result = (Class) src;
    } else if (src instanceof ParameterizedType) {
      result = (Class) ((ParameterizedType) src).getRawType();
    } else if (src instanceof GenericArrayType) {
      Type componentType = ((GenericArrayType) src).getGenericComponentType();
      if (componentType instanceof Class) {
        result = Array.newInstance((Class) componentType, 0).getClass();
      } else {
        Class componentClass = typeToClass(componentType);
        result = Array.newInstance(componentClass, 0).getClass();
      }
    }
    if (result == null) {
      result = Object.class;
    }
    return result;
  }

  /**
   * This method returns an array containing all methods declared in this class and any superclass.
   * 
* We use this method, instead of the simpler Class.getMethods(),
* because we want to look for private methods as well. * @param clazz The class * @return An array containing all methods in this class */ private Method[] getClassMethods(Class clazz) { Map uniqueMethods = new HashMap<>(); Class currentClass = clazz; while (currentClass != null && currentClass != Object.class) { addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods()); // we also need to look for interface methods - // because the class may be abstract Class[] interfaces = currentClass.getInterfaces(); for (Class anInterface : interfaces) { addUniqueMethods(uniqueMethods, anInterface.getMethods()); } currentClass = currentClass.getSuperclass(); } Collection methods = uniqueMethods.values(); return methods.toArray(new Method[0]); } private void addUniqueMethods(Map uniqueMethods, Method[] methods) { for (Method currentMethod : methods) { if (!currentMethod.isBridge()) { String signature = getSignature(currentMethod); // check to see if the method is already known // if it is known, then an extended class must have // overridden a method if (!uniqueMethods.containsKey(signature)) { uniqueMethods.put(signature, currentMethod); } } } } private String getSignature(Method method) { StringBuilder sb = new StringBuilder(); Class returnType = method.getReturnType(); sb.append(returnType.getName()).append('#'); sb.append(method.getName()); Class[] parameters = method.getParameterTypes(); for (int i = 0; i < parameters.length; i++) { sb.append(i == 0 ? ':' : ',').append(parameters[i].getName()); } return sb.toString(); } public Class getClazz() { return clazz; } public MethodAccess getMethodAccess() { return methodAccess; } public FieldAccess getFieldAccess() { return fieldAccess; } public Field[] getFields() { return this.getFieldAccess().getFields(); } public Constructor getDefaultConstructor() { if (defaultConstructor != null) { return defaultConstructor; } throw new ReflectionException("There is no default constructor for " + clazz); } public boolean hasDefaultConstructor() { return defaultConstructor != null; } /** * Gets the type for a property getter. * @param propertyName - the name of the property * @return The Class of the property getter */ public Class getSetterType(String propertyName) { Class typeClazz = setTypes.get(propertyName); if (typeClazz == null) { throw new ReflectionException( "There is no setter for property named '" + propertyName + "' in '" + clazz + "'"); } return typeClazz; } /** * Gets the type for a property getter. * @param propertyName - the name of the property * @return The Class of the property getter */ public Class getGetterType(String propertyName) { Class typeClazz = getTypes.get(propertyName); if (typeClazz == null) { throw new ReflectionException( "There is no getter for property named '" + propertyName + "' in '" + clazz + "'"); } return typeClazz; } public Invoker getSetInvoker(String propertyName) { Invoker method = setMethods.get(propertyName); if (method == null) { throw new ReflectionException( "There is no setter for property named '" + propertyName + "' in '" + clazz + "'"); } return method; } public Invoker getGetInvoker(String propertyName) { Invoker method = getMethods.get(propertyName); if (method == null) { throw new ReflectionException( "There is no getter for property named '" + propertyName + "' in '" + clazz + "'"); } return method; } /** * Check if the control member is accessible. * @return If the control member is accessible, it return {@literal true} * @since 1.0.4 */ public static boolean canControlMemberAccessible() { try { SecurityManager securityManager = System.getSecurityManager(); if (null != securityManager) { securityManager.checkPermission(new ReflectPermission("suppressAccessChecks")); } } catch (SecurityException e) { return false; } return true; } /** * Determine whether a field type is a regular attribute
* Fields modified with static and final do not belong to a regular field for the time being. * @param field field object * @return true or false */ public static boolean isNotGeneralProperty(Field field) { if (field == null) { return true; } int modifiers = field.getModifiers(); return Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy