blob: eb2b16c478f15f39141788dc69a3c4a6a25a4c98 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.hadoop.io;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/** This writable component builds from a throwable */
public final class ThrowableWritable implements Writable {
/** throwable classname */
private String classname;
/** throwable message */
private String message;
/** cause: may be null */
private ThrowableWritable cause;
/**
* Stack trace as string; will be null when an empty element is created,
* otherwise it will be an array of length zero.
*/
private String[] stack;
/**
* Empty constructor.
* Only use this when you are planning to deserialize data, as the object is
* otherwise incomplete.
*/
public ThrowableWritable() {
}
/**
* Construct a lightweight throwable writeable with no stack trace; and the
* message passed in
*
* @param message message to use
*/
public ThrowableWritable(String message) {
this.message = message;
stack = new String[0];
classname = "";
}
/**
* recursively construct from a throwable chain.
*
* @param thrown The throwable chain to build this writeable from.
*/
public ThrowableWritable(Throwable thrown) {
classname = thrown.getClass().getName();
message = thrown.getMessage();
StackTraceElement[] st = thrown.getStackTrace();
if (st != null) {
int sl = st.length;
stack = new String[sl];
for (int i = 0; i < sl; i++) {
stack[i] = st[i].toString();
}
} else {
stack = new String[0];
}
Throwable rootCause = thrown.getCause();
if (rootCause != null && rootCause != thrown) {
cause = new ThrowableWritable(rootCause);
}
}
/**
* Copy constructor.
*
* @param that the original instance to copy
*/
public ThrowableWritable(ThrowableWritable that) {
classname = that.classname;
message = that.message;
//copy stack trace
if (that.stack == null) {
stack = new String[0];
} else {
int l = that.stack.length;
stack = new String[l];
System.arraycopy(that.stack, 0, stack, 0, l);
}
//copy any nested cause
if (that.cause != null) {
cause = new ThrowableWritable(that.cause);
}
}
/**
* Get the classname of the underlying throwable
*
* @return the classname of the original throwable
*/
public String getClassname() {
return classname;
}
/**
* Get the text string this instance was constructed with
*
* @return the message of the underlying throwable
*/
public String getMessage() {
return message;
}
/**
* Get any nested cause of the exception
*
* @return any nested cause as another ThrowableWritable -or null
*/
public ThrowableWritable getCause() {
return cause;
}
/**
* Get the stack trace of the original throwable. It may be of size 0.
*
* @return the stack trace converted to strings
*/
public String[] getStack() {
return stack;
}
/**
* determine (recursively) the depth of this Throwable chain
*
* @return a number equal to or greater than 1
*/
public int getDepth() {
return 1 + (cause == null ? 0 : cause.getDepth());
}
/**
* {@inheritDoc}
*
* @param out <code>DataOutput</code> to serialize this object into.
*
* @throws IOException IO trouble
*/
public void write(DataOutput out) throws IOException {
out.writeUTF(classname);
out.writeUTF(message);
if (stack != null) {
out.writeInt(stack.length);
for (String call : stack) {
out.writeUTF(call);
}
} else {
out.writeInt(0);
}
//look for a cause
boolean hasCause = cause != null;
out.writeBoolean(hasCause);
if (hasCause) {
//recursively write it
cause.write(out);
}
}
/**
* {@inheritDoc}
*
* @param in <code>DataInput</code> to deseriablize this object from.
*
* @throws IOException IO trouble
*/
public void readFields(DataInput in) throws IOException {
classname = in.readUTF();
message = in.readUTF();
int stackLength = in.readInt();
if (stack == null || stack.length != stackLength) {
//create a new stack array
stack = new String[stackLength];
}
//read in the stack
for (int i = 0; i < stackLength; i++) {
stack[i] = in.readUTF();
}
//look for any nested cause
boolean hasCause = in.readBoolean();
if (hasCause) {
if (cause == null) {
cause = new ThrowableWritable();
}
cause.readFields(in);
}
}
/**
* {@inheritDoc}
*
* @throws CloneNotSupportedException this should not happen
*/
@SuppressWarnings({"CloneDoesntCallSuperClone"})
@Override
protected Object clone() throws CloneNotSupportedException {
return new ThrowableWritable(this);
}
/**
* {@inheritDoc}
*
* The classname and message are used for equality
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ThrowableWritable that = (ThrowableWritable) o;
if (classname != null ? !classname.equals(that.classname) : that.classname != null)
return false;
return !(message != null ? !message.equals(that.message) : that.message != null);
}
/**
* {@inheritDoc}
*
* The classname and message are used in the hash
*/
@Override
public int hashCode() {
int result = classname != null ? classname.hashCode() : 0;
result = 31 * result + (message != null ? message.hashCode() : 0);
return result;
}
/**
* Return the classname and message in the format classname: message The
* output is designed to resemble that of {@link Throwable#toString()} if the
* message and classname are both set. If only the message is set, only that
* is printed.
*
* @return a string representation of the object.
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (classname != null) {
builder.append(classname);
if (!classname.isEmpty()) {
builder.append(": ");
}
}
if (message != null) {
builder.append(message);
}
return builder.toString();
}
}