JacksonException.java
package tools.jackson.core;
import java.io.Closeable;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.function.BiFunction;
/**
* Base class for all Jackson-produced checked exceptions.
*<p>
* Note that in Jackson 2.x this exception extended {@link java.io.IOException}
* but in 3.x {@link RuntimeException}
*/
public class JacksonException
extends RuntimeException
{
private final static long serialVersionUID = 3L; // eclipse complains otherwise
/**
* Let's limit length of reference chain, to limit damage in cases
* of infinite recursion.
* Use maximum nesting limit allowed for reads.
*/
private final static int MAX_REFS_TO_LIST = StreamReadConstraints.DEFAULT_MAX_DEPTH;
/**
* Simple bean class used to contain references. References
* can be added to indicate execution/reference path that
* lead to the problem that caused this exception to be
* thrown.
*
* @since 3.0 (in 2.x was part of databind-level exceptions only)
*/
public static class Reference implements Serializable
{
private static final long serialVersionUID = 3L;
protected transient Object _from;
/**
* Name of property (for POJO) or key (for Maps) that is part
* of the reference. May be null for Collection types (which
* generally have {@link #_index} defined), or when resolving
* Map classes without (yet) having an instance to operate on.
*/
protected String _propertyName;
/**
* Index within a {@link Collection} instance that contained
* the reference; used if index is relevant and available.
* If either not applicable, or not available, -1 is used to
* denote "not known" (or not relevant).
*/
protected int _index = -1;
/**
* Lazily-constructed description of this instance; needed mostly to
* allow JDK serialization to work in case where {@link #_from} is
* non-serializable (and has to be dropped) but we still want to pass
* actual description along.
*/
protected String _desc;
/**
* Default constructor for deserialization purposes
*/
protected Reference() { }
public Reference(Object from) { _from = from; }
public Reference(Object from, String propertyName) {
_from = from;
_propertyName = Objects.requireNonNull(propertyName, "Cannot pass null 'propertyName'");
}
public Reference(Object from, int index) {
_from = from;
_index = index;
}
// Setters to let Jackson deserialize instances, but not to be called from outside
void setPropertyName(String n) { _propertyName = n; }
void setIndex(int ix) { _index = ix; }
void setDescription(String d) { _desc = d; }
/**
* Object through which reference was resolved. Can be either
* actual instance (usually the case for serialization), or
* Class (usually the case for deserialization).
*<p>
* Note that this the accessor is not a getter on purpose as we cannot
* (in general) serialize/deserialize this reference
*/
public Object from() { return _from; }
public String getPropertyName() { return _propertyName; }
public int getIndex() { return _index; }
public String getDescription()
{
if (_desc == null) {
StringBuilder sb = new StringBuilder();
if (_from == null) { // can this ever occur?
sb.append("UNKNOWN");
} else {
Class<?> cls = (_from instanceof Class<?>) ? (Class<?>)_from : _from.getClass();
// Hmmh. Although Class.getName() is mostly ok, it does look
// butt-ugly for arrays.
// 06-Oct-2016, tatu: as per [databind#1403], `getSimpleName()` not so good
// as it drops enclosing class. So let's try bit different approach
int arrays = 0;
while (cls.isArray()) {
cls = cls.getComponentType();
++arrays;
}
sb.append(cls.getName());
while (--arrays >= 0) {
sb.append("[]");
}
}
sb.append('[');
if (_propertyName != null) {
sb.append('"');
sb.append(_propertyName);
sb.append('"');
} else if (_index >= 0) {
sb.append(_index);
} else {
sb.append('?');
}
sb.append(']');
_desc = sb.toString();
}
return _desc;
}
@Override
public String toString() {
return getDescription();
}
/**
* May need some cleaning here, given that `from` may or may not be serializable.
*/
Object writeReplace() {
// as per [databind#1195], need to ensure description is not null, since
// `_from` is transient
getDescription();
return this;
}
}
/*
/**********************************************************************
/* State/configuration
/**********************************************************************
*/
protected TokenStreamLocation _location;
/**
* Path through which problem that triggering throwing of
* this exception was reached.
*/
protected LinkedList<Reference> _path;
/**
* Underlying processor ({@link JsonParser} or {@link JsonGenerator}),
* if known.
*<p>
* NOTE: typically not serializable hence <code>transient</code>
*/
protected transient Closeable _processor;
/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/
protected JacksonException(String msg) {
super(msg);
}
protected JacksonException(Throwable rootCause) {
super(rootCause);
}
protected JacksonException(String msg, Throwable rootCause) {
this(null, msg, null, rootCause);
}
protected JacksonException(String msg, TokenStreamLocation loc, Throwable rootCause) {
this(null, msg, loc, rootCause);
}
protected JacksonException(Closeable processor, Throwable rootCause) {
super(rootCause);
_processor = processor;
_location = _nonNullLocation(null);
}
protected JacksonException(Closeable processor, String msg, TokenStreamLocation loc,
Throwable rootCause) {
super(msg, rootCause);
_processor = processor;
_location = _nonNullLocation(loc);
}
protected JacksonException(Closeable processor, String msg)
{
super(msg);
_processor = processor;
TokenStreamLocation loc = null;
if (processor instanceof JsonParser) {
// 17-Aug-2015, tatu: Use of token location makes some sense from databinding,
// since actual parsing (current) location is typically only needed for low-level
// parsing exceptions.
// 10-Jun-2024, tatu: Used from streaming too, so not 100% sure. But won't change yet
loc = ((JsonParser) processor).currentTokenLocation();
}
_location = _nonNullLocation(loc);
}
protected JacksonException(Closeable processor, String msg, Throwable problem)
{
super(msg, problem);
_processor = processor;
TokenStreamLocation loc = null;
if (problem instanceof JacksonException) {
loc = ((JacksonException) problem).getLocation();
} else if (processor instanceof JsonParser) {
// 10-Jun-2024, tatu: Current vs token location?
loc = ((JsonParser) processor).currentTokenLocation();
}
_location = _nonNullLocation(loc);
}
protected JacksonException(Closeable processor, String msg, TokenStreamLocation loc)
{
super(msg);
_processor = processor;
_location = _nonNullLocation(loc);
}
private static TokenStreamLocation _nonNullLocation(TokenStreamLocation loc) {
return (loc == null) ? TokenStreamLocation.NA : loc;
}
// @since 3.0
public JacksonException withCause(Throwable cause) {
initCause(cause);
return this;
}
/**
* Method that allows to remove context information from this exception's message.
* Useful when you are parsing security-sensitive data and don't want original data excerpts
* to be present in Jackson parser error messages.
*
* @return This exception instance to allow call chaining
*/
public JacksonException clearLocation() {
_location = null;
return this;
}
/*
/**********************************************************************
/* Life-cycle: more advanced factory-like methods
/**********************************************************************
*/
/**
* Method that can be called to either create a new {@link JacksonException}
* (if underlying exception is not a {@link JacksonException}), or augment
* given exception with given path/reference information.
*
* This version of method is called when the reference is through a
* non-indexed object, such as a Map or POJO/bean.
*/
public static JacksonException wrapWithPath(Throwable src, Object refFrom,
String refPropertyName) {
return wrapWithPath(src, new Reference(refFrom, refPropertyName));
}
/**
* Method that can be called to either create a new {@link JacksonException}
* (if underlying exception is not a {@link JacksonException}), or augment
* given exception with given path/reference information.
*
* This version of method is called when the reference is through an
* index, which happens with arrays and Collections.
*/
public static JacksonException wrapWithPath(Throwable src, Object refFrom, int index) {
return wrapWithPath(src, new Reference(refFrom, index));
}
/**
* Method that can be called to either create a new {@link JacksonException}
* (if underlying exception is not a {@link JacksonException}), or augment
* given exception with given path/reference information.
*/
@SuppressWarnings("resource")
public static JacksonException wrapWithPath(Throwable src, Reference ref)
{
return wrapWithPath(src, ref, JacksonException::new);
}
public static JacksonException wrapWithPath(Throwable src, Reference ref,
BiFunction<String, Throwable, JacksonException> ctor)
{
JacksonException jme;
if (src instanceof JacksonException) {
jme = (JacksonException) src;
} else {
// [databind#2128]: try to avoid duplication
String msg = _exceptionMessage(src);
// Let's use a more meaningful placeholder if all we have is null
if (msg == null || msg.isEmpty()) {
msg = "(was "+src.getClass().getName()+")";
}
jme = ctor.apply(msg, src);
}
jme.prependPath(ref);
return jme;
}
public static String _exceptionMessage(Throwable t) {
if (t instanceof JacksonException) {
return ((JacksonException) t).getOriginalMessage();
}
if (t instanceof InvocationTargetException && t.getCause() != null) {
return t.getCause().getMessage();
}
return t.getMessage();
}
/*
/**********************************************************************
/* Life-cycle: information augmentation (cannot use factory style, alas)
/**********************************************************************
*/
/**
* Method called to prepend a reference information in front of
* current path
*/
public JacksonException prependPath(Object referrer, String propertyName) {
return prependPath(new Reference(referrer, propertyName));
}
/**
* Method called to prepend a reference information in front of
* current path
*/
public JacksonException prependPath(Object referrer, int index) {
return prependPath(new Reference(referrer, index));
}
public JacksonException prependPath(Reference r)
{
if (_path == null) {
_path = new LinkedList<Reference>();
}
// Also: let's not increase without bounds. Could choose either
// head or tail; tail is easier (no need to ever remove), as
// well as potentially more useful so let's use it:
if (_path.size() < MAX_REFS_TO_LIST) {
_path.addFirst(r);
}
return this;
}
/*
/**********************************************************************
/* Accessors
/**********************************************************************
*/
/**
* Method for accessing full structural path within type hierarchy
* down to problematic property.
*/
public List<Reference> getPath()
{
if (_path == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(_path);
}
/**
* Method for accessing description of path that lead to the
* problem that triggered this exception
*/
public String getPathReference()
{
return getPathReference(new StringBuilder()).toString();
}
public StringBuilder getPathReference(StringBuilder sb)
{
return _appendPathDesc(sb);
}
/*
/**********************************************************************
/* Extended API
/**********************************************************************
*/
/**
* Accessor for location information related to position within input
* or output (depending on operation), if available; if not available
* may return {@link TokenStreamLocation#NA} (but never {@code null}).
*<p>
* Accuracy of location information depends on backend (format) as well
* as (in some cases) operation being performed.
*
* @return Location in input or output that triggered the problem reported, if
* available; {@code null} otherwise.
*/
public TokenStreamLocation getLocation() { return _location; }
/**
* Method that allows accessing the original "message" argument,
* without additional decorations (like location information)
* that overridden {@link #getMessage} adds.
*
* @return Original, unmodified {@code message} argument used to construct
* this exception instance
*/
public String getOriginalMessage() { return super.getMessage(); }
/**
* Method that allows accessing underlying processor that triggered
* this exception; typically either {@link JsonParser} or {@link JsonGenerator}
* for exceptions that originate from streaming API, but may be other types
* when thrown by databinding.
*<p>
* Note that it is possible that {@code null} may be returned if code throwing
* exception either has no access to the processor; or has not been retrofitted
* to set it; this means that caller needs to take care to check for nulls.
* Subtypes override this method with co-variant return type, for more
* type-safe access.
*<p>
* NOTE: In Jackson 2.x, accessor was {@code getProcessor()}: in 3.0 changed to
* non-getter to avoid having to annotate for serialization.
*
* @return Originating processor, if available; {@code null} if not.
*/
public Object processor() {
return _processor;
}
/*
/**********************************************************************
/* Methods for sub-classes to use, override
/**********************************************************************
*/
/**
* Accessor that sub-classes can override to append additional
* information right after the main message, but before
* source location information.
*<p>
* NOTE: In Jackson 2.x, accessor was {@code getMessageSuffix()}: in 3.0 changed to
* non-getter to indicate it is not a "regular" getter (although it would not
* be serialized anyway due to lower visibility).
*
* @return Message suffix configured to be used, if any; {@code null} if none
*/
protected String messageSuffix() { return null; }
/*
/**********************************************************************
/* Overrides of standard methods
/**********************************************************************
*/
@Override
public String getLocalizedMessage() {
return _buildMessage();
}
/**
* Method is overridden so that we can properly inject description
* of problem path, if such is defined.
*/
@Override
public String getMessage() {
return _buildMessage();
}
/**
* Default method overridden so that we can add location information
*
* @return Message constructed based on possible optional prefix; explicit
* {@code message} passed to constructor as well trailing location description
* (separate from message by linefeed)
*/
protected String _buildMessage()
{
String baseMessage = super.getMessage();
if (baseMessage == null) {
baseMessage = "N/A";
}
TokenStreamLocation loc = getLocation();
String suffix = messageSuffix();
// mild optimization, if nothing extra is needed:
StringBuilder sb = new StringBuilder(200);
sb.append(baseMessage);
if ((loc != null) || suffix != null) {
if (suffix != null) {
sb.append(suffix);
}
if (loc != null) {
sb.append('\n');
sb.append(" at ");
sb = loc.toString(sb);
}
}
if (_path != null) {
sb = _appendReferenceChain(sb);
}
return sb.toString();
}
@Override
public String toString() { return getClass().getName()+": "+getMessage(); }
/*
/**********************************************************************
/* Internal methods
/**********************************************************************
*/
protected StringBuilder _appendReferenceChain(StringBuilder sb)
{
sb.append(" (through reference chain: ");
sb = _appendPathDesc(sb);
sb.append(')');
return sb;
}
protected StringBuilder _appendPathDesc(StringBuilder sb)
{
if (_path != null) {
Iterator<Reference> it = _path.iterator();
while (it.hasNext()) {
sb.append(it.next().toString());
if (it.hasNext()) {
sb.append("->");
}
}
}
return sb;
}
}