org.opendaylight.infrautils.diagstatus.ThrowableAdapterFactory Maven / Gradle / Ivy
/*
* Copyright (c) 2023 PANTHEON.tech s.r.o. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.infrautils.diagstatus;
import com.google.errorprone.annotations.Var;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
final class ThrowableAdapterFactory implements TypeAdapterFactory {
public static final ThrowableAdapterFactory INSTANCE = new ThrowableAdapterFactory();
private ThrowableAdapterFactory() {
// Hidden on purpose
}
@Override
public TypeAdapter create(Gson gson, TypeToken type) {
// Only handles Throwable and subclasses; let other factories handle any other type
if (!Throwable.class.isAssignableFrom(type.getRawType())) {
return null;
}
@SuppressWarnings("unchecked")
var adapter = (TypeAdapter) new TypeAdapter() {
@Override
public Throwable read(JsonReader in) throws IOException {
var token = in.peek();
if (token == JsonToken.NULL) {
in.nextNull();
return null;
}
@Var String exceptionType = null;
@Var String message = null;
@Var Throwable cause = null;
var suppressed = new ArrayList();
in.beginObject();
while (in.hasNext()) {
var name = in.nextName();
switch (name) {
case "type":
exceptionType = in.nextString();
break;
case "message":
message = in.nextString();
break;
case "cause":
// Recursively read the cause
cause = read(in);
break;
case "suppressed":
in.beginArray();
while (in.hasNext()) {
// Recursively read each suppressed exception
suppressed.add(read(in));
}
in.endArray();
break;
default:
break;
}
}
in.endObject();
// Create the appropriate exception instance based on the type name
if (exceptionType != null && !exceptionType.isEmpty()) {
try {
var exceptionClass = Class.forName("java.lang." + exceptionType);
var constructor = exceptionClass.getConstructor(String.class, Throwable.class);
var throwable = (Throwable) constructor.newInstance(message, cause);
suppressed.forEach(throwable::addSuppressed);
return throwable;
} catch (ClassNotFoundException e) {
throw new JsonParseException("Failed to create Throwable instance: Class not found", e);
} catch (NoSuchMethodException e) {
throw new JsonParseException("Failed to create Throwable instance: Constructor not found", e);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new JsonParseException("Failed to create Throwable instance: Instantiation error", e);
}
} else {
// If type is not available, create a generic Throwable with the message
return new Throwable(message, cause);
}
}
@Override
public void write(JsonWriter out, Throwable value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
out.beginObject();
// Include exception type name to give more context; for example NullPointerException might
// not have a message
out.name("type");
out.value(value.getClass().getSimpleName());
out.name("message");
out.value(value.getMessage());
var cause = value.getCause();
if (cause != null) {
out.name("cause");
write(out, cause);
}
var suppressedArray = value.getSuppressed();
if (suppressedArray.length > 0) {
out.name("suppressed");
out.beginArray();
for (var suppressed : suppressedArray) {
write(out, suppressed);
}
out.endArray();
}
out.endObject();
}
};
return adapter;
}
}