code: package org.gridkit.jvmtool.stacktrace;
  
  import java.io.BufferedOutputStream;
  import java.io.DataOutput;
  import java.io.DataOutputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.zip.DeflaterOutputStream;
  
  import org.gridkit.jvmtool.codec.stacktrace.ThreadSnapshotEvent;
  import org.gridkit.jvmtool.codec.stacktrace.ThreadSnapshotEventPojo;
  import org.gridkit.jvmtool.codec.stacktrace.ThreadTraceEvent;
  import org.gridkit.jvmtool.event.Event;
  import org.gridkit.jvmtool.event.MultiCounterEvent;
  import org.gridkit.jvmtool.event.SimpleTagCollection;
  import org.gridkit.jvmtool.event.TagCollection;
  import org.gridkit.jvmtool.event.TaggedEvent;
  import org.gridkit.jvmtool.event.TimestampedEvent;
  import org.gridkit.jvmtool.event.UniversalEventWriter;
  import org.gridkit.jvmtool.jvmevents.JvmEvents;
  
  class StackTraceEventWriterV4 implements UniversalEventWriter {
  
      private DataOutputStream dos;
      private Map<String, Integer> stringDic = new HashMap<String, Integer>();
      private RotatingStringDictionary dynDic = new RotatingStringDictionary(512);
      {
          for(String s: StackTraceCodec.PRESET_TAG_KEY_V4) {
              stringDic.put(s, stringDic.size() + 1);
          }
          for(String s: StackTraceCodec.PRESET_TAG_TAG_V4) {
              dynDic.intern(s);
          }
      }
      private TagDictionary tagSetDic = new TagDictionary(4 << 10);
      private TagDictionary counterSetDic = new TagDictionary(512);
      private Map<StackFrame, Integer> frameDic = new HashMap<StackFrame, Integer>();
  
      private TagEncoder encoder = new TagEncoder();
      private TagEncoder counterTagEncoder = new CounterTagEncoder();
  
      // event encoding details
      private SimpleTagCollection tagBuilder = new SimpleTagCollection();
      private SimpleTagCollection counterBuilder = new SimpleTagCollection();
      private int counterSetRef;
  
      private ThreadSnapshotEventPojo eventBuf = new ThreadSnapshotEventPojo();
  
      public StackTraceEventWriterV4(OutputStream os) throws IOException {
          // Magic is written by factory class
          DeflaterOutputStream def = new DeflaterOutputStream(os);
          this.dos = new DataOutputStream(new BufferedOutputStream(def, 32 << 10));
      }
  
      @Override
      public synchronized void store(Event event) throws IOException {
          try {
              if (event instanceof Error) {
                  throw new IllegalArgumentException();
              }
              else if (event instanceof ThreadSnapshotEvent) {
                  // enrich tags
                  copyToBuf((ThreadSnapshotEvent) event);
                  storeThreadEvent(eventBuf);
              }
              else if (event instanceof ThreadTraceEvent) {
                  storeThreadEvent((ThreadTraceEvent) event);
              }
              else {
                  storeCommonEvent(event);
              }
          }
          catch(IOException e) {
              close();
              throw e;
          }
          catch(RuntimeException e) {
              close();
              throw e;
          }
      }
  
      private void copyToBuf(ThreadSnapshotEvent event) {
          eventBuf.loadFrom(event);
  //        eventBuf.tags().put(JvmEvents.JVM_EVENT_KEY, JvmEvents.EVENT_THREAD_SNAPSHOT);
          if (event.threadId() >= 0) {
              eventBuf.counters().set(JvmEvents.THREAD_ID, event.threadId());
          }
          if (event.threadName() != null) {
              eventBuf.tags().remove(JvmEvents.THREAD_NAME);
              eventBuf.tags().put(JvmEvents.THREAD_NAME, event.threadName());
          }
          if (event.threadState() != null) {
              eventBuf.tags().remove(JvmEvents.THREAD_STATE);
              eventBuf.tags().put(JvmEvents.THREAD_STATE, event.threadState().toString());
          }
      }
  
      protected void storeThreadEvent(ThreadTraceEvent snap) throws IOException {
  
          TagCollection stags = (snap instanceof TaggedEvent) ? ((TaggedEvent)snap).tags() : null;
          CounterCollection scc = (snap instanceof MultiCounterEvent) ? ((MultiCounterEvent)snap).counters() : null;
  
          tagBuilder.clear();
          markTags(); // mark unconditionally
  
          long timestamp = -1;
          if (snap instanceof TimestampedEvent) {
              markTimestamp();
              timestamp = ((TimestampedEvent) snap).timestamp();
          }
  
          //TODO empty stack trace is encoded for compatibility with old readers
          if (snap.stackTrace() != null /*&& !snap.stackTrace().isEmpty()*/) {
              markThreadStackTrace();
              for(StackFrame ste: snap.stackTrace()) {
                  intern(ste);
              }
          }
  
          if (scc != null) {
              ensureCounters(scc);
          }
  
          int tagSetId = ensureTagSet(stags);
  
          dos.writeByte(StackTraceCodec.TAG_EVENT);
          StackTraceCodec.writeVarInt(dos, tagSetId);
  
          writeTimestamp(timestamp);
          writeCounters(scc);
          writeTrace(snap.stackTrace());
      }
  
      protected void storeCommonEvent(Event snap) throws IOException {
  
          tagBuilder.clear();
          markTags();
  
          long timestamp = -1;
          if (snap instanceof TimestampedEvent) {
              markTimestamp();
              timestamp = ((TimestampedEvent) snap).timestamp();
          }
  
          CounterCollection counters = null;
          if (snap instanceof MultiCounterEvent) {
              ensureCounters(((MultiCounterEvent) snap).counters());
              counters = ((MultiCounterEvent) snap).counters();
          }
  
          int tagSetId;
  
          if (snap instanceof TaggedEvent) {
              tagSetId = ensureTagSet(((TaggedEvent) snap).tags());
          }
          else {
              tagSetId = ensureTagSet(new SimpleTagCollection());
          }
  
          dos.writeByte(StackTraceCodec.TAG_EVENT);
          StackTraceCodec.writeVarInt(dos, tagSetId);
  
          writeTimestamp(timestamp);
          writeCounters(counters);
      }
  
      private void writeTimestamp(long timestamp) throws IOException {
          if (hasTimestampMark()) {
              StackTraceCodec.writeTimestamp(dos, timestamp);
          }
      }
  
      private void writeCounters(CounterCollection counters) throws IOException {
  
          if (hasCountersMark()) {
              StackTraceCodec.writeVarInt(dos, counterSetRef);
              for(String key: counterBuilder) {
                  long v = counters.getValue(key);
                  StackTraceCodec.writeVarLong(dos, v);
              }
          }
      }
  
      private void writeTrace(StackFrameList trace) throws IOException {
          if (hasStackTraceMark()) {
              int n = 0;
              for(@SuppressWarnings("unused") StackFrame sf: trace) {
                  ++n;
              }
              StackTraceCodec.writeVarInt(dos, n);
              for(StackFrame ste: trace) {
                  StackTraceCodec.writeVarInt(dos, intern(ste));
              }
          }
      }
  
      private int intern(String str) throws IOException {
          if (str == null) {
              return 0;
          }
          if (!stringDic.containsKey(str)) {
              dos.write(StackTraceCodec.TAG_STRING);
              dos.writeUTF(str);
              int n = stringDic.size() + 1;
              stringDic.put(str, n);
          }
          return stringDic.get(str);
      }
  
      private int internDyn(String str) throws IOException {
          if (str == null) {
              return 0;
          }
          int n = dynDic.intern(str);
          if (n < 0) {
              n = ~n;
              dos.write(StackTraceCodec.TAG_DYN_STRING);
              StackTraceCodec.writeVarInt(dos, n + 1);
              dos.writeUTF(str);
          }
          n = n + 1; // leave zero to be null
          return n;
      }
  
      private int intern(StackFrame ste) throws IOException {
          if (!frameDic.containsKey(ste)) {
              String pkg = ste.getClassName();
              int c = pkg.lastIndexOf('.');
              String cn = c < 0 ? pkg : pkg.substring(c + 1);
              pkg = c < 0 ? null : pkg.substring(0, c);
              String mtd = ste.getMethodName();
              String file = ste.getSourceFile();
              int line = ste.getLineNumber() + 2;
              if (line < 0) {
                  line = 0;
              }
              int npkg = intern(pkg);
              int ncn = intern(cn);
              int nmtd = intern(mtd);
              int nfile = intern(file);
              dos.writeByte(StackTraceCodec.TAG_FRAME);
              StackTraceCodec.writeVarInt(dos, npkg);
              StackTraceCodec.writeVarInt(dos, ncn);
              StackTraceCodec.writeVarInt(dos, nmtd);
              StackTraceCodec.writeVarInt(dos, nfile);
              StackTraceCodec.writeVarInt(dos, line);
              int n = frameDic.size() + 1;
              frameDic.put(ste, n);
          }
          return frameDic.get(ste);
      }
  
      private int ensureTagSet(TagCollection tags) throws IOException {
          try {
              if (tags != null) {
                  for(String key: tags) {
                      if (!StackTraceCodec.TK_PART.equals(key)) {
                          // ignore storage tags
                          for(String tag: tags.tagsFor(key)) {
                              tagBuilder.put(key, tag);
                          }
                      }
                  }
              }
  
              int tagSetId = tagSetDic.intern(tagBuilder, encoder);
              return tagSetId;
          }
          catch(CapsuleException e) {
              throw uncapsuleExcepion(e);
          }
      }
  
      private void ensureCounters(CounterCollection counters) throws IOException {
          try {
              counterSetRef = -1;
              if (counters != null) {
                  Iterator<String> it = counters.iterator();
                  if (it.hasNext()) {
                      markCounters();
  
                      counterBuilder.clear();
                      while(it.hasNext()) {
                          String key = it.next();
                          if (counters.getValue(key) >= 0) {
                              counterBuilder.put(key, "");
                          }
                      }
  
                      counterSetRef = counterSetDic.intern(counterBuilder, counterTagEncoder);
                      return ;
                  }
              }
          }
          catch(CapsuleException e) {
              throw uncapsuleExcepion(e);
          }
      }
  
      protected IOException uncapsuleExcepion(CapsuleException e) throws IOException {
          if (e.getCause() instanceof IOException) {
              throw (IOException)e.getCause();
          }
          else if (e.getCause() instanceof RuntimeException) {
              throw (RuntimeException)e.getCause();
          }
          else {
              throw new IOException(e.getCause());
          }
      }
  
      protected void markTimestamp() {
          tagBuilder.put(StackTraceCodec.TK_PART, StackTraceCodec.TK_PART_TIMESTAMP);
      }
  
      protected void markCounters() {
          tagBuilder.put(StackTraceCodec.TK_PART, StackTraceCodec.TK_PART_COUNTERS);
      }
  
      protected void markTags() {
          tagBuilder.put(StackTraceCodec.TK_PART, StackTraceCodec.TK_PART_TAGS);
      }
  
      protected void markThreadStackTrace() {
          tagBuilder.put(StackTraceCodec.TK_PART, StackTraceCodec.TK_PART_THREAD_STACK);
      }
  
      protected boolean hasTimestampMark() {
          return tagBuilder.contains(StackTraceCodec.TK_PART, StackTraceCodec.TK_PART_TIMESTAMP);
      }
  
      protected boolean hasCountersMark() {
          return tagBuilder.contains(StackTraceCodec.TK_PART, StackTraceCodec.TK_PART_COUNTERS);
      }
  
      protected boolean hasStackTraceMark() {
          return tagBuilder.contains(StackTraceCodec.TK_PART, StackTraceCodec.TK_PART_THREAD_STACK);
      }
  
      @Override
      public synchronized void close() {
          try {
              dos.close();
          } catch (IOException e) {
              // ignore
          }
          stringDic.clear();
          frameDic.clear();
      }
  
      private class TagEncoder implements TagDictionary.TagSetEncoder {
  
          DataBuffer buffer = new DataBuffer();
          TagChange pending;
  
          protected int startTag() {
              return StackTraceCodec.TAG_TAG_SET;
          }
  
          @Override
          public int cost(String key, String tag) {
              if (tag == null || tag.length() == 0) {
                  return 2 + keySize(key);
              }
              else {
                  return 3 + keySize(key) + tagSize(tag);
              }
          }
  
          private int keySize(String key) {
              return stringDic.containsKey(key) ? 2 : 2 + key.length();
          }
  
          private int tagSize(String tag) {
              return dynDic.contains(tag) ? 2 : 2 + tag.length();
          }
  
          @Override
          public void startTagSet(int setId, int baseId) {
              try {
                  buffer.writeByte(startTag());
                  StackTraceCodec.writeVarInt(buffer, setId);
                  StackTraceCodec.writeVarInt(buffer, baseId);
  //                System.out.print(" [" + setId + "(" + baseId + ")");
              } catch (IOException e) {
                  throw new CapsuleException(e);
              }
          }
  
          public void push(TagChange change) {
              try {
                  if (pending != null) {
                      if (    (pending.op == StackTraceCodec.DIC_REMOVE_KEY && change.op == StackTraceCodec.DIC_ADD_TAG)
                            ||(pending.op == StackTraceCodec.DIC_ADD_TAG && change.op == StackTraceCodec.DIC_REMOVE_KEY)) {
  
                          if (pending.key.equals(change.key)) {
                              // conflate
                              change.op = StackTraceCodec.DIC_SET_KEY;
                              if (pending.tag != null) {
                                  change.tag = pending.tag;
                              }
                              pending = null;
                          }
                      }
  
                      if (pending != null) {
                          pending.encode(buffer);
                      }
                      pending = null;
                  }
                  if (change.op == StackTraceCodec.DIC_REMOVE_KEY || change.op == StackTraceCodec.DIC_ADD_TAG) {
                      pending = change; // defer
                  }
                  else {
                      change.encode(buffer);
                  }
              } catch (IOException e) {
                  throw new CapsuleException(e);
              }
          }
  
          @Override
          public void append(String key, String tag) {
              if (tag.length() == 0) {
                  push(new TagChange(StackTraceCodec.DIC_ADD_KEY, key, null));
              }
              else {
                  push(new TagChange(StackTraceCodec.DIC_ADD_TAG, key, tag));
              }
          }
  
          @Override
          public void remove(String key) {
              push(new TagChange(StackTraceCodec.DIC_REMOVE_KEY, key, null));
          }
  
          @Override
          public void remove(String key, String tag) {
              push(new TagChange(StackTraceCodec.DIC_REMOVE_TAG, key, tag));
          }
  
          @Override
          public void finishTag() {
              try {
                  if (pending != null) {
                      pending.encode(buffer);
                      pending = null;
                  }
                  buffer.unloadTo(dos);
                  // end of tag delta
                  dos.writeByte(0);
  //                System.out.println("]");
              } catch (IOException e) {
                  throw new CapsuleException(e);
              }
          }
      }
  
      private class TagChange {
  
          int op;
          String key;
          String tag;
  
          public TagChange(int op, String key, String tag) {
              this.op = op;
              this.key = key;
              this.tag = tag;
          }
  
          public void encode(DataOutput out) throws IOException {
              switch(op) {
                  case StackTraceCodec.DIC_ADD_KEY:
                      out.writeByte(StackTraceCodec.DIC_ADD_KEY);
                      StackTraceCodec.writeVarInt(out, intern(key));
  //                    System.out.print(" +" + key);
                      break;
                  case StackTraceCodec.DIC_ADD_TAG:
                      out.writeByte(StackTraceCodec.DIC_ADD_TAG);
                      StackTraceCodec.writeVarInt(out, intern(key));
                      StackTraceCodec.writeVarInt(out, internDyn(tag));
  //                    System.out.print(" +" + key + ":" + tag);
                      break;
                  case StackTraceCodec.DIC_SET_KEY:
                      out.writeByte(StackTraceCodec.DIC_SET_KEY);
                      StackTraceCodec.writeVarInt(out, intern(key));
                      StackTraceCodec.writeVarInt(out, internDyn(tag));
  //                    System.out.print(" =" + key + ":" + tag);
                      break;
                  case StackTraceCodec.DIC_REMOVE_KEY:
                      out.writeByte(StackTraceCodec.DIC_REMOVE_KEY);
                      StackTraceCodec.writeVarInt(out, intern(key));
  //                    System.out.print(" -" + key);
                      break;
                  case StackTraceCodec.DIC_REMOVE_TAG:
                      out.writeByte(StackTraceCodec.DIC_REMOVE_TAG);
                      StackTraceCodec.writeVarInt(out, intern(key));
                      StackTraceCodec.writeVarInt(out, internDyn(tag));
  //                    System.out.print(" -" + key + ":" + tag);
                      break;
                  default:
                      throw new IOException("Encoding error");
              }
          }
      }
  
      private class CounterTagEncoder extends TagEncoder {
  
          @Override
          protected int startTag() {
              return StackTraceCodec.TAG_COUNTER;
          }
      }
  
  
      private static class CapsuleException extends RuntimeException {
  
          private static final long serialVersionUID = 20161218L;
  
          public CapsuleException(Throwable cause) {
              super(cause);
          }
      }
  }
  