/*
* Lilith - a log event viewer.
* Copyright (C) 2007-2016 Joern Huxhorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright 2007-2016 Joern Huxhorn
*
* 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 de.huxhorn.lilith.data.logging;
import java.io.Serializable;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Objects;
public class ThrowableInfo
implements Serializable, Cloneable
{
private static final long serialVersionUID = -6320441996003349426L;
public static final String CAUSED_BY_PREFIX = "Caused by: ";
public static final String SUPPRESSED_PREFIX = "Suppressed: ";
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private String name;
private String message;
private ExtendedStackTraceElement[] stackTrace;
private int omittedElements;
private ThrowableInfo[] suppressed;
private ThrowableInfo cause;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getMessage()
{
return message;
}
public void setMessage(String message)
{
this.message = message;
}
public ExtendedStackTraceElement[] getStackTrace()
{
return stackTrace;
}
public void setStackTrace(ExtendedStackTraceElement[] stackTrace)
{
this.stackTrace = stackTrace;
}
public ThrowableInfo[] getSuppressed()
{
return suppressed;
}
public void setSuppressed(ThrowableInfo[] suppressed)
{
this.suppressed = suppressed;
}
public ThrowableInfo getCause()
{
return cause;
}
public void setCause(ThrowableInfo cause)
{
this.cause = cause;
}
public int getOmittedElements()
{
return omittedElements;
}
public void setOmittedElements(int omittedElements)
{
this.omittedElements = omittedElements;
}
public boolean equals(Object o)
{
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
return recursiveEquals((ThrowableInfo) o, new IdentityHashMap<>());
}
private boolean recursiveEquals(ThrowableInfo that, IdentityHashMap<ThrowableInfo, Object> dejaVu)
{
if(this == that) return true;
if(that == null) return false;
if(dejaVu.containsKey(that))
{
return true; // A special kind of true. Equally b0rked.
}
dejaVu.put(that, null);
if(omittedElements != that.omittedElements) return false;
if(name != null ? !name.equals(that.name) : that.name != null) return false;
if(message != null ? !message.equals(that.message) : that.message != null) return false;
if(!Arrays.equals(stackTrace, that.stackTrace)) return false;
if(cause == null)
{
if(that.cause != null)
{
return false;
}
}
else
{
if(!cause.recursiveEquals(that.cause, dejaVu))
{
return false;
}
}
if(suppressed == null)
{
if(that.suppressed != null)
{
return false;
}
}
else
{
if(that.suppressed == null)
{
return false;
}
if(suppressed.length != that.suppressed.length)
{
return false;
}
for(int i=0;i<suppressed.length; i++)
{
ThrowableInfo thisSuppressedEntry = suppressed[i];
ThrowableInfo thatSuppressedEntry = that.suppressed[i];
if(thisSuppressedEntry == null)
{
if(thatSuppressedEntry != null)
{
return false;
}
}
else
{
if(!thisSuppressedEntry.recursiveEquals(thatSuppressedEntry, dejaVu))
{
return false;
}
}
}
}
return true;
}
public int hashCode()
{
return recursiveHashCode(this, new IdentityHashMap<>());
}
private static int recursiveHashCode(ThrowableInfo instance, IdentityHashMap<ThrowableInfo, Object> dejaVu)
{
if(instance == null)
{
return 0;
}
if(dejaVu.containsKey(instance))
{
return 0;
}
dejaVu.put(instance, null);
int result = instance.getOmittedElements();
String name = instance.getName();
result = 29 * result + (name != null ? name.hashCode() : 0);
String message = instance.getMessage();
result = 29 * result + (message != null ? message.hashCode() : 0);
ThrowableInfo cause = instance.getCause();
result = 29 * result + recursiveHashCode(cause, dejaVu);
ThrowableInfo[] suppressed = instance.getSuppressed();
if(suppressed != null)
{
for (ThrowableInfo throwableInfo : suppressed)
{
result = 29 * result + recursiveHashCode(throwableInfo, dejaVu);
}
}
return result;
}
@SuppressWarnings("CloneDoesntCallSuperClone")
@Override
public ThrowableInfo clone() throws CloneNotSupportedException
{
return recursiveClone(new IdentityHashMap<>());
}
private ThrowableInfo recursiveClone(IdentityHashMap<ThrowableInfo, ThrowableInfo> dejaVu)
throws CloneNotSupportedException
{
ThrowableInfo result = dejaVu.get(this);
if(result != null)
{
// we already cloned it.
return result;
}
result = (ThrowableInfo) super.clone();
dejaVu.put(this, result);
if(stackTrace != null)
{
result.stackTrace = new ExtendedStackTraceElement[stackTrace.length];
for(int i=0; i<stackTrace.length; i++)
{
ExtendedStackTraceElement current = stackTrace[i];
if(current != null)
{
result.stackTrace[i] = current.clone();
}
}
}
if(cause != null)
{
result.cause = cause.recursiveClone(dejaVu);
}
if(suppressed != null)
{
result.suppressed = new ThrowableInfo[suppressed.length];
for(int i=0; i<suppressed.length; i++)
{
ThrowableInfo current = suppressed[i];
if(current != null)
{
result.suppressed[i] = current.recursiveClone(dejaVu);
}
}
}
return result;
}
@Override
public String toString()
{
return toString(true);
}
/**
* Returns a string representation similar to printStackTrace.
*
* @param extended whether or not extended StackTraceElement information should be appended.
* @return the String representation of this ThrowableInfo.
*/
public String toString(boolean extended)
{
return appendTo(new StringBuilder(), extended).toString();
}
/**
* Appends this instance to the given StringBuilder.
*
* @param stringBuilder the StringBuilder to append this instance to.
* @param extended Whether or not extended info should be included, if available.
* @return the given StringBuilder instance.
* @throws NullPointerException if stringBuilder is null.
*/
public StringBuilder appendTo(StringBuilder stringBuilder, boolean extended)
{
Objects.requireNonNull(stringBuilder, "stringBuilder must not be null!");
recursiveAppend(stringBuilder, new IdentityHashMap<>(), null, 0, this, extended);
return stringBuilder;
}
private static void recursiveAppend(StringBuilder sb, IdentityHashMap<ThrowableInfo, Object> dejaVu, String prefix, int indent, ThrowableInfo throwableInfo, boolean extended)
{
if(throwableInfo == null)
{
return;
}
appendIndent(sb, indent);
if(prefix != null)
{
sb.append(prefix);
}
String name = throwableInfo.getName();
if(name != null)
{
sb.append(name);
String message = throwableInfo.getMessage();
if(message != null && !name.equals(message))
{
sb.append(": ").append(throwableInfo.getMessage());
}
}
else
{
sb.append(throwableInfo.getMessage());
}
if(dejaVu.containsKey(throwableInfo))
{
sb.append("[CIRCULAR REFERENCE]\n");
return;
}
dejaVu.put(throwableInfo, null);
sb.append(LINE_SEPARATOR);
appendSTEArray(sb, indent + 1, throwableInfo, extended);
ThrowableInfo[] suppressed = throwableInfo.getSuppressed();
if(suppressed != null)
{
for(ThrowableInfo current : suppressed)
{
recursiveAppend(sb, dejaVu, SUPPRESSED_PREFIX, indent + 1, current, extended);
}
}
recursiveAppend(sb, dejaVu, CAUSED_BY_PREFIX, indent, throwableInfo.getCause(), extended);
}
private static void appendIndent(StringBuilder sb, int indent)
{
for(int i=0;i<indent;i++)
{
sb.append('\t');
}
}
private static void appendSTEArray(StringBuilder sb, int indentLevel, ThrowableInfo throwableInfo, boolean extended)
{
ExtendedStackTraceElement[] steArray = throwableInfo.getStackTrace();
if(steArray != null)
{
for(ExtendedStackTraceElement ste : steArray)
{
if (ste == null)
{
continue;
}
appendIndent(sb, indentLevel);
sb.append("at ");
ste.appendTo(sb, extended);
sb.append(LINE_SEPARATOR);
}
}
int commonFrames = throwableInfo.getOmittedElements();
if(commonFrames > 0)
{
appendIndent(sb, indentLevel);
sb.append("... ").append(commonFrames).append(" more").append(LINE_SEPARATOR);
}
}
}