code: package org.gridkit.jvmtool.event;
  
  import java.util.Iterator;
  import java.util.NoSuchElementException;
  
  /**
   * This reader adds special handling for {@link ErrorEvent} and
   * enforces class type restriction on stream events.
   *
   * @author Alexey Ragozin (alexey.ragozin@gmail.com)
   */
  public class ShieldedEventReader<T extends Event> implements EventReader<T> {
  
      private static final ErrorHandler THROW_HANDLER = new ErrorHandler() {
  
          @Override
          public boolean onException(Exception e) {
              if (e instanceof RuntimeException) {
                  throw (RuntimeException)e;
              }
              else {
                  throw new RuntimeException(e);
              }
          }
      };
  
      private static final ErrorHandler SILENT_ERROR_HANDLER = new ErrorHandler() {
  
          @Override
          public boolean onException(Exception e) {
              return true; // suppress
          }
      };
  
      public static <T extends Event> EventReader<T> shield(EventReader<? super T> reader, Class<T> event, boolean suppressErrors) {
          return new ShieldedEventReader<T>(reader, event, suppressErrors ? SILENT_ERROR_HANDLER : THROW_HANDLER);
      }
  
      private final EventReader<? extends Event> nested;
      private final Class<T> classFilter;
      private final ErrorHandler errorHandler;
      private T nextEvent;
  
      public ShieldedEventReader(EventReader<? extends Event> nested, Class<T> classFilter) {
          this(nested, classFilter, THROW_HANDLER);
      }
  
      public ShieldedEventReader(EventReader<? extends Event> nested, Class<T> classFilter, ErrorHandler errorHandler) {
          this.nested = nested;
          this.classFilter = classFilter;
          this.errorHandler = errorHandler;
      }
  
      @Override
      public <M extends Event> EventReader<M> morph(EventMorpher<T, M> morpher) {
          return MorphingEventReader.morph(this, morpher);
      }
  
      @Override
      public Iterator<T> iterator() {
          return this;
      }
  
      private void seekNext() {
          try {
              while(nested.hasNext()) {
                  Event e = nested.next();
                  if (e instanceof ErrorEvent) {
                      // forward to error handler
                      if (errorHandler.onException(((ErrorEvent) e).exception())) {
                          // suppress
                          continue;
                      }
                  }
                  if (classFilter.isInstance(e)) {
                      nextEvent = classFilter.cast(e);
                      break;
                  }
              }
          }
          catch(Exception e) {
              errorHandler.onException(e);
              if (classFilter.isAssignableFrom(SimpleErrorEvent.class)) {
                  nextEvent = classFilter.cast(new SimpleErrorEvent(e));
              }
              else {
                  seekNext();
              }
          }
      }
  
      @Override
      public boolean hasNext() {
          if (nextEvent == null) {
              seekNext();
          }
          return nextEvent != null;
      }
  
      @Override
      public T next() {
          if (!hasNext()) {
              throw new NoSuchElementException();
          }
          T event = nextEvent;
          nextEvent = null;
          return event;
      }
  
      @Override
      public T peekNext() {
          if (!hasNext()) {
              throw new NoSuchElementException();
          }
          T event = nextEvent;
          return event;
      }
  
      @Override
      public void remove() {
          throw new UnsupportedOperationException();
      }
  
      @Override
      public void dispose() {
          nested.dispose();
      }
  }
  