Updated Bugsnag/Android to 5.19.2

This commit is contained in:
M66B 2022-02-03 18:36:29 +01:00
parent f78925fb23
commit 169f117950
70 changed files with 11285 additions and 267 deletions

View File

@ -338,7 +338,7 @@ dependencies {
def dnsjava_version = "2.1.9"
def openpgp_version = "12.0"
def badge_version = "1.1.22"
def bugsnag_version = "5.14.0"
def bugsnag_version = "5.19.2"
def biweekly_version = "0.6.6"
def vcard_version = "0.11.3"
def relinker_version = "1.4.3"

View File

@ -16,6 +16,11 @@ public class Breadcrumb implements JsonStream.Streamable {
final BreadcrumbInternal impl;
private final Logger logger;
Breadcrumb(@NonNull BreadcrumbInternal impl, @NonNull Logger logger) {
this.impl = impl;
this.logger = logger;
}
Breadcrumb(@NonNull String message, @NonNull Logger logger) {
this.impl = new BreadcrumbInternal(message);
this.logger = logger;

View File

@ -1,6 +1,5 @@
package com.bugsnag.android
import com.bugsnag.android.internal.DateUtils
import java.io.IOException
import java.util.concurrent.atomic.AtomicInteger
@ -43,7 +42,8 @@ internal class BreadcrumbState(
StateEvent.AddBreadcrumb(
breadcrumb.impl.message,
breadcrumb.impl.type,
DateUtils.toIso8601(breadcrumb.impl.timestamp),
// an encoding of milliseconds since the epoch
"t${breadcrumb.impl.timestamp.time}",
breadcrumb.impl.metadata ?: mutableMapOf()
)
}

View File

@ -38,4 +38,8 @@ enum class BreadcrumbType(private val type: String) {
USER("user");
override fun toString() = type
internal companion object {
internal fun fromDescriptor(type: String) = values().singleOrNull { it.type == type }
}
}

View File

@ -6,7 +6,6 @@ import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -404,6 +403,59 @@ public final class Bugsnag {
getClient().markLaunchCompleted();
}
/**
* Add a single feature flag with no variant. If there is an existing feature flag with the
* same name, it will be overwritten to have no variant.
*
* @param name the name of the feature flag to add
* @see #addFeatureFlag(String, String)
*/
public static void addFeatureFlag(@NonNull String name) {
getClient().addFeatureFlag(name);
}
/**
* Add a single feature flag with an optional variant. If there is an existing feature
* flag with the same name, it will be overwritten with the new variant. If the variant is
* {@code null} this method has the same behaviour as {@link #addFeatureFlag(String)}.
*
* @param name the name of the feature flag to add
* @param variant the variant to set the feature flag to, or {@code null} to specify a feature
* flag with no variant
*/
public static void addFeatureFlag(@NonNull String name, @Nullable String variant) {
getClient().addFeatureFlag(name, variant);
}
/**
* Add a collection of feature flags. This method behaves exactly the same as calling
* {@link #addFeatureFlag(String, String)} for each of the {@code FeatureFlag} objects.
*
* @param featureFlags the feature flags to add
* @see #addFeatureFlag(String, String)
*/
public static void addFeatureFlags(@NonNull Iterable<FeatureFlag> featureFlags) {
getClient().addFeatureFlags(featureFlags);
}
/**
* Remove a single feature flag regardless of its current status. This will stop the specified
* feature flag from being reported. If the named feature flag does not exist this will
* have no effect.
*
* @param name the name of the feature flag to remove
*/
public static void clearFeatureFlag(@NonNull String name) {
getClient().clearFeatureFlag(name);
}
/**
* Clear all of the feature flags. This will stop all feature flags from being reported.
*/
public static void clearFeatureFlags() {
getClient().clearFeatureFlags();
}
/**
* Get the current Bugsnag Client instance.
*/

View File

@ -0,0 +1,264 @@
package com.bugsnag.android
import com.bugsnag.android.internal.DateUtils
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
internal class BugsnagEventMapper(
private val logger: Logger
) {
@Suppress("UNCHECKED_CAST")
internal fun convertToEventImpl(map: Map<in String, Any?>, apiKey: String): EventInternal {
val event = EventInternal(apiKey)
// populate exceptions. check this early to avoid unnecessary serialization if
// no stacktrace was gathered.
val exceptions = map["exceptions"] as? List<MutableMap<String, Any?>>
exceptions?.mapTo(event.errors) { Error(convertErrorInternal(it), this.logger) }
// populate user
event.userImpl = convertUser(map.readEntry("user"))
// populate metadata
val metadataMap: Map<String, Map<String, Any?>> = map.readEntry("metaData")
metadataMap.forEach { (key, value) ->
event.addMetadata(key, value)
}
val featureFlagsList: List<Map<String, Any?>> = map.readEntry("featureFlags")
featureFlagsList.forEach { featureFlagMap ->
event.addFeatureFlag(
featureFlagMap.readEntry("featureFlag"),
featureFlagMap["variant"] as? String
)
}
// populate breadcrumbs
val breadcrumbList: List<MutableMap<String, Any?>> = map.readEntry("breadcrumbs")
breadcrumbList.mapTo(event.breadcrumbs) {
Breadcrumb(
convertBreadcrumbInternal(it),
logger
)
}
// populate context
event.context = map["context"] as? String
// populate groupingHash
event.groupingHash = map["groupingHash"] as? String
// populate app
event.app = convertAppWithState(map.readEntry("app"))
// populate device
event.device = convertDeviceWithState(map.readEntry("device"))
// populate session
val sessionMap = map["session"] as? Map<String, Any?>
sessionMap?.let {
event.session = Session(it, logger)
}
// populate threads
val threads = map["threads"] as? List<Map<String, Any?>>
threads?.mapTo(event.threads) { Thread(convertThread(it), logger) }
// populate projectPackages
val projectPackages = map["projectPackages"] as? List<String>
projectPackages?.let {
event.projectPackages = projectPackages
}
// populate severity
val severityStr: String = map.readEntry("severity")
val severity = Severity.fromDescriptor(severityStr)
val unhandled: Boolean = map.readEntry("unhandled")
val reason = deserializeSeverityReason(map, unhandled, severity)
event.updateSeverityReasonInternal(reason)
event.normalizeStackframeErrorTypes()
return event
}
internal fun convertErrorInternal(error: Map<String, Any?>): ErrorInternal {
return ErrorInternal(
error.readEntry("errorClass"),
error["message"] as? String,
type = error.readEntry<String>("type").let { type ->
ErrorType.fromDescriptor(type)
?: throw IllegalArgumentException("unknown ErrorType: '$type'")
},
stacktrace = convertStacktrace(error.readEntry("stacktrace"))
)
}
internal fun convertUser(user: Map<String, Any?>): User {
return User(
user["id"] as? String,
user["email"] as? String,
user["name"] as? String
)
}
@Suppress("UNCHECKED_CAST")
internal fun convertBreadcrumbInternal(breadcrumb: Map<String, Any?>): BreadcrumbInternal {
return BreadcrumbInternal(
breadcrumb.readEntry("name"),
breadcrumb.readEntry<String>("type").let { type ->
BreadcrumbType.fromDescriptor(type)
?: BreadcrumbType.MANUAL
},
breadcrumb["metaData"] as? MutableMap<String, Any?>,
breadcrumb.readEntry<String>("timestamp").toDate()
)
}
internal fun convertAppWithState(app: Map<String, Any?>): AppWithState {
return AppWithState(
app["binaryArch"] as? String,
app["id"] as? String,
app["releaseStage"] as? String,
app["version"] as? String,
app["codeBundleId"] as? String,
app["buildUUID"] as? String,
app["type"] as? String,
(app["versionCode"] as? Number)?.toInt(),
(app["duration"] as? Number)?.toLong(),
(app["durationInForeground"] as? Number)?.toLong(),
app["inForeground"] as? Boolean,
app["isLaunching"] as? Boolean
)
}
@Suppress("UNCHECKED_CAST")
internal fun convertDeviceWithState(device: Map<String, Any?>): DeviceWithState {
return DeviceWithState(
DeviceBuildInfo(
device["manufacturer"] as? String,
device["model"] as? String,
device["osVersion"] as? String,
null,
null,
null,
null,
null,
(device["cpuAbi"] as? List<String>)?.toTypedArray()
),
device["jailbroken"] as? Boolean,
device["id"] as? String,
device["locale"] as? String,
(device["totalMemory"] as? Number)?.toLong(),
(device["runtimeVersions"] as? Map<String, Any>)?.toMutableMap()
?: mutableMapOf(),
(device["freeDisk"] as? Number)?.toLong(),
(device["freeMemory"] as? Number)?.toLong(),
device["orientation"] as? String,
(device["time"] as? String)?.toDate()
)
}
@Suppress("UNCHECKED_CAST")
internal fun convertThread(thread: Map<String, Any?>): ThreadInternal {
return ThreadInternal(
(thread["id"] as? Number)?.toLong() ?: 0,
thread.readEntry("name"),
ThreadType.fromDescriptor(thread.readEntry("type")) ?: ThreadType.ANDROID,
thread["errorReportingThread"] == true,
thread.readEntry("state"),
(thread["stacktrace"] as? List<Map<String, Any?>>)?.let { convertStacktrace(it) }
?: Stacktrace(emptyList())
)
}
internal fun convertStacktrace(trace: List<Map<String, Any?>>): Stacktrace {
return Stacktrace(trace.map { convertStackframe(it) })
}
internal fun convertStackframe(frame: Map<String, Any?>): Stackframe {
val copy: MutableMap<String, Any?> = frame.toMutableMap()
val lineNumber = frame["lineNumber"] as? Number
copy["lineNumber"] = lineNumber?.toLong()
(frame["frameAddress"] as? String)?.let {
copy["frameAddress"] = java.lang.Long.decode(it)
}
(frame["symbolAddress"] as? String)?.let {
copy["symbolAddress"] = java.lang.Long.decode(it)
}
(frame["loadAddress"] as? String)?.let {
copy["loadAddress"] = java.lang.Long.decode(it)
}
(frame["isPC"] as? Boolean)?.let {
copy["isPC"] = it
}
return Stackframe(copy)
}
internal fun deserializeSeverityReason(
map: Map<in String, Any?>,
unhandled: Boolean,
severity: Severity?
): SeverityReason {
val severityReason: Map<String, Any> = map.readEntry("severityReason")
val unhandledOverridden: Boolean =
severityReason.readEntry("unhandledOverridden")
val type: String = severityReason.readEntry("type")
val originalUnhandled = when {
unhandledOverridden -> !unhandled
else -> unhandled
}
val attrMap: Map<String, String>? = severityReason.readEntry("attributes")
val entry = attrMap?.entries?.singleOrNull()
return SeverityReason(
type,
severity,
unhandled,
originalUnhandled,
entry?.value,
entry?.key
)
}
/**
* Convenience method for getting an entry from a Map in the expected type, which
* throws useful error messages if the expected type is not there.
*/
private inline fun <reified T> Map<*, *>.readEntry(key: String): T {
when (val value = get(key)) {
is T -> return value
null -> throw IllegalStateException("cannot find json property '$key'")
else -> throw IllegalArgumentException(
"json property '$key' not " +
"of expected type, found ${value.javaClass.name}"
)
}
}
// SimpleDateFormat isn't thread safe, cache one instance per thread as needed.
private val ndkDateFormatHolder = object : ThreadLocal<DateFormat>() {
override fun initialValue(): DateFormat {
return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
}
}
private fun String.toDate(): Date {
return try {
DateUtils.fromIso8601(this)
} catch (pe: IllegalArgumentException) {
ndkDateFormatHolder.get()!!.parse(this)
?: throw IllegalArgumentException("cannot parse date $this")
}
}
}

View File

@ -28,6 +28,8 @@ internal class BugsnagStateModule(
val metadataState = copyMetadataState(configuration)
val featureFlagState = configuration.impl.featureFlagState.copy()
private fun copyMetadataState(configuration: Configuration): MetadataState {
// performs deep copy of metadata to preserve immutability of Configuration interface
val orig = configuration.impl.metadataState.metadata

View File

@ -1,11 +1,12 @@
package com.bugsnag.android
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.CopyOnWriteArrayList
internal data class CallbackState(
val onErrorTasks: MutableCollection<OnErrorCallback> = ConcurrentLinkedQueue<OnErrorCallback>(),
val onBreadcrumbTasks: MutableCollection<OnBreadcrumbCallback> = ConcurrentLinkedQueue<OnBreadcrumbCallback>(),
val onSessionTasks: MutableCollection<OnSessionCallback> = ConcurrentLinkedQueue()
val onErrorTasks: MutableCollection<OnErrorCallback> = CopyOnWriteArrayList(),
val onBreadcrumbTasks: MutableCollection<OnBreadcrumbCallback> = CopyOnWriteArrayList(),
val onSessionTasks: MutableCollection<OnSessionCallback> = CopyOnWriteArrayList(),
val onSendTasks: MutableCollection<OnSendCallback> = CopyOnWriteArrayList()
) : CallbackAware {
override fun addOnError(onError: OnErrorCallback) {
@ -32,6 +33,14 @@ internal data class CallbackState(
onSessionTasks.remove(onSession)
}
fun addOnSend(onSend: OnSendCallback) {
onSendTasks.add(onSend)
}
fun removeOnSend(onSend: OnSendCallback) {
onSendTasks.remove(onSend)
}
fun runOnErrorTasks(event: Event, logger: Logger): Boolean {
// optimization to avoid construction of iterator when no callbacks set
if (onErrorTasks.isEmpty()) {
@ -83,9 +92,32 @@ internal data class CallbackState(
return true
}
fun runOnSendTasks(event: Event, logger: Logger): Boolean {
onSendTasks.forEach {
try {
if (!it.onSend(event)) {
return false
}
} catch (ex: Throwable) {
logger.w("OnSendCallback threw an Exception", ex)
}
}
return true
}
fun runOnSendTasks(eventSource: () -> Event, logger: Logger): Boolean {
if (onSendTasks.isEmpty()) {
// avoid constructing event from eventSource if not needed
return true
}
return this.runOnSendTasks(eventSource(), logger)
}
fun copy() = this.copy(
onErrorTasks = onErrorTasks,
onBreadcrumbTasks = onBreadcrumbTasks,
onSessionTasks = onSessionTasks
onSessionTasks = onSessionTasks,
onSendTasks = onSendTasks
)
}

View File

@ -20,6 +20,7 @@ import kotlin.jvm.functions.Function1;
import kotlin.jvm.functions.Function2;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
@ -41,11 +42,12 @@ import java.util.concurrent.RejectedExecutionException;
* @see Bugsnag
*/
@SuppressWarnings({"checkstyle:JavadocTagContinuationIndentation", "ConstantConditions"})
public class Client implements MetadataAware, CallbackAware, UserAware {
public class Client implements MetadataAware, CallbackAware, UserAware, FeatureFlagAware {
final ImmutableConfig immutableConfig;
final MetadataState metadataState;
final FeatureFlagState featureFlagState;
private final ContextState contextState;
private final CallbackState callbackState;
@ -152,6 +154,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
breadcrumbState = bugsnagStateModule.getBreadcrumbState();
contextState = bugsnagStateModule.getContextState();
metadataState = bugsnagStateModule.getMetadataState();
featureFlagState = bugsnagStateModule.getFeatureFlagState();
// lookup system services
final SystemServiceModule systemServiceModule = new SystemServiceModule(contextModule);
@ -179,12 +182,13 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
registerLifecycleCallbacks();
EventStorageModule eventStorageModule = new EventStorageModule(contextModule, configModule,
dataCollectionModule, bgTaskService, trackerModule, systemServiceModule, notifier);
dataCollectionModule, bgTaskService, trackerModule, systemServiceModule, notifier,
callbackState);
eventStorageModule.resolveDependencies(bgTaskService, TaskType.IO);
eventStore = eventStorageModule.getEventStore();
deliveryDelegate = new DeliveryDelegate(logger, eventStore,
immutableConfig, breadcrumbState, notifier, bgTaskService);
immutableConfig, callbackState, notifier, bgTaskService);
// Install a default exception handler with this client
exceptionHandler = new ExceptionHandler(this, logger);
@ -222,6 +226,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
ContextState contextState,
CallbackState callbackState,
UserState userState,
FeatureFlagState featureFlagState,
ClientObservable clientObservable,
Context appContext,
@NonNull DeviceDataCollector deviceDataCollector,
@ -243,6 +248,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
this.contextState = contextState;
this.callbackState = callbackState;
this.userState = userState;
this.featureFlagState = featureFlagState;
this.clientObservable = clientObservable;
this.appContext = appContext;
this.deviceDataCollector = deviceDataCollector;
@ -402,6 +408,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
deliveryDelegate.addObserver(observer);
launchCrashTracker.addObserver(observer);
memoryTrimState.addObserver(observer);
featureFlagState.addObserver(observer);
}
void removeObserver(StateObserver observer) {
@ -414,6 +421,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
deliveryDelegate.removeObserver(observer);
launchCrashTracker.removeObserver(observer);
memoryTrimState.removeObserver(observer);
featureFlagState.removeObserver(observer);
}
/**
@ -424,6 +432,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
contextState.emitObservableEvent();
userState.emitObservableEvent();
memoryTrimState.emitObservableEvent();
featureFlagState.emitObservableEvent();
}
/**
@ -677,7 +686,9 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
}
SeverityReason severityReason = SeverityReason.newInstance(REASON_HANDLED_EXCEPTION);
Metadata metadata = metadataState.getMetadata();
Event event = new Event(exc, immutableConfig, severityReason, metadata, logger);
FeatureFlags featureFlags = featureFlagState.getFeatureFlags();
Event event = new Event(exc, immutableConfig, severityReason, metadata, featureFlags,
logger);
populateAndNotifyAndroidEvent(event, onError);
} else {
logNull("notify");
@ -695,7 +706,8 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
SeverityReason handledState
= SeverityReason.newInstance(severityReason, Severity.ERROR, attributeValue);
Metadata data = Metadata.Companion.merge(metadataState.getMetadata(), metadata);
Event event = new Event(exc, immutableConfig, handledState, data, logger);
Event event = new Event(exc, immutableConfig, handledState,
data, featureFlagState.getFeatureFlags(), logger);
populateAndNotifyAndroidEvent(event, null);
// persist LastRunInfo so that on relaunch users can check the app crashed
@ -740,9 +752,8 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
@Nullable OnErrorCallback onError) {
// set the redacted keys on the event as this
// will not have been set for RN/Unity events
Set<String> redactedKeys = metadataState.getMetadata().getRedactedKeys();
Metadata eventMetadata = event.getImpl().getMetadata();
eventMetadata.setRedactedKeys(redactedKeys);
Collection<String> redactedKeys = metadataState.getMetadata().getRedactedKeys();
event.setRedactedKeys(redactedKeys);
// get session for event
Session currentSession = sessionTracker.getCurrentSession();
@ -754,11 +765,15 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
// Run on error tasks, don't notify if any return false
if (!callbackState.runOnErrorTasks(event, logger)
|| (onError != null && !onError.onError(event))) {
|| (onError != null
&& !onError.onError(event))) {
logger.d("Skipping notification - onError task returned false");
return;
}
// leave an error breadcrumb of this event - for the next event
leaveErrorBreadcrumb(event);
deliveryDelegate.deliver(event);
}
@ -922,6 +937,80 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
}
}
private void leaveErrorBreadcrumb(@NonNull Event event) {
// Add a breadcrumb for this event occurring
List<Error> errors = event.getErrors();
if (errors.size() > 0) {
String errorClass = errors.get(0).getErrorClass();
String message = errors.get(0).getErrorMessage();
Map<String, Object> data = new HashMap<>();
data.put("errorClass", errorClass);
data.put("message", message);
data.put("unhandled", String.valueOf(event.isUnhandled()));
data.put("severity", event.getSeverity().toString());
breadcrumbState.add(new Breadcrumb(errorClass,
BreadcrumbType.ERROR, data, new Date(), logger));
}
}
/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlag(@NonNull String name) {
if (name != null) {
featureFlagState.addFeatureFlag(name);
} else {
logNull("addFeatureFlag");
}
}
/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlag(@NonNull String name, @Nullable String variant) {
if (name != null) {
featureFlagState.addFeatureFlag(name, variant);
} else {
logNull("addFeatureFlag");
}
}
/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlags(@NonNull Iterable<FeatureFlag> featureFlags) {
if (featureFlags != null) {
featureFlagState.addFeatureFlags(featureFlags);
} else {
logNull("addFeatureFlags");
}
}
/**
* {@inheritDoc}
*/
@Override
public void clearFeatureFlag(@NonNull String name) {
if (name != null) {
featureFlagState.clearFeatureFlag(name);
} else {
logNull("clearFeatureFlag");
}
}
/**
* {@inheritDoc}
*/
@Override
public void clearFeatureFlags() {
featureFlagState.clearFeatureFlags();
}
/**
* Retrieves information about the last launch of the application, if it has been run before.
*
@ -976,8 +1065,15 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
private void warnIfNotAppContext(Context androidContext) {
if (!(androidContext instanceof Application)) {
logger.w("Warning - Non-Application context detected! Please ensure that you are "
+ "initializing Bugsnag from a custom Application class.");
logger.w("You should initialize Bugsnag from the onCreate() callback of your "
+ "Application subclass, as this guarantees errors are captured as early "
+ "as possible. "
+ "If a custom Application subclass is not possible in your app then you "
+ "should suppress this warning by passing the Application context instead: "
+ "Bugsnag.start(context.getApplicationContext()). "
+ "For further info see: "
+ "https://docs.bugsnag.com/platforms/android/#basic-configuration");
}
}
@ -1039,6 +1135,10 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
return metadataState;
}
FeatureFlagState getFeatureFlagState() {
return featureFlagState;
}
ContextState getContextState() {
return contextState;
}

View File

@ -3,7 +3,9 @@ package com.bugsnag.android
import android.content.Context
import java.io.File
internal class ConfigInternal(var apiKey: String) : CallbackAware, MetadataAware, UserAware {
internal class ConfigInternal(
var apiKey: String
) : CallbackAware, MetadataAware, UserAware, FeatureFlagAware {
private var user = User()
@ -13,6 +15,9 @@ internal class ConfigInternal(var apiKey: String) : CallbackAware, MetadataAware
@JvmField
internal val metadataState: MetadataState = MetadataState()
@JvmField
internal val featureFlagState: FeatureFlagState = FeatureFlagState()
var appVersion: String? = null
var versionCode: Int? = 0
var releaseStage: String? = null
@ -61,6 +66,8 @@ internal class ConfigInternal(var apiKey: String) : CallbackAware, MetadataAware
callbackState.removeOnBreadcrumb(onBreadcrumb)
override fun addOnSession(onSession: OnSessionCallback) = callbackState.addOnSession(onSession)
override fun removeOnSession(onSession: OnSessionCallback) = callbackState.removeOnSession(onSession)
fun addOnSend(onSend: OnSendCallback) = callbackState.addOnSend(onSend)
fun removeOnSend(onSend: OnSendCallback) = callbackState.removeOnSend(onSend)
override fun addMetadata(section: String, value: Map<String, Any?>) =
metadataState.addMetadata(section, value)
@ -71,6 +78,14 @@ internal class ConfigInternal(var apiKey: String) : CallbackAware, MetadataAware
override fun getMetadata(section: String) = metadataState.getMetadata(section)
override fun getMetadata(section: String, key: String) = metadataState.getMetadata(section, key)
override fun addFeatureFlag(name: String) = featureFlagState.addFeatureFlag(name)
override fun addFeatureFlag(name: String, variant: String?) =
featureFlagState.addFeatureFlag(name, variant)
override fun addFeatureFlags(featureFlags: Iterable<FeatureFlag>) =
featureFlagState.addFeatureFlags(featureFlags)
override fun clearFeatureFlag(name: String) = featureFlagState.clearFeatureFlag(name)
override fun clearFeatureFlags() = featureFlagState.clearFeatureFlags()
override fun getUser(): User = user
override fun setUser(id: String?, email: String?, name: String?) {
user = User(id, email, name)

View File

@ -16,7 +16,7 @@ import java.util.Set;
* specified at the client level, api-key and endpoint configuration.
*/
@SuppressWarnings("ConstantConditions") // suppress warning about making redundant null checks
public class Configuration implements CallbackAware, MetadataAware, UserAware {
public class Configuration implements CallbackAware, MetadataAware, UserAware, FeatureFlagAware {
private static final int MIN_BREADCRUMBS = 0;
private static final int MAX_BREADCRUMBS = 100;
@ -873,6 +873,38 @@ public class Configuration implements CallbackAware, MetadataAware, UserAware {
}
}
/**
* Add a callback which will be invoked prior to an event being delivered
* to Bugsnag. The callback can be used to modify events or cancel
* delivering the event altogether by returning <code>false</code>. Note
* that the callback may be invoked in the current or a subsequent app
* launch depending on whether the app terminated prior to delivering the
* event.
*
* @param onSend the callback to add
* @see OnSendCallback
*/
public void addOnSend(@NonNull OnSendCallback onSend) {
if (onSend != null) {
impl.addOnSend(onSend);
} else {
logNull("addOnSend");
}
}
/**
* Remove a callback previously added with {@link Configuration#addOnSend(OnSendCallback)}
*
* @param onSend the callback to remove
*/
public void removeOnSend(@NonNull OnSendCallback onSend) {
if (onSend != null) {
impl.removeOnSend(onSend);
} else {
logNull("removeOnSend");
}
}
/**
* Adds a map of multiple metadata key-value pairs to the specified section.
*/
@ -950,6 +982,62 @@ public class Configuration implements CallbackAware, MetadataAware, UserAware {
}
}
/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlag(@NonNull String name) {
if (name != null) {
impl.addFeatureFlag(name);
} else {
logNull("addFeatureFlag");
}
}
/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlag(@NonNull String name, @Nullable String variant) {
if (name != null) {
impl.addFeatureFlag(name, variant);
} else {
logNull("addFeatureFlag");
}
}
/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlags(@NonNull Iterable<FeatureFlag> featureFlags) {
if (featureFlags != null) {
impl.addFeatureFlags(featureFlags);
} else {
logNull("addFeatureFlags");
}
}
/**
* {@inheritDoc}
*/
@Override
public void clearFeatureFlag(@NonNull String name) {
if (name != null) {
impl.clearFeatureFlag(name);
} else {
logNull("clearFeatureFlag");
}
}
/**
* {@inheritDoc}
*/
@Override
public void clearFeatureFlags() {
impl.clearFeatureFlags();
}
/**
* Returns the currently set User information.
*/

View File

@ -9,6 +9,9 @@ import android.net.Network
import android.net.NetworkCapabilities
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import com.bugsnag.android.UnknownConnectivity.retrieveNetworkAccessState
import java.util.concurrent.atomic.AtomicBoolean
internal typealias NetworkChangeCallback = (hasConnection: Boolean, networkState: String) -> Unit
@ -89,10 +92,16 @@ internal class ConnectivityLegacy(
}
}
private inner class ConnectivityChangeReceiver(private val cb: NetworkChangeCallback?) :
BroadcastReceiver() {
private inner class ConnectivityChangeReceiver(
private val cb: NetworkChangeCallback?
) : BroadcastReceiver() {
private val receivedFirstCallback = AtomicBoolean(false)
override fun onReceive(context: Context, intent: Intent) {
cb?.invoke(hasNetworkConnection(), retrieveNetworkAccessState())
if (receivedFirstCallback.getAndSet(true)) {
cb?.invoke(hasNetworkConnection(), retrieveNetworkAccessState())
}
}
}
}
@ -122,22 +131,38 @@ internal class ConnectivityApi24(
}
}
private inner class ConnectivityTrackerCallback(private val cb: NetworkChangeCallback?) :
ConnectivityManager.NetworkCallback() {
@VisibleForTesting
internal class ConnectivityTrackerCallback(
private val cb: NetworkChangeCallback?
) : ConnectivityManager.NetworkCallback() {
private val receivedFirstCallback = AtomicBoolean(false)
override fun onUnavailable() {
super.onUnavailable()
cb?.invoke(false, retrieveNetworkAccessState())
invokeNetworkCallback(false)
}
override fun onAvailable(network: Network) {
super.onAvailable(network)
cb?.invoke(true, retrieveNetworkAccessState())
invokeNetworkCallback(true)
}
/**
* Invokes the network callback, as long as the ConnectivityManager callback has been
* triggered at least once before (when setting a NetworkCallback Android always
* invokes the callback with the current network state).
*/
private fun invokeNetworkCallback(hasConnection: Boolean) {
if (receivedFirstCallback.getAndSet(true)) {
cb?.invoke(hasConnection, retrieveNetworkAccessState())
}
}
}
}
/**
* Connectivity used in cases where we cannot access the system ConnectivityManager.
* Connectivity used in cases where we cannot access the system ConnectivityManager.
* We assume that there is some sort of network and do not attempt to report any network changes.
*/
internal object UnknownConnectivity : Connectivity {

View File

@ -105,32 +105,38 @@ internal class DefaultDelivery(
}
private fun logRequestInfo(code: Int, conn: HttpURLConnection, status: DeliveryStatus) {
logger.i(
"Request completed with code $code, " +
"message: ${conn.responseMessage}, " +
"headers: ${conn.headerFields}"
)
conn.inputStream.bufferedReader().use {
logger.d("Received request response: ${it.readText()}")
runCatching {
logger.i(
"Request completed with code $code, " +
"message: ${conn.responseMessage}, " +
"headers: ${conn.headerFields}"
)
}
runCatching {
conn.inputStream.bufferedReader().use {
logger.d("Received request response: ${it.readText()}")
}
}
if (status != DeliveryStatus.DELIVERED) {
conn.errorStream.bufferedReader().use {
logger.w("Request error details: ${it.readText()}")
runCatching {
if (status != DeliveryStatus.DELIVERED) {
conn.errorStream.bufferedReader().use {
logger.w("Request error details: ${it.readText()}")
}
}
}
}
internal fun getDeliveryStatus(responseCode: Int): DeliveryStatus {
val unrecoverableCodes = IntRange(HTTP_BAD_REQUEST, 499).filter {
it != HTTP_CLIENT_TIMEOUT && it != 429
}
return when (responseCode) {
in HTTP_OK..299 -> DeliveryStatus.DELIVERED
in unrecoverableCodes -> DeliveryStatus.FAILURE
return when {
responseCode in HTTP_OK..299 -> DeliveryStatus.DELIVERED
isUnrecoverableStatusCode(responseCode) -> DeliveryStatus.FAILURE
else -> DeliveryStatus.UNDELIVERED
}
}
private fun isUnrecoverableStatusCode(responseCode: Int) =
responseCode in HTTP_BAD_REQUEST..499 && // 400-499 are considered unrecoverable
responseCode != HTTP_CLIENT_TIMEOUT && // except for 408
responseCode != 429 // and 429
}

View File

@ -18,29 +18,26 @@ class DeliveryDelegate extends BaseObservable {
final Logger logger;
private final EventStore eventStore;
private final ImmutableConfig immutableConfig;
final BreadcrumbState breadcrumbState;
private final Notifier notifier;
private final CallbackState callbackState;
final BackgroundTaskService backgroundTaskService;
DeliveryDelegate(Logger logger,
EventStore eventStore,
ImmutableConfig immutableConfig,
BreadcrumbState breadcrumbState,
CallbackState callbackState,
Notifier notifier,
BackgroundTaskService backgroundTaskService) {
this.logger = logger;
this.eventStore = eventStore;
this.immutableConfig = immutableConfig;
this.breadcrumbState = breadcrumbState;
this.callbackState = callbackState;
this.notifier = notifier;
this.backgroundTaskService = backgroundTaskService;
}
void deliver(@NonNull Event event) {
logger.d("DeliveryDelegate#deliver() - event being stored/delivered by Client");
// Build the eventPayload
String apiKey = event.getApiKey();
EventPayload eventPayload = new EventPayload(apiKey, event, notifier, immutableConfig);
Session session = event.getSession();
if (session != null) {
@ -59,7 +56,10 @@ class DeliveryDelegate extends BaseObservable {
boolean promiseRejection = REASON_PROMISE_REJECTION.equals(severityReasonType);
boolean anr = event.getImpl().isAnr(event);
cacheEvent(event, anr || promiseRejection);
} else {
} else if (callbackState.runOnSendTasks(event, logger)) {
// Build the eventPayload
String apiKey = event.getApiKey();
EventPayload eventPayload = new EventPayload(apiKey, event, notifier, immutableConfig);
deliverPayloadAsync(event, eventPayload);
}
}
@ -92,13 +92,11 @@ class DeliveryDelegate extends BaseObservable {
switch (deliveryStatus) {
case DELIVERED:
logger.i("Sent 1 new event to Bugsnag");
leaveErrorBreadcrumb(event);
break;
case UNDELIVERED:
logger.w("Could not send event(s) to Bugsnag,"
+ " saving to disk to send later");
cacheEvent(event, false);
leaveErrorBreadcrumb(event);
break;
case FAILURE:
logger.w("Problem sending event to Bugsnag");
@ -115,22 +113,4 @@ class DeliveryDelegate extends BaseObservable {
eventStore.flushAsync();
}
}
private void leaveErrorBreadcrumb(@NonNull Event event) {
// Add a breadcrumb for this event occurring
List<Error> errors = event.getErrors();
if (errors.size() > 0) {
String errorClass = errors.get(0).getErrorClass();
String message = errors.get(0).getErrorMessage();
Map<String, Object> data = new HashMap<>();
data.put("errorClass", errorClass);
data.put("message", message);
data.put("unhandled", String.valueOf(event.isUnhandled()));
data.put("severity", event.getSeverity().toString());
breadcrumbState.add(new Breadcrumb(errorClass,
BreadcrumbType.ERROR, data, new Date(), logger));
}
}
}

View File

@ -36,7 +36,7 @@ open class Device internal constructor(
* A collection of names and their versions of the primary languages, frameworks or
* runtimes that the application is running on
*/
var runtimeVersions: MutableMap<String, Any>?
runtimeVersions: MutableMap<String, Any>?
) : JsonStream.Streamable {
/**
@ -59,6 +59,11 @@ open class Device internal constructor(
*/
var osVersion: String? = buildInfo.osVersion
var runtimeVersions: MutableMap<String, Any>? = sanitizeRuntimeVersions(runtimeVersions)
set(value) {
field = sanitizeRuntimeVersions(value)
}
internal open fun serializeFields(writer: JsonStream) {
writer.name("cpuAbi").value(cpuAbi)
writer.name("jailbroken").value(jailbroken)
@ -77,4 +82,7 @@ open class Device internal constructor(
serializeFields(writer)
writer.endObject()
}
private fun sanitizeRuntimeVersions(value: MutableMap<String, Any>?): MutableMap<String, Any>? =
value?.mapValuesTo(mutableMapOf()) { (_, value) -> value.toString() }
}

View File

@ -18,5 +18,9 @@ enum class ErrorType(internal val desc: String) {
/**
* An error captured from Android's C layer
*/
C("c")
C("c");
internal companion object {
internal fun fromDescriptor(desc: String) = values().find { it.desc == desc }
}
}

View File

@ -6,8 +6,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* An Event object represents a Throwable captured by Bugsnag and is available as a parameter on
@ -15,7 +17,7 @@ import java.util.Map;
* sent to Bugsnag's API.
*/
@SuppressWarnings("ConstantConditions")
public class Event implements JsonStream.Streamable, MetadataAware, UserAware {
public class Event implements JsonStream.Streamable, MetadataAware, UserAware, FeatureFlagAware {
private final EventInternal impl;
private final Logger logger;
@ -24,15 +26,17 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware {
@NonNull ImmutableConfig config,
@NonNull SeverityReason severityReason,
@NonNull Logger logger) {
this(originalError, config, severityReason, new Metadata(), logger);
this(originalError, config, severityReason, new Metadata(), new FeatureFlags(), logger);
}
Event(@Nullable Throwable originalError,
@NonNull ImmutableConfig config,
@NonNull SeverityReason severityReason,
@NonNull Metadata metadata,
@NonNull FeatureFlags featureFlags,
@NonNull Logger logger) {
this(new EventInternal(originalError, config, severityReason, metadata), logger);
this(new EventInternal(originalError, config, severityReason, metadata, featureFlags),
logger);
}
Event(@NonNull EventInternal impl, @NonNull Logger logger) {
@ -283,6 +287,62 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware {
}
}
/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlag(@NonNull String name) {
if (name != null) {
impl.addFeatureFlag(name);
} else {
logNull("addFeatureFlag");
}
}
/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlag(@NonNull String name, @Nullable String variant) {
if (name != null) {
impl.addFeatureFlag(name, variant);
} else {
logNull("addFeatureFlag");
}
}
/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlags(@NonNull Iterable<FeatureFlag> featureFlags) {
if (featureFlags != null) {
impl.addFeatureFlags(featureFlags);
} else {
logNull("addFeatureFlags");
}
}
/**
* {@inheritDoc}
*/
@Override
public void clearFeatureFlag(@NonNull String name) {
if (name != null) {
impl.clearFeatureFlag(name);
} else {
logNull("clearFeatureFlag");
}
}
/**
* {@inheritDoc}
*/
@Override
public void clearFeatureFlags() {
impl.clearFeatureFlags();
}
@Override
public void toStream(@NonNull JsonStream stream) throws IOException {
impl.toStream(stream);
@ -348,4 +408,8 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware {
EventInternal getImpl() {
return impl;
}
void setRedactedKeys(Collection<String> redactedKeys) {
impl.setRedactedKeys(redactedKeys);
}
}

View File

@ -3,16 +3,75 @@ package com.bugsnag.android
import com.bugsnag.android.internal.ImmutableConfig
import java.io.IOException
internal class EventInternal @JvmOverloads internal constructor(
val originalError: Throwable? = null,
config: ImmutableConfig,
private var severityReason: SeverityReason,
data: Metadata = Metadata()
) : JsonStream.Streamable, MetadataAware, UserAware {
internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, MetadataAware, UserAware {
val metadata: Metadata = data.copy()
private val discardClasses: Set<String> = config.discardClasses.toSet()
private val projectPackages = config.projectPackages
@JvmOverloads
internal constructor(
originalError: Throwable? = null,
config: ImmutableConfig,
severityReason: SeverityReason,
data: Metadata = Metadata(),
featureFlags: FeatureFlags = FeatureFlags()
) : this(
config.apiKey,
mutableListOf(),
config.discardClasses.toSet(),
when (originalError) {
null -> mutableListOf()
else -> Error.createError(originalError, config.projectPackages, config.logger)
},
data.copy(),
featureFlags.copy(),
originalError,
config.projectPackages,
severityReason,
ThreadState(originalError, severityReason.unhandled, config).threads,
User(),
config.redactedKeys.toSet()
)
internal constructor(
apiKey: String,
breadcrumbs: MutableList<Breadcrumb> = mutableListOf(),
discardClasses: Set<String> = setOf(),
errors: MutableList<Error> = mutableListOf(),
metadata: Metadata = Metadata(),
featureFlags: FeatureFlags = FeatureFlags(),
originalError: Throwable? = null,
projectPackages: Collection<String> = setOf(),
severityReason: SeverityReason = SeverityReason.newInstance(SeverityReason.REASON_HANDLED_EXCEPTION),
threads: MutableList<Thread> = mutableListOf(),
user: User = User(),
redactionKeys: Set<String>? = null
) {
this.apiKey = apiKey
this.breadcrumbs = breadcrumbs
this.discardClasses = discardClasses
this.errors = errors
this.metadata = metadata
this.featureFlags = featureFlags
this.originalError = originalError
this.projectPackages = projectPackages
this.severityReason = severityReason
this.threads = threads
this.userImpl = user
redactionKeys?.let {
this.redactedKeys = it
}
}
val originalError: Throwable?
internal var severityReason: SeverityReason
val metadata: Metadata
val featureFlags: FeatureFlags
private val discardClasses: Set<String>
internal var projectPackages: Collection<String>
private val jsonStreamer: ObjectJsonStreamer = ObjectJsonStreamer().apply {
redactedKeys = redactedKeys.toSet()
}
@JvmField
internal var session: Session? = null
@ -23,34 +82,36 @@ internal class EventInternal @JvmOverloads internal constructor(
severityReason.currentSeverity = value
}
var apiKey: String = config.apiKey
var apiKey: String
lateinit var app: AppWithState
lateinit var device: DeviceWithState
var breadcrumbs: MutableList<Breadcrumb> = mutableListOf()
var unhandled: Boolean
get() = severityReason.unhandled
set(value) {
severityReason.unhandled = value
}
val unhandledOverridden: Boolean
get() = severityReason.unhandledOverridden
val originalUnhandled: Boolean
get() = severityReason.originalUnhandled
var errors: MutableList<Error> = when (originalError) {
null -> mutableListOf()
else -> Error.createError(originalError, config.projectPackages, config.logger)
}
var threads: MutableList<Thread> = ThreadState(originalError, unhandled, config).threads
var breadcrumbs: MutableList<Breadcrumb>
var errors: MutableList<Error>
var threads: MutableList<Thread>
var groupingHash: String? = null
var context: String? = null
var redactedKeys: Collection<String>
get() = jsonStreamer.redactedKeys
set(value) {
jsonStreamer.redactedKeys = value.toSet()
metadata.redactedKeys = value.toSet()
}
/**
* @return user information associated with this Event
*/
internal var _user = User(null, null, null)
internal var userImpl: User
fun getUnhandledOverridden(): Boolean = severityReason.unhandledOverridden
fun getOriginalUnhandled(): Boolean = severityReason.originalUnhandled
protected fun shouldDiscardClass(): Boolean {
return when {
@ -70,7 +131,8 @@ internal class EventInternal @JvmOverloads internal constructor(
}
@Throws(IOException::class)
override fun toStream(writer: JsonStream) {
override fun toStream(parentWriter: JsonStream) {
val writer = JsonStream(parentWriter, jsonStreamer)
// Write error basics
writer.beginObject()
writer.name("context").value(context)
@ -93,7 +155,7 @@ internal class EventInternal @JvmOverloads internal constructor(
writer.endArray()
// Write user info
writer.name("user").value(_user)
writer.name("user").value(userImpl)
// Write diagnostics
writer.name("app").value(app)
@ -106,6 +168,8 @@ internal class EventInternal @JvmOverloads internal constructor(
threads.forEach { writer.value(it) }
writer.endArray()
writer.name("featureFlags").value(featureFlags)
if (session != null) {
val copy = Session.copySession(session)
writer.name("session").beginObject()
@ -129,12 +193,26 @@ internal class EventInternal @JvmOverloads internal constructor(
return errorTypes.plus(frameOverrideTypes)
}
internal fun normalizeStackframeErrorTypes() {
if (getErrorTypesFromStackframes().size == 1) {
errors.flatMap { it.stacktrace }.forEach {
it.type = null
}
}
}
internal fun updateSeverityReasonInternal(severityReason: SeverityReason) {
this.severityReason = severityReason
}
protected fun updateSeverityInternal(severity: Severity) {
severityReason = SeverityReason(
severityReason.severityReasonType,
severity,
severityReason.unhandled,
severityReason.attributeValue
severityReason.unhandledOverridden,
severityReason.attributeValue,
severityReason.attributeKey
)
}
@ -143,19 +221,22 @@ internal class EventInternal @JvmOverloads internal constructor(
reason,
severityReason.currentSeverity,
severityReason.unhandled,
severityReason.attributeValue
severityReason.unhandledOverridden,
severityReason.attributeValue,
severityReason.attributeKey
)
}
fun getSeverityReasonType(): String = severityReason.severityReasonType
override fun setUser(id: String?, email: String?, name: String?) {
_user = User(id, email, name)
userImpl = User(id, email, name)
}
override fun getUser() = _user
override fun getUser() = userImpl
override fun addMetadata(section: String, value: Map<String, Any?>) = metadata.addMetadata(section, value)
override fun addMetadata(section: String, value: Map<String, Any?>) =
metadata.addMetadata(section, value)
override fun addMetadata(section: String, key: String, value: Any?) =
metadata.addMetadata(section, key, value)
@ -167,4 +248,15 @@ internal class EventInternal @JvmOverloads internal constructor(
override fun getMetadata(section: String) = metadata.getMetadata(section)
override fun getMetadata(section: String, key: String) = metadata.getMetadata(section, key)
override fun addFeatureFlag(name: String) = featureFlags.addFeatureFlag(name)
override fun addFeatureFlag(name: String, variant: String?) = featureFlags.addFeatureFlag(name, variant)
override fun addFeatureFlags(featureFlags: MutableIterable<FeatureFlag>) =
this.featureFlags.addFeatureFlags(featureFlags)
override fun clearFeatureFlag(name: String) = featureFlags.clearFeatureFlag(name)
override fun clearFeatureFlags() = featureFlags.clearFeatureFlags()
}

View File

@ -15,7 +15,8 @@ internal class EventStorageModule(
bgTaskService: BackgroundTaskService,
trackerModule: TrackerModule,
systemServiceModule: SystemServiceModule,
notifier: Notifier
notifier: Notifier,
callbackState: CallbackState
) : DependencyModule() {
private val cfg = configModule.config
@ -34,5 +35,5 @@ internal class EventStorageModule(
)
}
val eventStore by future { EventStore(cfg, cfg.logger, notifier, bgTaskService, delegate) }
val eventStore by future { EventStore(cfg, cfg.logger, notifier, bgTaskService, delegate, callbackState) }
}

View File

@ -29,6 +29,7 @@ class EventStore extends FileStore {
private final Delegate delegate;
private final Notifier notifier;
private final BackgroundTaskService bgTaskSevice;
private final CallbackState callbackState;
final Logger logger;
static final Comparator<File> EVENT_COMPARATOR = new Comparator<File>() {
@ -51,7 +52,8 @@ class EventStore extends FileStore {
@NonNull Logger logger,
Notifier notifier,
BackgroundTaskService bgTaskSevice,
Delegate delegate) {
Delegate delegate,
CallbackState callbackState) {
super(new File(config.getPersistenceDirectory().getValue(), "bugsnag-errors"),
config.getMaxPersistedEvents(),
EVENT_COMPARATOR,
@ -62,6 +64,7 @@ class EventStore extends FileStore {
this.delegate = delegate;
this.notifier = notifier;
this.bgTaskSevice = bgTaskSevice;
this.callbackState = callbackState;
}
/**
@ -162,33 +165,64 @@ class EventStore extends FileStore {
try {
EventFilenameInfo eventInfo = EventFilenameInfo.Companion.fromFile(eventFile, config);
String apiKey = eventInfo.getApiKey();
EventPayload payload = new EventPayload(apiKey, null, eventFile, notifier, config);
DeliveryParams deliveryParams = config.getErrorApiDeliveryParams(payload);
Delivery delivery = config.getDelivery();
DeliveryStatus deliveryStatus = delivery.deliver(payload, deliveryParams);
EventPayload payload = createEventPayload(eventFile, apiKey);
switch (deliveryStatus) {
case DELIVERED:
deleteStoredFiles(Collections.singleton(eventFile));
logger.i("Deleting sent error file " + eventFile.getName());
break;
case UNDELIVERED:
cancelQueuedFiles(Collections.singleton(eventFile));
logger.w("Could not send previously saved error(s)"
+ " to Bugsnag, will try again later");
break;
case FAILURE:
Exception exc = new RuntimeException("Failed to deliver event payload");
handleEventFlushFailure(exc, eventFile);
break;
default:
break;
if (payload == null) {
deleteStoredFiles(Collections.singleton(eventFile));
} else {
deliverEventPayload(eventFile, payload);
}
} catch (Exception exception) {
handleEventFlushFailure(exception, eventFile);
}
}
private void deliverEventPayload(File eventFile, EventPayload payload) {
DeliveryParams deliveryParams = config.getErrorApiDeliveryParams(payload);
Delivery delivery = config.getDelivery();
DeliveryStatus deliveryStatus = delivery.deliver(payload, deliveryParams);
switch (deliveryStatus) {
case DELIVERED:
deleteStoredFiles(Collections.singleton(eventFile));
logger.i("Deleting sent error file " + eventFile.getName());
break;
case UNDELIVERED:
cancelQueuedFiles(Collections.singleton(eventFile));
logger.w("Could not send previously saved error(s)"
+ " to Bugsnag, will try again later");
break;
case FAILURE:
Exception exc = new RuntimeException("Failed to deliver event payload");
handleEventFlushFailure(exc, eventFile);
break;
default:
break;
}
}
@Nullable
private EventPayload createEventPayload(File eventFile, String apiKey) {
MarshalledEventSource eventSource = new MarshalledEventSource(eventFile, apiKey, logger);
try {
if (!callbackState.runOnSendTasks(eventSource, logger)) {
// do not send the payload at all, we must block sending
return null;
}
} catch (Exception ioe) {
eventSource.clear();
}
Event processedEvent = eventSource.getEvent();
if (processedEvent != null) {
apiKey = processedEvent.getApiKey();
return new EventPayload(apiKey, processedEvent, null, notifier, config);
} else {
return new EventPayload(apiKey, null, eventFile, notifier, config);
}
}
private void handleEventFlushFailure(Exception exc, File eventFile) {
if (delegate != null) {
delegate.onErrorIOFailure(exc, eventFile, "Crash Report Deserialization");

View File

@ -1,6 +1,7 @@
package com.bugsnag.android;
import android.os.StrictMode;
import androidx.annotation.NonNull;
import java.lang.Thread;
@ -35,37 +36,47 @@ class ExceptionHandler implements UncaughtExceptionHandler {
@Override
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
if (client.getConfig().shouldDiscardError(throwable)) {
return;
}
boolean strictModeThrowable = strictModeHandler.isStrictModeThrowable(throwable);
// Notify any subscribed clients of the uncaught exception
Metadata metadata = new Metadata();
String violationDesc = null;
if (strictModeThrowable) { // add strictmode policy violation to metadata
violationDesc = strictModeHandler.getViolationDescription(throwable.getMessage());
metadata = new Metadata();
metadata.addMetadata(STRICT_MODE_TAB, STRICT_MODE_KEY, violationDesc);
}
String severityReason = strictModeThrowable
? SeverityReason.REASON_STRICT_MODE : SeverityReason.REASON_UNHANDLED_EXCEPTION;
if (strictModeThrowable) { // writes to disk on main thread
StrictMode.ThreadPolicy originalThreadPolicy = StrictMode.getThreadPolicy();
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX);
client.notifyUnhandledException(throwable,
metadata, severityReason, violationDesc);
StrictMode.setThreadPolicy(originalThreadPolicy);
} else {
client.notifyUnhandledException(throwable,
metadata, severityReason, null);
try {
if (client.getConfig().shouldDiscardError(throwable)) {
return;
}
boolean strictModeThrowable = strictModeHandler.isStrictModeThrowable(throwable);
// Notify any subscribed clients of the uncaught exception
Metadata metadata = new Metadata();
String violationDesc = null;
if (strictModeThrowable) { // add strictmode policy violation to metadata
violationDesc = strictModeHandler.getViolationDescription(throwable.getMessage());
metadata = new Metadata();
metadata.addMetadata(STRICT_MODE_TAB, STRICT_MODE_KEY, violationDesc);
}
String severityReason = strictModeThrowable
? SeverityReason.REASON_STRICT_MODE : SeverityReason.REASON_UNHANDLED_EXCEPTION;
if (strictModeThrowable) { // writes to disk on main thread
StrictMode.ThreadPolicy originalThreadPolicy = StrictMode.getThreadPolicy();
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX);
client.notifyUnhandledException(throwable,
metadata, severityReason, violationDesc);
StrictMode.setThreadPolicy(originalThreadPolicy);
} else {
client.notifyUnhandledException(throwable,
metadata, severityReason, null);
}
} catch (Throwable ignored) {
// the runtime would ignore any exceptions here, we make that absolutely clear
// to avoid any possible unhandled-exception loops
} finally {
forwardToOriginalHandler(thread, throwable);
}
}
private void forwardToOriginalHandler(@NonNull Thread thread, @NonNull Throwable throwable) {
// Pass exception on to original exception handler
if (originalHandler != null) {
originalHandler.uncaughtException(thread, throwable);

View File

@ -0,0 +1,134 @@
package com.bugsnag.android;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
/**
* Represents a single feature-flag / experiment marker within Bugsnag. Each {@code FeatureFlag}
* object has a {@link #getName() name} and an optional {@link #getVariant() variant} which can be
* used to identify runtime experiments and groups when reporting errors.
*
* @see Bugsnag#addFeatureFlag(String, String)
* @see Event#addFeatureFlag(String, String)
*/
public final class FeatureFlag implements Map.Entry<String, String> {
private final String name;
private final String variant;
/**
* Create a named {@code FeatureFlag} with no variant
*
* @param name the identifying name of the new {@code FeatureFlag} (not {@code null})
* @see Bugsnag#addFeatureFlag(String)
* @see Event#addFeatureFlag(String)
*/
public FeatureFlag(@NonNull String name) {
this(name, null);
}
/**
* Create a new {@code FeatureFlag} with a name and (optionally) a variant.
*
* @param name the identifying name of the new {@code FeatureFlag} (not {@code null})
* @param variant the feature variant
*/
public FeatureFlag(@NonNull String name, @Nullable String variant) {
if (name == null) {
throw new NullPointerException("FeatureFlags cannot have null name");
}
this.name = name;
this.variant = variant;
}
/**
* Create a new {@code FeatureFlag} based on an existing {@code Map.Entry}. This is the same
* as {@code new FeatureFlag(mapEntry.getKey(), mapEntry.getValue())}.
*
* @param mapEntry an existing {@code Map.Entry} to copy the feature flag from
*/
public FeatureFlag(@NonNull Map.Entry<String, String> mapEntry) {
this(mapEntry.getKey(), mapEntry.getValue());
}
@NonNull
public String getName() {
return name;
}
@Nullable
public String getVariant() {
return variant;
}
/**
* Same as {@link #getName()}.
*
* @return the name of this {@code FeatureFlag}
* @see #getName()
*/
@NonNull
@Override
public String getKey() {
return name;
}
/**
* Same as {@link #getVariant()}.
*
* @return the variant of this {@code FeatureFlag} (may be {@code null})
* @see #getVariant()
*/
@Nullable
@Override
public String getValue() {
return variant;
}
/**
* Throws {@code UnsupportedOperationException} as {@code FeatureFlag} is considered immutable.
*
* @param value ignored
* @return nothing
*/
@Override
@Nullable
public String setValue(@Nullable String value) {
throw new UnsupportedOperationException("FeatureFlag is immutable");
}
@Override
public int hashCode() {
// Follows the Map.Entry contract
return getKey().hashCode() ^ (getValue() == null ? 0 : getValue().hashCode());
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
// This follows the contract defined in Map.Entry exactly
if (!(other instanceof Map.Entry)) {
return false;
}
Map.Entry<? extends Object, ? extends Object> e2 =
(Map.Entry<? extends Object, ? extends Object>) other;
return getKey().equals(e2.getKey())
&& (getValue() == null ? e2.getValue() == null : getValue().equals(e2.getValue()));
}
@Override
public String toString() {
return "FeatureFlag{"
+ "name='" + name + '\''
+ ", variant='" + variant + '\''
+ '}';
}
}

View File

@ -0,0 +1,49 @@
package com.bugsnag.android;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
interface FeatureFlagAware {
/**
* Add a single feature flag with no variant. If there is an existing feature flag with the
* same name, it will be overwritten to have no variant.
*
* @param name the name of the feature flag to add
* @see #addFeatureFlag(String, String)
*/
void addFeatureFlag(@NonNull String name);
/**
* Add a single feature flag with an optional variant. If there is an existing feature
* flag with the same name, it will be overwritten with the new variant. If the variant is
* {@code null} this method has the same behaviour as {@link #addFeatureFlag(String)}.
*
* @param name the name of the feature flag to add
* @param variant the variant to set the feature flag to, or {@code null} to specify a feature
* flag with no variant
*/
void addFeatureFlag(@NonNull String name, @Nullable String variant);
/**
* Add a collection of feature flags. This method behaves exactly the same as calling
* {@link #addFeatureFlag(String, String)} for each of the {@code FeatureFlag} objects.
*
* @param featureFlags the feature flags to add
* @see #addFeatureFlag(String, String)
*/
void addFeatureFlags(@NonNull Iterable<FeatureFlag> featureFlags);
/**
* Remove a single feature flag regardless of its current status. This will stop the specified
* feature flag from being reported. If the named feature flag does not exist this will
* have no effect.
*
* @param name the name of the feature flag to remove
*/
void clearFeatureFlag(@NonNull String name);
/**
* Clear all of the feature flags. This will stop all feature flags from being reported.
*/
void clearFeatureFlags();
}

View File

@ -0,0 +1,50 @@
package com.bugsnag.android
internal data class FeatureFlagState(
val featureFlags: FeatureFlags = FeatureFlags()
) : BaseObservable(), FeatureFlagAware {
override fun addFeatureFlag(name: String) {
this.featureFlags.addFeatureFlag(name)
updateState {
StateEvent.AddFeatureFlag(name)
}
}
override fun addFeatureFlag(name: String, variant: String?) {
this.featureFlags.addFeatureFlag(name, variant)
updateState {
StateEvent.AddFeatureFlag(name, variant)
}
}
override fun addFeatureFlags(featureFlags: Iterable<FeatureFlag>) {
featureFlags.forEach { (name, variant) ->
addFeatureFlag(name, variant)
}
}
override fun clearFeatureFlag(name: String) {
this.featureFlags.clearFeatureFlag(name)
updateState {
StateEvent.ClearFeatureFlag(name)
}
}
override fun clearFeatureFlags() {
this.featureFlags.clearFeatureFlags()
updateState {
StateEvent.ClearFeatureFlags
}
}
fun emitObservableEvent() {
val flags = toList()
flags.forEach { (name, variant) ->
updateState { StateEvent.AddFeatureFlag(name, variant) }
}
}
fun toList(): List<FeatureFlag> = featureFlags.toList()
fun copy() = FeatureFlagState(featureFlags.copy())
}

View File

@ -0,0 +1,52 @@
package com.bugsnag.android
import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
internal class FeatureFlags(
internal val store: MutableMap<String, String?> = ConcurrentHashMap()
) : JsonStream.Streamable, FeatureFlagAware {
private val emptyVariant = "__EMPTY_VARIANT_SENTINEL__"
override fun addFeatureFlag(name: String) {
store[name] = emptyVariant
}
override fun addFeatureFlag(name: String, variant: String?) {
store[name] = variant ?: emptyVariant
}
override fun addFeatureFlags(featureFlags: Iterable<FeatureFlag>) {
featureFlags.forEach { (name, variant) ->
addFeatureFlag(name, variant)
}
}
override fun clearFeatureFlag(name: String) {
store.remove(name)
}
override fun clearFeatureFlags() {
store.clear()
}
@Throws(IOException::class)
override fun toStream(stream: JsonStream) {
stream.beginArray()
store.forEach { (name, variant) ->
stream.beginObject()
stream.name("featureFlag").value(name)
if (variant != emptyVariant) {
stream.name("variant").value(variant)
}
stream.endObject()
}
stream.endArray()
}
fun toList(): List<FeatureFlag> = store.entries.map { (name, variant) ->
FeatureFlag(name, variant.takeUnless { it == emptyVariant })
}
fun copy() = FeatureFlags(store.toMutableMap())
}

View File

@ -13,6 +13,8 @@ import java.util.List;
class ForegroundDetector {
private static final int IMPORTANCE_FOREGROUND_SERVICE = 125;
@Nullable
private final ActivityManager activityManager;
@ -36,8 +38,7 @@ class ForegroundDetector {
ActivityManager.RunningAppProcessInfo info = getProcessInfo();
if (info != null) {
return info.importance
<= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
return info.importance <= IMPORTANCE_FOREGROUND_SERVICE;
} else {
return null;
}

View File

@ -33,6 +33,13 @@ public class JsonStream extends JsonWriter {
objectJsonStreamer = new ObjectJsonStreamer();
}
JsonStream(@NonNull JsonStream stream, @NonNull ObjectJsonStreamer streamer) {
super(stream.out);
setSerializeNulls(stream.getSerializeNulls());
this.out = stream.out;
this.objectJsonStreamer = streamer;
}
// Allow chaining name().value()
@NonNull
public JsonStream name(@Nullable String name) throws IOException {

View File

@ -89,7 +89,8 @@ private class KeyValueWriter {
private val sb = StringBuilder()
fun add(key: String, value: Any) {
sb.appendln("$key$KEY_VALUE_DELIMITER$value")
sb.append("$key$KEY_VALUE_DELIMITER$value")
sb.append("\n")
}
override fun toString() = sb.toString()

View File

@ -0,0 +1,42 @@
package com.bugsnag.android
import com.bugsnag.android.internal.JsonHelper
import java.io.File
internal class MarshalledEventSource(
private val eventFile: File,
private val apiKey: String,
private val logger: Logger
) : () -> Event {
/**
* The parsed and possibly processed event. This field remains `null` if the `EventSource`
* is not used, and may not reflect the same data as is stored in `eventFile` (as the `Event`
* is mutable, and may have been modified after loading).
*/
var event: Event? = null
private set
override fun invoke(): Event {
var unmarshalledEvent = event
if (unmarshalledEvent == null) {
unmarshalledEvent = unmarshall()
event = unmarshalledEvent
}
return unmarshalledEvent
}
fun clear() {
event = null
}
private fun unmarshall(): Event {
val eventMapper = BugsnagEventMapper(logger)
val jsonMap = JsonHelper.deserialize(eventFile)
return Event(
eventMapper.convertToEventImpl(jsonMap, apiKey),
logger
)
}
}

View File

@ -61,7 +61,7 @@ internal data class MetadataState(val metadata: Metadata = Metadata()) :
private fun notifyMetadataAdded(section: String, value: Map<String, Any?>) {
value.entries.forEach {
updateState { AddMetadata(section, it.key, metadata.getMetadata(it.key)) }
updateState { AddMetadata(section, it.key, metadata.getMetadata(section, it.key)) }
}
}
}

View File

@ -397,12 +397,22 @@ public class NativeInterface {
});
}
/**
* Create an {@code Event} object
*
* @param exc the Throwable object that caused the event
* @param client the Client object that the event is associated with
* @param severityReason the severity of the Event
* @return a new {@code Event} object
*/
@NonNull
public static Event createEvent(@Nullable Throwable exc,
@NonNull Client client,
@NonNull SeverityReason severityReason) {
Metadata metadata = client.getMetadataState().getMetadata();
return new Event(exc, client.getConfig(), severityReason, metadata, client.logger);
FeatureFlags featureFlags = client.getFeatureFlagState().getFeatureFlags();
return new Event(exc, client.getConfig(), severityReason, metadata, featureFlags,
client.logger);
}
@NonNull

View File

@ -35,13 +35,18 @@ class NativeStackframe internal constructor(
/**
* The address of the library where the event occurred.
*/
var loadAddress: Long?
) : JsonStream.Streamable {
var loadAddress: Long?,
/**
* Whether this frame identifies the program counter
*/
var isPC: Boolean?,
/**
* The type of the error
*/
var type: ErrorType? = ErrorType.C
var type: ErrorType? = null
) : JsonStream.Streamable {
@Throws(IOException::class)
override fun toStream(writer: JsonStream) {
@ -52,6 +57,7 @@ class NativeStackframe internal constructor(
writer.name("frameAddress").value(frameAddress)
writer.name("symbolAddress").value(symbolAddress)
writer.name("loadAddress").value(loadAddress)
writer.name("isPC").value(isPC)
type?.let {
writer.name("type").value(it.desc)

View File

@ -7,7 +7,7 @@ import java.io.IOException
*/
class Notifier @JvmOverloads constructor(
var name: String = "Android Bugsnag Notifier",
var version: String = "5.14.0",
var version: String = "5.19.2",
var url: String = "https://bugsnag.com"
) : JsonStream.Streamable {

View File

@ -8,8 +8,8 @@ import java.util.Date
internal class ObjectJsonStreamer {
companion object {
private const val REDACTED_PLACEHOLDER = "[REDACTED]"
private const val OBJECT_PLACEHOLDER = "[OBJECT]"
internal const val REDACTED_PLACEHOLDER = "[REDACTED]"
internal const val OBJECT_PLACEHOLDER = "[OBJECT]"
}
var redactedKeys = setOf("password")

View File

@ -0,0 +1,12 @@
package com.bugsnag.android;
import androidx.annotation.NonNull;
/**
* A callback to be invoked before an {@link Event} is uploaded to a server. Similar to
* {@link OnErrorCallback}, an {@code OnSendCallback} may modify the {@code Event}
* contents or even reject the entire payload by returning {@code false}.
*/
public interface OnSendCallback {
boolean onSend(@NonNull Event event);
}

View File

@ -1,5 +1,7 @@
package com.bugsnag.android;
import com.bugsnag.android.internal.DateUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -7,6 +9,7 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@ -40,6 +43,23 @@ public final class Session implements JsonStream.Streamable, UserAware {
return copy;
}
Session(Map<String, Object> map, Logger logger) {
this(null, null, logger);
setId((String) map.get("id"));
String timestamp = (String) map.get("startedAt");
setStartedAt(DateUtils.fromIso8601(timestamp));
@SuppressWarnings("unchecked")
Map<String, Object> events = (Map<String, Object>) map.get("events");
Number handled = (Number) events.get("handled");
handledCount.set(handled.intValue());
Number unhandled = (Number) events.get("unhandled");
unhandledCount.set(unhandled.intValue());
}
Session(String id, Date startedAt, User user, boolean autoCaptured,
Notifier notifier, Logger logger) {
this(null, notifier, logger);
@ -60,9 +80,14 @@ public final class Session implements JsonStream.Streamable, UserAware {
Session(File file, Notifier notifier, Logger logger) {
this.file = file;
this.logger = logger;
Notifier copy = new Notifier(notifier.getName(), notifier.getVersion(), notifier.getUrl());
copy.setDependencies(new ArrayList<>(notifier.getDependencies()));
this.notifier = copy;
if (notifier != null) {
Notifier copy = new Notifier(notifier.getName(),
notifier.getVersion(), notifier.getUrl());
copy.setDependencies(new ArrayList<>(notifier.getDependencies()));
this.notifier = copy;
} else {
this.notifier = null;
}
}
private void logNull(String property) {

View File

@ -38,7 +38,7 @@ class SessionTracker extends BaseObservable {
// The first Activity in this 'session' was started at this time.
private final AtomicLong lastEnteredForegroundMs = new AtomicLong(0);
private final AtomicReference<Session> currentSession = new AtomicReference<>();
private volatile Session currentSession = null;
private final ForegroundDetector foregroundDetector;
final BackgroundTaskService backgroundTaskService;
final Logger logger;
@ -89,9 +89,11 @@ class SessionTracker extends BaseObservable {
}
String id = UUID.randomUUID().toString();
Session session = new Session(id, date, user, autoCaptured, client.getNotifier(), logger);
currentSession.set(session);
trackSessionIfNeeded(session);
return session;
if (trackSessionIfNeeded(session)) {
return session;
} else {
return null;
}
}
Session startSession(boolean autoCaptured) {
@ -102,7 +104,7 @@ class SessionTracker extends BaseObservable {
}
void pauseSession() {
Session session = currentSession.get();
Session session = currentSession;
if (session != null) {
session.isPaused.set(true);
@ -111,7 +113,7 @@ class SessionTracker extends BaseObservable {
}
boolean resumeSession() {
Session session = currentSession.get();
Session session = currentSession;
boolean resumed;
if (session == null) {
@ -159,7 +161,7 @@ class SessionTracker extends BaseObservable {
} else {
updateState(StateEvent.PauseSession.INSTANCE);
}
currentSession.set(session);
currentSession = session;
return session;
}
@ -168,23 +170,27 @@ class SessionTracker extends BaseObservable {
* stored and sent to the Bugsnag API, otherwise no action will occur in this method.
*
* @param session the session
* @return true if the Session should be tracked
*/
private void trackSessionIfNeeded(final Session session) {
private boolean trackSessionIfNeeded(final Session session) {
logger.d("SessionTracker#trackSessionIfNeeded() - session captured by Client");
session.setApp(client.getAppDataCollector().generateApp());
session.setDevice(client.getDeviceDataCollector().generateDevice());
boolean deliverSession = callbackState.runOnSessionTasks(session, logger);
if (deliverSession && session.isTracked().compareAndSet(false, true)) {
currentSession = session;
notifySessionStartObserver(session);
flushAsync();
flushInMemorySession(session);
flushAsync();
return true;
}
return false;
}
@Nullable
Session getCurrentSession() {
Session session = currentSession.get();
Session session = currentSession;
if (session != null && !session.isPaused.get()) {
return session;

View File

@ -17,4 +17,8 @@ enum class Severity(private val str: String) : JsonStream.Streamable {
override fun toStream(writer: JsonStream) {
writer.value(str)
}
internal companion object {
internal fun fromDescriptor(desc: String) = values().find { it.str == desc }
}
}

View File

@ -1,5 +1,8 @@
package com.bugsnag.android;
import static com.bugsnag.android.Severity.ERROR;
import static com.bugsnag.android.Severity.WARNING;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringDef;
@ -11,8 +14,8 @@ import java.lang.annotation.RetentionPolicy;
final class SeverityReason implements JsonStream.Streamable {
@StringDef({REASON_UNHANDLED_EXCEPTION, REASON_STRICT_MODE, REASON_HANDLED_EXCEPTION,
REASON_USER_SPECIFIED, REASON_CALLBACK_SPECIFIED, REASON_PROMISE_REJECTION,
REASON_LOG, REASON_SIGNAL, REASON_ANR})
REASON_HANDLED_ERROR, REASON_USER_SPECIFIED, REASON_CALLBACK_SPECIFIED,
REASON_PROMISE_REJECTION, REASON_LOG, REASON_SIGNAL, REASON_ANR })
@Retention(RetentionPolicy.SOURCE)
@interface SeverityReasonType {
}
@ -20,6 +23,7 @@ final class SeverityReason implements JsonStream.Streamable {
static final String REASON_UNHANDLED_EXCEPTION = "unhandledException";
static final String REASON_STRICT_MODE = "strictMode";
static final String REASON_HANDLED_EXCEPTION = "handledException";
static final String REASON_HANDLED_ERROR = "handledError";
static final String REASON_USER_SPECIFIED = "userSpecifiedSeverity";
static final String REASON_CALLBACK_SPECIFIED = "userCallbackSetSeverity";
static final String REASON_PROMISE_REJECTION = "unhandledPromiseRejection";
@ -30,6 +34,9 @@ final class SeverityReason implements JsonStream.Streamable {
@SeverityReasonType
private final String severityReasonType;
@Nullable
private final String attributeKey;
@Nullable
private final String attributeValue;
@ -42,51 +49,52 @@ final class SeverityReason implements JsonStream.Streamable {
return newInstance(severityReasonType, null, null);
}
static SeverityReason newInstance(@SeverityReasonType String severityReasonType,
static SeverityReason newInstance(@SeverityReasonType String reason,
@Nullable Severity severity,
@Nullable String attrVal) {
if (severityReasonType.equals(REASON_STRICT_MODE) && Intrinsics.isEmpty(attrVal)) {
if (reason.equals(REASON_STRICT_MODE) && Intrinsics.isEmpty(attrVal)) {
throw new IllegalArgumentException("No reason supplied for strictmode");
}
if (!(severityReasonType.equals(REASON_STRICT_MODE)
|| severityReasonType.equals(REASON_LOG)) && !Intrinsics.isEmpty(attrVal)) {
if (!(reason.equals(REASON_STRICT_MODE)
|| reason.equals(REASON_LOG)) && !Intrinsics.isEmpty(attrVal)) {
throw new IllegalArgumentException("attributeValue should not be supplied");
}
switch (severityReasonType) {
switch (reason) {
case REASON_UNHANDLED_EXCEPTION:
case REASON_PROMISE_REJECTION:
case REASON_ANR:
return new SeverityReason(severityReasonType, Severity.ERROR, true, null);
return new SeverityReason(reason, ERROR, true, true, null, null);
case REASON_STRICT_MODE:
return new SeverityReason(severityReasonType, Severity.WARNING, true, attrVal);
return new SeverityReason(reason, WARNING, true, true, attrVal, "violationType");
case REASON_HANDLED_ERROR:
case REASON_HANDLED_EXCEPTION:
return new SeverityReason(severityReasonType, Severity.WARNING, false, null);
return new SeverityReason(reason, WARNING, false, false, null, null);
case REASON_USER_SPECIFIED:
case REASON_CALLBACK_SPECIFIED:
return new SeverityReason(severityReasonType, severity, false, null);
return new SeverityReason(reason, severity, false, false, null, null);
case REASON_LOG:
return new SeverityReason(severityReasonType, severity, false, attrVal);
return new SeverityReason(reason, severity, false, false, attrVal, "level");
default:
String msg = "Invalid argument for severityReason: '" + severityReasonType + '\'';
String msg = "Invalid argument for severityReason: '" + reason + '\'';
throw new IllegalArgumentException(msg);
}
}
SeverityReason(String severityReasonType, Severity currentSeverity, boolean unhandled,
@Nullable String attributeValue) {
this(severityReasonType, currentSeverity, unhandled, unhandled, attributeValue);
}
SeverityReason(String severityReasonType, Severity currentSeverity, boolean unhandled,
boolean originalUnhandled, @Nullable String attributeValue) {
SeverityReason(String severityReasonType,
Severity currentSeverity,
boolean unhandled,
boolean originalUnhandled,
@Nullable String attributeValue,
@Nullable String attributeKey) {
this.severityReasonType = severityReasonType;
this.unhandled = unhandled;
this.originalUnhandled = originalUnhandled;
this.defaultSeverity = currentSeverity;
this.currentSeverity = currentSeverity;
this.attributeValue = attributeValue;
this.attributeKey = attributeKey;
}
String calculateSeverityReasonType() {
@ -118,6 +126,10 @@ final class SeverityReason implements JsonStream.Streamable {
return attributeValue;
}
String getAttributeKey() {
return attributeKey;
}
void setCurrentSeverity(Severity severity) {
this.currentSeverity = severity;
}
@ -132,25 +144,11 @@ final class SeverityReason implements JsonStream.Streamable {
.name("type").value(calculateSeverityReasonType())
.name("unhandledOverridden").value(getUnhandledOverridden());
if (attributeValue != null) {
String attributeKey = null;
switch (severityReasonType) {
case REASON_LOG:
attributeKey = "level";
break;
case REASON_STRICT_MODE:
attributeKey = "violationType";
break;
default:
break;
}
if (attributeKey != null) {
writer.name("attributes").beginObject()
if (attributeKey != null && attributeValue != null) {
writer.name("attributes").beginObject()
.name(attributeKey).value(attributeValue)
.endObject();
}
}
writer.endObject();
}
}
}

View File

@ -10,28 +10,16 @@ class Stackframe : JsonStream.Streamable {
* The name of the method that was being executed
*/
var method: String?
set(value) {
nativeFrame?.method = value
field = value
}
/**
* The location of the source file
*/
var file: String?
set(value) {
nativeFrame?.file = value
field = value
}
/**
* The line number within the source file this stackframe refers to
*/
var lineNumber: Number?
set(value) {
nativeFrame?.lineNumber = value
field = value
}
/**
* Whether the package is considered to be in your project for the purposes of grouping and
@ -50,14 +38,30 @@ class Stackframe : JsonStream.Streamable {
*/
var columnNumber: Number?
/**
* The address of the instruction where the event occurred.
*/
var frameAddress: Long? = null
/**
* The address of the function where the event occurred.
*/
var symbolAddress: Long? = null
/**
* The address of the library where the event occurred.
*/
var loadAddress: Long? = null
/**
* Whether this frame identifies the program counter
*/
var isPC: Boolean? = null
/**
* The type of the error
*/
var type: ErrorType? = null
set(value) {
nativeFrame?.type = value
field = value
}
@JvmOverloads
internal constructor(
@ -76,34 +80,52 @@ class Stackframe : JsonStream.Streamable {
this.columnNumber = columnNumber
}
private var nativeFrame: NativeStackframe? = null
constructor(nativeFrame: NativeStackframe) : this(
nativeFrame.method,
nativeFrame.file,
nativeFrame.lineNumber,
false,
null,
null
) {
this.nativeFrame = nativeFrame
this.frameAddress = nativeFrame.frameAddress
this.symbolAddress = nativeFrame.symbolAddress
this.loadAddress = nativeFrame.loadAddress
this.isPC = nativeFrame.isPC
this.type = nativeFrame.type
}
internal constructor(json: Map<String, Any?>) {
method = json["method"] as? String
file = json["file"] as? String
lineNumber = json["lineNumber"] as? Number
inProject = json["inProject"] as? Boolean
columnNumber = json["columnNumber"] as? Number
frameAddress = (json["frameAddress"] as? Number)?.toLong()
symbolAddress = (json["symbolAddress"] as? Number)?.toLong()
loadAddress = (json["loadAddress"] as? Number)?.toLong()
isPC = json["isPC"] as? Boolean
@Suppress("UNCHECKED_CAST")
code = json["code"] as? Map<String, String?>
type = (json["type"] as? String)?.let { ErrorType.fromDescriptor(it) }
}
@Throws(IOException::class)
override fun toStream(writer: JsonStream) {
val ndkFrame = nativeFrame
if (ndkFrame != null) {
ndkFrame.toStream(writer)
return
}
writer.beginObject()
writer.name("method").value(method)
writer.name("file").value(file)
writer.name("lineNumber").value(lineNumber)
writer.name("inProject").value(inProject)
inProject?.let { writer.name("inProject").value(it) }
writer.name("columnNumber").value(columnNumber)
frameAddress?.let { writer.name("frameAddress").value(it) }
symbolAddress?.let { writer.name("symbolAddress").value(it) }
loadAddress?.let { writer.name("loadAddress").value(it) }
isPC?.let { writer.name("isPC").value(it) }
type?.let {
writer.name("type").value(it.desc)
}

View File

@ -68,4 +68,15 @@ sealed class StateEvent { // JvmField allows direct field access optimizations
@JvmField val memoryTrimLevel: Int? = null,
@JvmField val memoryTrimLevelDescription: String = "None"
) : StateEvent()
class AddFeatureFlag(
@JvmField val name: String,
@JvmField val variant: String? = null
) : StateEvent()
class ClearFeatureFlag(
@JvmField val name: String
) : StateEvent()
object ClearFeatureFlags : StateEvent()
}

View File

@ -23,7 +23,13 @@ public class Thread implements JsonStream.Streamable {
@NonNull Thread.State state,
@NonNull Stacktrace stacktrace,
@NonNull Logger logger) {
this.impl = new ThreadInternal(id, name, type, errorReportingThread, state, stacktrace);
this.impl = new ThreadInternal(
id, name, type, errorReportingThread, state.getDescriptor(), stacktrace);
this.logger = logger;
}
Thread(@NonNull ThreadInternal impl, @NonNull Logger logger) {
this.impl = impl;
this.logger = logger;
}
@ -88,7 +94,7 @@ public class Thread implements JsonStream.Streamable {
*/
public void setState(@NonNull Thread.State threadState) {
if (threadState != null) {
impl.setState(threadState);
impl.setState(threadState.getDescriptor());
} else {
logNull("state");
}
@ -99,7 +105,7 @@ public class Thread implements JsonStream.Streamable {
*/
@NonNull
public Thread.State getState() {
return impl.getState();
return Thread.State.byDescriptor(impl.getState());
}
/**

View File

@ -7,7 +7,7 @@ class ThreadInternal internal constructor(
var name: String,
var type: ThreadType,
val isErrorReportingThread: Boolean,
var state: Thread.State,
var state: String,
stacktrace: Stacktrace
) : JsonStream.Streamable {
@ -19,7 +19,7 @@ class ThreadInternal internal constructor(
writer.name("id").value(id)
writer.name("name").value(name)
writer.name("type").value(type.desc)
writer.name("state").value(state.descriptor)
writer.name("state").value(state)
writer.name("stacktrace")
writer.beginArray()

View File

@ -18,5 +18,9 @@ enum class ThreadType(internal val desc: String) {
/**
* A thread captured from JavaScript
*/
REACTNATIVEJS("reactnativejs")
REACTNATIVEJS("reactnativejs");
internal companion object {
internal fun fromDescriptor(desc: String) = ThreadType.values().find { it.desc == desc }
}
}

View File

@ -0,0 +1,29 @@
package com.bugsnag.android.internal
import com.bugsnag.android.ObjectJsonStreamer
import com.bugsnag.android.repackaged.dslplatform.json.DslJson
import java.io.InputStream
import java.io.OutputStream
import java.lang.reflect.Type
internal class FallbackWriter : DslJson.Fallback<MutableMap<String, Any>> {
private val placeholder = "\"${ObjectJsonStreamer.OBJECT_PLACEHOLDER}\"".toByteArray()
override fun serialize(instance: Any?, stream: OutputStream) {
stream.write(placeholder)
}
override fun deserialize(
context: MutableMap<String, Any>?,
manifest: Type,
body: ByteArray,
size: Int
): Any = throw UnsupportedOperationException()
override fun deserialize(
context: MutableMap<String, Any>?,
manifest: Type,
stream: InputStream
): Any = throw UnsupportedOperationException()
}

View File

@ -0,0 +1,79 @@
package com.bugsnag.android.internal
import com.bugsnag.android.repackaged.dslplatform.json.DslJson
import com.bugsnag.android.repackaged.dslplatform.json.JsonWriter
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.Date
internal object JsonHelper {
// ignore deprecation warnings as there is no other API that allows
// serializing a placeholder for a type and all its subtypes
@Suppress("deprecation")
private val settings = DslJson.Settings<MutableMap<String, Any>>().fallbackTo(FallbackWriter())
// Only one global DslJson is needed, and is thread-safe
// Note: dsl-json adds about 150k to the final binary size.
private val dslJson = DslJson(settings)
init {
dslJson.registerWriter(Date::class.java) { writer: JsonWriter, value: Date? ->
value?.let {
val timestamp = DateUtils.toIso8601(it)
writer.writeString(timestamp)
}
}
}
fun serialize(value: Any, stream: OutputStream) {
dslJson.serialize(value, stream)
}
fun serialize(value: Any, file: File) {
val parentFile = file.parentFile
if (parentFile != null && !parentFile.exists()) {
if (!parentFile.mkdirs()) {
throw FileSystemException(file, null, "Could not create parent dirs of file")
}
}
try {
FileOutputStream(file).use { stream -> dslJson.serialize(value, stream) }
} catch (ex: IOException) {
throw IOException("Could not serialize JSON document to $file", ex)
}
}
fun deserialize(bytes: ByteArray): MutableMap<String, Any> {
val document = dslJson.deserialize(
MutableMap::class.java,
bytes,
bytes.size
)
requireNotNull(document) { "JSON document is invalid" }
@Suppress("UNCHECKED_CAST")
return document as MutableMap<String, Any>
}
fun deserialize(stream: InputStream): MutableMap<in String, out Any> {
val document = dslJson.deserialize(MutableMap::class.java, stream)
requireNotNull(document) { "JSON document is invalid" }
@Suppress("UNCHECKED_CAST")
return document as MutableMap<String, Any>
}
fun deserialize(file: File): MutableMap<in String, out Any> {
try {
FileInputStream(file).use { stream -> return deserialize(stream) }
} catch (ex: FileNotFoundException) {
throw ex
} catch (ex: IOException) {
throw IOException("Could not deserialize from $file", ex)
}
}
}

View File

@ -0,0 +1,190 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import java.util.Arrays;
/** A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance
* with RFC 2045.<br><br>
* On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 times faster
* on small arrays (10 - 1000 bytes) and 2-3 times as fast on larger arrays (10000 - 1000000 bytes)
* compared to <code>sun.misc.Encoder()/Decoder()</code>.<br><br>
*
* On byte arrays the encoder is about 20% faster than Jakarta Commons Base64 Codec for encode and
* about 50% faster for decoding large arrays. This implementation is about twice as fast on very small
* arrays (&lt 30 bytes). If source/destination is a <code>String</code> this
* version is about three times as fast due to the fact that the Commons Codec result has to be recoded
* to a <code>String</code> from <code>byte[]</code>, which is very expensive.<br><br>
*
* This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only
* allocates the resulting array. This produces less garbage and it is possible to handle arrays twice
* as large as algorithms that create a temporary array. (E.g. Jakarta Commons Codec). It is unknown
* whether Sun's <code>sun.misc.Encoder()/Decoder()</code> produce temporary arrays but since performance
* is quite low it probably does.<br><br>
*
* The encoder produces the same output as the Sun one except that the Sun's encoder appends
* a trailing line separator if the last character isn't a pad. Unclear why but it only adds to the
* length and is probably a side effect. Both are in conformance with RFC 2045 though.<br>
* Commons codec seem to always att a trailing line separator.<br><br>
*
* <b>Note!</b>
* The encode/decode method pairs (types) come in three versions with the <b>exact</b> same algorithm and
* thus a lot of code redundancy. This is to not create any temporary arrays for transcoding to/from different
* format types. The methods not used can simply be commented out.<br><br>
*
* There is also a "fast" version of all decode methods that works the same way as the normal ones, but
* har a few demands on the decoded input. Normally though, these fast verions should be used if the source if
* the input is known and it hasn't bee tampered with.<br><br>
*
* If you find the code useful or you find a bug, please send me a note at base64 @ miginfocom . com.
*
* Licence (BSD):
* ==============
*
* Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
* Neither the name of the MiG InfoCom AB nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* @version 2.2
* @author Mikael Grev
* Date: 2004-aug-02
* Time: 11:31:11
*/
abstract class Base64 {
private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
private static final byte[] BA;
private static final int[] IA = new int[256];
static {
Arrays.fill(IA, -1);
for (int i = 0, iS = CA.length; i < iS; i++) {
IA[CA[i]] = i;
}
IA['='] = 0;
BA = new byte[CA.length];
for (int i = 0; i < CA.length; i++) {
BA[i] = (byte)CA[i];
}
}
static int encodeToBytes(byte[] sArr, byte[] dArr, final int start) {
final int sLen = sArr.length;
final int eLen = (sLen / 3) * 3; // Length of even 24-bits.
final int dLen = ((sLen - 1) / 3 + 1) << 2; // Returned character count
// Encode even 24-bits
for (int s = 0, d = start; s < eLen;) {
// Copy next three bytes into lower 24 bits of int, paying attension to sign.
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
// Encode the int into four chars
dArr[d++] = BA[(i >>> 18) & 0x3f];
dArr[d++] = BA[(i >>> 12) & 0x3f];
dArr[d++] = BA[(i >>> 6) & 0x3f];
dArr[d++] = BA[i & 0x3f];
}
// Pad and encode last bits if source isn't even 24 bits.
int left = sLen - eLen; // 0 - 2.
if (left > 0) {
// Prepare the int
int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
// Set last four chars
dArr[start + dLen - 4] = BA[i >> 12];
dArr[start + dLen - 3] = BA[(i >>> 6) & 0x3f];
dArr[start + dLen - 2] = left == 2 ? BA[i & 0x3f] : (byte)'=';
dArr[start + dLen - 1] = '=';
}
return dLen;
}
static int findEnd(final byte[] sArr, final int start) {
for (int i = start; i < sArr.length; i++)
if (IA[sArr[i] & 0xff] < 0)
return i;
return sArr.length;
}
private final static byte[] EMPTY_ARRAY = new byte[0];
static byte[] decodeFast(final byte[] sArr, final int start, final int end) {
// Check special case
int sLen = end - start;
if (sLen == 0)
return EMPTY_ARRAY;
int sIx = start, eIx = end - 1; // Start and end index after trimming.
// Trim illegal chars from start
while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0) {
sIx++;
}
// Trim illegal chars from end
while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0) {
eIx--;
}
// get the padding count (=) (0, 1 or 2)
final int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end.
final int cCnt = eIx - sIx + 1; // Content count including possible separators
final int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
final int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
final byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
// Decode all but the last 0 - 2 bytes.
int d = 0;
for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
// Assemble three bytes into an int from four "valid" characters.
int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];
// Add the bytes
dArr[d++] = (byte) (i >> 16);
dArr[d++] = (byte) (i >> 8);
dArr[d++] = (byte) i;
// If line separator, jump over it.
if (sepCnt > 0 && ++cc == 19) {
sIx += 2;
cc = 0;
}
}
if (d < len) {
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
int i = 0;
for (int j = 0; sIx <= eIx - pad; j++) {
i |= IA[sArr[sIx++]] << (18 - j * 6);
}
for (int r = 16; d < len; r -= 8) {
dArr[d++] = (byte) (i >> r);
}
}
return dArr;
}
}

View File

@ -0,0 +1,57 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@SuppressWarnings({"rawtypes", "unchecked"}) // suppress pre-existing warnings
public abstract class BinaryConverter {
static final JsonReader.ReadObject<byte[]> Base64Reader = new JsonReader.ReadObject<byte[]>() {
@Nullable
@Override
public byte[] read(JsonReader reader) throws IOException {
return reader.wasNull() ? null : deserialize(reader);
}
};
static final JsonWriter.WriteObject<byte[]> Base64Writer = new JsonWriter.WriteObject<byte[]>() {
@Override
public void write(JsonWriter writer, @Nullable byte[] value) {
serialize(value, writer);
}
};
public static void serialize(@Nullable final byte[] value, final JsonWriter sw) {
if (value == null) {
sw.writeNull();
} else if (value.length == 0) {
sw.writeAscii("\"\"");
} else {
sw.writeBinary(value);
}
}
public static byte[] deserialize(final JsonReader reader) throws IOException {
return reader.readBase64();
}
@SuppressWarnings("unchecked")
public static ArrayList<byte[]> deserializeCollection(final JsonReader reader) throws IOException {
return reader.deserializeCollection(Base64Reader);
}
public static void deserializeCollection(final JsonReader reader, final Collection<byte[]> res) throws IOException {
reader.deserializeCollection(Base64Reader, res);
}
@SuppressWarnings("unchecked")
public static ArrayList<byte[]> deserializeNullableCollection(final JsonReader reader) throws IOException {
return reader.deserializeNullableCollection(Base64Reader);
}
public static void deserializeNullableCollection(final JsonReader reader, final Collection<byte[]> res) throws IOException {
reader.deserializeNullableCollection(Base64Reader, res);
}
}

View File

@ -0,0 +1,128 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@SuppressWarnings({"rawtypes", "unchecked"}) // suppress pre-existing warnings
public abstract class BoolConverter {
public final static boolean[] EMPTY_ARRAY = new boolean[0];
public static final JsonReader.ReadObject<Boolean> READER = new JsonReader.ReadObject<Boolean>() {
@Override
public Boolean read(JsonReader reader) throws IOException {
return deserialize(reader);
}
};
public static final JsonReader.ReadObject<Boolean> NULLABLE_READER = new JsonReader.ReadObject<Boolean>() {
@Nullable
@Override
public Boolean read(JsonReader reader) throws IOException {
return reader.wasNull() ? null : deserialize(reader);
}
};
public static final JsonWriter.WriteObject<Boolean> WRITER = new JsonWriter.WriteObject<Boolean>() {
@Override
public void write(JsonWriter writer, @Nullable Boolean value) {
serializeNullable(value, writer);
}
};
public static final JsonReader.ReadObject<boolean[]> ARRAY_READER = new JsonReader.ReadObject<boolean[]>() {
@Nullable
@Override
public boolean[] read(JsonReader reader) throws IOException {
if (reader.wasNull()) return null;
if (reader.last() != '[') throw reader.newParseError("Expecting '[' for boolean array start");
reader.getNextToken();
return deserializeBoolArray(reader);
}
};
public static final JsonWriter.WriteObject<boolean[]> ARRAY_WRITER = new JsonWriter.WriteObject<boolean[]>() {
@Override
public void write(JsonWriter writer, @Nullable boolean[] value) {
serialize(value, writer);
}
};
public static void serializeNullable(@Nullable final Boolean value, final JsonWriter sw) {
if (value == null) {
sw.writeNull();
} else if (value) {
sw.writeAscii("true");
} else {
sw.writeAscii("false");
}
}
public static void serialize(final boolean value, final JsonWriter sw) {
if (value) {
sw.writeAscii("true");
} else {
sw.writeAscii("false");
}
}
public static void serialize(@Nullable final boolean[] value, final JsonWriter sw) {
if (value == null) {
sw.writeNull();
} else if (value.length == 0) {
sw.writeAscii("[]");
} else {
sw.writeByte(JsonWriter.ARRAY_START);
sw.writeAscii(value[0] ? "true" : "false");
for(int i = 1; i < value.length; i++) {
sw.writeAscii(value[i] ? ",true" : ",false");
}
sw.writeByte(JsonWriter.ARRAY_END);
}
}
public static boolean deserialize(final JsonReader reader) throws IOException {
if (reader.wasTrue()) {
return true;
} else if (reader.wasFalse()) {
return false;
}
throw reader.newParseErrorAt("Found invalid boolean value", 0);
}
public static boolean[] deserializeBoolArray(final JsonReader reader) throws IOException {
if (reader.last() == ']') {
return EMPTY_ARRAY;
}
boolean[] buffer = new boolean[4];
buffer[0] = deserialize(reader);
int i = 1;
while (reader.getNextToken() == ',') {
reader.getNextToken();
if (i == buffer.length) {
buffer = Arrays.copyOf(buffer, buffer.length << 1);
}
buffer[i++] = deserialize(reader);
}
reader.checkArrayEnd();
return Arrays.copyOf(buffer, i);
}
@SuppressWarnings("unchecked")
public static ArrayList<Boolean> deserializeCollection(final JsonReader reader) throws IOException {
return reader.deserializeCollection(READER);
}
public static void deserializeCollection(final JsonReader reader, final Collection<Boolean> res) throws IOException {
reader.deserializeCollection(READER, res);
}
@SuppressWarnings("unchecked")
public static ArrayList<Boolean> deserializeNullableCollection(final JsonReader reader) throws IOException {
return reader.deserializeNullableCollection(READER);
}
public static void deserializeNullableCollection(final JsonReader reader, final Collection<Boolean> res) throws IOException {
reader.deserializeNullableCollection(READER, res);
}
}

View File

@ -0,0 +1,16 @@
package com.bugsnag.android.repackaged.dslplatform.json;
/**
* Configuration API for setting up readers/writers during library initialization.
* DslJson will use ServiceLoader.load(Configuration.class) in default constructor.
* This will load services registered in META-INF/services/com.bugsnag.dslplatform.json.Configuration file.
*/
@SuppressWarnings("rawtypes") // suppress pre-existing warnings
public interface Configuration {
/**
* Configure library instance with appropriate readers/writers/etc...
*
* @param json library instance
*/
void configure(DslJson json);
}

View File

@ -0,0 +1,16 @@
package com.bugsnag.android.repackaged.dslplatform.json;
@SuppressWarnings("serial") // suppress pre-existing warnings
public class ConfigurationException extends RuntimeException {
public ConfigurationException(String reason) {
super(reason);
}
public ConfigurationException(Throwable cause) {
super(cause);
}
public ConfigurationException(String reason, Throwable cause) {
super(reason, cause);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import java.util.*;
class ExternalConverterAnalyzer {
private final Set<String> lookedUpClasses = new HashSet<String>();
private final ClassLoader[] classLoaders;
ExternalConverterAnalyzer(Collection<ClassLoader> classLoaders) {
this.classLoaders = classLoaders.toArray(new ClassLoader[0]);
}
synchronized boolean tryFindConverter(Class<?> manifest, DslJson<?> dslJson) {
final String className = manifest.getName();
if (!lookedUpClasses.add(className)) return false;
String[] converterClassNames = resolveExternalConverterClassNames(className);
for (ClassLoader cl : classLoaders) {
for (String ccn : converterClassNames) {
try {
Class<?> converterClass = cl.loadClass(ccn);
if (!Configuration.class.isAssignableFrom(converterClass)) continue;
Configuration converter = (Configuration) converterClass.newInstance();
converter.configure(dslJson);
return true;
} catch (ClassNotFoundException ignored) {
} catch (IllegalAccessException ignored) {
} catch (InstantiationException ignored) {
}
}
}
return false;
}
private String[] resolveExternalConverterClassNames(final String fullClassName) {
int dotIndex = fullClassName.lastIndexOf('.');
if (dotIndex == -1) {
return new String[]{String.format("_%s_DslJsonConverter", fullClassName)};
}
String packageName = fullClassName.substring(0, dotIndex);
String className = fullClassName.substring(dotIndex + 1);
return new String[]{
String.format("%s._%s_DslJsonConverter", packageName, className),
String.format("dsl_json.%s._%s_DslJsonConverter", packageName, className),
String.format("dsl_json.%s.%sDslJsonConverter", packageName, className)};
}
}

View File

@ -0,0 +1,924 @@
// Copyright 2010 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Ported to Java from Mozilla's version of V8-dtoa by Hannes Wallnoefer.
// The original revision was 67d1049b0bf9 from the mozilla-central tree.
// Modified by Rikard Pavelic do avoid allocations
// and unused code paths due to external checks
package com.bugsnag.android.repackaged.dslplatform.json;
@SuppressWarnings("fallthrough") // suppress pre-existing warnings
abstract class Grisu3 {
// FastDtoa will produce at most kFastDtoaMaximalLength digits.
static final int kFastDtoaMaximalLength = 17;
// The minimal and maximal target exponent define the range of w's binary
// exponent, where 'w' is the result of multiplying the input by a cached power
// of ten.
//
// A different range might be chosen on a different platform, to optimize digit
// generation, but a smaller range requires more powers of ten to be cached.
static final int minimal_target_exponent = -60;
private static final class DiyFp {
long f;
int e;
static final int kSignificandSize = 64;
static final long kUint64MSB = 0x8000000000000000L;
private static final long kM32 = 0xFFFFFFFFL;
private static final long k10MSBits = 0xFFC00000L << 32;
DiyFp() {
this.f = 0;
this.e = 0;
}
// this = this - other.
// The exponents of both numbers must be the same and the significand of this
// must be bigger than the significand of other.
// The result will not be normalized.
void subtract(DiyFp other) {
f -= other.f;
}
// this = this * other.
void multiply(DiyFp other) {
// Simply "emulates" a 128 bit multiplication.
// However: the resulting number only contains 64 bits. The least
// significant 64 bits are only used for rounding the most significant 64
// bits.
long a = f >>> 32;
long b = f & kM32;
long c = other.f >>> 32;
long d = other.f & kM32;
long ac = a * c;
long bc = b * c;
long ad = a * d;
long bd = b * d;
long tmp = (bd >>> 32) + (ad & kM32) + (bc & kM32);
// By adding 1U << 31 to tmp we round the final result.
// Halfway cases will be round up.
tmp += 1L << 31;
long result_f = ac + (ad >>> 32) + (bc >>> 32) + (tmp >>> 32);
e += other.e + 64;
f = result_f;
}
void normalize() {
long f = this.f;
int e = this.e;
// This method is mainly called for normalizing boundaries. In general
// boundaries need to be shifted by 10 bits. We thus optimize for this case.
while ((f & k10MSBits) == 0) {
f <<= 10;
e -= 10;
}
while ((f & kUint64MSB) == 0) {
f <<= 1;
e--;
}
this.f = f;
this.e = e;
}
void reset() {
e = 0;
f = 0;
}
@Override
public String toString() {
return "[DiyFp f:" + f + ", e:" + e + "]";
}
}
private static class CachedPowers {
static final double kD_1_LOG2_10 = 0.30102999566398114; // 1 / lg(10)
static class CachedPower {
final long significand;
final short binaryExponent;
final short decimalExponent;
CachedPower(long significand, short binaryExponent, short decimalExponent) {
this.significand = significand;
this.binaryExponent = binaryExponent;
this.decimalExponent = decimalExponent;
}
}
static int getCachedPower(int e, int alpha, DiyFp c_mk) {
final int kQ = DiyFp.kSignificandSize;
final double k = Math.ceil((alpha - e + kQ - 1) * kD_1_LOG2_10);
final int index = (GRISU_CACHE_OFFSET + (int) k - 1) / CACHED_POWERS_SPACING + 1;
final CachedPower cachedPower = CACHED_POWERS[index];
c_mk.f = cachedPower.significand;
c_mk.e = cachedPower.binaryExponent;
return cachedPower.decimalExponent;
}
// Code below is converted from GRISU_CACHE_NAME(8) in file "powers-ten.h"
// Regexp to convert this from original C++ source:
// \{GRISU_UINT64_C\((\w+), (\w+)\), (\-?\d+), (\-?\d+)\}
// interval between entries of the powers cache below
static final int CACHED_POWERS_SPACING = 8;
static final CachedPower[] CACHED_POWERS = {
new CachedPower(0xe61acf033d1a45dfL, (short) -1087, (short) -308),
new CachedPower(0xab70fe17c79ac6caL, (short) -1060, (short) -300),
new CachedPower(0xff77b1fcbebcdc4fL, (short) -1034, (short) -292),
new CachedPower(0xbe5691ef416bd60cL, (short) -1007, (short) -284),
new CachedPower(0x8dd01fad907ffc3cL, (short) -980, (short) -276),
new CachedPower(0xd3515c2831559a83L, (short) -954, (short) -268),
new CachedPower(0x9d71ac8fada6c9b5L, (short) -927, (short) -260),
new CachedPower(0xea9c227723ee8bcbL, (short) -901, (short) -252),
new CachedPower(0xaecc49914078536dL, (short) -874, (short) -244),
new CachedPower(0x823c12795db6ce57L, (short) -847, (short) -236),
new CachedPower(0xc21094364dfb5637L, (short) -821, (short) -228),
new CachedPower(0x9096ea6f3848984fL, (short) -794, (short) -220),
new CachedPower(0xd77485cb25823ac7L, (short) -768, (short) -212),
new CachedPower(0xa086cfcd97bf97f4L, (short) -741, (short) -204),
new CachedPower(0xef340a98172aace5L, (short) -715, (short) -196),
new CachedPower(0xb23867fb2a35b28eL, (short) -688, (short) -188),
new CachedPower(0x84c8d4dfd2c63f3bL, (short) -661, (short) -180),
new CachedPower(0xc5dd44271ad3cdbaL, (short) -635, (short) -172),
new CachedPower(0x936b9fcebb25c996L, (short) -608, (short) -164),
new CachedPower(0xdbac6c247d62a584L, (short) -582, (short) -156),
new CachedPower(0xa3ab66580d5fdaf6L, (short) -555, (short) -148),
new CachedPower(0xf3e2f893dec3f126L, (short) -529, (short) -140),
new CachedPower(0xb5b5ada8aaff80b8L, (short) -502, (short) -132),
new CachedPower(0x87625f056c7c4a8bL, (short) -475, (short) -124),
new CachedPower(0xc9bcff6034c13053L, (short) -449, (short) -116),
new CachedPower(0x964e858c91ba2655L, (short) -422, (short) -108),
new CachedPower(0xdff9772470297ebdL, (short) -396, (short) -100),
new CachedPower(0xa6dfbd9fb8e5b88fL, (short) -369, (short) -92),
new CachedPower(0xf8a95fcf88747d94L, (short) -343, (short) -84),
new CachedPower(0xb94470938fa89bcfL, (short) -316, (short) -76),
new CachedPower(0x8a08f0f8bf0f156bL, (short) -289, (short) -68),
new CachedPower(0xcdb02555653131b6L, (short) -263, (short) -60),
new CachedPower(0x993fe2c6d07b7facL, (short) -236, (short) -52),
new CachedPower(0xe45c10c42a2b3b06L, (short) -210, (short) -44),
new CachedPower(0xaa242499697392d3L, (short) -183, (short) -36),
new CachedPower(0xfd87b5f28300ca0eL, (short) -157, (short) -28),
new CachedPower(0xbce5086492111aebL, (short) -130, (short) -20),
new CachedPower(0x8cbccc096f5088ccL, (short) -103, (short) -12),
new CachedPower(0xd1b71758e219652cL, (short) -77, (short) -4),
new CachedPower(0x9c40000000000000L, (short) -50, (short) 4),
new CachedPower(0xe8d4a51000000000L, (short) -24, (short) 12),
new CachedPower(0xad78ebc5ac620000L, (short) 3, (short) 20),
new CachedPower(0x813f3978f8940984L, (short) 30, (short) 28),
new CachedPower(0xc097ce7bc90715b3L, (short) 56, (short) 36),
new CachedPower(0x8f7e32ce7bea5c70L, (short) 83, (short) 44),
new CachedPower(0xd5d238a4abe98068L, (short) 109, (short) 52),
new CachedPower(0x9f4f2726179a2245L, (short) 136, (short) 60),
new CachedPower(0xed63a231d4c4fb27L, (short) 162, (short) 68),
new CachedPower(0xb0de65388cc8ada8L, (short) 189, (short) 76),
new CachedPower(0x83c7088e1aab65dbL, (short) 216, (short) 84),
new CachedPower(0xc45d1df942711d9aL, (short) 242, (short) 92),
new CachedPower(0x924d692ca61be758L, (short) 269, (short) 100),
new CachedPower(0xda01ee641a708deaL, (short) 295, (short) 108),
new CachedPower(0xa26da3999aef774aL, (short) 322, (short) 116),
new CachedPower(0xf209787bb47d6b85L, (short) 348, (short) 124),
new CachedPower(0xb454e4a179dd1877L, (short) 375, (short) 132),
new CachedPower(0x865b86925b9bc5c2L, (short) 402, (short) 140),
new CachedPower(0xc83553c5c8965d3dL, (short) 428, (short) 148),
new CachedPower(0x952ab45cfa97a0b3L, (short) 455, (short) 156),
new CachedPower(0xde469fbd99a05fe3L, (short) 481, (short) 164),
new CachedPower(0xa59bc234db398c25L, (short) 508, (short) 172),
new CachedPower(0xf6c69a72a3989f5cL, (short) 534, (short) 180),
new CachedPower(0xb7dcbf5354e9beceL, (short) 561, (short) 188),
new CachedPower(0x88fcf317f22241e2L, (short) 588, (short) 196),
new CachedPower(0xcc20ce9bd35c78a5L, (short) 614, (short) 204),
new CachedPower(0x98165af37b2153dfL, (short) 641, (short) 212),
new CachedPower(0xe2a0b5dc971f303aL, (short) 667, (short) 220),
new CachedPower(0xa8d9d1535ce3b396L, (short) 694, (short) 228),
new CachedPower(0xfb9b7cd9a4a7443cL, (short) 720, (short) 236),
new CachedPower(0xbb764c4ca7a44410L, (short) 747, (short) 244),
new CachedPower(0x8bab8eefb6409c1aL, (short) 774, (short) 252),
new CachedPower(0xd01fef10a657842cL, (short) 800, (short) 260),
new CachedPower(0x9b10a4e5e9913129L, (short) 827, (short) 268),
new CachedPower(0xe7109bfba19c0c9dL, (short) 853, (short) 276),
new CachedPower(0xac2820d9623bf429L, (short) 880, (short) 284),
new CachedPower(0x80444b5e7aa7cf85L, (short) 907, (short) 292),
new CachedPower(0xbf21e44003acdd2dL, (short) 933, (short) 300),
new CachedPower(0x8e679c2f5e44ff8fL, (short) 960, (short) 308),
new CachedPower(0xd433179d9c8cb841L, (short) 986, (short) 316),
new CachedPower(0x9e19db92b4e31ba9L, (short) 1013, (short) 324),
new CachedPower(0xeb96bf6ebadf77d9L, (short) 1039, (short) 332),
new CachedPower(0xaf87023b9bf0ee6bL, (short) 1066, (short) 340)
};
// nb elements (8): 82
static final int GRISU_CACHE_OFFSET = 308;
}
private static class DoubleHelper {
static final long kExponentMask = 0x7FF0000000000000L;
static final long kSignificandMask = 0x000FFFFFFFFFFFFFL;
static final long kHiddenBit = 0x0010000000000000L;
static void asDiyFp(long d64, DiyFp v) {
v.f = significand(d64);
v.e = exponent(d64);
}
// this->Significand() must not be 0.
static void asNormalizedDiyFp(long d64, DiyFp w) {
long f = significand(d64);
int e = exponent(d64);
// The current double could be a denormal.
while ((f & kHiddenBit) == 0) {
f <<= 1;
e--;
}
// Do the final shifts in one go. Don't forget the hidden bit (the '-1').
f <<= DiyFp.kSignificandSize - kSignificandSize - 1;
e -= DiyFp.kSignificandSize - kSignificandSize - 1;
w.f = f;
w.e = e;
}
static int exponent(long d64) {
if (isDenormal(d64)) return kDenormalExponent;
int biased_e = (int) (((d64 & kExponentMask) >>> kSignificandSize) & 0xffffffffL);
return biased_e - kExponentBias;
}
static long significand(long d64) {
long significand = d64 & kSignificandMask;
if (!isDenormal(d64)) {
return significand + kHiddenBit;
} else {
return significand;
}
}
// Returns true if the double is a denormal.
private static boolean isDenormal(long d64) {
return (d64 & kExponentMask) == 0L;
}
// Returns the two boundaries of first argument.
// The bigger boundary (m_plus) is normalized. The lower boundary has the same
// exponent as m_plus.
static void normalizedBoundaries(DiyFp v, long d64, DiyFp m_minus, DiyFp m_plus) {
asDiyFp(d64, v);
final boolean significand_is_zero = (v.f == kHiddenBit);
m_plus.f = (v.f << 1) + 1;
m_plus.e = v.e - 1;
m_plus.normalize();
if (significand_is_zero && v.e != kDenormalExponent) {
// The boundary is closer. Think of v = 1000e10 and v- = 9999e9.
// Then the boundary (== (v - v-)/2) is not just at a distance of 1e9 but
// at a distance of 1e8.
// The only exception is for the smallest normal: the largest denormal is
// at the same distance as its successor.
// Note: denormals have the same exponent as the smallest normals.
m_minus.f = (v.f << 2) - 1;
m_minus.e = v.e - 2;
} else {
m_minus.f = (v.f << 1) - 1;
m_minus.e = v.e - 1;
}
m_minus.f = m_minus.f << (m_minus.e - m_plus.e);
m_minus.e = m_plus.e;
}
private static final int kSignificandSize = 52; // Excludes the hidden bit.
private static final int kExponentBias = 0x3FF + kSignificandSize;
private static final int kDenormalExponent = -kExponentBias + 1;
}
static class FastDtoa {
// Adjusts the last digit of the generated number, and screens out generated
// solutions that may be inaccurate. A solution may be inaccurate if it is
// outside the safe interval, or if we ctannot prove that it is closer to the
// input than a neighboring representation of the same length.
//
// Input: * buffer containing the digits of too_high / 10^kappa
// * distance_too_high_w == (too_high - w).f() * unit
// * unsafe_interval == (too_high - too_low).f() * unit
// * rest = (too_high - buffer * 10^kappa).f() * unit
// * ten_kappa = 10^kappa * unit
// * unit = the common multiplier
// Output: returns true if the buffer is guaranteed to contain the closest
// representable number to the input.
// Modifies the generated digits in the buffer to approach (round towards) w.
static boolean roundWeed(
final FastDtoaBuilder buffer,
final long distance_too_high_w,
final long unsafe_interval,
long rest,
final long ten_kappa,
final long unit) {
final long small_distance = distance_too_high_w - unit;
final long big_distance = distance_too_high_w + unit;
// Let w_low = too_high - big_distance, and
// w_high = too_high - small_distance.
// Note: w_low < w < w_high
//
// The real w (* unit) must lie somewhere inside the interval
// ]w_low; w_low[ (often written as "(w_low; w_low)")
// Basically the buffer currently contains a number in the unsafe interval
// ]too_low; too_high[ with too_low < w < too_high
//
// too_high - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// ^v 1 unit ^ ^ ^ ^
// boundary_high --------------------- . . . .
// ^v 1 unit . . . .
// - - - - - - - - - - - - - - - - - - - + - - + - - - - - - . .
// . . ^ . .
// . big_distance . . .
// . . . . rest
// small_distance . . . .
// v . . . .
// w_high - - - - - - - - - - - - - - - - - - . . . .
// ^v 1 unit . . . .
// w ---------------------------------------- . . . .
// ^v 1 unit v . . .
// w_low - - - - - - - - - - - - - - - - - - - - - . . .
// . . v
// buffer --------------------------------------------------+-------+--------
// . .
// safe_interval .
// v .
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .
// ^v 1 unit .
// boundary_low ------------------------- unsafe_interval
// ^v 1 unit v
// too_low - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
//
// Note that the value of buffer could lie anywhere inside the range too_low
// to too_high.
//
// boundary_low, boundary_high and w are approximations of the real boundaries
// and v (the input number). They are guaranteed to be precise up to one unit.
// In fact the error is guaranteed to be strictly less than one unit.
//
// Anything that lies outside the unsafe interval is guaranteed not to round
// to v when read again.
// Anything that lies inside the safe interval is guaranteed to round to v
// when read again.
// If the number inside the buffer lies inside the unsafe interval but not
// inside the safe interval then we simply do not know and bail out (returning
// false).
//
// Similarly we have to take into account the imprecision of 'w' when rounding
// the buffer. If we have two potential representations we need to make sure
// that the chosen one is closer to w_low and w_high since v can be anywhere
// between them.
//
// By generating the digits of too_high we got the largest (closest to
// too_high) buffer that is still in the unsafe interval. In the case where
// w_high < buffer < too_high we try to decrement the buffer.
// This way the buffer approaches (rounds towards) w.
// There are 3 conditions that stop the decrementation process:
// 1) the buffer is already below w_high
// 2) decrementing the buffer would make it leave the unsafe interval
// 3) decrementing the buffer would yield a number below w_high and farther
// away than the current number. In other words:
// (buffer{-1} < w_high) && w_high - buffer{-1} > buffer - w_high
// Instead of using the buffer directly we use its distance to too_high.
// Conceptually rest ~= too_high - buffer
while (rest < small_distance && // Negated condition 1
unsafe_interval - rest >= ten_kappa && // Negated condition 2
(rest + ten_kappa < small_distance || // buffer{-1} > w_high
small_distance - rest >= rest + ten_kappa - small_distance)) {
buffer.decreaseLast();
rest += ten_kappa;
}
// We have approached w+ as much as possible. We now test if approaching w-
// would require changing the buffer. If yes, then we have two possible
// representations close to w, but we cannot decide which one is closer.
if (rest < big_distance &&
unsafe_interval - rest >= ten_kappa &&
(rest + ten_kappa < big_distance ||
big_distance - rest > rest + ten_kappa - big_distance)) {
return false;
}
// Weeding test.
// The safe interval is [too_low + 2 ulp; too_high - 2 ulp]
// Since too_low = too_high - unsafe_interval this is equivalent to
// [too_high - unsafe_interval + 4 ulp; too_high - 2 ulp]
// Conceptually we have: rest ~= too_high - buffer
return (2 * unit <= rest) && (rest <= unsafe_interval - 4 * unit);
}
static final int kTen4 = 10000;
static final int kTen5 = 100000;
static final int kTen6 = 1000000;
static final int kTen7 = 10000000;
static final int kTen8 = 100000000;
static final int kTen9 = 1000000000;
// Returns the biggest power of ten that is less than or equal than the given
// number. We furthermore receive the maximum number of bits 'number' has.
// If number_bits == 0 then 0^-1 is returned
// The number of bits must be <= 32.
// Precondition: (1 << number_bits) <= number < (1 << (number_bits + 1)).
static long biggestPowerTen(int number, int number_bits) {
int power, exponent;
switch (number_bits) {
case 32:
case 31:
case 30:
if (kTen9 <= number) {
power = kTen9;
exponent = 9;
break;
} // else fallthrough
case 29:
case 28:
case 27:
if (kTen8 <= number) {
power = kTen8;
exponent = 8;
break;
} // else fallthrough
case 26:
case 25:
case 24:
if (kTen7 <= number) {
power = kTen7;
exponent = 7;
break;
} // else fallthrough
case 23:
case 22:
case 21:
case 20:
if (kTen6 <= number) {
power = kTen6;
exponent = 6;
break;
} // else fallthrough
case 19:
case 18:
case 17:
if (kTen5 <= number) {
power = kTen5;
exponent = 5;
break;
} // else fallthrough
case 16:
case 15:
case 14:
if (kTen4 <= number) {
power = kTen4;
exponent = 4;
break;
} // else fallthrough
case 13:
case 12:
case 11:
case 10:
if (1000 <= number) {
power = 1000;
exponent = 3;
break;
} // else fallthrough
case 9:
case 8:
case 7:
if (100 <= number) {
power = 100;
exponent = 2;
break;
} // else fallthrough
case 6:
case 5:
case 4:
if (10 <= number) {
power = 10;
exponent = 1;
break;
} // else fallthrough
case 3:
case 2:
case 1:
if (1 <= number) {
power = 1;
exponent = 0;
break;
} // else fallthrough
case 0:
power = 0;
exponent = -1;
break;
default:
// Following assignments are here to silence compiler warnings.
power = 0;
exponent = 0;
// UNREACHABLE();
}
return ((long) power << 32) | (0xffffffffL & exponent);
}
// Generates the digits of input number w.
// w is a floating-point number (DiyFp), consisting of a significand and an
// exponent. Its exponent is bounded by minimal_target_exponent and
// maximal_target_exponent.
// Hence -60 <= w.e() <= -32.
//
// Returns false if it fails, in which case the generated digits in the buffer
// should not be used.
// Preconditions:
// * low, w and high are correct up to 1 ulp (unit in the last place). That
// is, their error must be less that a unit of their last digits.
// * low.e() == w.e() == high.e()
// * low < w < high, and taking into account their error: low~ <= high~
// * minimal_target_exponent <= w.e() <= maximal_target_exponent
// Postconditions: returns false if procedure fails.
// otherwise:
// * buffer is not null-terminated, but len contains the number of digits.
// * buffer contains the shortest possible decimal digit-sequence
// such that LOW < buffer * 10^kappa < HIGH, where LOW and HIGH are the
// correct values of low and high (without their error).
// * if more than one decimal representation gives the minimal number of
// decimal digits then the one closest to W (where W is the correct value
// of w) is chosen.
// Remark: this procedure takes into account the imprecision of its input
// numbers. If the precision is not enough to guarantee all the postconditions
// then false is returned. This usually happens rarely (~0.5%).
//
// Say, for the sake of example, that
// w.e() == -48, and w.f() == 0x1234567890abcdef
// w's value can be computed by w.f() * 2^w.e()
// We can obtain w's integral digits by simply shifting w.f() by -w.e().
// -> w's integral part is 0x1234
// w's fractional part is therefore 0x567890abcdef.
// Printing w's integral part is easy (simply print 0x1234 in decimal).
// In order to print its fraction we repeatedly multiply the fraction by 10 and
// get each digit. Example the first digit after the point would be computed by
// (0x567890abcdef * 10) >> 48. -> 3
// The whole thing becomes slightly more complicated because we want to stop
// once we have enough digits. That is, once the digits inside the buffer
// represent 'w' we can stop. Everything inside the interval low - high
// represents w. However we have to pay attention to low, high and w's
// imprecision.
static boolean digitGen(FastDtoaBuilder buffer, int mk) {
final DiyFp low = buffer.scaled_boundary_minus;
final DiyFp w = buffer.scaled_w;
final DiyFp high = buffer.scaled_boundary_plus;
// low, w and high are imprecise, but by less than one ulp (unit in the last
// place).
// If we remove (resp. add) 1 ulp from low (resp. high) we are certain that
// the new numbers are outside of the interval we want the final
// representation to lie in.
// Inversely adding (resp. removing) 1 ulp from low (resp. high) would yield
// numbers that are certain to lie in the interval. We will use this fact
// later on.
// We will now start by generating the digits within the uncertain
// interval. Later we will weed out representations that lie outside the safe
// interval and thus _might_ lie outside the correct interval.
long unit = 1;
final DiyFp too_low = buffer.too_low;
too_low.f = low.f - unit;
too_low.e = low.e;
final DiyFp too_high = buffer.too_high;
too_high.f = high.f + unit;
too_high.e = high.e;
// too_low and too_high are guaranteed to lie outside the interval we want the
// generated number in.
final DiyFp unsafe_interval = buffer.unsafe_interval;
unsafe_interval.f = too_high.f;
unsafe_interval.e = too_high.e;
unsafe_interval.subtract(too_low);
// We now cut the input number into two parts: the integral digits and the
// fractionals. We will not write any decimal separator though, but adapt
// kappa instead.
// Reminder: we are currently computing the digits (stored inside the buffer)
// such that: too_low < buffer * 10^kappa < too_high
// We use too_high for the digit_generation and stop as soon as possible.
// If we stop early we effectively round down.
final DiyFp one = buffer.one;
one.f = 1L << -w.e;
one.e = w.e;
// Division by one is a shift.
int integrals = (int) ((too_high.f >>> -one.e) & 0xffffffffL);
// Modulo by one is an and.
long fractionals = too_high.f & (one.f - 1);
long result = biggestPowerTen(integrals, DiyFp.kSignificandSize - (-one.e));
int divider = (int) ((result >>> 32) & 0xffffffffL);
int divider_exponent = (int) (result & 0xffffffffL);
int kappa = divider_exponent + 1;
// Loop invariant: buffer = too_high / 10^kappa (integer division)
// The invariant holds for the first iteration: kappa has been initialized
// with the divider exponent + 1. And the divider is the biggest power of ten
// that is smaller than integrals.
while (kappa > 0) {
int digit = integrals / divider;
buffer.append((byte) ('0' + digit));
integrals %= divider;
kappa--;
// Note that kappa now equals the exponent of the divider and that the
// invariant thus holds again.
final long rest = ((long) integrals << -one.e) + fractionals;
// Invariant: too_high = buffer * 10^kappa + DiyFp(rest, one.e())
// Reminder: unsafe_interval.e() == one.e()
if (rest < unsafe_interval.f) {
// Rounding down (by not emitting the remaining digits) yields a number
// that lies within the unsafe interval.
buffer.point = buffer.end - mk + kappa;
final DiyFp minus_round = buffer.minus_round;
minus_round.f = too_high.f;
minus_round.e = too_high.e;
minus_round.subtract(w);
return roundWeed(buffer, minus_round.f,
unsafe_interval.f, rest,
(long) divider << -one.e, unit);
}
divider /= 10;
}
// The integrals have been generated. We are at the point of the decimal
// separator. In the following loop we simply multiply the remaining digits by
// 10 and divide by one. We just need to pay attention to multiply associated
// data (like the interval or 'unit'), too.
// Instead of multiplying by 10 we multiply by 5 (cheaper operation) and
// increase its (imaginary) exponent. At the same time we decrease the
// divider's (one's) exponent and shift its significand.
// Basically, if fractionals was a DiyFp (with fractionals.e == one.e):
// fractionals.f *= 10;
// fractionals.f >>= 1; fractionals.e++; // value remains unchanged.
// one.f >>= 1; one.e++; // value remains unchanged.
// and we have again fractionals.e == one.e which allows us to divide
// fractionals.f() by one.f()
// We simply combine the *= 10 and the >>= 1.
while (true) {
fractionals *= 5;
unit *= 5;
unsafe_interval.f = unsafe_interval.f * 5;
unsafe_interval.e = unsafe_interval.e + 1; // Will be optimized out.
one.f = one.f >>> 1;
one.e = one.e + 1;
// Integer division by one.
final int digit = (int) ((fractionals >>> -one.e) & 0xffffffffL);
buffer.append((byte) ('0' + digit));
fractionals &= one.f - 1; // Modulo by one.
kappa--;
if (fractionals < unsafe_interval.f) {
buffer.point = buffer.end - mk + kappa;
final DiyFp minus_round = buffer.minus_round;
minus_round.f = too_high.f;
minus_round.e = too_high.e;
minus_round.subtract(w);
return roundWeed(buffer, minus_round.f * unit,
unsafe_interval.f, fractionals, one.f, unit);
}
}
}
}
public static boolean tryConvert(final double value, final FastDtoaBuilder buffer) {
final long bits;
final int firstDigit;
buffer.reset();
if (value < 0) {
buffer.append((byte) '-');
bits = Double.doubleToLongBits(-value);
firstDigit = 1;
} else {
bits = Double.doubleToLongBits(value);
firstDigit = 0;
}
// Provides a decimal representation of v.
// Returns true if it succeeds, otherwise the result cannot be trusted.
// There will be *length digits inside the buffer (not null-terminated).
// If the function returns true then
// v == (double) (buffer * 10^decimal_exponent).
// The digits in the buffer are the shortest representation possible: no
// 0.09999999999999999 instead of 0.1. The shorter representation will even be
// chosen even if the longer one would be closer to v.
// The last digit will be closest to the actual v. That is, even if several
// digits might correctly yield 'v' when read again, the closest will be
// computed.
final int mk = buffer.initialize(bits);
// DigitGen will generate the digits of scaled_w. Therefore we have
// v == (double) (scaled_w * 10^-mk).
// Set decimal_exponent == -mk and pass it to DigitGen. If scaled_w is not an
// integer than it will be updated. For instance if scaled_w == 1.23 then
// the buffer will be filled with "123" und the decimal_exponent will be
// decreased by 2.
if (FastDtoa.digitGen(buffer, mk)) {
buffer.write(firstDigit);
return true;
} else {
return false;
}
}
static class FastDtoaBuilder {
private final DiyFp v = new DiyFp();
private final DiyFp w = new DiyFp();
private final DiyFp boundary_minus = new DiyFp();
private final DiyFp boundary_plus = new DiyFp();
private final DiyFp ten_mk = new DiyFp();
private final DiyFp scaled_w = new DiyFp();
private final DiyFp scaled_boundary_minus = new DiyFp();
private final DiyFp scaled_boundary_plus = new DiyFp();
private final DiyFp too_low = new DiyFp();
private final DiyFp too_high = new DiyFp();
private final DiyFp unsafe_interval = new DiyFp();
private final DiyFp one = new DiyFp();
private final DiyFp minus_round = new DiyFp();
int initialize(final long bits) {
DoubleHelper.asNormalizedDiyFp(bits, w);
// boundary_minus and boundary_plus are the boundaries between v and its
// closest floating-point neighbors. Any number strictly between
// boundary_minus and boundary_plus will round to v when convert to a double.
// Grisu3 will never output representations that lie exactly on a boundary.
boundary_minus.reset();
boundary_plus.reset();
DoubleHelper.normalizedBoundaries(v, bits, boundary_minus, boundary_plus);
ten_mk.reset(); // Cached power of ten: 10^-k
final int mk = CachedPowers.getCachedPower(w.e + DiyFp.kSignificandSize, minimal_target_exponent, ten_mk);
// Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
// 64 bit significand and ten_mk is thus only precise up to 64 bits.
// The DiyFp::Times procedure rounds its result, and ten_mk is approximated
// too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
// off by a small amount.
// In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
// In other words: let f = scaled_w.f() and e = scaled_w.e(), then
// (f-1) * 2^e < w*10^k < (f+1) * 2^e
scaled_w.f = w.f;
scaled_w.e = w.e;
scaled_w.multiply(ten_mk);
// In theory it would be possible to avoid some recomputations by computing
// the difference between w and boundary_minus/plus (a power of 2) and to
// compute scaled_boundary_minus/plus by subtracting/adding from
// scaled_w. However the code becomes much less readable and the speed
// enhancements are not terriffic.
scaled_boundary_minus.f = boundary_minus.f;
scaled_boundary_minus.e = boundary_minus.e;
scaled_boundary_minus.multiply(ten_mk);
scaled_boundary_plus.f = boundary_plus.f;
scaled_boundary_plus.e = boundary_plus.e;
scaled_boundary_plus.multiply(ten_mk);
return mk;
}
// allocate buffer for generated digits + extra notation + padding zeroes
private final byte[] chars = new byte[kFastDtoaMaximalLength + 10];
private int end = 0;
private int point;
void reset() {
end = 0;
}
void append(byte c) {
chars[end++] = c;
}
void decreaseLast() {
chars[end - 1]--;
}
@Override
public String toString() {
return "[chars:" + new String(chars, 0, end) + ", point:" + point + "]";
}
int copyTo(final byte[] target, final int position) {
for (int i = 0; i < end; i++) {
target[i + position] = chars[i];
}
return end;
}
public void write(int firstDigit) {
// check for minus sign
int decPoint = point - firstDigit;
if (decPoint < -5 || decPoint > 21) {
toExponentialFormat(firstDigit, decPoint);
} else {
toFixedFormat(firstDigit, decPoint);
}
}
private void toFixedFormat(int firstDigit, int decPoint) {
if (point < end) {
// insert decimal point
if (decPoint > 0) {
// >= 1, split decimals and insert point
for (int i = end; i >= point; i--) {
chars[i + 1] = chars[i];
}
chars[point] = '.';
end++;
} else {
// < 1,
final int offset = 2 - decPoint;
for (int i = end + firstDigit; i >= firstDigit; i--) {
chars[i + offset] = chars[i];
}
chars[firstDigit] = '0';
chars[firstDigit + 1] = '.';
if (decPoint < 0) {
int target = firstDigit + 2 - decPoint;
for (int i = firstDigit + 2; i < target; i++) {
chars[i] = '0';
}
}
end += 2 - decPoint;
}
} else if (point > end) {
// large integer, add trailing zeroes
for (int i = end; i < point; i++) {
chars[i] = '0';
}
end += point - end;
chars[end] = '.';
chars[end + 1] = '0';
end += 2;
} else {
chars[end] = '.';
chars[end + 1] = '0';
end += 2;
}
}
private void toExponentialFormat(int firstDigit, int decPoint) {
if (end - firstDigit > 1) {
// insert decimal point if more than one digit was produced
int dot = firstDigit + 1;
System.arraycopy(chars, dot, chars, dot + 1, end - dot);
chars[dot] = '.';
end++;
}
chars[end++] = 'E';
byte sign = '+';
int exp = decPoint - 1;
if (exp < 0) {
sign = '-';
exp = -exp;
}
chars[end++] = sign;
int charPos = exp > 99 ? end + 2 : exp > 9 ? end + 1 : end;
end = charPos + 1;
do {
int r = exp % 10;
chars[charPos--] = digits[r];
exp = exp / 10;
} while (exp != 0);
}
final static byte[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
}
}

View File

@ -0,0 +1,35 @@
package com.bugsnag.android.repackaged.dslplatform.json;
/**
* Objects which implement this interface are supported for serialization in DslJson.
* This is used by DSL Platform POJO objects.
* Annotation processor uses a different method, since it can't modify existing objects to add such signature into them.
*
* Objects which implement JsonObject support convention based deserialization in form of public static JSON_READER
* An example:
*
* <pre>
* public class MyCustomPojo implements JsonObject {
* public void serialize(JsonWriter writer, boolean minimal) {
* //implement serialization logic, eg: writer.writeAscii("{\"my\":\"object\"}");
* }
* public static final JsonReader.ReadJsonObject&lt;MyCustomPojo&gt; JSON_READER = new JsonReader.ReadJsonObject&lt;MyCustomPojo&gt;() {
* public MyCustomPojo deserialize(JsonReader reader) throws IOException {
* //implement deserialization logic, eg: return new MyCustomPojo();
* }
* }
* }
* </pre>
*
*/
public interface JsonObject {
/**
* Serialize object instance into JsonWriter.
* In DslJson minimal serialization stands for serialization which omits unnecessary information from JSON.
* An example of such data is false for boolean or null for Integer which can be reconstructed from type definition.
*
* @param writer write JSON into target writer
* @param minimal is minimal serialization requested
*/
void serialize(JsonWriter writer, boolean minimal);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,909 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.*;
/**
* DslJson writes JSON into JsonWriter which has two primary modes of operation:
*
* * targeting specific output stream
* * buffering the entire response in memory
*
* In both cases JsonWriter writes into an byte[] buffer.
* If stream is used as target, it will copy buffer into the stream whenever there is no more room in buffer for new data.
* If stream is not used as target, it will grow the buffer to hold the encoded result.
* To use stream as target reset(OutputStream) must be called before processing.
* This class provides low level methods for JSON serialization.
* <p>
* After the processing is done,
* in case then stream was used as target, flush() must be called to copy the remaining of the buffer into stream.
* When entire response was buffered in memory, buffer can be copied to stream or resulting byte[] can be used directly.
* <p>
* For maximum performance JsonWriter instances should be reused (to avoid allocation of new byte[] buffer instances).
* They should not be shared across threads (concurrently) so for Thread reuse it's best to use patterns such as ThreadLocal.
*/
@SuppressWarnings({"rawtypes", "unchecked"}) // suppress pre-existing warnings
public final class JsonWriter {
private static final Charset UTF_8 = Charset.forName("UTF-8");
final byte[] ensureCapacity(final int free) {
if (position + free >= buffer.length) {
enlargeOrFlush(position, free);
}
return buffer;
}
void advance(int size) {
position += size;
}
private int position;
private long flushed;
private OutputStream target;
private byte[] buffer;
private final UnknownSerializer unknownSerializer;
private final Grisu3.FastDtoaBuilder doubleBuilder = new Grisu3.FastDtoaBuilder();
/**
* Prefer creating JsonWriter through DslJson#newWriter
* This instance is safe to use when all type information is known and lookups to custom writers is not required.
*/
@Deprecated
public JsonWriter() {
this(512, null);
}
JsonWriter(@Nullable final UnknownSerializer unknownSerializer) {
this(512, unknownSerializer);
}
JsonWriter(final int size, @Nullable final UnknownSerializer unknownSerializer) {
this(new byte[size], unknownSerializer);
}
JsonWriter(final byte[] buffer, @Nullable final UnknownSerializer unknownSerializer) {
this.buffer = buffer;
this.unknownSerializer = unknownSerializer;
}
/**
* Helper for writing JSON object start: {
*/
public static final byte OBJECT_START = '{';
/**
* Helper for writing JSON object end: }
*/
public static final byte OBJECT_END = '}';
/**
* Helper for writing JSON array start: [
*/
public static final byte ARRAY_START = '[';
/**
* Helper for writing JSON array end: ]
*/
public static final byte ARRAY_END = ']';
/**
* Helper for writing comma separator: ,
*/
public static final byte COMMA = ',';
/**
* Helper for writing semicolon: :
*/
public static final byte SEMI = ':';
/**
* Helper for writing JSON quote: "
*/
public static final byte QUOTE = '"';
/**
* Helper for writing JSON escape: \\
*/
public static final byte ESCAPE = '\\';
private void enlargeOrFlush(final int size, final int padding) {
if (target != null) {
try {
target.write(buffer, 0, size);
} catch (IOException ex) {
throw new SerializationException("Unable to write to target stream.", ex);
}
position = 0;
flushed += size;
if (padding > buffer.length) {
buffer = Arrays.copyOf(buffer, buffer.length + buffer.length / 2 + padding);
}
} else {
buffer = Arrays.copyOf(buffer, buffer.length + buffer.length / 2 + padding);
}
}
/**
* Optimized method for writing 'null' into the JSON.
*/
public final void writeNull() {
if ((position + 4) >= buffer.length) {
enlargeOrFlush(position, 0);
}
final int s = position;
final byte[] _result = buffer;
_result[s] = 'n';
_result[s + 1] = 'u';
_result[s + 2] = 'l';
_result[s + 3] = 'l';
position += 4;
}
/**
* Write a single byte into the JSON.
*
* @param value byte to write into the JSON
*/
public final void writeByte(final byte value) {
if (position == buffer.length) {
enlargeOrFlush(position, 0);
}
buffer[position++] = value;
}
/**
* Write a quoted string into the JSON.
* String will be appropriately escaped according to JSON escaping rules.
*
* @param value string to write
*/
public final void writeString(final String value) {
final int len = value.length();
if (position + (len << 2) + (len << 1) + 2 >= buffer.length) {
enlargeOrFlush(position, (len << 2) + (len << 1) + 2);
}
final byte[] _result = buffer;
_result[position] = QUOTE;
int cur = position + 1;
for (int i = 0; i < len; i++) {
final char c = value.charAt(i);
if (c > 31 && c != '"' && c != '\\' && c < 126) {
_result[cur++] = (byte) c;
} else {
writeQuotedString(value, i, cur, len);
return;
}
}
_result[cur] = QUOTE;
position = cur + 1;
}
/**
* Write a quoted string into the JSON.
* Char sequence will be appropriately escaped according to JSON escaping rules.
*
* @param value char sequence to write
*/
public final void writeString(final CharSequence value) {
final int len = value.length();
if (position + (len << 2) + (len << 1) + 2 >= buffer.length) {
enlargeOrFlush(position, (len << 2) + (len << 1) + 2);
}
final byte[] _result = buffer;
_result[position] = QUOTE;
int cur = position + 1;
for (int i = 0; i < len; i++) {
final char c = value.charAt(i);
if (c > 31 && c != '"' && c != '\\' && c < 126) {
_result[cur++] = (byte) c;
} else {
writeQuotedString(value, i, cur, len);
return;
}
}
_result[cur] = QUOTE;
position = cur + 1;
}
private void writeQuotedString(final CharSequence str, int i, int cur, final int len) {
final byte[] _result = this.buffer;
for (; i < len; i++) {
final char c = str.charAt(i);
if (c == '"') {
_result[cur++] = ESCAPE;
_result[cur++] = QUOTE;
} else if (c == '\\') {
_result[cur++] = ESCAPE;
_result[cur++] = ESCAPE;
} else if (c < 32) {
if (c == 8) {
_result[cur++] = ESCAPE;
_result[cur++] = 'b';
} else if (c == 9) {
_result[cur++] = ESCAPE;
_result[cur++] = 't';
} else if (c == 10) {
_result[cur++] = ESCAPE;
_result[cur++] = 'n';
} else if (c == 12) {
_result[cur++] = ESCAPE;
_result[cur++] = 'f';
} else if (c == 13) {
_result[cur++] = ESCAPE;
_result[cur++] = 'r';
} else {
_result[cur] = ESCAPE;
_result[cur + 1] = 'u';
_result[cur + 2] = '0';
_result[cur + 3] = '0';
switch (c) {
case 0:
_result[cur + 4] = '0';
_result[cur + 5] = '0';
break;
case 1:
_result[cur + 4] = '0';
_result[cur + 5] = '1';
break;
case 2:
_result[cur + 4] = '0';
_result[cur + 5] = '2';
break;
case 3:
_result[cur + 4] = '0';
_result[cur + 5] = '3';
break;
case 4:
_result[cur + 4] = '0';
_result[cur + 5] = '4';
break;
case 5:
_result[cur + 4] = '0';
_result[cur + 5] = '5';
break;
case 6:
_result[cur + 4] = '0';
_result[cur + 5] = '6';
break;
case 7:
_result[cur + 4] = '0';
_result[cur + 5] = '7';
break;
case 11:
_result[cur + 4] = '0';
_result[cur + 5] = 'B';
break;
case 14:
_result[cur + 4] = '0';
_result[cur + 5] = 'E';
break;
case 15:
_result[cur + 4] = '0';
_result[cur + 5] = 'F';
break;
case 16:
_result[cur + 4] = '1';
_result[cur + 5] = '0';
break;
case 17:
_result[cur + 4] = '1';
_result[cur + 5] = '1';
break;
case 18:
_result[cur + 4] = '1';
_result[cur + 5] = '2';
break;
case 19:
_result[cur + 4] = '1';
_result[cur + 5] = '3';
break;
case 20:
_result[cur + 4] = '1';
_result[cur + 5] = '4';
break;
case 21:
_result[cur + 4] = '1';
_result[cur + 5] = '5';
break;
case 22:
_result[cur + 4] = '1';
_result[cur + 5] = '6';
break;
case 23:
_result[cur + 4] = '1';
_result[cur + 5] = '7';
break;
case 24:
_result[cur + 4] = '1';
_result[cur + 5] = '8';
break;
case 25:
_result[cur + 4] = '1';
_result[cur + 5] = '9';
break;
case 26:
_result[cur + 4] = '1';
_result[cur + 5] = 'A';
break;
case 27:
_result[cur + 4] = '1';
_result[cur + 5] = 'B';
break;
case 28:
_result[cur + 4] = '1';
_result[cur + 5] = 'C';
break;
case 29:
_result[cur + 4] = '1';
_result[cur + 5] = 'D';
break;
case 30:
_result[cur + 4] = '1';
_result[cur + 5] = 'E';
break;
default:
_result[cur + 4] = '1';
_result[cur + 5] = 'F';
break;
}
cur += 6;
}
} else if (c < 0x007F) {
_result[cur++] = (byte) c;
} else {
final int cp = Character.codePointAt(str, i);
if (Character.isSupplementaryCodePoint(cp)) {
i++;
}
if (cp == 0x007F) {
_result[cur++] = (byte) cp;
} else if (cp <= 0x7FF) {
_result[cur++] = (byte) (0xC0 | ((cp >> 6) & 0x1F));
_result[cur++] = (byte) (0x80 | (cp & 0x3F));
} else if ((cp < 0xD800) || (cp > 0xDFFF && cp <= 0xFFFF)) {
_result[cur++] = (byte) (0xE0 | ((cp >> 12) & 0x0F));
_result[cur++] = (byte) (0x80 | ((cp >> 6) & 0x3F));
_result[cur++] = (byte) (0x80 | (cp & 0x3F));
} else if (cp >= 0x10000 && cp <= 0x10FFFF) {
_result[cur++] = (byte) (0xF0 | ((cp >> 18) & 0x07));
_result[cur++] = (byte) (0x80 | ((cp >> 12) & 0x3F));
_result[cur++] = (byte) (0x80 | ((cp >> 6) & 0x3F));
_result[cur++] = (byte) (0x80 | (cp & 0x3F));
} else {
throw new SerializationException("Unknown unicode codepoint in string! " + Integer.toHexString(cp));
}
}
}
_result[cur] = QUOTE;
position = cur + 1;
}
/**
* Write string consisting of only ascii characters.
* String will not be escaped according to JSON escaping rules.
*
* @param value ascii string
*/
@SuppressWarnings("deprecation")
public final void writeAscii(final String value) {
final int len = value.length();
if (position + len >= buffer.length) {
enlargeOrFlush(position, len);
}
value.getBytes(0, len, buffer, position);
position += len;
}
/**
* Write part of string consisting of only ascii characters.
* String will not be escaped according to JSON escaping rules.
*
* @param value ascii string
* @param len part of the provided string to use
*/
@SuppressWarnings("deprecation")
public final void writeAscii(final String value, final int len) {
if (position + len >= buffer.length) {
enlargeOrFlush(position, len);
}
value.getBytes(0, len, buffer, position);
position += len;
}
/**
* Copy bytes into JSON as is.
* Provided buffer can't be null.
*
* @param buf byte buffer to copy
*/
public final void writeAscii(final byte[] buf) {
final int len = buf.length;
if (position + len >= buffer.length) {
enlargeOrFlush(position, len);
}
final int p = position;
final byte[] _result = buffer;
for (int i = 0; i < buf.length; i++) {
_result[p + i] = buf[i];
}
position += len;
}
/**
* Copy part of byte buffer into JSON as is.
* Provided buffer can't be null.
*
* @param buf byte buffer to copy
* @param len part of buffer to copy
*/
public final void writeAscii(final byte[] buf, final int len) {
if (position + len >= buffer.length) {
enlargeOrFlush(position, len);
}
final int p = position;
final byte[] _result = buffer;
for (int i = 0; i < len; i++) {
_result[p + i] = buf[i];
}
position += len;
}
/**
* Copy part of byte buffer into JSON as is.
* Provided buffer can't be null.
*
* @param buf byte buffer to copy
* @param offset in buffer to start from
* @param len part of buffer to copy
*/
public final void writeRaw(final byte[] buf, final int offset, final int len) {
if (position + len >= buffer.length) {
enlargeOrFlush(position, len);
}
System.arraycopy(buf, offset, buffer, position, len);
position += len;
}
/**
* Encode bytes as Base 64.
* Provided value can't be null.
*
* @param value bytes to encode
*/
public final void writeBinary(final byte[] value) {
if (position + (value.length << 1) + 2 >= buffer.length) {
enlargeOrFlush(position, (value.length << 1) + 2);
}
buffer[position++] = '"';
position += Base64.encodeToBytes(value, buffer, position);
buffer[position++] = '"';
}
final void writeDouble(final double value) {
if (value == Double.POSITIVE_INFINITY) {
writeAscii("\"Infinity\"");
} else if (value == Double.NEGATIVE_INFINITY) {
writeAscii("\"-Infinity\"");
} else if (value != value) {
writeAscii("\"NaN\"");
} else if (value == 0.0) {
writeAscii("0.0");
} else {
if (Grisu3.tryConvert(value, doubleBuilder)) {
if (position + 24 >= buffer.length) {
enlargeOrFlush(position, 24);
}
final int len = doubleBuilder.copyTo(buffer, position);
position += len;
} else {
writeAscii(Double.toString(value));
}
}
}
@Override
public String toString() {
return new String(buffer, 0, position, UTF_8);
}
/**
* Content of buffer can be copied to another array of appropriate size.
* This method can't be used when targeting output stream.
* Ideally it should be avoided if possible, since it will create an array copy.
* It's better to use getByteBuffer and size instead.
*
* @return copy of the buffer up to the current position
*/
public final byte[] toByteArray() {
if (target != null) {
throw new ConfigurationException("Method is not available when targeting stream");
}
return Arrays.copyOf(buffer, position);
}
/**
* When JsonWriter does not target stream, this method should be used to copy content of the buffer into target stream.
* It will also reset the buffer position to 0 so writer can be continued to be used even without a call to reset().
*
* @param stream target stream
* @throws IOException propagates from stream.write
*/
public final void toStream(final OutputStream stream) throws IOException {
if (target != null) {
throw new ConfigurationException("Method should not be used when targeting streams. Instead use flush() to copy what's remaining in the buffer");
}
stream.write(buffer, 0, position);
flushed += position;
position = 0;
}
/**
* Current buffer.
* If buffer grows, a new instance will be created and old one will not be used anymore.
*
* @return current buffer
*/
public final byte[] getByteBuffer() {
return buffer;
}
/**
* Current position in the buffer. When stream is not used, this is also equivalent
* to the size of the resulting JSON in bytes
*
* @return position in the populated buffer
*/
public final int size() {
return position;
}
/**
* Total bytes currently flushed to stream
*
* @return bytes flushed
*/
public final long flushed() {
return flushed;
}
/**
* Resets the writer - same as calling reset(OutputStream = null)
*/
public final void reset() {
reset(null);
}
/**
* Resets the writer - specifies the target stream and sets the position in buffer to 0.
* If stream is set to null, JsonWriter will work in growing byte[] buffer mode (entire response will be buffered in memory).
*
* @param stream sets/clears the target stream
*/
public final void reset(@Nullable OutputStream stream) {
position = 0;
target = stream;
flushed = 0;
}
/**
* If stream was used, copies the buffer to stream and resets the position in buffer to 0.
* It will not reset the stream as target,
* meaning new usages of the JsonWriter will try to use the already provided stream.
* It will not do anything if stream was not used
* <p>
* To reset the stream to null use reset() or reset(OutputStream) methods.
*/
public final void flush() {
if (target != null && position != 0) {
try {
target.write(buffer, 0, position);
} catch (IOException ex) {
throw new SerializationException("Unable to write to target stream.", ex);
}
flushed += position;
position = 0;
}
}
/**
* This is deprecated method which exists only for backward compatibility
*
* @throws java.io.IOException unable to write to target stream
*/
@Deprecated
public void close() throws IOException {
if (target != null && position != 0) {
target.write(buffer, 0, position);
position = 0;
flushed = 0;
}
}
/**
* Custom objects can be serialized based on the implementation specified through this interface.
* Annotation processor creates custom deserializers at compile time and registers them into DslJson.
*
* @param <T> type
*/
public interface WriteObject<T> {
void write(JsonWriter writer, @Nullable T value);
}
/**
* Convenience method for serializing array of JsonObject's.
* Array can't be null nor can't contain null values (it will result in NullPointerException).
*
* @param array input objects
* @param <T> type of objects
*/
public <T extends JsonObject> void serialize(final T[] array) {
writeByte(ARRAY_START);
if (array.length != 0) {
array[0].serialize(this, false);
for (int i = 1; i < array.length; i++) {
writeByte(COMMA);
array[i].serialize(this, false);
}
}
writeByte(ARRAY_END);
}
/**
* Convenience method for serializing only part of JsonObject's array.
* Useful when array is reused and only part of it needs to be serialized.
* Array can't be null nor can't contain null values (it will result in NullPointerException).
*
* @param array input objects
* @param len size of array which should be serialized
* @param <T> type of objects
*/
public <T extends JsonObject> void serialize(final T[] array, final int len) {
writeByte(ARRAY_START);
if (array.length != 0 && len != 0) {
array[0].serialize(this, false);
for (int i = 1; i < len; i++) {
writeByte(COMMA);
array[i].serialize(this, false);
}
}
writeByte(ARRAY_END);
}
/**
* Convenience method for serializing list of JsonObject's.
* List can't be null nor can't contain null values (it will result in NullPointerException).
* It will use list .get(index) method to access the object.
* When using .get(index) is not appropriate,
* it's better to call the serialize(Collection&lt;JsonObject&gt;) method instead.
*
* @param list input objects
* @param <T> type of objects
*/
public <T extends JsonObject> void serialize(final List<T> list) {
writeByte(ARRAY_START);
if (list.size() != 0) {
list.get(0).serialize(this, false);
for (int i = 1; i < list.size(); i++) {
writeByte(COMMA);
list.get(i).serialize(this, false);
}
}
writeByte(ARRAY_END);
}
/**
* Convenience method for serializing array through instance serializer (WriteObject).
* Array can be null and can contain null values.
* Instance serializer will not be invoked for null values
*
* @param array array to serialize
* @param encoder instance serializer
* @param <T> type of object
*/
public <T> void serialize(@Nullable final T[] array, final WriteObject<T> encoder) {
if (array == null) {
writeNull();
return;
}
writeByte(ARRAY_START);
if (array.length != 0) {
T item = array[0];
if (item != null) {
encoder.write(this, item);
} else {
writeNull();
}
for (int i = 1; i < array.length; i++) {
writeByte(COMMA);
item = array[i];
if (item != null) {
encoder.write(this, item);
} else {
writeNull();
}
}
}
writeByte(ARRAY_END);
}
/**
* Convenience method for serializing list through instance serializer (WriteObject).
* List can be null and can contain null values.
* Instance serializer will not be invoked for null values
* It will use list .get(index) method to access the object.
* When using .get(index) is not appropriate,
* it's better to call the serialize(Collection&lt;JsonObject&gt;, WriteObject) method instead.
*
* @param list list to serialize
* @param encoder instance serializer
* @param <T> type of object
*/
public <T> void serialize(@Nullable final List<T> list, final WriteObject<T> encoder) {
if (list == null) {
writeNull();
return;
}
writeByte(ARRAY_START);
if (!list.isEmpty()) {
if (list instanceof RandomAccess) {
T item = list.get(0);
if (item != null) {
encoder.write(this, item);
} else {
writeNull();
}
for (int i = 1; i < list.size(); i++) {
writeByte(COMMA);
item = list.get(i);
if (item != null) {
encoder.write(this, item);
} else {
writeNull();
}
}
} else {
Iterator<T> iter = list.iterator();
T item = iter.next();
if (item != null) {
encoder.write(this, item);
} else {
writeNull();
}
while (iter.hasNext()) {
writeByte(COMMA);
item = iter.next();
if (item != null) {
encoder.write(this, item);
} else {
writeNull();
}
}
}
}
writeByte(ARRAY_END);
}
public void serializeRaw(@Nullable final List list, final WriteObject encoder) {
serialize(list, encoder);
}
/**
* Convenience method for serializing collection through instance serializer (WriteObject).
* Collection can be null and can contain null values.
* Instance serializer will not be invoked for null values
*
* @param collection collection to serialize
* @param encoder instance serializer
* @param <T> type of object
*/
public <T> void serialize(@Nullable final Collection<T> collection, final WriteObject<T> encoder) {
if (collection == null) {
writeNull();
return;
}
writeByte(ARRAY_START);
if (!collection.isEmpty()) {
final Iterator<T> it = collection.iterator();
T item = it.next();
if (item != null) {
encoder.write(this, item);
} else {
writeNull();
}
while (it.hasNext()) {
writeByte(COMMA);
item = it.next();
if (item != null) {
encoder.write(this, item);
} else {
writeNull();
}
}
}
writeByte(ARRAY_END);
}
public void serializeRaw(@Nullable final Collection collection, final WriteObject encoder) {
serialize(collection, encoder);
}
public <K, V> void serialize(@Nullable final Map<K, V> map, final WriteObject<K> keyEncoder, final WriteObject<V> valueEncoder) {
if (map == null) {
writeNull();
return;
}
writeByte(OBJECT_START);
final int size = map.size();
if (size > 0) {
final Iterator<Map.Entry<K, V>> iterator = map.entrySet().iterator();
Map.Entry<K, V> kv = iterator.next();
writeQuoted(keyEncoder, kv.getKey());
writeByte(SEMI);
valueEncoder.write(this, kv.getValue());
for (int i = 1; i < size; i++) {
writeByte(COMMA);
kv = iterator.next();
writeQuoted(keyEncoder, kv.getKey());
writeByte(SEMI);
valueEncoder.write(this, kv.getValue());
}
}
writeByte(OBJECT_END);
}
public void serializeRaw(@Nullable final Map map, final WriteObject keyEncoder, final WriteObject valueEncoder) {
serialize(map, keyEncoder, valueEncoder);
}
public <T> void writeQuoted(final JsonWriter.WriteObject<T> keyWriter, final T key) {
if (key instanceof Double) {
final double value = (Double) key;
if (Double.isNaN(value)) writeAscii("\"NaN\"");
else if (value == Double.POSITIVE_INFINITY) writeAscii("\"Infinity\"");
else if (value == Double.NEGATIVE_INFINITY) writeAscii("\"-Infinity\"");
else {
writeByte(QUOTE);
NumberConverter.serialize(value, this);
writeByte(QUOTE);
}
} else if (key instanceof Float) {
final float value = (Float) key;
if (Float.isNaN(value)) writeAscii("\"NaN\"");
else if (value == Float.POSITIVE_INFINITY) writeAscii("\"Infinity\"");
else if (value == Float.NEGATIVE_INFINITY) writeAscii("\"-Infinity\"");
else {
writeByte(QUOTE);
NumberConverter.serialize(value, this);
writeByte(QUOTE);
}
} else if (key instanceof Number) {
writeByte(QUOTE);
keyWriter.write(this, key);
writeByte(QUOTE);
} else {
keyWriter.write(this, key);
}
}
/**
* Generic object serializer which is used for "unknown schema" objects.
* It will throw SerializationException in case if it doesn't know how to serialize provided instance.
* Will delegate the serialization to UnknownSerializer, which in most cases is the DslJson instance from which the writer was created.
* This enables it to use DslJson configuration and serialize using custom serializers (when they are provided).
*
* @param value instance to serialize
*/
public void serializeObject(@Nullable final Object value) {
if (value == null) {
writeNull();
} else if (unknownSerializer != null) {
try {
unknownSerializer.serialize(this, value);
} catch (IOException ex) { //serializing unknown stuff can fail in various ways ;(
throw new SerializationException(ex);
}
} else {
throw new ConfigurationException("Unable to serialize: " + value.getClass() + ".\n" +
"Check that JsonWriter was created through DslJson#newWriter.");
}
}
}

View File

@ -0,0 +1,88 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.*;
@SuppressWarnings({"rawtypes", "unchecked"}) // suppress pre-existing warnings
public abstract class MapConverter {
private static final JsonReader.ReadObject<Map<String, String>> TypedMapReader = new JsonReader.ReadObject<Map<String, String>>() {
@Nullable
@Override
public Map<String, String> read(JsonReader reader) throws IOException {
return reader.wasNull() ? null : deserialize(reader);
}
};
public static void serializeNullable(@Nullable final Map<String, String> value, final JsonWriter sw) {
if (value == null) {
sw.writeNull();
} else {
serialize(value, sw);
}
}
public static void serialize(final Map<String, String> value, final JsonWriter sw) {
sw.writeByte(JsonWriter.OBJECT_START);
final int size = value.size();
if (size > 0) {
final Iterator<Map.Entry<String, String>> iterator = value.entrySet().iterator();
Map.Entry<String, String> kv = iterator.next();
StringConverter.serializeShort(kv.getKey(), sw);
sw.writeByte(JsonWriter.SEMI);
StringConverter.serializeNullable(kv.getValue(), sw);
for (int i = 1; i < size; i++) {
sw.writeByte(JsonWriter.COMMA);
kv = iterator.next();
StringConverter.serializeShort(kv.getKey(), sw);
sw.writeByte(JsonWriter.SEMI);
StringConverter.serializeNullable(kv.getValue(), sw);
}
}
sw.writeByte(JsonWriter.OBJECT_END);
}
public static Map<String, String> deserialize(final JsonReader reader) throws IOException {
if (reader.last() != '{') throw reader.newParseError("Expecting '{' for map start");
byte nextToken = reader.getNextToken();
if (nextToken == '}') return new LinkedHashMap<String, String>(0);
final LinkedHashMap<String, String> res = new LinkedHashMap<String, String>();
String key = StringConverter.deserialize(reader);
nextToken = reader.getNextToken();
if (nextToken != ':') throw reader.newParseError("Expecting ':' after attribute name");
reader.getNextToken();
String value = StringConverter.deserializeNullable(reader);
res.put(key, value);
while ((nextToken = reader.getNextToken()) == ',') {
reader.getNextToken();
key = StringConverter.deserialize(reader);
nextToken = reader.getNextToken();
if (nextToken != ':') throw reader.newParseError("Expecting ':' after attribute name");
reader.getNextToken();
value = StringConverter.deserializeNullable(reader);
res.put(key, value);
}
if (nextToken != '}') throw reader.newParseError("Expecting '}' for map end");
return res;
}
@SuppressWarnings("unchecked")
public static ArrayList<Map<String, String>> deserializeCollection(final JsonReader reader) throws IOException {
return reader.deserializeCollection(TypedMapReader);
}
public static void deserializeCollection(final JsonReader reader, final Collection<Map<String, String>> res) throws IOException {
reader.deserializeCollection(TypedMapReader, res);
}
@SuppressWarnings("unchecked")
public static ArrayList<Map<String, String>> deserializeNullableCollection(final JsonReader reader) throws IOException {
return reader.deserializeNullableCollection(TypedMapReader);
}
public static void deserializeNullableCollection(final JsonReader reader, final Collection<Map<String, String>> res) throws IOException {
reader.deserializeNullableCollection(TypedMapReader, res);
}
}

View File

@ -0,0 +1,110 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
@SuppressWarnings({"rawtypes", "unchecked"}) // suppress pre-existing warnings
public abstract class NetConverter {
static final JsonReader.ReadObject<URI> UriReader = new JsonReader.ReadObject<URI>() {
@Nullable
@Override
public URI read(JsonReader reader) throws IOException {
return reader.wasNull() ? null : deserializeUri(reader);
}
};
static final JsonWriter.WriteObject<URI> UriWriter = new JsonWriter.WriteObject<URI>() {
@Override
public void write(JsonWriter writer, @Nullable URI value) {
serializeNullable(value, writer);
}
};
static final JsonReader.ReadObject<InetAddress> AddressReader = new JsonReader.ReadObject<InetAddress>() {
@Nullable
@Override
public InetAddress read(JsonReader reader) throws IOException {
return reader.wasNull() ? null : deserializeIp(reader);
}
};
static final JsonWriter.WriteObject<InetAddress> AddressWriter = new JsonWriter.WriteObject<InetAddress>() {
@Override
public void write(JsonWriter writer, @Nullable InetAddress value) {
serializeNullable(value, writer);
}
};
public static void serializeNullable(@Nullable final URI value, final JsonWriter sw) {
if (value == null) {
sw.writeNull();
} else {
serialize(value, sw);
}
}
public static void serialize(final URI value, final JsonWriter sw) {
StringConverter.serializeShort(value.toString(), sw);
}
public static URI deserializeUri(final JsonReader reader) throws IOException {
return URI.create(reader.readString());
}
@SuppressWarnings("unchecked")
public static ArrayList<URI> deserializeUriCollection(final JsonReader reader) throws IOException {
return reader.deserializeCollection(UriReader);
}
public static void deserializeUriCollection(final JsonReader reader, final Collection<URI> res) throws IOException {
reader.deserializeCollection(UriReader, res);
}
@SuppressWarnings("unchecked")
public static ArrayList<URI> deserializeUriNullableCollection(final JsonReader reader) throws IOException {
return reader.deserializeNullableCollection(UriReader);
}
public static void deserializeUriNullableCollection(final JsonReader reader, final Collection<URI> res) throws IOException {
reader.deserializeNullableCollection(UriReader, res);
}
public static void serializeNullable(@Nullable final InetAddress value, final JsonWriter sw) {
if (value == null) {
sw.writeNull();
} else {
serialize(value, sw);
}
}
public static void serialize(final InetAddress value, final JsonWriter sw) {
sw.writeByte(JsonWriter.QUOTE);
sw.writeAscii(value.getHostAddress());
sw.writeByte(JsonWriter.QUOTE);
}
public static InetAddress deserializeIp(final JsonReader reader) throws IOException {
return InetAddress.getByName(reader.readSimpleString());
}
@SuppressWarnings("unchecked")
public static ArrayList<InetAddress> deserializeIpCollection(final JsonReader reader) throws IOException {
return reader.deserializeCollection(AddressReader);
}
public static void deserializeIpCollection(final JsonReader reader, final Collection<InetAddress> res) throws IOException {
reader.deserializeCollection(AddressReader, res);
}
@SuppressWarnings("unchecked")
public static ArrayList<InetAddress> deserializeIpNullableCollection(final JsonReader reader) throws IOException {
return reader.deserializeNullableCollection(AddressReader);
}
public static void deserializeIpNullableCollection(final JsonReader reader, final Collection<InetAddress> res) throws IOException {
reader.deserializeNullableCollection(AddressReader, res);
}
}

View File

@ -0,0 +1,135 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.*;
@SuppressWarnings({"rawtypes", "unchecked"}) // suppress pre-existing warnings
public abstract class ObjectConverter {
private static final JsonReader.ReadObject<Map<String, Object>> TypedMapReader = new JsonReader.ReadObject<Map<String, Object>>() {
@Nullable
@Override
public Map<String, Object> read(JsonReader reader) throws IOException {
return reader.wasNull() ? null : deserializeMap(reader);
}
};
@SuppressWarnings("rawtypes")
static final JsonReader.ReadObject<LinkedHashMap> MapReader = new JsonReader.ReadObject<LinkedHashMap>() {
@Nullable
@Override
public LinkedHashMap read(JsonReader reader) throws IOException {
return reader.wasNull() ? null : deserializeMap(reader);
}
};
public static void serializeNullableMap(@Nullable final Map<String, Object> value, final JsonWriter sw) {
if (value == null) {
sw.writeNull();
} else {
serializeMap(value, sw);
}
}
public static void serializeMap(final Map<String, Object> value, final JsonWriter sw) {
sw.writeByte(JsonWriter.OBJECT_START);
final int size = value.size();
if (size > 0) {
final Iterator<Map.Entry<String, Object>> iterator = value.entrySet().iterator();
Map.Entry<String, Object> kv = iterator.next();
sw.writeString(kv.getKey());
sw.writeByte(JsonWriter.SEMI);
sw.serializeObject(kv.getValue());
for (int i = 1; i < size; i++) {
sw.writeByte(JsonWriter.COMMA);
kv = iterator.next();
sw.writeString(kv.getKey());
sw.writeByte(JsonWriter.SEMI);
sw.serializeObject(kv.getValue());
}
}
sw.writeByte(JsonWriter.OBJECT_END);
}
public static void serializeObject(@Nullable final Object value, final JsonWriter sw) throws IOException {
sw.serializeObject(value);
}
@Nullable
public static Object deserializeObject(final JsonReader reader) throws IOException {
switch (reader.last()) {
case 'n':
if (!reader.wasNull()) {
throw reader.newParseErrorAt("Expecting 'null' for null constant", 0);
}
return null;
case 't':
if (!reader.wasTrue()) {
throw reader.newParseErrorAt("Expecting 'true' for true constant", 0);
}
return true;
case 'f':
if (!reader.wasFalse()) {
throw reader.newParseErrorAt("Expecting 'false' for false constant", 0);
}
return false;
case '"':
return reader.readString();
case '{':
return deserializeMap(reader);
case '[':
return deserializeList(reader);
default:
return NumberConverter.deserializeNumber(reader);
}
}
public static ArrayList<Object> deserializeList(final JsonReader reader) throws IOException {
if (reader.last() != '[') throw reader.newParseError("Expecting '[' for list start");
byte nextToken = reader.getNextToken();
if (nextToken == ']') return new ArrayList<Object>(0);
final ArrayList<Object> res = new ArrayList<Object>(4);
res.add(deserializeObject(reader));
while ((nextToken = reader.getNextToken()) == ',') {
reader.getNextToken();
res.add(deserializeObject(reader));
}
if (nextToken != ']') throw reader.newParseError("Expecting ']' for list end");
return res;
}
public static LinkedHashMap<String, Object> deserializeMap(final JsonReader reader) throws IOException {
if (reader.last() != '{') throw reader.newParseError("Expecting '{' for map start");
byte nextToken = reader.getNextToken();
if (nextToken == '}') return new LinkedHashMap<String, Object>(0);
final LinkedHashMap<String, Object> res = new LinkedHashMap<String, Object>();
String key = reader.readKey();
res.put(key, deserializeObject(reader));
while ((nextToken = reader.getNextToken()) == ',') {
reader.getNextToken();
key = reader.readKey();
res.put(key, deserializeObject(reader));
}
if (nextToken != '}') throw reader.newParseError("Expecting '}' for map end");
return res;
}
@SuppressWarnings("unchecked")
public static ArrayList<Map<String, Object>> deserializeMapCollection(final JsonReader reader) throws IOException {
return reader.deserializeCollection(TypedMapReader);
}
public static void deserializeMapCollection(final JsonReader reader, final Collection<Map<String, Object>> res) throws IOException {
reader.deserializeCollection(TypedMapReader, res);
}
@SuppressWarnings("unchecked")
public static ArrayList<Map<String, Object>> deserializeNullableMapCollection(final JsonReader reader) throws IOException {
return reader.deserializeNullableCollection(TypedMapReader);
}
public static void deserializeNullableMapCollection(final JsonReader reader, final Collection<Map<String, Object>> res) throws IOException {
reader.deserializeNullableCollection(TypedMapReader, res);
}
}

View File

@ -0,0 +1,44 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import java.io.IOException;
@SuppressWarnings("serial") // suppress pre-existing warnings
public class ParsingException extends IOException {
private ParsingException(String reason) {
super(reason);
}
private ParsingException(String reason, Throwable cause) {
super(reason, cause);
}
public static ParsingException create(String reason, boolean withStackTrace) {
return withStackTrace
? new ParsingException(reason)
: new ParsingStacklessException(reason);
}
public static ParsingException create(String reason, Throwable cause, boolean withStackTrace) {
return withStackTrace
? new ParsingException(reason, cause)
: new ParsingStacklessException(reason, cause);
}
private static class ParsingStacklessException extends ParsingException {
private ParsingStacklessException(String reason) {
super(reason);
}
private ParsingStacklessException(String reason, Throwable cause) {
super(reason, cause);
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
}

View File

@ -0,0 +1,18 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
@SuppressWarnings("serial") // suppress pre-existing warnings
public class SerializationException extends RuntimeException {
public SerializationException(@Nullable String reason) {
super(reason);
}
public SerializationException(@Nullable Throwable cause) {
super(cause);
}
public SerializationException(@Nullable String reason, @Nullable Throwable cause) {
super(reason, cause);
}
}

View File

@ -0,0 +1,5 @@
package com.bugsnag.android.repackaged.dslplatform.json;
public interface StringCache {
String get(char[] chars, int len);
}

View File

@ -0,0 +1,119 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@SuppressWarnings({"rawtypes", "unchecked"}) // suppress pre-existing warnings
public abstract class StringConverter {
public static final JsonReader.ReadObject<String> READER = new JsonReader.ReadObject<String>() {
@Nullable
@Override
public String read(JsonReader reader) throws IOException {
if (reader.wasNull()) return null;
return reader.readString();
}
};
public static final JsonWriter.WriteObject<String> WRITER = new JsonWriter.WriteObject<String>() {
@Override
public void write(JsonWriter writer, @Nullable String value) {
serializeNullable(value, writer);
}
};
public static final JsonWriter.WriteObject<CharSequence> WRITER_CHARS = new JsonWriter.WriteObject<CharSequence>() {
@Override
public void write(JsonWriter writer, @Nullable CharSequence value) {
if (value == null) writer.writeNull();
else writer.writeString(value);
}
};
public static final JsonReader.ReadObject<StringBuilder> READER_BUILDER = new JsonReader.ReadObject<StringBuilder>() {
@Nullable
@Override
public StringBuilder read(JsonReader reader) throws IOException {
if (reader.wasNull()) return null;
StringBuilder builder = new StringBuilder();
return reader.appendString(builder);
}
};
public static final JsonReader.ReadObject<StringBuffer> READER_BUFFER = new JsonReader.ReadObject<StringBuffer>() {
@Nullable
@Override
public StringBuffer read(JsonReader reader) throws IOException {
if (reader.wasNull()) return null;
StringBuffer builder = new StringBuffer();
return reader.appendString(builder);
}
};
public static void serializeShortNullable(@Nullable final String value, final JsonWriter sw) {
if (value == null) {
sw.writeNull();
} else {
sw.writeString(value);
}
}
public static void serializeShort(final String value, final JsonWriter sw) {
sw.writeString(value);
}
public static void serializeNullable(@Nullable final String value, final JsonWriter sw) {
if (value == null) {
sw.writeNull();
} else {
sw.writeString(value);
}
}
public static void serialize(final String value, final JsonWriter sw) {
sw.writeString(value);
}
public static String deserialize(final JsonReader reader) throws IOException {
return reader.readString();
}
@Nullable
public static String deserializeNullable(final JsonReader reader) throws IOException {
if (reader.last() == 'n') {
if (!reader.wasNull()) throw reader.newParseErrorAt("Expecting 'null' for null constant", 0);
return null;
}
return reader.readString();
}
@SuppressWarnings("unchecked")
public static ArrayList<String> deserializeCollection(final JsonReader reader) throws IOException {
return reader.deserializeCollection(READER);
}
public static void deserializeCollection(final JsonReader reader, final Collection<String> res) throws IOException {
reader.deserializeCollection(READER, res);
}
@SuppressWarnings("unchecked")
public static ArrayList<String> deserializeNullableCollection(final JsonReader reader) throws IOException {
return reader.deserializeNullableCollection(READER);
}
public static void deserializeNullableCollection(final JsonReader reader, final Collection<String> res) throws IOException {
reader.deserializeNullableCollection(READER, res);
}
public static void serialize(final List<String> list, final JsonWriter writer) {
writer.writeByte(JsonWriter.ARRAY_START);
if (list.size() != 0) {
writer.writeString(list.get(0));
for (int i = 1; i < list.size(); i++) {
writer.writeByte(JsonWriter.COMMA);
writer.writeString(list.get(i));
}
}
writer.writeByte(JsonWriter.ARRAY_END);
}
}

View File

@ -0,0 +1,10 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
interface TypeLookup {
@Nullable
<T> JsonReader.ReadObject<T> tryFindReader(Class<T> manifest);
@Nullable
<T> JsonReader.BindObject<T> tryFindBinder(Class<T> manifest);
}

View File

@ -0,0 +1,198 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
@SuppressWarnings({"rawtypes", "unchecked"}) // suppress pre-existing warnings
public abstract class UUIDConverter {
public static final UUID MIN_UUID = new java.util.UUID(0L, 0L);
public static final JsonReader.ReadObject<UUID> READER = new JsonReader.ReadObject<UUID>() {
@Nullable
@Override
public UUID read(JsonReader reader) throws IOException {
return reader.wasNull() ? null : deserialize(reader);
}
};
public static final JsonWriter.WriteObject<UUID> WRITER = new JsonWriter.WriteObject<UUID>() {
@Override
public void write(JsonWriter writer, @Nullable UUID value) {
serializeNullable(value, writer);
}
};
private static final char[] Lookup;
private static final byte[] Values;
static {
Lookup = new char[256];
Values = new byte['f' + 1 - '0'];
for (int i = 0; i < 256; i++) {
int hi = (i >> 4) & 15;
int lo = i & 15;
Lookup[i] = (char) (((hi < 10 ? '0' + hi : 'a' + hi - 10) << 8) + (lo < 10 ? '0' + lo : 'a' + lo - 10));
}
for (char c = '0'; c <= '9'; c++) {
Values[c - '0'] = (byte) (c - '0');
}
for (char c = 'a'; c <= 'f'; c++) {
Values[c - '0'] = (byte) (c - 'a' + 10);
}
for (char c = 'A'; c <= 'F'; c++) {
Values[c - '0'] = (byte) (c - 'A' + 10);
}
}
public static void serializeNullable(@Nullable final UUID value, final JsonWriter sw) {
if (value == null) {
sw.writeNull();
} else {
serialize(value, sw);
}
}
public static void serialize(final UUID value, final JsonWriter sw) {
serialize(value.getMostSignificantBits(), value.getLeastSignificantBits(), sw);
}
public static void serialize(final long hi, final long lo, final JsonWriter sw) {
final int hi1 = (int) (hi >> 32);
final int hi2 = (int) hi;
final int lo1 = (int) (lo >> 32);
final int lo2 = (int) lo;
final byte[] buf = sw.ensureCapacity(38);
final int pos = sw.size();
buf[pos] = '"';
int v = (hi1 >> 24) & 255;
int l = Lookup[v];
buf[pos + 1] = (byte) (l >> 8);
buf[pos + 2] = (byte) l;
v = (hi1 >> 16) & 255;
l = Lookup[v];
buf[pos + 3] = (byte) (l >> 8);
buf[pos + 4] = (byte) l;
v = (hi1 >> 8) & 255;
l = Lookup[v];
buf[pos + 5] = (byte) (l >> 8);
buf[pos + 6] = (byte) l;
v = hi1 & 255;
l = Lookup[v];
buf[pos + 7] = (byte) (l >> 8);
buf[pos + 8] = (byte) l;
buf[pos + 9] = '-';
v = (hi2 >> 24) & 255;
l = Lookup[v];
buf[pos + 10] = (byte) (l >> 8);
buf[pos + 11] = (byte) l;
v = (hi2 >> 16) & 255;
l = Lookup[v];
buf[pos + 12] = (byte) (l >> 8);
buf[pos + 13] = (byte) l;
buf[pos + 14] = '-';
v = (hi2 >> 8) & 255;
l = Lookup[v];
buf[pos + 15] = (byte) (l >> 8);
buf[pos + 16] = (byte) l;
v = hi2 & 255;
l = Lookup[v];
buf[pos + 17] = (byte) (l >> 8);
buf[pos + 18] = (byte) l;
buf[pos + 19] = '-';
v = (lo1 >> 24) & 255;
l = Lookup[v];
buf[pos + 20] = (byte) (l >> 8);
buf[pos + 21] = (byte) l;
v = (lo1 >> 16) & 255;
l = Lookup[v];
buf[pos + 22] = (byte) (l >> 8);
buf[pos + 23] = (byte) l;
buf[pos + 24] = '-';
v = (lo1 >> 8) & 255;
l = Lookup[v];
buf[pos + 25] = (byte) (l >> 8);
buf[pos + 26] = (byte) l;
v = lo1 & 255;
l = Lookup[v];
buf[pos + 27] = (byte) (l >> 8);
buf[pos + 28] = (byte) l;
v = (lo2 >> 24) & 255;
l = Lookup[v];
buf[pos + 29] = (byte) (l >> 8);
buf[pos + 30] = (byte) l;
v = (lo2 >> 16) & 255;
l = Lookup[v];
buf[pos + 31] = (byte) (l >> 8);
buf[pos + 32] = (byte) l;
v = (lo2 >> 8) & 255;
l = Lookup[v];
buf[pos + 33] = (byte) (l >> 8);
buf[pos + 34] = (byte) l;
v = lo2 & 255;
l = Lookup[v];
buf[pos + 35] = (byte) (l >> 8);
buf[pos + 36] = (byte) l;
buf[pos + 37] = '"';
sw.advance(38);
}
public static UUID deserialize(final JsonReader reader) throws IOException {
final char[] buf = reader.readSimpleQuote();
final int len = reader.getCurrentIndex() - reader.getTokenStart();
if (len == 37 && buf[8] == '-' && buf[13] == '-' && buf[18] == '-' && buf[23] == '-') {
try {
long hi = 0;
for (int i = 0; i < 8; i++)
hi = (hi << 4) + Values[buf[i] - '0'];
for (int i = 9; i < 13; i++)
hi = (hi << 4) + Values[buf[i] - '0'];
for (int i = 14; i < 18; i++)
hi = (hi << 4) + Values[buf[i] - '0'];
long lo = 0;
for (int i = 19; i < 23; i++)
lo = (lo << 4) + Values[buf[i] - '0'];
for (int i = 24; i < 36; i++)
lo = (lo << 4) + Values[buf[i] - '0'];
return new UUID(hi, lo);
} catch (ArrayIndexOutOfBoundsException ex) {
return UUID.fromString(new String(buf, 0, 36));
}
} else if (len == 33) {
try {
long hi = 0;
for (int i = 0; i < 16; i++)
hi = (hi << 4) + Values[buf[i] - '0'];
long lo = 0;
for (int i = 16; i < 32; i++)
lo = (lo << 4) + Values[buf[i] - '0'];
return new UUID(hi, lo);
} catch (ArrayIndexOutOfBoundsException ex) {
return UUID.fromString(new String(buf, 0, 32));
}
} else {
return UUID.fromString(new String(buf, 0, len - 1));
}
}
@SuppressWarnings("unchecked")
public static ArrayList<UUID> deserializeCollection(final JsonReader reader) throws IOException {
return reader.deserializeCollection(READER);
}
public static void deserializeCollection(final JsonReader reader, final Collection<UUID> res) throws IOException {
reader.deserializeCollection(READER, res);
}
@SuppressWarnings("unchecked")
public static ArrayList<UUID> deserializeNullableCollection(final JsonReader reader) throws IOException {
return reader.deserializeNullableCollection(READER);
}
public static void deserializeNullableCollection(final JsonReader reader, final Collection<UUID> res) throws IOException {
reader.deserializeNullableCollection(READER, res);
}
}

View File

@ -0,0 +1,9 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
import java.io.IOException;
interface UnknownSerializer {
void serialize(JsonWriter writer, @Nullable Object unknown) throws IOException;
}

View File

@ -0,0 +1,217 @@
package com.bugsnag.android.repackaged.dslplatform.json;
import androidx.annotation.Nullable;
import org.w3c.dom.*;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.*;
@SuppressWarnings({"rawtypes", "unchecked"}) // suppress pre-existing warnings
public abstract class XmlConverter {
static final JsonReader.ReadObject<Element> Reader = new JsonReader.ReadObject<Element>() {
@Nullable
@Override
public Element read(JsonReader reader) throws IOException {
return reader.wasNull() ? null : deserialize(reader);
}
};
static final JsonWriter.WriteObject<Element> Writer = new JsonWriter.WriteObject<Element>() {
@Override
public void write(JsonWriter writer, @Nullable Element value) {
serializeNullable(value, writer);
}
};
private static final DocumentBuilder documentBuilder;
static {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
try {
documentBuilder = dbFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
public static void serializeNullable(@Nullable final Element value, final JsonWriter sw) {
if (value == null)
sw.writeNull();
else
serialize(value, sw);
}
public static void serialize(final Element value, final JsonWriter sw) {
Document document = value.getOwnerDocument();
DOMImplementationLS domImplLS = (DOMImplementationLS) document.getImplementation();
LSSerializer serializer = domImplLS.createLSSerializer();
LSOutput lsOutput = domImplLS.createLSOutput();
lsOutput.setEncoding("UTF-8");
StringWriter writer = new StringWriter();
lsOutput.setCharacterStream(writer);
serializer.write(document, lsOutput);
StringConverter.serialize(writer.toString(), sw);
}
public static Element deserialize(final JsonReader reader) throws IOException {
if (reader.last() == '"') {
try {
InputSource source = new InputSource(new StringReader(reader.readString()));
return documentBuilder.parse(source).getDocumentElement();
} catch (SAXException ex) {
throw reader.newParseErrorAt("Invalid XML value", 0, ex);
}
} else {
final Map<String, Object> map = ObjectConverter.deserializeMap(reader);
return mapToXml(map);
}
}
public static Element mapToXml(final Map<String, Object> map) throws IOException {
final Set<String> xmlRootElementNames = map.keySet();
if (xmlRootElementNames.size() > 1) {
throw ParsingException.create("Invalid XML. Expecting root element", true);
}
final String rootName = xmlRootElementNames.iterator().next();
final Document document = createDocument();
final Element rootElement = document.createElement(rootName);
document.appendChild(rootElement);
buildXmlFromHashMap(document, rootElement, map.get(rootName));
return rootElement;
}
private static synchronized Document createDocument() {
try {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final DocumentBuilder builder = factory.newDocumentBuilder();
return builder.newDocument();
} catch (ParserConfigurationException e) {
throw new ConfigurationException(e);
}
}
private static final String TEXT_NODE_TAG = "#text";
private static final String COMMENT_NODE_TAG = "#comment";
private static final String CDATA_NODE_TAG = "#cdata-section";
@SuppressWarnings("unchecked")
private static void buildXmlFromHashMap(
final Document doc,
final Element subtreeRootElement,
@Nullable final Object elementContent) {
if (elementContent instanceof HashMap) {
final HashMap<String, Object> elementContentMap = (HashMap<String, Object>) elementContent;
for (final Map.Entry<String, Object> childEntry : elementContentMap.entrySet()) {
final String key = childEntry.getKey();
if (key.startsWith("@")) {
subtreeRootElement.setAttribute(key.substring(1), childEntry.getValue().toString());
} else if (key.startsWith("#")) {
if (key.equals(TEXT_NODE_TAG)) {
if (childEntry.getValue() instanceof List) {
buildTextNodeList(doc, subtreeRootElement, (List<String>) childEntry.getValue());
} else {
final Node textNode = doc.createTextNode(childEntry.getValue().toString());
subtreeRootElement.appendChild(textNode);
}
} else if (key.equals(CDATA_NODE_TAG)) {
if (childEntry.getValue() instanceof List) {
buildCDataList(doc, subtreeRootElement, (List<String>) childEntry.getValue());
} else {
final Node cDataNode = doc.createCDATASection(childEntry.getValue().toString());
subtreeRootElement.appendChild(cDataNode);
}
} else if (key.equals(COMMENT_NODE_TAG)) {
if (childEntry.getValue() instanceof List) {
buildCommentList(doc, subtreeRootElement, (List<String>) childEntry.getValue());
} else {
final Node commentNode = doc.createComment(childEntry.getValue().toString());
subtreeRootElement.appendChild(commentNode);
}
} //else if (key.equals(WHITESPACE_NODE_TAG)
// || key.equals(SIGNIFICANT_WHITESPACE_NODE_TAG)) {
// Ignore
//} else {
/*
* All other nodes whose name starts with a '#' are invalid XML
* nodes, and thus ignored:
*/
//}
} else {
final Element newElement = doc.createElement(key);
subtreeRootElement.appendChild(newElement);
buildXmlFromHashMap(doc, newElement, childEntry.getValue());
}
}
} else if (elementContent instanceof List) {
buildXmlFromJsonArray(doc, subtreeRootElement, (List<Object>) elementContent);
} else {
if (elementContent != null) {
subtreeRootElement.setTextContent(elementContent.toString());
}
}
}
private static void buildTextNodeList(final Document doc, final Node subtreeRoot, final List<String> nodeValues) {
final StringBuilder sb = new StringBuilder();
for (final String nodeValue : nodeValues) {
sb.append(nodeValue);
}
subtreeRoot.appendChild(doc.createTextNode(sb.toString()));
}
private static void buildCDataList(final Document doc, final Node subtreeRoot, final List<String> nodeValues) {
for (final String nodeValue : nodeValues) {
subtreeRoot.appendChild(doc.createCDATASection(nodeValue));
}
}
private static void buildCommentList(final Document doc, final Node subtreeRoot, final List<String> nodeValues) {
for (final String nodeValue : nodeValues) {
subtreeRoot.appendChild(doc.createComment(nodeValue));
}
}
private static void buildXmlFromJsonArray(
final Document doc,
final Node listHeadNode,
final List<Object> elementContentList) {
final Node subtreeRootNode = listHeadNode.getParentNode();
/* The head node (already exists) */
buildXmlFromHashMap(doc, (Element) listHeadNode, elementContentList.get(0));
/* The rest of the list */
for (final Object elementContent : elementContentList.subList(1, elementContentList.size())) {
final Element newElement = doc.createElement(listHeadNode.getNodeName());
subtreeRootNode.appendChild(newElement);
buildXmlFromHashMap(doc, newElement, elementContent);
}
}
@SuppressWarnings("unchecked")
public static ArrayList<Element> deserializeCollection(final JsonReader reader) throws IOException {
return reader.deserializeCollection(Reader);
}
public static void deserializeCollection(final JsonReader reader, final Collection<Element> res) throws IOException {
reader.deserializeCollection(Reader, res);
}
@SuppressWarnings("unchecked")
public static ArrayList<Element> deserializeNullableCollection(final JsonReader reader) throws IOException {
return reader.deserializeNullableCollection(Reader);
}
public static void deserializeNullableCollection(final JsonReader reader, final Collection<Element> res) throws IOException {
reader.deserializeNullableCollection(Reader, res);
}
}

View File

@ -1,17 +1,8 @@
From 3270faf44aea11754c940ba43ee6db72b7462f14 Mon Sep 17 00:00:00 2001
From: M66B <M66B@users.noreply.github.com>
Date: Sat, 15 May 2021 22:07:24 +0200
Subject: [PATCH] Bugsnag failure on I/O error
---
app/src/main/java/com/bugsnag/android/DefaultDelivery.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt b/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
index a7995164cb4e..5620f0bacd80 100644
index 0ce2eec8c..e1bac196e 100644
--- a/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
+++ b/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
@@ -64,7 +64,7 @@ internal class DefaultDelivery(
@@ -66,7 +66,7 @@ internal class DefaultDelivery(
return DeliveryStatus.UNDELIVERED
} catch (exception: IOException) {
logger.w("IOException encountered in request", exception)
@ -20,3 +11,30 @@ index a7995164cb4e..5620f0bacd80 100644
} catch (exception: Exception) {
logger.w("Unexpected error delivering payload", exception)
return DeliveryStatus.FAILURE
diff --git a/patches/Bugsnag.patch b/patches/Bugsnag.patch
index 25c19fd4c..e69de29bb 100644
--- a/patches/Bugsnag.patch
+++ b/patches/Bugsnag.patch
@@ -1,22 +0,0 @@
-From 3270faf44aea11754c940ba43ee6db72b7462f14 Mon Sep 17 00:00:00 2001
-From: M66B <M66B@users.noreply.github.com>
-Date: Sat, 15 May 2021 22:07:24 +0200
-Subject: [PATCH] Bugsnag failure on I/O error
-
----
- app/src/main/java/com/bugsnag/android/DefaultDelivery.kt | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt b/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
-index a7995164cb4e..5620f0bacd80 100644
---- a/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
-+++ b/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
-@@ -64,7 +64,7 @@ internal class DefaultDelivery(
- return DeliveryStatus.UNDELIVERED
- } catch (exception: IOException) {
- logger.w("IOException encountered in request", exception)
-- return DeliveryStatus.UNDELIVERED
-+ return DeliveryStatus.FAILURE
- } catch (exception: Exception) {
- logger.w("Unexpected error delivering payload", exception)
- return DeliveryStatus.FAILURE