public Object invoke(Object proxy, Method method, Object[] args) {
        String methodName = method.getName();

        switch (methodName) {
            case "validate":
                checkArgumentLength(method, 0, methodName);
                model.validate(proxy);
                return proxy;

            case "dump":
                checkArgumentLength(method, 0, methodName);
                return invokeDump();

            case "toString":
                checkArgumentLength(method, 0, methodName);
                return invokeToString();

            case "hashCode":
                checkArgumentLength(method, 0, methodName);
                return invokeHashCode();

            case "equals":
                checkArgumentLength(method, 1, methodName);
                if (args[0] instanceof Proxy) {
                    Object otherHandler = Proxy.getInvocationHandler(args[0]);
                    return invokeEquals(otherHandler);
                }
                return false;

            default: {
                String fieldName;
                fieldName = getterFieldNameOrNull(methodName);
                if (fieldName != null) {
                    if (method.isDefault() && !this.objects.containsKey(fieldName)) {
                        // If and only if the method has default implementation, and @Config is not annotated there,
                        // it is tried to call the default implementation directly without proxying.
                        //
                        // methodWithDefaultImpl.invoke(proxy) without this hack would cause infinite recursive calls.
                        //
                        // See hints:
                        // https://rmannibucau.wordpress.com/2014/03/27/java-8-default-interface-methods-and-jdk-dynamic-proxies/
                        // https://stackoverflow.com/questions/22614746/how-do-i-invoke-java-8-default-methods-reflectively
                        //
                        // This hack is required to support `org.joda.time.DateTimeZone` in some Tasks, for example
                        // TimestampParser.Task and TimestampParser.TimestampColumnOption.
                        //
                        // TODO: Remove the hack once a cleaner way is found, or Joda-Time is finally removed.
                        // https://github.com/embulk/embulk/issues/890
                        if (CONSTRUCTOR_MethodHandles_Lookup != null) {
                            synchronized (CONSTRUCTOR_MethodHandles_Lookup) {
                                boolean hasSetAccessible = false;
                                try {
                                    CONSTRUCTOR_MethodHandles_Lookup.setAccessible(true);
                                    hasSetAccessible = true;
                                } catch (SecurityException ex) {
                                    // Skip handling default implementation in case of errors.
                                }

                                if (hasSetAccessible) {
                                    try {
                                        return CONSTRUCTOR_MethodHandles_Lookup
                                                .newInstance(
                                                        method.getDeclaringClass(),
                                                        MethodHandles.Lookup.PUBLIC
                                                                | MethodHandles.Lookup.PRIVATE
                                                                | MethodHandles.Lookup.PROTECTED
                                                                | MethodHandles.Lookup.PACKAGE)
                                                .unreflectSpecial(method, method.getDeclaringClass())
                                                .bindTo(proxy)
                                                .invokeWithArguments();
                                    } catch (Throwable ex) {
                                        // Skip handling default implementation in case of errors.
                                    } finally {
                                        CONSTRUCTOR_MethodHandles_Lookup.setAccessible(false);
                                    }
                                }
                            }
                        }
                    }
                    checkArgumentLength(method, 0, methodName);
                    return invokeGetter(method, fieldName);
                }
                fieldName = setterFieldNameOrNull(methodName);
                if (fieldName != null) {
                    checkArgumentLength(method, 1, methodName);
                    invokeSetter(method, fieldName, args[0]);
                    return this;
                }
            }
        }

        throw new IllegalArgumentException(String.format("Undefined method '%s'", methodName));
    }
Returns a Multimap from fieldName Strings to their getter Methods.

It expects to be called only from TaskSerDe. Multimap is used inside org.embulk.config.
embulk-deps/src/main/java/org/embulk/deps/config/TaskInvocationHandler.java