org.gridkit.jvmtool.stacktrace.StackFrame Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sjk-stacktrace Show documentation
Show all versions of sjk-stacktrace Show documentation
Thread dumps: capture and encoding
The newest version!
package org.gridkit.jvmtool.stacktrace;
import java.util.Comparator;
public class StackFrame implements CharSequence, GenericStackElement {
public static final Comparator COMPARATOR = new FrameComparator();
private static final String NATIVE_METHOD = "Native Method";
private static final String UNKNOWN_SOURCE = "Unknown Source";
private static final int NO_LINE_NUMBER = 0;
private static final int NO_SOURCE = -1;
private static final int NATIVE = -2;
private static final int[] PO10 = {1, 10, 100, 1000, 10000, 100000};
private final String classPrefix;
private final String className;
private String fqcn = null; // fully qualified name
private final String methodName;
private final String fileName;
private final int lineNumber;
private final short textLen;
private final short lineNumberDigits;
private final int hash;
public StackFrame(StackTraceElement ste) {
this(null, ste.getClassName(), ste.getMethodName(), ste.getFileName(), ste.getLineNumber());
}
public StackFrame(String classPrefix, String className, String methodName, String fileName, int lineNumber) {
this.classPrefix = "".equals(classPrefix) ? null : classPrefix;
if (className == null) {
throw new NullPointerException("Class name cannot be null");
}
this.className = className;
this.methodName = methodName;
this.fileName = lineNumber == -2 ? null : fileName; // fileName is ignored for native
this.lineNumber = lineNumber;
if (lineNumber == -2) {
lineNumberDigits = NATIVE;
}
else if (fileName == null) {
lineNumberDigits = NO_SOURCE;
}
else if (lineNumber == -1) {
lineNumberDigits = NO_LINE_NUMBER;
}
else {
lineNumberDigits = (short) (
lineNumber < 10 ? 1 :
lineNumber < 100 ? 2 :
lineNumber < 1000 ? 3 :
lineNumber < 10000 ? 4 :
lineNumber < 100000 ? 5 :
lineNumber < 1000000 ? 6 :
String.valueOf(lineNumber).length()
);
}
int len = calcLen();
if (len > Short.MAX_VALUE) {
throw new IllegalArgumentException("Frame is too big");
}
else {
textLen = (short) len;
}
hash = genHash();
}
private int calcLen() {
int len = classPrefix == null ? 0 : (classPrefix.length() + 1);
len += className.length();
len += methodName.length() + 1;
len += 1; // (
switch(lineNumberDigits) {
case NO_LINE_NUMBER: len += fileName.length();
break;
case NO_SOURCE: len += UNKNOWN_SOURCE.length();
break;
case NATIVE: len += NATIVE_METHOD.length();
break;
default:
len += fileName.length() + 1 + lineNumberDigits;
}
len += 1; // )
return len;
}
public String getClassName() {
if (fqcn == null) {
fqcn = classPrefix == null ? className : (classPrefix + "." + className);
}
return fqcn;
}
String getClassPrefix() {
return classPrefix;
}
String getShortClassName() {
return className;
}
public String getMethodName() {
return methodName;
}
public String getSourceFile() {
return fileName;
}
public int getLineNumber() {
return lineNumber;
}
public boolean isNative() {
return lineNumber == NATIVE;
}
public StackFrame internSymbols() {
String cp = classPrefix == null ? null : classPrefix.intern();
String cn = className.intern();
String mn = methodName.intern();
String fn = fileName == null ? null : fileName.intern();
if (cp != classPrefix || cn != className || mn != methodName || fn != fileName) {
return new StackFrame(cp, cn, mn, fn, lineNumber);
}
else {
return this;
}
}
@Override
public int length() {
return textLen;
}
@Override
public char charAt(int index) {
if (index > (textLen - 1)) {
throw new IndexOutOfBoundsException();
}
else if (index == (textLen - 1)) {
return ')';
}
int pref = classPrefix == null ? 0 : (classPrefix.length());
if (pref > 0) {
if (index < pref) {
return classPrefix.charAt(index);
}
else if (index == pref) {
return '.';
}
pref += 1;
}
int cn = pref + className.length();
if (index < cn) {
return className.charAt(index - pref);
}
else if (index == cn) {
return '.';
}
cn += 1;
int mn = cn + methodName.length();
if (index < mn) {
return methodName.charAt(index - cn);
}
else if (index == mn) {
return '(';
}
mn += 1;
switch(lineNumberDigits) {
case NO_LINE_NUMBER:
return fileName.charAt(index - mn);
case NO_SOURCE:
try {
return UNKNOWN_SOURCE.charAt(index - mn);
}
catch ( StringIndexOutOfBoundsException e) {
throw e;
}
case NATIVE:
return NATIVE_METHOD.charAt(index - mn);
default:
int fn = mn + fileName.length();
if (index < fn) {
return fileName.charAt(index - mn);
}
else if (index == fn) {
return ':';
}
int d = lineNumberDigits - (index - fn);
int p = PO10[d];
return (char)('0' + ((lineNumber / p) % 10));
}
}
@Override
public CharSequence subSequence(int start, int end) {
if (start > textLen || end > textLen) {
throw new IndexOutOfBoundsException();
}
if (start > end) {
throw new IllegalArgumentException();
}
return new Subsequence(this, start, end - start);
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
StackFrame other = (StackFrame) obj;
if (textLen != other.textLen || hash != other.hash) {
return false;
}
if (fileName == null) {
if (other.fileName != null)
return false;
} else if (!fileName.equals(other.fileName))
return false;
if (lineNumber != other.lineNumber)
return false;
if (methodName == null) {
if (other.methodName != null)
return false;
} else if (!methodName.equals(other.methodName)) {
return false;
}
if (classPrefix != null && other.classPrefix != null) {
if (!classPrefix.equals(other.classPrefix)) {
return false;
}
if (!className.equals(other.className)) {
return false;
}
}
else if (classPrefix == null && other.classPrefix == null) {
if (!className.equals(other.className)) {
return false;
}
}
else {
if (classPrefix == null) {
if (!className.startsWith(other.classPrefix) || !className.endsWith(other.className) || className.charAt(other.classPrefix.length()) != '.') {
return false;
}
}
else {
if (!other.className.startsWith(classPrefix) || !other.className.endsWith(className) || other.className.charAt(classPrefix.length()) != '.') {
return false;
}
}
}
return true;
}
/**
* @return stack frame instance with removed source information
*/
public StackFrame withoutSource() {
return new StackFrame(classPrefix, className, methodName, null, -1);
}
public StackTraceElement toStackTraceElement() {
String cn = classPrefix == null ? className : (classPrefix + '.' + className);
return new StackTraceElement(cn, methodName, fileName, lineNumber);
}
@Override
public String toString() {
return toString(this);
}
public void toString(StringBuilder builder) {
if (classPrefix != null) {
builder.append(classPrefix).append('.');
}
builder.append(className).append('.');
builder.append(methodName).append('(');
switch(lineNumberDigits) {
case NO_LINE_NUMBER:
builder.append(fileName);
break;
case NO_SOURCE:
builder.append(UNKNOWN_SOURCE);
break;
case NATIVE:
builder.append(NATIVE_METHOD);
break;
default:
builder.append(fileName).append(':').append(lineNumber);
}
builder.append(')');
}
private int genHash() {
int hash = 1033;
if (classPrefix != null) {
hash = hashText(hash, classPrefix);
hash = 31 * hash + '.';
}
hash = hashText(hash, className);
hash = 31 * hash + '.';
hash = hashText(hash, methodName);
switch(lineNumberDigits) {
case NO_LINE_NUMBER:
hash = hashText(hash, fileName);
break;
case NO_SOURCE:
hash = 31 * hash + -1;
break;
case NATIVE:
hash = 31 * hash + -2;
break;
default:
hash = hashText(hash, fileName);
hash = 31 * hash + lineNumber;
}
return hash;
}
private int hashText(int hash, String text) {
for(int i = 0; i != text.length(); ++i) {
hash = 31 * hash + text.charAt(i);
}
return hash;
}
private static String toString(CharSequence seq) {
char[] buf = new char[seq.length()];
for(int i = 0; i != buf.length; ++i) {
buf[i] = seq.charAt(i);
}
return new String(buf);
}
public static StackFrame parseFrame(String line) {
StringBuilder sb = new StringBuilder(line.length());
int dot1 = -1;
int dot2 = -1;
int n = 0;
while(true) {
char ch = line.charAt(n);
if (ch == '(') {
break;
}
if (ch == '.') {
dot2 = dot1;
dot1 = n;
}
sb.append(ch);
++n;
if (n >= line.length()) {
throw new IllegalArgumentException("Cannot parse [" + line + "]");
}
}
if (dot1 == -1) {
throw new IllegalArgumentException("Cannot parse [" + line + "]");
}
String pref = null;
String cn = null;
String mn = null;
if (dot2 != -1) {
pref = sb.substring(0, dot2);
cn = sb.substring(dot2 + 1, dot1);
mn = sb.substring(dot1 + 1);
}
else {
cn = sb.substring(0, dot1);
mn = sb.substring(dot1 + 1);
}
sb.setLength(0);
int col = -1;
++n;
int off = n;
while(true) {
char ch = line.charAt(n);
if (ch == ')') {
break;
}
if (ch == ':') {
col = n - off;
}
sb.append(ch);
++n;
if (n >= line.length()) {
throw new IllegalArgumentException("Cannot parse [" + line + "]");
}
}
String file = null;
int lnum = -1;
if (col != -1) {
file = sb.substring(0, col);
try {
lnum = Integer.parseInt(sb.substring(col + 1));
}
catch(NumberFormatException e) {
throw new IllegalArgumentException("Number format exception '" + e.getMessage() + "' parsing [" + line + "]");
}
}
else {
file = sb.toString();
if (file.equals(NATIVE_METHOD)) {
file = null;
lnum = -2;
}
else if (file.equals(UNKNOWN_SOURCE)) {
file = null;
}
}
return new StackFrame(pref, cn, mn, file, lnum);
}
private static class Subsequence implements CharSequence {
private CharSequence seq;
private int offs;
private int len;
public Subsequence(CharSequence seq, int offs, int len) {
this.seq = seq;
this.offs = offs;
this.len = len;
}
@Override
public int length() {
return len;
}
@Override
public char charAt(int index) {
if (index >= len) {
throw new IndexOutOfBoundsException();
}
return seq.charAt(offs + index);
}
@Override
public CharSequence subSequence(int start, int end) {
if (start > len || end > len) {
throw new IndexOutOfBoundsException();
}
if (start > end) {
throw new IllegalArgumentException();
}
return new Subsequence(seq, offs + start, end - start);
}
@Override
public String toString() {
return StackFrame.toString(this);
}
}
private static class FrameComparator implements Comparator {
@Override
public int compare(StackFrame o1, StackFrame o2) {
int n = compare(o1.getClassName(), o2.getClassName());
if (n != 0) {
return n;
}
n = compare(o1.getLineNumber(), o2.getLineNumber());
if (n != 0) {
return n;
}
n = compare(o1.getMethodName(), o2.getMethodName());
if (n != 0) {
return n;
}
n = compare(o1.getSourceFile(), o2.getSourceFile());
return 0;
}
private int compare(int n1, int n2) {
return Long.signum(((long)n1) - ((long)n2));
}
private int compare(String str1, String str2) {
if (str1 == str2) {
return 0;
}
else if (str1 == null) {
return -1;
}
else if (str2 == null) {
return 1;
}
return str1.compareTo(str2);
}
}
}