code: /**
   * Copyright 2014 Alexey Ragozin
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.gridkit.jvmtool.stacktrace;
  
  import java.io.DataInput;
  import java.io.DataInputStream;
  import java.io.DataOutput;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.io.UnsupportedEncodingException;
  import java.lang.Thread.State;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.NoSuchElementException;
  
  import org.gridkit.jvmtool.jvmevents.JvmEvents;
  
  public class StackTraceCodec {
  
      static final byte[] MAGIC =  toBytes("TRACEDUMP_1 ");
      static final byte[] MAGIC2 = toBytes("TRACEDUMP_2 ");
      //static final byte[] MAGIC3 = toBytes("TRACEDUMP_3 ");
      static final byte[] MAGIC4 = toBytes("EVENTDUMP_1 ");
  
      static final String TK_PART = "(stored-parts)";
      static final String TK_PART_TIMESTAMP = "timestamp";
      static final String TK_PART_THREAD_DETAILS = "thread-details";
      static final String TK_PART_THREAD_STACK = "thread-stack";
      static final String TK_PART_TAGS = "tags";
      static final String TK_PART_COUNTERS = "counters";
  
      private static byte[] toBytes(String text) {
          try {
              return text.getBytes("UTF8");
          } catch (UnsupportedEncodingException e) {
              throw new RuntimeException(e);
          }
      }
  
      static final byte TAG_STRING = 1;
      static final byte TAG_FRAME = 2;
      static final byte TAG_EVENT = 3;
      static final byte TAG_DYN_STRING = 4;
      static final byte TAG_COUNTER = 5;
      static final byte TAG_TAG_SET = 6;
  
      static final long TIME_ANCHOR = 1391255854894l;
  
      static final byte DIC_ADD_TAG = 1;
      static final byte DIC_ADD_KEY = 2;
      static final byte DIC_SET_KEY = 3;
      static final byte DIC_REMOVE_KEY = 4;
      static final byte DIC_REMOVE_TAG = 5;
  
      static final String[] PRESET_TAG_KEY_V4 = new String[] {
              "(stored-parts)",
              JvmEvents.JVM_EVENT_KEY,
              JvmEvents.THREAD_ID,
              JvmEvents.THREAD_NAME,
              JvmEvents.THREAD_STATE,
              ThreadCounters.CPU_TIME_MS,
              ThreadCounters.USER_TIME_MS,
              ThreadCounters.ALLOCATED_BYTES,
              ThreadCounters.WAIT_COUNTER,
              ThreadCounters.WAIT_TIME_MS,
              ThreadCounters.BLOCKED_COUNTER,
              ThreadCounters.BLOCKED_TIME_MS,
              JvmEvents.GC_NAME,
              JvmEvents.GC_MEMORY_SPACES,
              JvmEvents.GC_COUNT,
              JvmEvents.GC_TOTAL_TIME_MS,
  
      };
  
      static final String[] PRESET_TAG_TAG_V4 = new String[] {
              "",
              TK_PART_COUNTERS,
              TK_PART_TAGS,
              TK_PART_THREAD_DETAILS,
              TK_PART_THREAD_STACK,
              TK_PART_TIMESTAMP,
              JvmEvents.EVENT_THREAD_SNAPSHOT,
              JvmEvents.EVENT_GC,
              JvmEvents.EVENT_STW
      };
  
      public static StackTraceWriter newWriter(OutputStream os) throws IOException {
          return new StackTraceWriterV2(os);
      }
  
      public static StackTraceReader newReader(InputStream is) throws IOException {
          return newReaderInternal(is);
      }
  
      public static StackTraceReader newReader(String... files) throws IOException {
          return newReaderInternal(files);
      }
  
      public static StackTraceReader newEventReader(InputStream is) throws IOException {
          return newReaderInternal(is);
      }
  
      public static StackTraceReader newEventReader(String... files) throws IOException {
          return newReaderInternal(files);
      }
  
      private static StackTraceReader newReaderInternal(InputStream is) throws IOException {
          DataInputStream dis = new DataInputStream(is);
          byte[] magic = new byte[MAGIC.length];
          dis.readFully(magic);
          if (Arrays.equals(MAGIC, magic)) {
              return new StackTraceReaderV1(is);
          }
          else if (Arrays.equals(MAGIC2, magic)) {
              return new StackTraceReaderV2(is);
          }
          else if (Arrays.equals(MAGIC4, magic)) {
              return new LegacyStackReader(ThreadEventCodec.createEventReader(magic,  is));
          }
          else {
              throw new IOException("Unknown magic [" + new String(magic) + "]");
          }
      }
  
      private static StackTraceReader newReaderInternal(String... files) throws IOException {
          final List<String> fileList = new ArrayList<String>(Arrays.asList(files));
          return new ChainedStackTraceReader() {
  
              @Override
              protected StackTraceReader next() {
                  while(!fileList.isEmpty()) {
                      String file = fileList.remove(0);
                      File f = new File(file);
                      if (!f.isFile()) {
                          continue;
                      }
                      try {
                          FileInputStream fis = new FileInputStream(file);
                          return newReaderInternal(fis);
                      } catch (IOException e) {
                          throw new RuntimeException(e);
                      }
                  }
                  return null;
              }
          };
      }
  
      public abstract static class ChainedStackTraceReader implements StackTraceReader {
  
          private StackTraceReader current;
  
          protected abstract StackTraceReader next();
  
          @Override
          public boolean isLoaded() {
              if (current == null) {
                  current = next();
              }
              return current != null && current.isLoaded();
          }
  
          @Override
          public long getThreadId() {
              if (current == null) {
                  new NoSuchElementException();
              }
              return current.getThreadId();
          }
  
          @Override
          public String getThreadName() {
              if (current == null) {
                  new NoSuchElementException();
              }
              return current.getThreadName();
          }
  
          @Override
          public long getTimestamp() {
              if (current == null) {
                  new NoSuchElementException();
              }
              return current.getTimestamp();
          }
  
          @Override
          public State getThreadState() {
              if (current == null) {
                  new NoSuchElementException();
              }
              return current.getThreadState();
          }
  
          @Override
          public CounterCollection getCounters() {
              if (current == null) {
                  new NoSuchElementException();
              }
              return current.getCounters();
          }
  
          @Override
          public StackTraceElement[] getTrace() {
              if (current == null) {
                  new NoSuchElementException();
              }
              return current.getTrace();
          }
  
          @Override
          public StackFrameList getStackTrace() {
              if (current == null) {
                  new NoSuchElementException();
              }
              return current.getStackTrace();
          }
  
          @Override
          public boolean loadNext() throws IOException {
              if (current == null) {
                  current = next();
                  if (current == null) {
                      return false;
                  }
              }
              if (current.loadNext()) {
                  return true;
              }
              else {
                  current = null;
                  return loadNext();
              }
          }
      }
  
      static int readVarInt(DataInput dis) throws IOException {
          int b = dis.readByte();
          if ((b & 0x80) == 0) {
              return 0x7F & b;
          }
          else {
              int v = (0x7F & b);
              b = dis.readByte();
              v |= ((0x7F & b) << 7);
              if ((b & 0x80) == 0) {
                  return v;
              }
              b = dis.readByte();
              v |= ((0x7F & b) << 14);
              if ((b & 0x80) == 0) {
                  return v;
              }
              b = dis.readByte();
              v |= ((0xFF & b) << 21);
              return v;
          }
      }
  
      static long readVarLong(DataInput dis) throws IOException {
          byte b = dis.readByte();
          if ((b & 0x80) == 0) {
              return 0x7F & b;
          }
          else {
              long v = (0x7F & b);
              b = dis.readByte();
              v |= ((0x7Fl & b) << 7); // byte 2
              if ((b & 0x80) == 0) {
                  return v;
              }
              b = dis.readByte();
              v |= ((0x7Fl & b) << 14); // byte 3
              if ((b & 0x80) == 0) {
                  return v;
              }
              b = dis.readByte();
              v |= ((0x7Fl & b) << 21); // byte 4
              if ((b & 0x80) == 0) {
                  return v;
              }
              b = dis.readByte();
              v |= ((0x7Fl & b) << 28); // byte 5
              if ((b & 0x80) == 0) {
                  return v;
              }
              b = dis.readByte();
              v |= ((0x7Fl & b) << 35); // byte 6
              if ((b & 0x80) == 0) {
                  return v;
              }
              b = dis.readByte();
              v |= ((0x7Fl & b) << 42); // byte 7
              if ((b & 0x80) == 0) {
                  return v;
              }
              b = dis.readByte();
              v |= ((0x7Fl & b) << 49); // byte 8
              if ((b & 0x80) == 0) {
                  return v;
              }
              b = dis.readByte();
              v |= ((0xFFl & b) << 56); // byte 9
  
              return v;
          }
      }
  
      static long readTimestamp(DataInput dis) throws IOException {
          return TIME_ANCHOR + readVarLong(dis);
      }
  
      static void writeVarInt(DataOutput dos, int v) throws IOException {
          if (v < 0) {
              throw new IllegalArgumentException("Out of bounds: " + v);
          }
          int val = v;
          if ((val & 0xFFFFFF80) == 0) {
              dos.write(val);
              return;
          }
          dos.write(0x80 | (0x7F & val));
          val >>= 7;
          if ((val & 0xFFFFFF80) == 0) {
              dos.write(val);
              return;
          }
          dos.write(0x80 | (0x7F & val));
          val >>= 7;
          if ((val & 0xFFFFFF80) == 0) {
              dos.write(val);
              return;
          }
          dos.write(0x80 | (0x7F & val));
          val >>= 7;
          if ((val & 0xFFFFFF00) == 0) {
              dos.write(val);
              return;
          }
          else {
              throw new IllegalArgumentException("Out of bounds: " + v);
          }
      }
  
      static void writeVarLong(DataOutput dos, long v) throws IOException {
          long val = v;
          if ((val & 0xFFFFFFFFFFFFFF80l) == 0) {
              dos.write((int)(0xFF & val));
              return;
          }
          dos.write(0x80 | (int)(0x7F & val));
          val >>>= 7;
          if ((val & 0xFFFFFFFFFFFFFF80l) == 0) { // byte 2
              dos.write((int)(0xFF & val));
              return;
          }
          dos.write(0x80 | (int)(0x7F & val));
          val >>>= 7;
          if ((val & 0xFFFFFFFFFFFFFF80l) == 0) { // byte 3
              dos.write((int)(0xFF & val));
              return;
          }
          dos.write(0x80 | (int)(0x7F & val));
          val >>>= 7;
          if ((val & 0xFFFFFFFFFFFFFF80l) == 0) { // byte 4
              dos.write((int)(0xFF & val));
              return;
          }
          dos.write(0x80 | (int)(0x7F & val));
          val >>>= 7;
          if ((val & 0xFFFFFFFFFFFFFF80l) == 0) { // byte 5
              dos.write((int)(0xFF & val));
              return;
          }
          dos.write(0x80 | (int)(0x7F & val));
          val >>>= 7;
          if ((val & 0xFFFFFFFFFFFFFF80l) == 0) { // byte 6
              dos.write((int)(0xFF & val));
              return;
          }
          dos.write(0x80 | (int)(0x7F & val));
          val >>>= 7;
          if ((val & 0xFFFFFFFFFFFFFF80l) == 0) { // byte 7
              dos.write((int)(0xFF & val));
              return;
          }
          dos.write(0x80 | (int)(0x7F & val));
          val >>>= 7;
          if ((val & 0xFFFFFFFFFFFFFF80l) == 0) { // byte 8
              dos.write((int)(0xFF & val));
              return;
          }
          dos.write(0x80 | (int)(0x7F & val));
          val >>>= 7;
          if ((val & 0xFFFFFFFFFFFFFF00l) == 0) { // byte 9
              dos.write((int)(0xFF & val));
              return;
          }
          else {
              throw new IllegalArgumentException("Out of bounds: " + v);
          }
      }
  
      static void writeTimestamp(DataOutput dos, long epoch) throws IOException {
          writeVarLong(dos, epoch - TIME_ANCHOR);
      }
  }
  