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() {} }
  private static final Constructor<?> noConstructor = NotExisting.class.getConstructors()[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 = super.getConstructor(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 = super.getClass(className);
    if(k == null) {
      classCache.put(className, NotExisting.class);
    } else {
      classCache.put(className, k);
    }
    return k;
  }


  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.k != k) return false;
      if(m.nArgs != nArgs) return false;
      return m.method.equals(method);
    }
  }

  @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 != null) return cached;

    Method method = super.findMatchingMethod(methodName, k, nArgs);

    this.methodsByNameAndArgs.put(key, method);
    return method;
  }
}

@@ -881,32 +881,33 @@
 get(key);%0A    if
+ 
 (cached == noCon
@@ -927,32 +927,33 @@
 urn null;%0A    if
+ 
 (cached != null)
@@ -1043,16 +1043,17 @@
 ;%0A    if
+ 
 (constru
@@ -1339,24 +1339,25 @@
 ame);%0A    if
+ 
 (k == NotExi
@@ -1388,16 +1388,17 @@
 ;%0A    if
+ 
 (k != nu
@@ -1453,16 +1453,17 @@
 ;%0A    if
+ 
 (k == nu
@@ -1692,24 +1692,25 @@
  int nArgs;%0A
+%0A
     public M
@@ -2013,16 +2013,17 @@
       if
+ 
 (!(o ins
@@ -2099,16 +2099,17 @@
       if
+ 
 (m.k != 
@@ -2133,16 +2133,17 @@
       if
+ 
 (m.nArgs
@@ -2429,16 +2429,17 @@
 ;%0A    if
+ 
 (cached 

Add some whitespace