code: package org.gridkit.jvmtool.event;
  
  import java.io.IOException;
  import java.lang.reflect.InvocationHandler;
  import java.lang.reflect.Method;
  import java.lang.reflect.Proxy;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  public class TypedEventWriterProxy {
  
      public static <T extends TypedEventWriter> T createWriter(Class<T> facade, TypedEventWriterProvider provider) {
          return facade.cast(Proxy.newProxyInstance(facade.getClassLoader(), new Class<?>[]{facade}, new Handler(facade, provider)));
      }
  
      public static WriterBuilder decorate(UniversalEventWriter writer) {
          return new WriterBuilder(writer);
      }
  
      private static class Handler implements InvocationHandler {
  
          private final Class<?> facade;
          private final TypedEventWriterProvider provider;
          private final Map<Method, UniversalEventWriter> methodMap = new HashMap<Method, UniversalEventWriter>();
  
          public Handler(Class<?> facade, TypedEventWriterProvider provider) {
              this.facade = facade;
              this.provider = provider;
          }
  
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args); // delegate
              }
              else if ("close".equals(method.getName()) && method.getParameterTypes().length == 0) {
                  provider.close();
              }
              else {
                  UniversalEventWriter writer = writerFor(method);
                  writer.store((Event) args[0]);
              }
              return null;
          }
  
          private UniversalEventWriter writerFor(Method method) {
              if (methodMap.containsKey(method)) {
                  return methodMap.get(method);
              }
              else {
                  if (method.getParameterTypes().length == 1
                          && Event.class.isAssignableFrom(method.getParameterTypes()[0])
                          && method.getParameterTypes()[0].isInterface()
                          && Event.class.isAssignableFrom(method.getParameterTypes()[0])
                          && method.getReturnType() == void.class) {
  
                      Class<?> type = method.getParameterTypes()[0];
                      @SuppressWarnings({ "unchecked", "rawtypes" })
                      UniversalEventWriter writer = provider.getWriterFor((Class)type);
                      methodMap.put(method, writer);
                      return writer;
                  }
                  else {
                      throw new IllegalArgumentException("Invalid event write method " + method.getName());
                  }
              }
          }
  
          @Override
          public String toString() {
              return facade.getSimpleName() + "(" + provider + ")";
          }
      }
  
      private static class GenericEventWriterProvider implements TypedEventWriterProvider {
  
          private Class<?>[] eventTypes = new Class<?>[0];
          private UniversalEventWriter[] writers = new UniversalEventWriter[0];
  
          @Override
          public <T extends Event> UniversalEventWriter getWriterFor(Class<T> eventInterface) {
              int n = 0;
              for(Class<?> c: eventTypes) {
                  if (c.isAssignableFrom(eventInterface)) {
                      return writers[n];
                  }
                  ++n;
              }
              throw new IllegalArgumentException("Event type " + eventInterface.getSimpleName() + " is not supported");
          }
  
          @Override
          public void close() {
              for(UniversalEventWriter writer: writers) {
                  try {
                      writer.close();
                  }
                  catch(Exception e) {
                  }
              }
          }
  
          public void add(Class<?> facade, UniversalEventWriter writer) {
              int n = eventTypes.length;
              eventTypes = Arrays.copyOf(eventTypes, n + 1);
              writers = Arrays.copyOf(writers, n + 1);
  
              eventTypes[n] = facade;
              writers[n] = writer;
          }
      }
  
      private static class TransformingWriter implements UniversalEventWriter {
  
          private final UniversalEventWriter nested;
          EventTransformer<Event, Event> transformer;
  
          public TransformingWriter(UniversalEventWriter nested, EventTransformer<Event, Event> transformer) {
              this.nested = nested;
              this.transformer = transformer;
          }
  
          @Override
          public void store(Event event) throws IOException {
              event = transformer == null ? event : transformer.transform(event);
              if (event != null) {
                  nested.store(event);
              }
          }
  
          @Override
          public void close() throws IOException {
              nested.close();
          }
      }
  
      private static class ChainTransformer implements EventTransformer<Event, Event> {
  
          private final EventTransformer<Event, Event>[] tchain;
  
          public ChainTransformer(EventTransformer<Event, Event>[] tchain) {
              this.tchain = tchain;
          }
  
          @Override
          public Event transform(Event source) {
              Event e = source;
              for(int i = tchain.length - 1; i >= 0; --i) {
                  e = tchain[i].transform(e);
                  if (e == null) {
                      return null;
                  }
              }
              return e;
          }
      }
  
      public static class WriterBuilder {
  
          private final List<EventTransformer<Event, Event>> tailTransformers = new ArrayList<EventTransformer<Event, Event>>();
          private final TransformingWriter root;
          private GenericEventWriterProvider provider;
  
          public WriterBuilder(UniversalEventWriter writer) {
              root = new TransformingWriter(writer, null);
              provider = new GenericEventWriterProvider();
          }
  
          public WriterBuilder morth(EventTransformer<Event, Event> transformer) {
              tailTransformers.add(transformer);
              return this;
          }
  
          public <T extends Event> WriterBuilder pass(Class<T> facade) {
              provider.add(facade, root);
              return this;
          }
  
          @SuppressWarnings({ "unchecked", "rawtypes" })
          public <T extends Event> WriterBuilder pass(Class<T> facade, EventTransformer<T, Event> transformer) {
              provider.add(facade, new TransformingWriter(root, (EventTransformer)transformer));
              return this;
          }
  
          @SuppressWarnings("unchecked")
          public <T extends Event> WriterBuilder ignore(Class<T> facade) {
              return pass(facade, new VoidTransformer());
          }
  
          @SuppressWarnings({ "unchecked", "rawtypes" })
          public <T extends TypedEventWriter> T facade(Class<T> facade) {
              if (!tailTransformers.isEmpty()) {
                  EventTransformer[] chain = tailTransformers.toArray(new EventTransformer[0]);
                  ChainTransformer tchain = new ChainTransformer(chain);
                  root.transformer = tchain;
              }
  
              T writer = createWriter(facade, provider);
              provider = null;
  
              return writer;
          }
      }
  
      @SuppressWarnings("rawtypes")
      private static class VoidTransformer implements EventTransformer {
          @Override
          public Event transform(Event source) {
              return null;
          }
      }
  }
  