diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7e483c3d5f..7f2e59ec64 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -105,7 +105,6 @@ If you are a project maintainer, you can build and release a new version of - "Promote" the release build on Maven Central - Go to the [sonatype open source dashboard](https://oss.sonatype.org/index.html#stagingRepositories) - - Click “Staging Repositories” - Click the search box at the top right, and type “com.bugsnag” - Select the com.bugsnag staging repository - Click the “close” button in the toolbar, no message diff --git a/src/androidTest/java/com/bugsnag/android/BugsnagTestCase.java b/src/androidTest/java/com/bugsnag/android/BugsnagTestCase.java index f2844e0630..cd40771487 100644 --- a/src/androidTest/java/com/bugsnag/android/BugsnagTestCase.java +++ b/src/androidTest/java/com/bugsnag/android/BugsnagTestCase.java @@ -1,5 +1,6 @@ package com.bugsnag.android; +import java.io.IOException; import java.io.StringWriter; import org.json.JSONArray; @@ -9,10 +10,9 @@ import android.test.AndroidTestCase; public class BugsnagTestCase extends AndroidTestCase { - protected String streamableToString(JsonStream.Streamable streamable) { + protected String streamableToString(JsonStream.Streamable streamable) throws IOException { StringWriter writer = new StringWriter(); JsonStream jsonStream = new JsonStream(writer); - streamable.toStream(jsonStream); return writer.toString(); @@ -23,7 +23,7 @@ protected JSONObject streamableToJson(JsonStream.Streamable streamable) { try { return new JSONObject(streamableToString(streamable)); - } catch (JSONException e) { + } catch (Exception e) { e.printStackTrace(); } @@ -35,7 +35,7 @@ protected JSONArray streamableToJsonArray(JsonStream.Streamable streamable) { try { return new JSONArray(streamableToString(streamable)); - } catch (JSONException e) { + } catch (Exception e) { e.printStackTrace(); } diff --git a/src/androidTest/java/com/bugsnag/android/JsonStreamTest.java b/src/androidTest/java/com/bugsnag/android/JsonStreamTest.java new file mode 100644 index 0000000000..23eb155863 --- /dev/null +++ b/src/androidTest/java/com/bugsnag/android/JsonStreamTest.java @@ -0,0 +1,45 @@ +package com.bugsnag.android; + +import java.io.IOException; +import java.io.StringWriter; + +import org.json.JSONException; +import org.json.JSONObject; + +public class JsonStreamTest extends BugsnagTestCase { + public void testSaneValues() throws JSONException, IOException { + StringWriter writer = new StringWriter(); + JsonStream stream = new JsonStream(writer); + + Long nullLong = null; + Boolean nullBoolean = null; + String nullString = null; + Integer nullInteger = null; + Float nullFloat = null; + Double nullDouble = null; + + stream.beginObject(); + stream.name("nullLong").value(nullLong); + stream.name("nullBoolean").value(nullBoolean); + stream.name("nullString").value(nullString); + stream.name("nullInteger").value(nullInteger); + stream.name("nullFloat").value(nullFloat); + stream.name("nullDouble").value(nullDouble); + stream.name("string").value("string"); + stream.name("int").value(123); + stream.name("long").value(123l); + stream.name("float").value(123.45f); + stream.endObject(); + + JSONObject json = new JSONObject(writer.toString()); + assertTrue(json.isNull("nullLong")); + assertTrue(json.isNull("nullBoolean")); + assertTrue(json.isNull("nullString")); + assertTrue(json.isNull("nullInteger")); + assertTrue(json.isNull("nullFloat")); + assertTrue(json.isNull("nullDouble")); + assertEquals("string", json.getString("string")); + assertEquals(123, json.getInt("int")); + assertEquals(123l, json.getLong("long")); + } +} diff --git a/src/main/java/com/bugsnag/android/AppData.java b/src/main/java/com/bugsnag/android/AppData.java index 2a812b746b..e0c26e54c2 100644 --- a/src/main/java/com/bugsnag/android/AppData.java +++ b/src/main/java/com/bugsnag/android/AppData.java @@ -1,5 +1,7 @@ package com.bugsnag.android; +import java.io.IOException; + import android.content.Context; import android.content.pm.ApplicationInfo;; import android.content.pm.PackageManager; @@ -32,7 +34,7 @@ class AppData implements JsonStream.Streamable { guessedReleaseStage = guessReleaseStage(); } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { writer.beginObject(); writer.name("id").value(packageName); writer.name("name").value(appName); diff --git a/src/main/java/com/bugsnag/android/AppState.java b/src/main/java/com/bugsnag/android/AppState.java index 9a38416c8e..88f62a27f9 100644 --- a/src/main/java/com/bugsnag/android/AppState.java +++ b/src/main/java/com/bugsnag/android/AppState.java @@ -1,5 +1,6 @@ package com.bugsnag.android; +import java.io.IOException; import java.util.List; import android.app.ActivityManager; @@ -36,7 +37,7 @@ static void init() {} lowMemory = isLowMemory(); } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { writer.beginObject(); writer.name("duration").value(duration); writer.name("inForeground").value(inForeground); diff --git a/src/main/java/com/bugsnag/android/Breadcrumbs.java b/src/main/java/com/bugsnag/android/Breadcrumbs.java index 091d921b22..de4bda2a53 100644 --- a/src/main/java/com/bugsnag/android/Breadcrumbs.java +++ b/src/main/java/com/bugsnag/android/Breadcrumbs.java @@ -1,5 +1,6 @@ package com.bugsnag.android; +import java.io.IOException; import java.util.LinkedList; import java.util.Date; import java.util.List; @@ -20,7 +21,7 @@ private static class Breadcrumb { private List store = new LinkedList(); private int maxSize = DEFAULT_MAX_SIZE; - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { writer.beginArray(); for(Breadcrumb breadcrumb : store) { diff --git a/src/main/java/com/bugsnag/android/DeviceData.java b/src/main/java/com/bugsnag/android/DeviceData.java index 1351c8b4a6..d80809244f 100644 --- a/src/main/java/com/bugsnag/android/DeviceData.java +++ b/src/main/java/com/bugsnag/android/DeviceData.java @@ -1,6 +1,7 @@ package com.bugsnag.android; import java.io.File; +import java.io.IOException; import java.util.Locale; import android.content.Context; @@ -38,7 +39,7 @@ class DeviceData implements JsonStream.Streamable { id = getAndroidId(); } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { writer.beginObject(); writer.name("manufacturer").value(android.os.Build.MANUFACTURER); writer.name("brand").value(android.os.Build.BRAND); diff --git a/src/main/java/com/bugsnag/android/DeviceState.java b/src/main/java/com/bugsnag/android/DeviceState.java index 5efe85d1ae..ef42924245 100644 --- a/src/main/java/com/bugsnag/android/DeviceState.java +++ b/src/main/java/com/bugsnag/android/DeviceState.java @@ -1,5 +1,6 @@ package com.bugsnag.android; +import java.io.IOException; import java.util.Date; import android.content.ContentResolver; @@ -45,7 +46,7 @@ class DeviceState implements JsonStream.Streamable { time = getTime(); } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { writer.beginObject(); writer.name("freeMemory").value(freeMemory); writer.name("orientation").value(orientation); diff --git a/src/main/java/com/bugsnag/android/Error.java b/src/main/java/com/bugsnag/android/Error.java index b2c851ceca..f1824a165c 100644 --- a/src/main/java/com/bugsnag/android/Error.java +++ b/src/main/java/com/bugsnag/android/Error.java @@ -1,5 +1,7 @@ package com.bugsnag.android; +import java.io.IOException; + /** * Information and associated diagnostics relating to a handled or unhandled * Exception. @@ -30,7 +32,7 @@ public class Error implements JsonStream.Streamable { this.exception = exception; } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { // Merge error metaData into global metadata and apply filters MetaData mergedMetaData = MetaData.merge(config.metaData, metaData); mergedMetaData.setFilters(config.filters); diff --git a/src/main/java/com/bugsnag/android/ErrorStore.java b/src/main/java/com/bugsnag/android/ErrorStore.java index 083043fdfe..98e05e1a7d 100644 --- a/src/main/java/com/bugsnag/android/ErrorStore.java +++ b/src/main/java/com/bugsnag/android/ErrorStore.java @@ -74,13 +74,19 @@ void write(Error error) { if(path == null) return; String filename = String.format("%s%d.json", path, System.currentTimeMillis()); + Writer out = null; try { - Writer out = new FileWriter(filename); - new JsonStream(out).value(error).close(); + out = new FileWriter(filename); + + JsonStream stream = new JsonStream(out); + stream.value(error); + stream.close(); Logger.info(String.format("Saved unsent error to disk (%s) ", filename)); } catch (Exception e) { Logger.warn(String.format("Couldn't save unsent error to disk (%s) ", filename), e); + } finally { + IOUtils.closeQuietly(out); } } } diff --git a/src/main/java/com/bugsnag/android/ExceptionChain.java b/src/main/java/com/bugsnag/android/ExceptionChain.java index 9e2a70ce84..db3fe66ec9 100644 --- a/src/main/java/com/bugsnag/android/ExceptionChain.java +++ b/src/main/java/com/bugsnag/android/ExceptionChain.java @@ -1,5 +1,7 @@ package com.bugsnag.android; +import java.io.IOException; + /** * Unwrap and serialize exception information and any "cause" exceptions. */ @@ -12,7 +14,7 @@ class ExceptionChain implements JsonStream.Streamable { this.exception = exception; } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { writer.beginArray(); Throwable currentEx = exception; diff --git a/src/main/java/com/bugsnag/android/HttpClient.java b/src/main/java/com/bugsnag/android/HttpClient.java index 94595c5fde..c9b5d1f212 100644 --- a/src/main/java/com/bugsnag/android/HttpClient.java +++ b/src/main/java/com/bugsnag/android/HttpClient.java @@ -38,13 +38,7 @@ static void post(String urlString, JsonStream.Streamable payload) throws Network payload.toStream(stream); stream.close(); } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } + IOUtils.closeQuietly(out); } // End the request, get the response code @@ -55,9 +49,7 @@ static void post(String urlString, JsonStream.Streamable payload) throws Network } catch (IOException e) { throw new NetworkException(urlString, e); } finally { - if(conn != null) { - conn.disconnect(); - } + IOUtils.close(conn); } } } diff --git a/src/main/java/com/bugsnag/android/IOUtils.java b/src/main/java/com/bugsnag/android/IOUtils.java new file mode 100644 index 0000000000..e426968fcf --- /dev/null +++ b/src/main/java/com/bugsnag/android/IOUtils.java @@ -0,0 +1,45 @@ +package com.bugsnag.android; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.net.HttpURLConnection; +import java.net.URLConnection; + +class IOUtils { + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + private static final int EOF = -1; + + public static void closeQuietly(final Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (final IOException ioe) { + // ignore + } + } + + public static void close(final URLConnection conn) { + if (conn instanceof HttpURLConnection) { + ((HttpURLConnection) conn).disconnect(); + } + } + + public static int copy(final Reader input, final Writer output) throws IOException { + char[] buffer = new char[DEFAULT_BUFFER_SIZE]; + long count = 0; + int n = 0; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + + if (count > Integer.MAX_VALUE) { + return -1; + } + + return (int) count; + } +} diff --git a/src/main/java/com/bugsnag/android/JsonStream.java b/src/main/java/com/bugsnag/android/JsonStream.java index 69018c2b67..a1586c6c4c 100644 --- a/src/main/java/com/bugsnag/android/JsonStream.java +++ b/src/main/java/com/bugsnag/android/JsonStream.java @@ -5,135 +5,51 @@ import java.io.IOException; import java.io.Writer; -class JsonStream { +class JsonStream extends JsonWriter { static interface Streamable { - void toStream(JsonStream stream); + void toStream(JsonStream stream) throws IOException; } - private JsonWriter writer; private Writer out; JsonStream(Writer out) { - writer = new JsonWriter(out); + super(out); this.out = out; } - // Wrap JsonWriter methods to swallow exceptions and allow chaining - JsonStream beginObject() { - try { - writer.beginObject(); - } catch (IOException e) { - e.printStackTrace(); - } - return this; - } - - JsonStream endObject() { - try { - writer.endObject(); - } catch (IOException e) { - e.printStackTrace(); - } - return this; - } - - JsonStream beginArray() { - try { - writer.beginArray(); - } catch (IOException e) { - e.printStackTrace(); - } + // Allow chaining name().value() + public JsonStream name(String name) throws IOException { + super.name(name); return this; } - JsonStream endArray() { - try { - writer.endArray(); - } catch (IOException e) { - e.printStackTrace(); - } - return this; - } - - JsonStream name(String name) { - try { - writer.name(name); - } catch (IOException e) { - e.printStackTrace(); + // Add null-protection + void value(Boolean value) throws IOException { + if (value == null) { + nullValue(); + } else { + super.value(value); } - return this; - } - - JsonStream nullValue() { - try { - writer.nullValue(); - } catch (IOException e) { - e.printStackTrace(); - } - return this; } - JsonStream value(String val) { - try { - writer.value(val); - } catch (IOException e) { - e.printStackTrace(); - } - return this; + // Add support for Streamable values + void value(Streamable streamable) throws IOException { + streamable.toStream(this); } - JsonStream value(Number val) { - try { - writer.value(val); - } catch (IOException e) { - e.printStackTrace(); - } - return this; - } + // Add support for File values + void value(File file) throws IOException { + super.flush(); - JsonStream value(Boolean val) { + // Copy the file contents onto the stream + FileReader input = null; try { - if (val == null) { - writer.nullValue(); - } else { - writer.value(val); - } - } catch (IOException e) { - e.printStackTrace(); + input = new FileReader(file); + IOUtils.copy(input, out); + } finally { + IOUtils.closeQuietly(input); } - return this; - } - void close() { - try { - writer.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - // Add support for streaming File and Streamable objects - JsonStream value(Streamable streamable) { - streamable.toStream(this); - return this; - } - - JsonStream value(File file) { - try { - writer.flush(); - - // Buffer the file contents onto the stream - FileReader input = new FileReader(file); - char[] buffer = new char[1024 * 4]; - int n = 0; - while (-1 != (n = input.read(buffer))) { - out.write(buffer, 0, n); - } - - out.flush(); - } catch (IOException e) { - e.printStackTrace(); - } - return this; + out.flush(); } } diff --git a/src/main/java/com/bugsnag/android/MetaData.java b/src/main/java/com/bugsnag/android/MetaData.java index efae2ead1d..2ff576def1 100644 --- a/src/main/java/com/bugsnag/android/MetaData.java +++ b/src/main/java/com/bugsnag/android/MetaData.java @@ -1,5 +1,6 @@ package com.bugsnag.android; +import java.io.IOException; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; @@ -36,7 +37,7 @@ public MetaData(Map m) { store = new HashMap(m); } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { objectToStream(store, writer); } @@ -129,7 +130,7 @@ private static Map mergeMaps(Map... maps) { } // Write complex/nested values to a JsonStreamer - private void objectToStream(Object obj, JsonStream writer) { + private void objectToStream(Object obj, JsonStream writer) throws IOException { if(obj == null) { writer.nullValue(); } else if(obj instanceof String) { diff --git a/src/main/java/com/bugsnag/android/Notification.java b/src/main/java/com/bugsnag/android/Notification.java index 883257c310..54fe3546f4 100644 --- a/src/main/java/com/bugsnag/android/Notification.java +++ b/src/main/java/com/bugsnag/android/Notification.java @@ -1,6 +1,7 @@ package com.bugsnag.android; import java.io.File; +import java.io.IOException; import java.util.Collection; import java.util.LinkedList; @@ -21,7 +22,7 @@ class Notification implements JsonStream.Streamable { this.errorFiles = new LinkedList(); } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { // Create a JSON stream and top-level object writer.beginObject(); diff --git a/src/main/java/com/bugsnag/android/Notifier.java b/src/main/java/com/bugsnag/android/Notifier.java index 17c2c4831c..7729b5caa3 100644 --- a/src/main/java/com/bugsnag/android/Notifier.java +++ b/src/main/java/com/bugsnag/android/Notifier.java @@ -1,5 +1,7 @@ package com.bugsnag.android; +import java.io.IOException; + /** * Information about this library, including name and version. */ @@ -13,7 +15,7 @@ public static Notifier getInstance() { return instance; } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { writer.beginObject(); writer.name("name").value(NOTIFIER_NAME); writer.name("version").value(NOTIFIER_VERSION); diff --git a/src/main/java/com/bugsnag/android/Severity.java b/src/main/java/com/bugsnag/android/Severity.java index a981f2d59c..61b270157b 100644 --- a/src/main/java/com/bugsnag/android/Severity.java +++ b/src/main/java/com/bugsnag/android/Severity.java @@ -1,5 +1,7 @@ package com.bugsnag.android; +import java.io.IOException; + /** * The severity of an Error, one of "error", "warning" or "info". * @@ -17,7 +19,7 @@ public enum Severity implements JsonStream.Streamable { this.name = name; } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { writer.value(name); } } diff --git a/src/main/java/com/bugsnag/android/Stacktrace.java b/src/main/java/com/bugsnag/android/Stacktrace.java index d88fe55207..b8438d0feb 100644 --- a/src/main/java/com/bugsnag/android/Stacktrace.java +++ b/src/main/java/com/bugsnag/android/Stacktrace.java @@ -1,5 +1,7 @@ package com.bugsnag.android; +import java.io.IOException; + /** * Serialize an exception stacktrace and mark frames as "in-project" * where appropriate. @@ -13,7 +15,7 @@ class Stacktrace implements JsonStream.Streamable { this.stacktrace = stacktrace; } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { writer.beginArray(); for(StackTraceElement el : stacktrace) { diff --git a/src/main/java/com/bugsnag/android/ThreadState.java b/src/main/java/com/bugsnag/android/ThreadState.java index 16200ecf19..133047c7c6 100644 --- a/src/main/java/com/bugsnag/android/ThreadState.java +++ b/src/main/java/com/bugsnag/android/ThreadState.java @@ -1,5 +1,6 @@ package com.bugsnag.android; +import java.io.IOException; import java.util.Arrays; import java.util.Comparator; import java.util.Map; @@ -14,7 +15,7 @@ class ThreadState implements JsonStream.Streamable { this.config = config; } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { long currentId = Thread.currentThread().getId(); Map liveThreads = Thread.getAllStackTraces(); diff --git a/src/main/java/com/bugsnag/android/User.java b/src/main/java/com/bugsnag/android/User.java index 060b11fb28..f6f9c9a3cb 100644 --- a/src/main/java/com/bugsnag/android/User.java +++ b/src/main/java/com/bugsnag/android/User.java @@ -1,5 +1,7 @@ package com.bugsnag.android; +import java.io.IOException; + /** * Information about the current user of your application. */ @@ -20,7 +22,7 @@ class User implements JsonStream.Streamable { this(u.id, u.email, u.name); } - public void toStream(JsonStream writer) { + public void toStream(JsonStream writer) throws IOException { writer.beginObject(); if(id != null) {