package org.wikipedia.util; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.github.kevinsawicki.http.HttpRequest; import org.json.JSONException; import org.mediawiki.api.json.ApiException; import org.wikipedia.R; import org.wikipedia.createaccount.CreateAccountException; import org.wikipedia.dataclient.mwapi.MwException; import org.wikipedia.dataclient.okhttp.HttpStatusException; import org.wikipedia.login.LoginClient; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.concurrent.TimeoutException; import javax.net.ssl.SSLException; public final class ThrowableUtil { // TODO: replace with Apache Commons Lang ExceptionUtils. @NonNull public static Throwable getInnermostThrowable(@NonNull Throwable e) { Throwable t = e; while (t.getCause() != null) { t = t.getCause(); } return t; } // TODO: replace with Apache Commons Lang ExceptionUtils. public static boolean throwableContainsException(@NonNull Throwable e, Class<?> exClass) { Throwable t = e; while (t != null) { if (t.getClass().equals(exClass)) { return true; } t = t.getCause(); } return false; } /** * DEPRECATED: This is a rarely-used function intended to sift through server error responses * and pass through the relevant bits along with standardized strings in certain cases. * * Getting the handful of canned strings depends on processing contexts that might be null by * the time we make it here. Further, we're moving away from using raw server messages in favor * of statically defined XML error views, which are safer. This should no longer be used. */ @NonNull @Deprecated public static AppError getAppError(@NonNull Context context, @NonNull Throwable e) { Throwable inner = ThrowableUtil.getInnermostThrowable(e); AppError result; // look at what kind of exception it is... if (inner instanceof ApiException) { // it's a well-formed error response from the server! result = new AppError(getApiError(context, (ApiException) inner), getApiErrorMessage(context, (ApiException) inner)); } else if (isNetworkError(e)) { result = new AppError(context.getString(R.string.error_network_error), context.getString(R.string.format_error_server_message, inner.getLocalizedMessage())); } else if (e instanceof HttpStatusException) { result = new AppError(e.getMessage(), Integer.toString(((HttpStatusException) e).code())); } else if (inner instanceof LoginClient.LoginFailedException || inner instanceof CreateAccountException || inner instanceof MwException) { result = new AppError(inner.getLocalizedMessage(), ""); } else if (ThrowableUtil.throwableContainsException(e, JSONException.class)) { result = new AppError(context.getString(R.string.error_response_malformed), inner.getLocalizedMessage()); } else { // everything else has fallen through, so just treat it as an "unknown" error result = new AppError(context.getString(R.string.error_unknown), inner.getLocalizedMessage()); } return result; } public static boolean isOffline(@Nullable Throwable caught) { return caught instanceof UnknownHostException || caught instanceof SocketException || caught instanceof SocketTimeoutException; } @SuppressWarnings("checkstyle:magicnumber") public static boolean is404(@NonNull Throwable caught) { return caught instanceof HttpStatusException && ((HttpStatusException) caught).code() == 404; } private static boolean isNetworkError(@NonNull Throwable e) { return ThrowableUtil.throwableContainsException(e, HttpRequest.HttpRequestException.class) || ThrowableUtil.throwableContainsException(e, HttpStatusException.class) || ThrowableUtil.throwableContainsException(e, UnknownHostException.class) || ThrowableUtil.throwableContainsException(e, TimeoutException.class) || ThrowableUtil.throwableContainsException(e, SSLException.class); } @NonNull @Deprecated private static String getApiError(@NonNull Context context, @NonNull ApiException e) { String text; if ("missingtitle".equals(e.getCode()) || "invalidtitle".equals(e.getCode())) { text = context.getResources().getString(R.string.page_does_not_exist_error); } else { text = context.getString(R.string.error_server_response); } return text; } // TODO: migrate this to ApiException.toString() @NonNull @Deprecated private static String getApiErrorMessage(@NonNull Context c, @NonNull ApiException e) { String text; if (e.getInfo() != null) { // if we have an actual message from the server, then prefer it text = c.getString(R.string.format_error_server_message, e.getInfo()); } else if (e.getCode() != null) { // otherwise, just show the error code text = c.getString(R.string.format_error_server_code, e.getCode()); } else { // if all else fails, show the message of the exception text = e.getMessage(); } return text; } @Deprecated public static class AppError { private String error; private String detail; public AppError(@NonNull String error, @Nullable String detail) { this.error = error; this.detail = detail; } @NonNull public String getError() { return error; } @Nullable public String getDetail() { return detail; } } private ThrowableUtil() { } }