diff --git a/mobile/android/base/background/healthreport/HealthReportGenerator.java b/mobile/android/base/background/healthreport/HealthReportGenerator.java index 029fdbc61a4..0c05e1f8bac 100644 --- a/mobile/android/base/background/healthreport/HealthReportGenerator.java +++ b/mobile/android/base/background/healthreport/HealthReportGenerator.java @@ -4,10 +4,8 @@ package org.mozilla.gecko.background.healthreport; -import java.util.HashMap; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; +import org.json.JSONException; +import org.json.JSONObject; import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field; @@ -32,8 +30,9 @@ public class HealthReportGenerator { /** * @return null if no environment could be computed, or else the resulting document. + * @throws JSONException if there was an error adding environment data to the resulting document. */ - public JSONObject generateDocument(long since, long lastPingTime, String profilePath) { + public JSONObject generateDocument(long since, long lastPingTime, String profilePath) throws JSONException { Logger.info(LOG_TAG, "Generating FHR document from " + since + "; last ping " + lastPingTime + ", for profile " + profilePath); ProfileInformationCache cache = new ProfileInformationCache(profilePath); if (!cache.restoreUnlessInitialized()) { @@ -55,8 +54,9 @@ public class HealthReportGenerator { * * * days is a map from date strings to {hash: {measurement: {_v: version, fields...}}}. + * @throws JSONException if there was an error adding environment data to the resulting document. */ - public JSONObject generateDocument(long since, long lastPingTime, Environment currentEnvironment) { + public JSONObject generateDocument(long since, long lastPingTime, Environment currentEnvironment) throws JSONException { Logger.debug(LOG_TAG, "Current environment hash: " + currentEnvironment.getHash()); // We want to map field IDs to some strings as we go. @@ -64,26 +64,21 @@ public class HealthReportGenerator { JSONObject document = new JSONObject(); - // Defeat "unchecked" warnings with JDK7. See Bug 875088. - @SuppressWarnings("unchecked") - HashMap doc = ((HashMap) document); - if (lastPingTime >= HealthReportConstants.EARLIEST_LAST_PING) { - doc.put("lastPingDate", HealthReportUtils.getDateString(lastPingTime)); + document.put("lastPingDate", HealthReportUtils.getDateString(lastPingTime)); } - doc.put("thisPingDate", HealthReportUtils.getDateString(now())); - doc.put("version", PAYLOAD_VERSION); + document.put("thisPingDate", HealthReportUtils.getDateString(now())); + document.put("version", PAYLOAD_VERSION); - doc.put("environments", getEnvironmentsJSON(currentEnvironment, envs)); - doc.put("data", getDataJSON(currentEnvironment, envs, since)); + document.put("environments", getEnvironmentsJSON(currentEnvironment, envs)); + document.put("data", getDataJSON(currentEnvironment, envs, since)); return document; } - @SuppressWarnings("unchecked") protected JSONObject getDataJSON(Environment currentEnvironment, - SparseArray envs, long since) { + SparseArray envs, long since) throws JSONException { SparseArray fields = storage.getFieldsByID(); JSONObject days = getDaysJSON(currentEnvironment, envs, fields, since); @@ -96,8 +91,7 @@ public class HealthReportGenerator { return data; } - @SuppressWarnings("unchecked") - protected JSONObject getDaysJSON(Environment currentEnvironment, SparseArray envs, SparseArray fields, long since) { + protected JSONObject getDaysJSON(Environment currentEnvironment, SparseArray envs, SparseArray fields, long since) throws JSONException { JSONObject days = new JSONObject(); Cursor cursor = storage.getRawEventsSince(since); try { @@ -140,7 +134,7 @@ public class HealthReportGenerator { } final Field field = fields.get(cField); - JSONObject measurement = (JSONObject) envObject.get(field.measurementName); + JSONObject measurement = envObject.optJSONObject(field.measurementName); if (measurement == null) { // We will never have more than one measurement version within a // single environment -- to do so involves changing the build ID. And @@ -151,15 +145,10 @@ public class HealthReportGenerator { envObject.put(field.measurementName, measurement); } if (field.isDiscreteField()) { - JSONArray discrete = (JSONArray) measurement.get(field.fieldName); - if (discrete == null) { - discrete = new JSONArray(); - measurement.put(field.fieldName, discrete); - } if (field.isStringField()) { - discrete.add(cursor.getString(3)); + HealthReportUtils.append(measurement, field.fieldName, cursor.getString(3)); } else if (field.isIntegerField()) { - discrete.add(cursor.getLong(3)); + HealthReportUtils.append(measurement, field.fieldName, cursor.getLong(3)); } else { // Uh oh! throw new IllegalStateException("Unknown field type: " + field.flags); @@ -182,9 +171,8 @@ public class HealthReportGenerator { return days; } - @SuppressWarnings("unchecked") protected JSONObject getEnvironmentsJSON(Environment currentEnvironment, - SparseArray envs) { + SparseArray envs) throws JSONException { JSONObject environments = new JSONObject(); // Always do this, even if it hasn't recorded anything in the DB. @@ -201,8 +189,7 @@ public class HealthReportGenerator { return environments; } - @SuppressWarnings("unchecked") - private JSONObject jsonify(Environment e, Environment current) { + private JSONObject jsonify(Environment e, Environment current) throws JSONException { JSONObject age = getProfileAge(e, current); JSONObject sysinfo = getSysInfo(e, current); JSONObject gecko = getGeckoInfo(e, current); @@ -231,8 +218,7 @@ public class HealthReportGenerator { return out; } - @SuppressWarnings("unchecked") - private JSONObject getProfileAge(Environment e, Environment current) { + private JSONObject getProfileAge(Environment e, Environment current) throws JSONException { JSONObject age = new JSONObject(); int changes = 0; if (current == null || current.profileCreation != e.profileCreation) { @@ -246,8 +232,7 @@ public class HealthReportGenerator { return age; } - @SuppressWarnings("unchecked") - private JSONObject getSysInfo(Environment e, Environment current) { + private JSONObject getSysInfo(Environment e, Environment current) throws JSONException { JSONObject sysinfo = new JSONObject(); int changes = 0; if (current == null || current.cpuCount != e.cpuCount) { @@ -277,8 +262,7 @@ public class HealthReportGenerator { return sysinfo; } - @SuppressWarnings("unchecked") - private JSONObject getGeckoInfo(Environment e, Environment current) { + private JSONObject getGeckoInfo(Environment e, Environment current) throws JSONException { JSONObject gecko = new JSONObject(); int changes = 0; if (current == null || !current.vendor.equals(e.vendor)) { @@ -328,8 +312,7 @@ public class HealthReportGenerator { return gecko; } - @SuppressWarnings("unchecked") - private JSONObject getAppInfo(Environment e, Environment current) { + private JSONObject getAppInfo(Environment e, Environment current) throws JSONException { JSONObject appinfo = new JSONObject(); int changes = 0; if (current == null || current.isBlocklistEnabled != e.isBlocklistEnabled) { @@ -347,8 +330,7 @@ public class HealthReportGenerator { return appinfo; } - @SuppressWarnings("unchecked") - private JSONObject getAddonCounts(Environment e, Environment current) { + private JSONObject getAddonCounts(Environment e, Environment current) throws JSONException { JSONObject counts = new JSONObject(); int changes = 0; if (current == null || current.extensionCount != e.extensionCount) { @@ -370,8 +352,7 @@ public class HealthReportGenerator { return counts; } - @SuppressWarnings("unchecked") - private JSONObject getActiveAddons(Environment e, Environment current) { + private JSONObject getActiveAddons(Environment e, Environment current) throws JSONException { JSONObject active = new JSONObject(); int changes = 0; if (current != null && changes == 0) { diff --git a/mobile/android/base/background/healthreport/HealthReportUtils.java b/mobile/android/base/background/healthreport/HealthReportUtils.java index d61761eee61..1510af77b5b 100644 --- a/mobile/android/base/background/healthreport/HealthReportUtils.java +++ b/mobile/android/base/background/healthreport/HealthReportUtils.java @@ -5,9 +5,17 @@ package org.mozilla.gecko.background.healthreport; import java.text.SimpleDateFormat; +import java.util.HashSet; +import java.util.Iterator; import java.util.Locale; +import java.util.Set; +import java.util.SortedSet; import java.util.TimeZone; +import java.util.TreeSet; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; import org.mozilla.apache.commons.codec.digest.DigestUtils; import android.content.ContentUris; @@ -45,4 +53,88 @@ public class HealthReportUtils { public static Uri getEventURI(Uri environmentURI) { return environmentURI.buildUpon().path("/events/" + ContentUris.parseId(environmentURI) + "/").build(); } + + /** + * Copy the keys from the provided {@link JSONObject} into the provided {@link Set}. + */ + private static > T intoKeySet(T keys, JSONObject o) { + if (o == null || o == JSONObject.NULL) { + return keys; + } + + @SuppressWarnings("unchecked") + Iterator it = o.keys(); + while (it.hasNext()) { + keys.add(it.next()); + } + return keys; + } + + /** + * Produce a {@link SortedSet} containing the string keys of the provided + * object. + * + * @param o a {@link JSONObject} with string keys. + * @return a sorted set. + */ + public static SortedSet sortedKeySet(JSONObject o) { + return intoKeySet(new TreeSet(), o); + } + + /** + * Produce a {@link Set} containing the string keys of the provided object. + * @param o a {@link JSONObject} with string keys. + * @return an unsorted set. + */ + public static Set keySet(JSONObject o) { + return intoKeySet(new HashSet(), o); + } + + /** + * {@link JSONObject} doesn't provide a clone method, nor any + * useful constructors, so we do this the hard way. + * + * @return a new object containing the same keys and values as the old. + * @throws JSONException + * if JSONObject is even more stupid than expected and cannot store + * a value from the provided object in the new object. This should + * never happen. + */ + public static JSONObject shallowCopyObject(JSONObject o) throws JSONException { + if (o == null) { + return null; + } + + JSONObject out = new JSONObject(); + @SuppressWarnings("unchecked") + Iterator keys = out.keys(); + while (keys.hasNext()) { + final String key = keys.next(); + out.put(key, o.get(key)); + } + return out; + } + + /** + * Just like {@link JSONObject#accumulate(String, Object)}, but doesn't do the wrong thing for single values. + * @throws JSONException + */ + public static void append(JSONObject o, String key, Object value) throws JSONException { + if (!o.has(key)) { + JSONArray arr = new JSONArray(); + arr.put(value); + o.put(key, arr); + return; + } + Object dest = o.get(key); + if (dest instanceof JSONArray) { + ((JSONArray) dest).put(value); + return; + } + JSONArray arr = new JSONArray(); + arr.put(dest); + arr.put(value); + o.put(key, arr); + return; + } }