package com.bumptech.glide.load.engine; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.Key; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * An exception with zero or more causes indicating why a load in Glide failed. */ public final class GlideException extends Exception { private static final StackTraceElement[] EMPTY_ELEMENTS = new StackTraceElement[0]; private final List<Exception> causes; private Key key; private DataSource dataSource; private Class<?> dataClass; public GlideException(String message) { this(message, Collections.<Exception>emptyList()); } public GlideException(String detailMessage, Exception cause) { this(detailMessage, Collections.singletonList(cause)); } public GlideException(String detailMessage, List<Exception> causes) { super(detailMessage); setStackTrace(EMPTY_ELEMENTS); this.causes = causes; } void setLoggingDetails(Key key, DataSource dataSource) { setLoggingDetails(key, dataSource, null); } void setLoggingDetails(Key key, DataSource dataSource, Class<?> dataClass) { this.key = key; this.dataSource = dataSource; this.dataClass = dataClass; } @Override public Throwable fillInStackTrace() { // Avoid an expensive allocation by doing nothing here. Causes should contain all relevant // stack traces. return this; } /** * Returns a list of causes that are immediate children of this exception. * * <p>Causes may or may not be {@link GlideException GlideExceptions}. Causes may also not be root * causes, and in turn my have been caused by other failures.</p> * * @see #getRootCauses() */ public List<Exception> getCauses() { return causes; } /** * Returns the list of root causes that are the leaf nodes of all children of this exception. * * <p>Use this method to do things like look for http exceptions that indicate the load may have * failed due to an error that can be retried. Keep in mind that because Glide may attempt to load * a given model using multiple different pathways, there may be multiple related or unrelated * reasons for a load to fail. */ public List<Exception> getRootCauses() { List<Exception> rootCauses = new ArrayList<>(); addRootCauses(this, rootCauses); return rootCauses; } /** * Logs all root causes using the given tag. * * <p>Each root cause is logged separately to avoid throttling. {@link #printStackTrace()} will * provide a more succinct overview of why the exception occurred, although it does not include * complete stack traces. */ public void logRootCauses(String tag) { Log.e(tag, getClass() + ": " + getMessage()); List<Exception> causes = getRootCauses(); for (int i = 0, size = causes.size(); i < size; i++) { Log.i(tag, "Root cause (" + (i + 1) + " of " + size + ")", causes.get(i)); } } private void addRootCauses(Exception exception, List<Exception> rootCauses) { if (exception instanceof GlideException) { GlideException glideException = (GlideException) exception; for (Exception e : glideException.getCauses()) { addRootCauses(e, rootCauses); } } else { rootCauses.add(exception); } } @Override public void printStackTrace() { printStackTrace(System.err); } @Override public void printStackTrace(PrintStream err) { printStackTrace((Appendable) err); } @Override public void printStackTrace(PrintWriter err) { printStackTrace((Appendable) err); } private void printStackTrace(Appendable appendable) { appendExceptionMessage(this, appendable); appendCauses(getCauses(), new IndentedAppendable(appendable)); } @Override public String getMessage() { return super.getMessage() + (dataClass != null ? ", " + dataClass : "") + (dataSource != null ? ", " + dataSource : "") + (key != null ? ", " + key : ""); } // Appendable throws, PrintWriter, PrintStream, and IndentedAppendable do not, so this should // never happen. @SuppressWarnings("PMD.PreserveStackTrace") private static void appendExceptionMessage(Exception e, Appendable appendable) { try { appendable.append(e.getClass().toString()).append(": ").append(e.getMessage()).append('\n'); } catch (IOException e1) { throw new RuntimeException(e); } } // Appendable throws, PrintWriter, PrintStream, and IndentedAppendable do not, so this should // never happen. @SuppressWarnings("PMD.PreserveStackTrace") private static void appendCauses(List<Exception> causes, Appendable appendable) { try { appendCausesWrapped(causes, appendable); } catch (IOException e) { throw new RuntimeException(e); } } @SuppressWarnings("ThrowableResultOfMethodCallIgnored") private static void appendCausesWrapped(List<Exception> causes, Appendable appendable) throws IOException { int size = causes.size(); for (int i = 0; i < size; i++) { appendable.append("Cause (") .append(String.valueOf(i + 1)) .append(" of ") .append(String.valueOf(size)) .append("): "); Exception cause = causes.get(i); if (cause instanceof GlideException) { GlideException glideCause = (GlideException) cause; glideCause.printStackTrace(appendable); } else { appendExceptionMessage(cause, appendable); } } } private static final class IndentedAppendable implements Appendable { private static final String EMPTY_SEQUENCE = ""; private static final String INDENT = " "; private final Appendable appendable; private boolean printedNewLine = true; IndentedAppendable(Appendable appendable) { this.appendable = appendable; } @Override public Appendable append(char c) throws IOException { if (printedNewLine) { printedNewLine = false; appendable.append(INDENT); } printedNewLine = c == '\n'; appendable.append(c); return this; } @Override public Appendable append(@Nullable CharSequence charSequence) throws IOException { charSequence = safeSequence(charSequence); return append(charSequence, 0, charSequence.length()); } @Override public Appendable append(@Nullable CharSequence charSequence, int start, int end) throws IOException { charSequence = safeSequence(charSequence); if (printedNewLine) { printedNewLine = false; appendable.append(INDENT); } printedNewLine = charSequence.length() > 0 && charSequence.charAt(end - 1) == '\n'; appendable.append(charSequence, start, end); return this; } @NonNull private CharSequence safeSequence(@Nullable CharSequence sequence) { if (sequence == null) { return EMPTY_SEQUENCE; } else { return sequence; } } } }