class PropertyFoundResult {
    MethodHandle handle;
    boolean isBooleanType;
    ClassMethodFunction func;



    public PropertyFoundResult(final ClassMethodFunction func) {
      super();
      this.func = func;
    }

    public PropertyFoundResult(final MethodHandle handle, final boolean isBooleanType) {
      super();
      this.isBooleanType = isBooleanType;
      this.handle = handle;
    }

    @Override
    public String toString() {
      return "MethodHandleResult [handle=" + this.handle + ", isBooleanType=" + this.isBooleanType
          + ", func=" + this.func + "]";
    }
  }

  /**
   * static and instance fields property caching
   */
  public static ConcurrentHashMap<Class<?>, Reference<Map<String, PropertyFoundResult>>> cachedProperties =
      new ConcurrentHashMap<Class<?>, Reference<Map<String, PropertyFoundResult>>>();

  /**
   * instance fields setter caching.
   */
  public static ConcurrentHashMap<Class<?>, Reference<Map<String, PropertyFoundResult>>> cachedSettters =
      new ConcurrentHashMap<Class<?>, Reference<Map<String, PropertyFoundResult>>>();

  private static final ReferenceQueue<Map<String, PropertyFoundResult>> cachedSetterRq =
      new ReferenceQueue<>();


  private static final ReferenceQueue<Map<String, PropertyFoundResult>> cachePropertyRq =
      new ReferenceQueue<>();

  /**
   * static method caching
   */
  public static ConcurrentHashMap<Class<?>, Reference<Map<String, PropertyFoundResult>>> cachedMethods =
      new ConcurrentHashMap<Class<?>, Reference<Map<String, PropertyFoundResult>>>();

  private static final ReferenceQueue<Map<String, PropertyFoundResult>> cacheMethodRq =
      new ReferenceQueue<>();

  private static String genGetterName(final String prefix, final String name) {
    StringBuilder sb = new StringBuilder(prefix);
    capitalize(sb, name);
    return sb.toString();
  }

  public static enum PropertyType {
    Getter, StaticField, StaticMethod;

    boolean isStaticProperty() {
      return this == StaticField || this == StaticMethod;
    }
  }

  public static Object fastGetProperty(final Object obj, final String name,
      final PropertyType type) {
    final Class<?> clazz = type.isStaticProperty() ? (Class<?>) obj : obj.getClass();
    Map<String, PropertyFoundResult> results = null;

    if (type == PropertyType.StaticMethod) {
      results = getClassPropertyResults(cachedMethods, cacheMethodRq, clazz);
    } else {
      results = getClassPropertyResults(cachedProperties, cachePropertyRq, clazz);
    }

    try {
      PropertyFoundResult result = results.get(name);
      if (result == null) {
        switch (type) {
          case StaticField:
            result = retrieveStaticFieldHandle(results, clazz, name);
            break;
          case Getter:
            result = retrieveGetterHandle(results, clazz, name);
            break;
          case StaticMethod:
            result = retrieveStaticFunction(results, clazz, name);
            break;
        }
      }

      if (type == PropertyType.StaticMethod) {
        return result.func;
      }

      if (result.handle != null) {
        Object ret =
            type == PropertyType.StaticField ? result.handle.invoke() : result.handle.invoke(obj);
        if (result.isBooleanType && !(ret instanceof Boolean)) {
          putDummyHandle(name, results);
          return throwNoSuchPropertyException(
              "Property `" + name + "` not found in java bean: " + obj);
        }
        return ret;
      } else {
        if (type == PropertyType.StaticMethod) {
          return null;
        }
        return throwNoSuchPropertyException(
            "Property `" + name + "` not found in java bean: " + obj);
      }
    } catch (Throwable t) {
      if (!results.containsKey(name)) {
        putDummyHandle(name, results);
      }
      throw sneakyThrow(t);
    }
  }

  public static Object throwNoSuchPropertyException(final String msg) {
    throw new NoSuchPropertyException(msg);
  }

  private static PropertyFoundResult retrieveStaticFieldHandle(
      final Map<String, PropertyFoundResult> results, final Class<?> clazz, final String name)
      throws IllegalAccessException, NoSuchFieldException {
    PropertyFoundResult result;
    Field field = null;
    try {
      field = clazz.getDeclaredField(name);
    } catch (NoSuchFieldException e) {

    }
    if (field != null && Modifier.isStatic(field.getModifiers())) {
      field.setAccessible(true);
      MethodHandle handle = MethodHandles.lookup().unreflectGetter(field);
      result = new PropertyFoundResult(handle, false);
    } else {
      result = new PropertyFoundResult(null, false);
    }
    results.put(name, result);
    return result;
  }

  private static PropertyFoundResult retrieveStaticFunction(
      final Map<String, PropertyFoundResult> results, final Class<?> clazz, final String name)
      throws IllegalAccessException, NoSuchMethodException {
    PropertyFoundResult result;
    List<Method> methods = getStaticMethods(clazz, name);

    if (methods != null && !methods.isEmpty()) {
      // cast the methods into a function.
      ClassMethodFunction func = new ClassMethodFunction(clazz, true, name, name, methods);
      result = new PropertyFoundResult(func);
    } else {
      result = new PropertyFoundResult(null);
    }
    results.put(name, result);
    return result;
  }

  private static PropertyFoundResult retrieveGetterHandle(
      final Map<String, PropertyFoundResult> results, final Class<?> clazz, final String name)
      throws IllegalAccessException {<FILL_FUNCTION_BODY>

    PropertyFoundResult result;
    List<Method> methods = getInstanceMethods(clazz, genGetterName("get", name));
    boolean isBooleanType = false;

    if (methods == null || methods.isEmpty()) {
      methods = getInstanceMethods(clazz, genGetterName("is", name));
      isBooleanType = true;
    }

    if ((methods == null || methods.isEmpty()) && name.startsWith("is")) {
      // Fix https://github.com/killme2008/aviatorscript/issues/517
      methods = getInstanceMethods(clazz, name);
      isBooleanType = true;
    }

    if (methods != null && !methods.isEmpty()) {
      Method method = methods.get(0);
      for (Method m : methods) {
        if (m.getParameterTypes().length == 0) {
          method = m;
          break;
        }
      }
      method.setAccessible(true);
      MethodHandle handle = MethodHandles.lookup().unreflect(method);
      result = new PropertyFoundResult(handle, isBooleanType);
    } else {
      result = new PropertyFoundResult(null, isBooleanType);
    }
    results.put(name, result);
    return result;
  
<no_super_class>