mirror of https://github.com/M66B/FairEmail.git
parent
f78925fb23
commit
169f117950
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -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 + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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())
|
||||
}
|
@ -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())
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 (< 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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
@ -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)};
|
||||
}
|
||||
}
|
@ -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'};
|
||||
}
|
||||
}
|
@ -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<MyCustomPojo> JSON_READER = new JsonReader.ReadJsonObject<MyCustomPojo>() {
|
||||
* 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
@ -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<JsonObject>) 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<JsonObject>, 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.");
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.bugsnag.android.repackaged.dslplatform.json;
|
||||
|
||||
public interface StringCache {
|
||||
String get(char[] chars, int len);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in new issue