package fitnesse.slim.fixtureInteraction;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class CachedInteraction extends DefaultInteraction {
  private static final class NotExisting { public NotExisting() {} public void doIt() {} }
  private static final Constructor<?> noConstructor = NotExisting.class.getConstructors()[0];
  private static final Method noMethod = NotExisting.class.getDeclaredMethods()[0];

  private final Map<String, Constructor<?>> constructorsByClassAndArgs = new HashMap<>();
  private final Map<String, Class<?>> classCache = new HashMap<>();
  private final Map<MethodKey, Method> methodsByNameAndArgs = new HashMap<>();

  @Override
  protected Constructor<?> getConstructor(Class<?> clazz,
                                          Object[] args) {
    String key = String.format("%s_%d", clazz.getName(), args.length);
    Constructor<?> cached = constructorsByClassAndArgs.get(key);
    if (cached == noConstructor) return null;
    if (cached != null) return cached;

    Constructor<?> constructor = handleConstructorCacheMiss(clazz, args);
    if (constructor == null) {
      constructorsByClassAndArgs.put(key, noConstructor);
    } else {
      constructorsByClassAndArgs.put(key, constructor);
    }
    return constructor;
  }

  @Override
  protected Class<?> getClass(String className) {
    Class<?> k = classCache.get(className);
    if (k == NotExisting.class) return null;
    if (k != null) return k;

    k = handleClassCacheMiss(className);
    if (k == null) {
      classCache.put(className, NotExisting.class);
    } else {
      classCache.put(className, k);
    }
    return k;
  }

  @Override
  protected Method findMatchingMethod(String methodName, Class<?> k, int nArgs) {
    MethodKey key = new MethodKey(k, methodName, nArgs);
    Method cached = this.methodsByNameAndArgs.get(key);
    if (cached == noMethod) return null;
    if (cached != null) return cached;

    Method method = handleMethodCacheMiss(methodName, k, nArgs);

    if (method == null) {
      methodsByNameAndArgs.put(key, noMethod);
    } else {
      methodsByNameAndArgs.put(key, method);
    }
    return method;
  }

  protected Constructor<?> handleConstructorCacheMiss(Class<?> clazz, Object[] args) {
    return super.getConstructor(clazz, args);
  }

  protected Class<?> handleClassCacheMiss(String className) {
    return super.getClass(className);
  }

  protected Method handleMethodCacheMiss(String methodName, Class<?> k, int nArgs) {
    return super.findMatchingMethod(methodName, k, nArgs);
  }

  private static class MethodKey {
    final String k;
    final String method;
    final int nArgs;

    public MethodKey(Class<?> k, String method, int nArgs) {
      this.k = k.getSimpleName();
      this.method = method;
      this.nArgs = nArgs;
    }

    public int hashCode() {
      return nArgs * 31 + method.hashCode() + 31 * k.hashCode();
    }

    @Override
    public boolean equals(Object o) {
      if (!(o instanceof MethodKey)) return false;
      MethodKey m = (MethodKey) o;
      if (m.nArgs != nArgs) return false;
      if (!m.k.equals(k)) return false;
      return m.method.equals(method);
    }
  }
}

@@ -220,99 +220,8 @@
 n %7B%0A
-  private static final class NotExisting %7B public NotExisting() %7B%7D public void doIt() %7B%7D %7D%0A
   pr
@@ -3152,11 +3152,113 @@
    %7D%0A  %7D
+%0A%0A  private static final class NotExisting %7B%0A    public NotExisting() %7B%7D%0A    public void doIt() %7B%7D%0A  %7D
 %0A%7D%0A

Move private static NonExisting class to bottom of file