code: package org.gridkit.jvmtool.codec.stacktrace;
  
  import static org.assertj.core.api.Assertions.assertThat;
  import static org.gridkit.jvmtool.codec.stacktrace.EventEqualToCondition.eventEquals;
  import static org.gridkit.jvmtool.codec.stacktrace.EventSeqEqualToCondition.exactlyAs;
  
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.lang.Thread.State;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.TreeMap;
  
  import org.gridkit.jvmtool.event.CommonEvent;
  import org.gridkit.jvmtool.event.ErrorEvent;
  import org.gridkit.jvmtool.event.Event;
  import org.gridkit.jvmtool.event.EventReader;
  import org.gridkit.jvmtool.event.MorphingEventReader;
  import org.gridkit.jvmtool.event.SimpleTagCollection;
  import org.gridkit.jvmtool.event.TagCollection;
  import org.gridkit.jvmtool.event.UniversalEventWriter;
  import org.gridkit.jvmtool.stacktrace.CounterCollection;
  import org.gridkit.jvmtool.stacktrace.StackFrame;
  import org.gridkit.jvmtool.stacktrace.StackFrameArray;
  import org.gridkit.jvmtool.stacktrace.StackFrameList;
  import org.gridkit.jvmtool.stacktrace.ThreadEventCodec;
  import org.junit.Test;
  
  public class EventReadWriteTest {
  
      private EventReader<Event> writeReadEvents(Event... events) throws IOException {
          ByteArrayOutputStream bos = new ByteArrayOutputStream();
  
          UniversalEventWriter writer = ThreadEventCodec.createEventWriter(bos);
          for(Event e: events) {
              writer.store(e);
          }
          writer.close();
  
          byte[] data = bos.toByteArray();
          System.out.println("Encoded " + events.length + " events into " + data.length + " bytes");
  
          ByteArrayInputStream bis = new ByteArrayInputStream(data);
  
          EventReader<Event> reader = ThreadEventCodec.createEventReader(bis);
          return reader;
      }
  
      private EventReader<Event> safeWriteReadEvents(Event... events) throws IOException {
          return new MorphingEventReader<Event>(writeReadEvents(events)) {
  
              @Override
              protected Event transform(Event event) {
                  if (event instanceof ErrorEvent) {
                      throw new RuntimeException(((ErrorEvent) event).exception());
                  }
                  return event;
              }
          };
      }
  
      @Test
      public void verify_simple_event() throws Exception {
  
          TestDataEvent a = new TestDataEvent();
  
          a.timestamp(1000);
          a.tag("A", "1");
          a.tag("B", "1");
          a.tag("B", "2");
          a.tag("C", "0");
          a.set("qwerty", 10);
          a.set("QWERTY", Long.MAX_VALUE - Integer.MAX_VALUE);
  
          CommonEvent b = (CommonEvent) safeWriteReadEvents(a).iterator().next();
  
          assertThat(b).isNotInstanceOf(ThreadSnapshotEvent.class);
          assertThat(b.timestamp()).isEqualTo(1000);
          assertThat(b.counters().getValue("qwerty")).isEqualTo(10);
          assertThat(b.counters().getValue("QWERTY")).isEqualTo(Long.MAX_VALUE - Integer.MAX_VALUE);
          assertThat(b.tags()).containsOnly("A", "B", "C");
          assertThat(b.tags().tagsFor("A")).containsOnly("1");
          assertThat(b.tags().tagsFor("B")).containsOnly("1", "2");
          assertThat(b.tags().tagsFor("C")).containsOnly("0");
  
          assertThat(b).is(eventEquals(a));
      }
  
  
      @Test
      public void verify_3_same_events() throws Exception {
  
          TestDataEvent a = new TestDataEvent();
  
          a.timestamp(1000);
          a.tag("A", "1");
          a.tag("B", "1");
          a.tag("B", "2");
          a.tag("C", "0");
          a.set("qwerty", 10);
          a.set("QWERTY", Long.MAX_VALUE - Integer.MAX_VALUE);
  
          EventReader<Event> reader = safeWriteReadEvents(a, a, a);
  
          assertThat((Iterator<Event>)reader).is(EventSeqEqualToCondition.exactlyAs(a, a, a));
      }
  
      @Test
      public void verify_more_simple_events() throws Exception {
  
          TestDataEvent a1 = new TestDataEvent();
  
          a1.timestamp(1000);
          a1.tag("A", "1");
          a1.tag("B", "1");
          a1.tag("B", "2");
          a1.tag("C", "0");
          a1.set("qwerty", 10);
          a1.set("QWERTY", Long.MAX_VALUE - Integer.MAX_VALUE);
  
          TestDataEvent a2 = new TestDataEvent();
  
          a2.timestamp(2000);
          a2.tag("AA", "1");
          a2.tag("BB", "1");
          a2.tag("BB", "2");
          a2.tag("CC", "0");
          a2.set("Q", 20);
          a2.set("q", Long.MAX_VALUE - Integer.MAX_VALUE);
  
          EventReader<Event> reader = safeWriteReadEvents(a1, a2, a1, a2, a1);
  
          assertThat(reader.next()).is(eventEquals(a1));
          assertThat(reader.next()).is(eventEquals(a2));
          assertThat(reader.next()).is(eventEquals(a1));
          assertThat(reader.next()).is(eventEquals(a2));
          assertThat(reader.next()).is(eventEquals(a1));
          assertThat(reader.hasNext()).isFalse();
          assertThat(reader.hasNext()).isFalse();
      }
  
      @Test
      public void verify_thread_snapshot() throws IOException {
  
          ThreadEvent a = new ThreadEvent();
          a.timestamp(10000);
          a.threadId(100);
          a.threadName("Abc");
          a.stackTrace(genTrace(10));
  
          ThreadSnapshotEvent b = (ThreadSnapshotEvent) safeWriteReadEvents(a).iterator().next();
  
          assertThat(b.timestamp()).isEqualTo(10000);
          assertThat(b.threadId()).isEqualTo(100);
          assertThat(b.threadName()).isEqualTo("Abc");
          assertThat(b.threadState()).isNull();
          assertThat(b.counters()).containsExactly("thread.javaId");
          assertThat(b.tags().toString()).isEqualTo("[thread.javaName:Abc]");
  
          assertThat(b).is(eventEquals(a));
      }
  
      @Test
      public void verify_mixed_event_set() throws IOException {
  
          ThreadEvent a1 = new ThreadEvent();
          a1.timestamp(10000);
          a1.threadId(100);
          a1.threadName("Abc");
          a1.stackTrace(genTrace(10));
  
          ThreadEvent a2 = new ThreadEvent();
          a2.timestamp(20000);
          a2.threadId(101);
          a2.threadName("xYZ");
          a2.stackTrace(genTrace(5));
          a2.set("thread.cpu", 1000);
  
          TestDataEvent a3 = new TestDataEvent();
  
          a3.timestamp(1000);
          a3.tag("A", "1");
          a3.tag("B", "1");
          a3.tag("B", "2");
          a3.tag("C", "0");
          a3.set("qwerty", 10);
          a3.set("QWERTY", Long.MAX_VALUE - Integer.MAX_VALUE);
  
          TestDataEvent a4 = new TestDataEvent();
  
          a4.timestamp(2000);
          a4.tag("AA", "1");
          a4.tag("BB", "1");
          a4.tag("BB", "2");
          a4.tag("CC", "0");
          a4.set("Q", 20);
          a4.set("qwerty", Long.MAX_VALUE - Integer.MAX_VALUE);
  
  
          EventReader<Event> reader = safeWriteReadEvents(a1, a2, a3, a4 , a1, a3, a2, a4);
  
          assertThat((Iterable<Event>)reader).is(exactlyAs(a1, a2, a3, a4 , a1, a3, a2, a4));
      }
  
      private StackTraceElement[] genTrace(int n) {
          if (n == 0) {
              return Thread.currentThread().getStackTrace();
          }
          else if (n == 1) {
              return genTrace(n - 1);
          }
          else if (n == 2) {
              return genTrace(n - 1);
          }
          else if (n == 3) {
              return genTrace(n - 1);
          }
          else if (n == 4) {
              return genTrace(n - 1);
          }
          else if (n == 5) {
              return genTrace(n - 1);
          }
          else {
              return genTrace(n - 1);
          }
      }
  
      public static class TestDataEvent implements CommonEvent {
  
          long timestamp = 0;
          SimpleTagCollection tags = new SimpleTagCollection();
          TestCounterCollection counters = new TestCounterCollection();
  
          @Override
          public long timestamp() {
              return timestamp;
          }
  
          @Override
          public CounterCollection counters() {
              return counters;
          }
  
          @Override
          public TagCollection tags() {
              return tags;
          }
  
          public void timestamp(long timestamp) {
              this.timestamp = timestamp;
          }
  
          public void tag(String key, String tag) {
              tags.put(key, tag);
          }
  
          public void set(String key, long value) {
              counters.set(key, value);
          }
      }
  
      public static class ThreadEvent extends TestDataEvent implements ThreadSnapshotEvent {
  
          String threadName;
          long threadId = -1;
          State state = null;
          StackFrameList trace;
  
          @Override
          public long threadId() {
              return threadId;
          }
  
          public void threadId(long threadId) {
              this.threadId = threadId;
              counters.set("thread.javaId", threadId);
          }
  
          @Override
          public String threadName() {
              return threadName;
          }
  
          public void threadName(String threadName) {
              this.threadName = threadName;
              tags.put("thread.javaName", threadName);
          }
  
          @Override
          public State threadState() {
              return state;
          }
  
          public void threadState(State state) {
              this.state = state;
          }
  
          @Override
          public StackFrameList stackTrace() {
              return trace;
          }
  
          public void stackTrace(StackTraceElement[] trace) {
              StackFrame[] ftrace = new StackFrame[trace.length];
              for(int i = 0; i != trace.length; ++i) {
                  ftrace[i] = new StackFrame(trace[i]);
              }
              this.trace = new StackFrameArray(ftrace);
          }
      }
  
      private static class TestCounterCollection implements CounterCollection {
  
          private Map<String, Long> counters = new TreeMap<String, Long>();
  
          public void set(String key, long value) {
              counters.put(key, value);
          }
  
          @Override
          public Iterator<String> iterator() {
              return counters.keySet().iterator();
          }
  
          @Override
          public long getValue(String key) {
              Long n  = counters.get(key);
              return  n == 0 ? Long.MIN_VALUE : n;
          }
  
          public TestCounterCollection clone() {
              throw new UnsupportedOperationException();
          }
  
          public String toString() {
              StringBuilder sb = new StringBuilder();
              sb.append('[');
              for(String key: this) {
                  if (sb.length() > 1) {
                      sb.append(", ");
                  }
                  sb.append(key).append(": ").append(getValue(key));
              }
              sb.append(']');
              return sb.toString();
          }
      }
  }
  