增加离线地图下载流程

This commit is contained in:
squallzhjch 2023-03-30 10:50:20 +08:00
parent 97a48237ba
commit 3a80a4ee5d
129 changed files with 1590 additions and 13847 deletions

View File

@ -1,6 +1,8 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
}
android {
@ -50,4 +52,18 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
//
implementation 'com.github.getActivity:XXPermissions:16.5'
//
//hilt
implementation "com.google.dagger:hilt-android:2.44"
kapt "com.google.dagger:hilt-compiler:2.44"
// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")
}
//
kapt {
correctErrorTypes true
}

View File

@ -27,6 +27,7 @@
<!-- 读取缓存数据 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name=".OMQSApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@ -37,9 +38,11 @@
android:theme="@style/Theme.OMQualityInspection"
tools:targetApi="31">
<activity
android:name=".ui.activity.LoginActivity"
android:name=".ui.activity.login.LoginActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="landscape"
android:theme="@style/Theme.OMQualityInspection">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -48,9 +51,11 @@
</intent-filter>
</activity>
<activity
android:name=".ui.activity.MainActivity"
android:exported="true"
android:theme="@style/Theme.OMQualityInspection"></activity>
android:name=".ui.activity.map.MainActivity"
android:launchMode="singleTask"
android:screenOrientation="landscape"
android:theme="@style/Theme.OMQualityInspection" />
</application>
</manifest>

View File

@ -0,0 +1,19 @@
package com.navinfo.omqs
class Constant {
companion object {
/**
* 服务器地址
*/
const val SERVER_ADDRESS = "http://fastmap.navinfo.com/drdc/"
const val DEBUG = true
const val message_status_late = "预约,待发送"
const val message_status_send_over = "已发送"
const val message_version_right_off = "1" //立即发送
const val MESSAGE_PAGE_SIZE = 30 //消息列表一页最多数量
}
}

View File

@ -0,0 +1,8 @@
package com.navinfo.omqs
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class OMQSApplication : Application() {
}

View File

@ -1,6 +1,6 @@
package com.navinfo.omqs.model
package com.navinfo.omqs.bean
data class LoginUser(
data class LoginUserBean(
var username: String = "",
var password: String = ""
)

View File

@ -0,0 +1,32 @@
package com.navinfo.omqs.bean
data class OfflineMapCityBean(
val id: String,
val fileName: String,
val name: String,
val url: String,
val version: Long,
val fileSize: Long,
var currentSize:Long = 0,
var status:Int = NONE
) {
companion object Status{
const val NONE = 0 //无状态
const val WAITING = 1 //等待中
const val LOADING = 2 //下载中
const val PAUSE = 3 //暂停
const val ERROR = 4 //错误
const val DONE = 5 //完成
const val UPDATE = 6 //有新版本要更新
}
fun getFileSizeText(): String {
return if (fileSize < 1024.0)
"$fileSize B"
else if (fileSize < 1048576.0)
"%.2f K".format(fileSize / 1024.0)
else if (fileSize < 1073741824.0)
"%.2f M".format(fileSize / 1048576.0)
else
"%.2f M".format(fileSize / 1073741824.0)
}
}

View File

@ -0,0 +1,94 @@
package com.navinfo.omqs.hilt
import android.app.Application
import android.util.Log
import com.google.gson.Gson
import com.navinfo.omqs.Constant
import com.navinfo.omqs.OMQSApplication
import com.navinfo.omqs.http.RetrofitNetworkServiceAPI
import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
/**
* 全局单例 注入对象
*/
@Module
@InstallIn(SingletonComponent::class)
class GlobalModule {
@Singleton
@Provides
fun provideApplication(application: Application): OMQSApplication {
return application as OMQSApplication
}
/**
* 注入 网络OKHttp 对象
*/
@Provides
@Singleton
fun provideOkHttpClient(interceptor: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder().addInterceptor(interceptor)
.connectTimeout(60, TimeUnit.SECONDS).writeTimeout(5, TimeUnit.MINUTES)
.readTimeout(5, TimeUnit.MINUTES).build()
}
/**
* 注入 OHHttp log
*/
@Provides
@Singleton
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor {
if (Constant.DEBUG) {
Log.e("jingo", it)
}
}.apply {
level = if (Constant.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
}
}
@Provides
@Singleton
fun provideRetrofit(
client: Lazy<OkHttpClient>,
converterFactory: GsonConverterFactory,
): Retrofit {
val retrofitBuilder = Retrofit.Builder()
.baseUrl(Constant.SERVER_ADDRESS)
.client(client.get())
.addConverterFactory(converterFactory)
return retrofitBuilder.build()
}
@Provides
@Singleton
fun provideGson(): Gson = Gson()
@Provides
@Singleton
fun provideGsonConverterFactory(gson: Gson): GsonConverterFactory {
return GsonConverterFactory.create(gson)
}
@Provides
@Singleton
fun provideNetworkService(retrofit: Retrofit): RetrofitNetworkServiceAPI {
return retrofit.create(RetrofitNetworkServiceAPI::class.java)
}
}

View File

@ -0,0 +1,30 @@
package com.navinfo.omqs.hilt
import android.util.Log
import com.navinfo.collect.library.map.NIMapController
import com.navinfo.omqs.ui.activity.map.MainViewModel
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityRetainedComponent
import dagger.hilt.android.scopes.ActivityRetainedScoped
@InstallIn(ActivityRetainedComponent::class)
@Module
class MainActivityModule {
@ActivityRetainedScoped
@Provides
fun providesMapController(): NIMapController = NIMapController()
/**
* 实验失败这样创建viewmodel不会在activity销毁的时候同时销毁
*/
// @ActivityRetainedScoped
// @Provides
// fun providesMainViewModel(mapController: NIMapController): MainViewModel {
// Log.e("jingo", "MainViewModel 被创建")
// return MainViewModel(mapController)
// }
}

View File

@ -0,0 +1,16 @@
package com.navinfo.omqs.hilt
import com.navinfo.omqs.http.NetworkService
import com.navinfo.omqs.http.NetworkServiceImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@InstallIn(SingletonComponent::class)
@Module
abstract class NetworkServiceModule {
@Binds
abstract fun bindNetworkService(networkServiceImpl: NetworkServiceImpl): NetworkService
}

View File

@ -0,0 +1,53 @@
package com.navinfo.omqs.http
/**
* Created by Mayokun Adeniyi on 23/05/2020.
*/
/**
* 网络返回封装
*/
/**
* 在类名之前使用sealed关键字将类声明为密封类
* 密封类仅在编译时限制类型集来确保类型安全的重要性
* 密封类隐式是一个无法实例化的抽象类
*/
/**
* :密封类所具有的特性和与别的类具有不同之处
Sealed class密封类 是一个有特定数量子类的类看上去和枚举有点类似所不同的是在枚举中我们每个类型只有一个对象实例而在密封类中同一个类可以拥有几个对象
Sealed class密封类的所有子类都必须与密封类在同一文件中
Sealed class密封类的子类的子类可以定义在任何地方并不需要和密封类定义在同一个文件中
Sealed class密封类没有构造函数不可以直接实例化只能实例化内部的子类
*/
/**
* 如何获取密封类里面的函数方法
* 只能创建密封类子类对象 通过密封类的子类对象调用密封类里的函数方法
*/
sealed class NetResult<out R> {
data class Success<out T>(val data: T?) : NetResult<T>()
data class Failure(val code: Int, val msg: String) : NetResult<Nothing>()
data class Error(val exception: Exception) : NetResult<Nothing>()
object Loading : NetResult<Nothing>()
/**
* 密封类通常与表达when时一起使用 由于密封类的子类将自身类型作为一种情况
因此密封类中的when表达式涵盖所有情况从而避免使用else子句
*/
override fun toString(): String {
return when (this) {
is Success<*> -> "网络访问成功返回正确结果Success[data=$data]"
is Failure -> "网络访问成功返回错误结果Failure[$msg]"
is Error -> "网络访问出错 Error[exception=$exception]"
is Loading -> "网络访问中 Loading"
}
}
}
/**
* 密封类里面可以有若干个子类这些子类如果要继承密封类,则必须和密封类在同一个文件里
*/

View File

@ -0,0 +1,13 @@
package com.navinfo.omqs.http
import com.navinfo.omqs.bean.OfflineMapCityBean
/**
* 网络访问 业务接口
*/
interface NetworkService {
/**
* 获取离线地图城市列表
*/
suspend fun getOfflineMapCityList():NetResult<List<OfflineMapCityBean>>
}

View File

@ -0,0 +1,35 @@
package com.navinfo.omqs.http
import com.navinfo.omqs.bean.OfflineMapCityBean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
/**
* 网络访问业务接口的具体实现
*/
class NetworkServiceImpl @Inject constructor(
private val netApi: RetrofitNetworkServiceAPI,
) : NetworkService {
/**
* 获取离线地图城市列表
*/
override suspend fun getOfflineMapCityList(): NetResult<List<OfflineMapCityBean>> =
//在IO线程中运行
withContext(Dispatchers.IO) {
return@withContext try {
val result = netApi.retrofitGetOfflineMapCityList()
if (result.isSuccessful) {
if (result.code() == 200) {
NetResult.Success(result.body())
} else {
NetResult.Failure(result.code(), result.message())
}
} else {
NetResult.Failure(result.code(), result.message())
}
} catch (e: Exception) {
NetResult.Error(e)
}
}
}

View File

@ -0,0 +1,237 @@
package com.navinfo.omqs.http
import androidx.lifecycle.LiveData
import com.navinfo.omqs.bean.OfflineMapCityBean
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Streaming
import retrofit2.http.Url
import java.util.concurrent.Flow
/**
* retrofit2 网络请求接口
*/
interface RetrofitNetworkServiceAPI {
/**
* Retrofit2 可以使用不同类型的返回值来获取 API 调用的结果以下是 Retrofit2 支持的一些常见返回类型
1. `Call<T>`表示一个异步请求其中 `T` API 响应的类型通常是一个自定义的数据类使用 `enqueue` 方法来执行异步请求并在回调中处理响应
2. `Response<T>`表示一个同步请求的响应其中 `T` API 响应的类型使用 `execute` 方法来执行同步请求并返回一个 `Response` 对象其中包含了响应的状态码响应头和响应体等信息
3. `Observable<T>`表示一个 RxJava Observable 对象其中 `T` API 响应的类型使用 `subscribe` 方法来执行请求并在 onNext 回调中处理响应
4. `Single<T>`表示一个 RxJava Single 对象其中 `T` API 响应的类型使用 `subscribe` 方法来执行请求并在 onSuccess 回调中处理响应
5. `Completable`表示一个 RxJava Completable 对象用于执行没有响应体的 API 调用使用 `subscribe` 方法来执行请求并在 onComplete 回调中处理响应
6. `Flowable<T>`表示一个 RxJava Flowable 对象其中 `T` API 响应的类型类似于 Observable但支持背压处理
7. `Deferred<T>`表示一个 Kotlin Deferred 对象其中 `T` API 响应的类型使用 `await` 方法来执行请求并返回响应的结果
8. `LiveData<Response<T>>`表示一个 LiveData 对象其中 `T` API 响应的类型使用 `observe` 方法来执行请求并在回调中处理响应
总之Retrofit2 支持多种返回类型开发者可以根据项目需求选择合适的方式来处理 API 响应结果
*/
/**
* 获取离线地图城市列表
*/
@GET("/drdc/MapDownload/maplist")
suspend fun retrofitGetOfflineMapCityList(): Response<List<OfflineMapCityBean>>
/**
* 下载文件
*/
@Streaming
@GET
suspend fun retrofitDownLoadFile(@Url url: String):Response<ResponseBody>
/**
* @FormUrlEncoded 请求格式注解请求实体是一个From表单每个键值对需要使用@Field注解
@Field 请求参数注解提交请求的表单字段必须要添加而且需要配合@FormUrlEncoded使用
token 参数字段与后台给的字段需要一致
String 声明的参数类型
token 实际参数表示后面token的取值作为"token"的值
Post请求如果有参数需要在头部添加@FormUrlEncoded注解表示请求实体是一个From表单每个键值对需要使用@Field注解使用@Field添加参数这是发送Post请求时提交请求的表单字段必须要添加的而且需要配合@FormUrlEncoded使用若为在头部添加@FormUrlEncoded注解会抛出如下异常
当有多个不确定参数时我们可以使用@FieldMap注解@FieldMap与@Field的作用一致可以用于添加多个不确定的参数类似@QueryMapMap的key作为表单的键Map的value作为表单的值
*/
// @FormUrlEncoded
// @POST("api/dog")
// fun postCall(@Field("token") token: String?): Call<Any?>?
/**
* 当有多个不确定参数时我们可以使用@FieldMap注解@FieldMap与@Field的作用一致可以用于添加多个不确定的参数类似@QueryMapMap的key作为表单的键Map的value作为表单的值
*/
// @FormUrlEncoded
// @POST("api/dog")
// open fun postCall(@FieldMap map: Map<String?, Any?>?): Call<Any?>?
/**
* @GET("v7/weather/now")
Call<Wth_now_out> getCall(@QueryMap Map<String, Object> map);
*/
// @GET("v7/weather/now")
// open fun getCall(@QueryMap map: Map<String?, Any?>?): Call<Wth_now_out?>?
/**
*@Query 请求参数注解用于Get请求中的参数
location/key 参数字段与后台给的字段需要一致
*/
// @GET("v7/weather/now")
// open fun getCall(@QueryMap map: Map<String?, Any?>?): Call<Wth_now_out?>?
/**
* @FieldMap 请求参数注解@Field作用一致用于不确定表单参数
Map<String, Object> map 通过Map将不确定的参数传入相当于多个Field参数
适用于Post请求的还有一个注解@Body@Body可以传递自定义类型数据给服务器多用于post请求发送非表单数据比如用传递Json格式数据它可以注解很多东西比如HashMap实体类等我们来看看它用法
特别注意@Body注解不能用于表单或者支持文件上传的表单的编码即不能与@FormUrlEncoded和@Multipart注解同时使用
*/
// @POST("")
// open fun getPsotDataBody(@Body body: RequestBody?): Call<Any?>?
/**
* @Path
使用第一个get请求网址
*/
// @GET("v7/weather/{time}")
// open fun getCall(@Path("time") time: String?,@QueryMap map: Map<String?, Any?>?): Call<Wth_now_out?>?
/**
* @QueryMap get请求方法参数的注解上面已经解释了这里就不重复讲
@Path 请求参数注解用于Url中的占位符{}在网址中的参数
@Path注解用于Url中的占位符{}在网址中的参数如上面 @GET(v7/weather/{time})的time通过{}占位符来标记time使用@Path注解传入time的值注意有的Url既有占位符又有"?"后面的键值对其实@Query和@Path两者是可以共用的在发起请求时{time}会被替换为方法中第二个参数的值time
*/
// Map<String ,Object> map = new HashMap<>()Map<String ,Object> map = new HashMap<>();
// map.put("location","101010100");
// map.put("key","a5cf6ab782a14013b08fb92a57dd2f72");
// Call<Wth_now_out> call = apiService.getCall("now",map);
/**
* 最终{time}会被替换为now,此时形成的牵绊部分Url为https://devapi.qweather.com/v7/weather/now剩余**?号**后面的Url由@Query传入。
*/
/**
* @HTTP
@HTTP注解的作用是替换@GET@POST@PUT@DELETE@HEAD以及更多拓展功能
如可通过此注解代替@POST注解实现post请求如下
method 表示请求的方法区分大小写这里的值retrofit不会再做任何处理必须要保证正确
path 网络请求地址路径
hasBody 是否有请求体boolean类型
除请求接口部分的方法有变别的部分与正常使用@POST请求一致另外需要注意在使用@HTTP注解实现post请求时若含有请求体那么对应的标识注解@FormUrlEncoded一定要加上若为别的请求体那么就添加与之对应的标识注解
@HTTP注解可以通过method字段灵活设置具体请求方法通过path设置网络请求地址用的比较少使用方法与设置的请求方法种类一致
*/
// @FormUrlEncoded
// @POST("api/dog")
// Call<Object> postCall(@FieldMap Map<String,Object> map);
//
// @FormUrlEncoded
// @HTTP(method = "POST",path = "api/dog", hasBody = true)
// Call<Object> postCall1(@FieldMap Map<String,Object> map);
/**
* @Url
如果需要重新地址接口地址可以使用@Url将地址以参数的形式传入即可如果有@Url注解时GET传入的Url必须省略不然会抛出如下异常
*/
//错误的
// @FormUrlEncoded
// @POST("api/dog")
// open fun postCall(@Url url: String?, @FieldMap map: Map<String?, Any?>?): Call<Any?>?
//正确的
// @FormUrlEncoded
// @POST
// open fun postCall(@Url url: String?, @FieldMap map: Map<String?, Any?>?): Call<Any?>?
/**
* @Streaming
* @Streaming 表示响应体的数据用流的方式返回使用于返回数据比较大该注解在下载大文件时特别有用
*/
// @Streaming
// @POST("gists/public")
// open fun getStreamingBig(): Call<ResponseBody?>?
/**
* @Multipart@part@PartMap
* @Multipart 表示请求实体是一个支持文件上传的表单需要配合@Part和@PartMap使用适用于文件上传
@Part 用于表单字段适用于文件上传的情况@Part支持三种类型RequestBodyMultipartBody.Part 任意类型
@PartMap 用于多文件上传 @FieldMap和@QueryMap的使用类似
*/
// @Multipart
// @POST("user/followers")
// open fun getPartData(@Part("name") name: RequestBody?, @Part file: Part?): Call<ResponseBody?>?
/**
* 代码使用逻辑
* 首先声明类型通过MediaType实现类型的声明此处使用的时文本类型然后会根据该类型转化为RequestBody对象此处使用的参数委RequestBody对象相当于讲周润发以文本格式转换委RequestBody对象最终上传时与name形成键值对
*/
// //声明类型,这里是文字类型
// MediaType textType = MediaType.parse("text/plain");
// //根据声明的类型创建RequestBody,就是转化为RequestBody对象
// RequestBody name = RequestBody.create(textType, "周润发");
////创建文件,这里演示图片上传
// File file = new File("文件路径");
// if (!file.exists()) {
// file.mkdir();
// }
////将文件转化为RequestBody对象
////需要在表单中进行文件上传时就需要使用该格式multipart/form-data
// RequestBody imgBody = RequestBody.create(MediaType.parse("image/png"), file);
////将文件转化为MultipartBody.Part
////第一个参数上传文件的key第二个参数文件名第三个参数RequestBody对象
// MultipartBody.Part filePart = MultipartBody.Part.createFormData("key", file.getName(), imgBody);
/**
* 对于文件类型的上传我们一般使用MultipartBody.Part类型上传在上面讲两个参数设置好后然后调用接口使用请求方法即可
* 此种格式属于文本与文件混合发送的格式
*/
// var partDataCall: Call<ResponseBody?>? = retrofit.create(Api::class.java).getPartData(name, filePart)
/**
* @PartMap的使用与@FieldMap和@QueryMap的使用类似用于多文件上传我们直接看代码
*/
// @Multipart
// @POST("user/followers")
// open fun getPartMapData(@PartMap map: Map<String?, Part?>?): Call<ResponseBody?>?
// File file1 = new File("文件路径");
// File file2 = new File("文件路径");
// if (!file1.exists()) {
// file1.mkdir();
// }
// if (!file2.exists()) {
// file2.mkdir();
// }
// RequestBody requestBody1 = RequestBody.create(MediaType.parse("image/png"), file1);
// RequestBody requestBody2 = RequestBody.create(MediaType.parse("image/png"), file2);
// MultipartBody.Part filePart1 = MultipartBody.Part.createFormData("file1", file1.getName(), requestBody1);
// MultipartBody.Part filePart2 = MultipartBody.Part.createFormData("file2", file2.getName(), requestBody2);
//
// Map<String,MultipartBody.Part> mapPart = new HashMap<>();
// mapPart.put("file1",filePart1);
// mapPart.put("file2",filePart2);
/**
* 有关MediaType方法
* 概述MediaType即是Internet Media Type互联网媒体类型也叫做MIME类型在Http协议消息头中使用Content-Type来表示具体请求中的媒体类型信息也就是说MediaType在网络协议的消息头里面叫做Content-Type它使用两部分的标识符来确定一个类型是为了表明我们传的东西是什么类型
常见的媒体格式类型如下
text/html HTML格式
text/plain 纯文本格式
text/xml XML格式
image/gif gif图片格式
image/jpeg jpg图片格式
image/pngpng图片格式
以application开头的媒体格式类型
application/xhtml+xml XHTML格式
application/xml XML数据格式
application/atom+xml Atom XML聚合格式
application/json JSON数据格式
application/pdf pdf格式
application/msword Word文档格式
application/octet-stream 二进制流数据如常见的文件下载上传
application/x-www-form-urlencoded 中默认的encTypeform表单数据被编码为key/value格式发送到服务器表单默认的提交数据的格式
另外一种常见的媒体格式是上传文件之时使用的
multipart/form-data 需要在表单中进行文件上传时就需要使用该格式
*/
}

View File

@ -1,45 +0,0 @@
package com.navinfo.omqs.ui.activity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.core.view.WindowCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.ActivityMainBinding
/**
* 地图主页面
*/
class MainActivity : BaseActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
//关联生命周期
binding.lifecycleOwner = this
//给xml转递对象
binding.mainActivity = this
//给xml传递viewModel对象
binding.viewModel = viewModel
//初始化地图
viewModel.initMap(this, binding.mapView.mainActivityMap)
//让viewModel监听activity生命周期
lifecycle.addObserver(viewModel)
}
/**
* 打开个人中菜单
*/
fun openMenu() {
binding.mainActivityDrawer.open()
}
}

View File

@ -1,51 +0,0 @@
package com.navinfo.omqs.ui.activity
import android.content.Context
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import com.navinfo.collect.library.map.NIMapController
import com.navinfo.collect.library.map.NIMapView
class MainViewModel : ViewModel(), DefaultLifecycleObserver {
/**
* 地图控制器
*/
private lateinit var mapController: NIMapController
/**
* 初始化地图
*/
fun initMap(context: Context, mapView: NIMapView) {
mapController = NIMapController(context = context, mapView = mapView)
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
//开启定位
mapController.locationLayerHandler.startLocation()
}
override fun onPause(owner: LifecycleOwner) {
mapController.mMapView.onPause()
}
override fun onDestroy(owner: LifecycleOwner) {
mapController.mMapView.onDestroy()
//结束定位
mapController.locationLayerHandler.stopLocation()
}
override fun onResume(owner: LifecycleOwner) {
mapController.mMapView.onResume()
}
/**
* 点击我的位置回到我的位置
*/
fun onClickLocationButton() {
mapController.locationLayerHandler.animateToCurrentPosition()
}
}

View File

@ -1,4 +1,4 @@
package com.navinfo.omqs.ui.activity
package com.navinfo.omqs.ui.activity.login
import android.content.Intent
import android.os.Bundle
@ -6,6 +6,8 @@ import androidx.activity.viewModels
import androidx.databinding.DataBindingUtil
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.ActivityLoginBinding
import com.navinfo.omqs.ui.activity.map.MainActivity
import com.navinfo.omqs.ui.activity.PermissionsActivity
/**
* 登陆页面
@ -35,6 +37,6 @@ class LoginActivity : PermissionsActivity() {
fun onClickLoginButton() {
val intent = Intent(this@LoginActivity, MainActivity::class.java)
startActivity(intent)
finish()
// finish()
}
}

View File

@ -1,15 +1,15 @@
package com.navinfo.omqs.ui.activity
package com.navinfo.omqs.ui.activity.login
import android.view.View
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.navinfo.omqs.model.LoginUser
import com.navinfo.omqs.bean.LoginUserBean
class LoginViewModel : ViewModel() {
val loginUser: MutableLiveData<LoginUser> = MutableLiveData()
val loginUser: MutableLiveData<LoginUserBean> = MutableLiveData()
init {
loginUser.value = LoginUser(username = "admin", password = "123456")
loginUser.value = LoginUserBean(username = "admin", password = "123456")
}
/**
@ -18,6 +18,5 @@ class LoginViewModel : ViewModel() {
fun onClick(view: View) {
loginUser.value!!.username = "admin2"
loginUser.postValue(loginUser.value)
}
}

View File

@ -0,0 +1,77 @@
package com.navinfo.omqs.ui.activity.map
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.core.view.WindowCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.viewModelScope
import com.navinfo.collect.library.map.NIMapController
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.ActivityMainBinding
import com.navinfo.omqs.ui.activity.BaseActivity
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/**
* 地图主页面
*/
@AndroidEntryPoint
class MainActivity : BaseActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel by viewModels<MainViewModel>()
//注入地图控制器
@Inject
lateinit var mapController: NIMapController
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
//初始化地图
mapController.init(this, binding.mapView.mainActivityMap)
//关联生命周期
binding.lifecycleOwner = this
//给xml转递对象
binding.mainActivity = this
//给xml传递viewModel对象
binding.viewModel = viewModel
// lifecycle.addObserver(viewModel)
}
override fun onStart() {
super.onStart()
//开启定位
mapController.locationLayerHandler.startLocation()
}
override fun onPause() {
super.onPause()
mapController.mMapView.onPause()
}
override fun onDestroy() {
super.onDestroy()
mapController.mMapView.onDestroy()
mapController.locationLayerHandler.stopLocation()
Log.e("jingo","MainActivity 销毁")
}
override fun onResume() {
super.onResume()
mapController.mMapView.onResume()
}
/**
* 打开个人中菜单
*/
fun openMenu() {
binding.mainActivityDrawer.open()
}
}

View File

@ -0,0 +1,31 @@
package com.navinfo.omqs.ui.activity.map
import android.util.Log
import androidx.lifecycle.ViewModel
import com.navinfo.collect.library.map.NIMapController
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.scopes.ActivityRetainedScoped
import javax.inject.Inject
import javax.inject.Singleton
/**
* 创建Activity全局viewmode
*/
@HiltViewModel
class MainViewModel @Inject constructor(
private val mapController: NIMapController,
) : ViewModel() {
/**
* 点击我的位置回到我的位置
*/
fun onClickLocationButton() {
mapController.locationLayerHandler.animateToCurrentPosition()
}
override fun onCleared() {
Log.e("jingo","MainViewModel 被释放了")
super.onCleared()
}
}

View File

@ -1,45 +0,0 @@
package com.navinfo.omqs.ui.fragment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.FragmentFirstBinding
/**
* A simple [Fragment] subclass as the default destination in the navigation.
*/
class FirstFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentFirstBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonFirst.setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -0,0 +1,25 @@
package com.navinfo.omqs.ui.fragment.offlinemap
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
/**
* 离线地图主页面viewpage适配器
*/
class OfflineMapAdapter(activity: FragmentActivity) :
FragmentStateAdapter(activity) {
private val stateFragment = OfflineMapStateListFragment()
private val cityListFragment = OfflineMapCityListFragment()
override fun getItemCount(): Int {
return 2
}
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> stateFragment
else ->
cityListFragment
}
}
}

View File

@ -0,0 +1,33 @@
package com.navinfo.omqs.ui.fragment.offlinemap
import androidx.databinding.ViewDataBinding
import com.navinfo.omqs.R
import com.navinfo.omqs.BR
import com.navinfo.omqs.bean.OfflineMapCityBean
import com.navinfo.omqs.databinding.AdapterOfflineMapCityBinding
import com.navinfo.omqs.ui.other.BaseRecyclerViewAdapter
import com.navinfo.omqs.ui.other.BaseViewHolder
import javax.inject.Inject
/**
* 离线地图城市列表 RecyclerView 适配器
*/
class OfflineMapCityListAdapter @Inject constructor() :
BaseRecyclerViewAdapter<OfflineMapCityBean>() {
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
var binding: ViewDataBinding = holder.dataBinding
//立刻刷新UI解决闪烁
// binding.executePendingBindings()
binding.setVariable(BR.cityBean, data[position])
(binding as AdapterOfflineMapCityBinding).offlineMapDownloadBtn.setOnClickListener {
}
}
override fun getItemViewType(position: Int): Int {
return R.layout.adapter_offline_map_city
}
}

View File

@ -0,0 +1,49 @@
package com.navinfo.omqs.ui.fragment.offlinemap
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.navinfo.omqs.databinding.FragmentOfflineMapCityListBinding
import dagger.hilt.android.AndroidEntryPoint
/**
* 离线地图城市列表
*/
@AndroidEntryPoint
class OfflineMapCityListFragment : Fragment() {
private var _binding: FragmentOfflineMapCityListBinding? = null
private val viewModel by viewModels<OfflineMapCityListViewModel>()
private val binding get() = _binding!!
private val adapter: OfflineMapCityListAdapter by lazy { OfflineMapCityListAdapter() }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentOfflineMapCityListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val layoutManager = LinearLayoutManager(context)
_binding!!.offlineMapCityListRecyclerview.layoutManager = layoutManager
_binding!!.offlineMapCityListRecyclerview.adapter = adapter
viewModel.cityListLiveData.observe(viewLifecycleOwner) {
adapter.refreshData(it)
}
viewModel.getCityList()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
Log.e("jingo","OfflineMapCityListFragment onDestroyView")
}
}

View File

@ -0,0 +1,49 @@
package com.navinfo.omqs.ui.fragment.offlinemap
import android.app.Application
import android.content.Context
import android.widget.Toast
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.navinfo.omqs.http.NetResult
import com.navinfo.omqs.http.NetworkService
import com.navinfo.omqs.bean.OfflineMapCityBean
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.launch
import javax.inject.Inject
/**
* 离线地图城市列表viewModel
*/
@HiltViewModel
class OfflineMapCityListViewModel @Inject constructor(
private val networkService: NetworkService,
@ApplicationContext val context: Context
) : ViewModel() {
val cityListLiveData = MutableLiveData<List<OfflineMapCityBean>>()
/**
* 去获取离线地图列表
*/
fun getCityList() {
viewModelScope.launch {
when (val result = networkService.getOfflineMapCityList()) {
is NetResult.Success -> {
cityListLiveData.postValue(result.data!!)
}
is NetResult.Error -> {
Toast.makeText(context, "${result.exception.message}", Toast.LENGTH_SHORT)
.show()
}
is NetResult.Failure -> {
Toast.makeText(context, "${result.code}:${result.msg}", Toast.LENGTH_SHORT)
.show()
}
NetResult.Loading -> {}
}
}
}
}

View File

@ -0,0 +1,66 @@
package com.navinfo.omqs.ui.fragment.offlinemap
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.google.android.material.tabs.TabLayoutMediator
import com.navinfo.omqs.databinding.FragmentOfflineMapBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* 离线地图
*/
class OfflineMapFragment : Fragment() {
private var _binding: FragmentOfflineMapBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentOfflineMapBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//禁止滑动,因为页面在抽屉里,和抽屉的滑动有冲突
binding.offlineMapViewpager.isUserInputEnabled = false
//创建viewpager2的适配器
binding.offlineMapViewpager.adapter = activity?.let { OfflineMapAdapter(it) }
//绑定viewpager2与tabLayout
TabLayoutMediator(
binding.offlineMapTabLayout,
binding.offlineMapViewpager
) { tab, position ->
when (position) {
0 -> tab.text = "下载管理"
1 -> tab.text = "城市列表"
}
}.attach()
//处理返回按钮
binding.offlineMapBack.setOnClickListener {
findNavController().popBackStack()
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
Log.e("jingo","OfflineMapFragment onDestroyView")
}
}

View File

@ -0,0 +1,38 @@
package com.navinfo.omqs.ui.fragment.offlinemap
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.navinfo.omqs.databinding.FragmentOfflineMapBinding
import com.navinfo.omqs.databinding.FragmentOfflineMapStateListBinding
class OfflineMapStateListFragment : Fragment() {
private var _binding: FragmentOfflineMapStateListBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentOfflineMapStateListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
Log.e("jingo","OfflineMapStateListFragment onDestroyView")
}
}

View File

@ -1,40 +1,42 @@
package com.navinfo.omqs.ui.fragment
package com.navinfo.omqs.ui.fragment.personalcenter
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.FragmentSecondBinding
import com.navinfo.omqs.databinding.FragmentPersonalCenterBinding
/**
* A simple [Fragment] subclass as the second destination in the navigation.
* 个人中心
*/
class SecondFragment : Fragment() {
class PersonalCenterFragment : Fragment() {
private var _binding: FragmentSecondBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private var _binding: FragmentPersonalCenterBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentSecondBinding.inflate(inflater, container, false)
_binding = FragmentPersonalCenterBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonSecond.setOnClickListener {
findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
Log.e("jingo", "NIMapController PersonalCenterFragment onViewCreated")
binding.root.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.personal_center_menu_offline_map ->
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}
true
}
}

View File

@ -0,0 +1,32 @@
package com.navinfo.omqs.ui.other
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
/**
* RecyclerView 适配器基础类
*/
abstract class BaseRecyclerViewAdapter<T>(var data: List<T> = listOf()) :
RecyclerView.Adapter<BaseViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
return BaseViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
viewType,
parent,
false
)
)
}
override fun getItemCount(): Int {
return data.size
}
fun refreshData(newData:List<T>){
this.data = newData
this.notifyDataSetChanged()
}
}

View File

@ -0,0 +1,11 @@
package com.navinfo.omqs.ui.other
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
/**
* dataBinding viewHolder 基类
*/
open class BaseViewHolder(var dataBinding: ViewDataBinding) :
RecyclerView.ViewHolder(dataBinding.root) {
}

View File

@ -0,0 +1,64 @@
package com.navinfo.omqs.ui.widget
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.opengl.ETC1.getHeight
import android.opengl.ETC1.getWidth
import android.util.AttributeSet
import android.widget.ProgressBar
/**
* 带文字提示的进度条
*/
class MyProgressBar : ProgressBar {
private lateinit var mPaint: Paint
private var text: String = ""
private var rate = 0f
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
initView()
}
constructor(context: Context?) : super(context) {
initView()
}
private fun initView() {
mPaint = Paint()
mPaint.isAntiAlias = true
mPaint.color = Color.BLUE
}
@Synchronized
override fun setProgress(progress: Int) {
setText(progress)
super.setProgress(progress)
}
private fun setText(progress: Int) {
rate = progress * 1.0f / this.getMax()
val i = (rate * 100).toInt()
text = "$i%"
}
@Synchronized
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val rect = Rect()
mPaint.getTextBounds(text, 0, text.length, rect)
// int x = (getWidth()/2) - rect.centerX();
// int y = (getHeight()/2) - rect.centerY();
var x = (width * rate).toInt()
if (x == width) {
// 如果为百分之百则在左边绘制。
x = width - rect.right
}
val y: Int = 0 - rect.top
mPaint.textSize = 22f
canvas.drawText(text, x.toFloat(), y.toFloat(), mPaint)
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="#8cc1ff"/>
<item android:state_checked="true" android:color="@color/white"></item>
<item android:state_selected="true" android:color="@color/white"></item>
<item android:state_pressed="true" android:color="@color/white"></item>
<item android:color="#108ee9"></item>
</selector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#100F0F"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48V20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48V3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM15,19l-6,-2.11V5l6,2.11V19z"/>
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_enabled="true" android:state_pressed="false" android:drawable="@mipmap/icon_back_n"/>
<item android:state_pressed="true" android:drawable="@mipmap/icon_back_p"/>
<item android:state_enabled="false" android:drawable="@mipmap/icon_back_n"/>
</selector>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"><shape android:shape="rectangle">
<gradient android:type="linear" android:useLevel="true" android:startColor="#03bef3" android:endColor="#0c5ce8" android:angle="45" />
<corners android:topLeftRadius="2dp" android:topRightRadius="0dp" android:bottomLeftRadius="2dp" android:bottomRightRadius="0dp" />
</shape></item>
<item android:state_focused="true"><shape android:shape="rectangle">
<gradient android:type="linear" android:useLevel="true" android:startColor="#03bef3" android:endColor="#0c5ce8" android:angle="45" />
<corners android:topLeftRadius="2dp" android:topRightRadius="0dp" android:bottomLeftRadius="2dp" android:bottomRightRadius="0dp" />
</shape></item>
<item android:state_checked="true"><shape android:shape="rectangle">
<gradient android:type="linear" android:useLevel="true" android:startColor="#03bef3" android:endColor="#0c5ce8" android:angle="45" />
<corners android:topLeftRadius="2dp" android:topRightRadius="0dp" android:bottomLeftRadius="2dp" android:bottomRightRadius="0dp" />
</shape></item>
<item android:state_selected="true"><shape android:shape="rectangle">
<gradient android:type="linear" android:useLevel="true" android:startColor="#03bef3" android:endColor="#0c5ce8" android:angle="45" />
<corners android:topLeftRadius="2dp" android:topRightRadius="0dp" android:bottomLeftRadius="2dp" android:bottomRightRadius="0dp" />
</shape></item>
<item>
<shape android:shape="rectangle">
<solid android:color="#ff043683" />
<corners android:topLeftRadius="0dp" android:topRightRadius="2dp" android:bottomLeftRadius="0dp" android:bottomRightRadius="2dp" />
</shape></item>
</selector>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false">
<shape>
<stroke android:width="1.1dp" android:color="@color/cv_gray_153" />
<solid android:color="@color/cv_gray_153" />
<padding android:bottom="@dimen/default_widget_padding" android:left="@dimen/default_widget_padding" android:right="@dimen/default_widget_padding" android:top="@dimen/default_widget_padding"></padding>
</shape>
</item>
<item android:state_pressed="true">
<shape>
<stroke android:width="1.1dp" android:color="@color/white" />
<solid android:color="@color/white" />
<padding android:bottom="@dimen/default_widget_padding" android:left="@dimen/default_widget_padding" android:right="@dimen/default_widget_padding" android:top="@dimen/default_widget_padding"></padding>
</shape>
</item>
<item android:state_selected="true">
<shape>
<stroke android:width="1.1dp" android:color="@color/cvm_red" />
<solid android:color="@color/cvm_red" />
<padding android:bottom="@dimen/default_widget_padding" android:left="@dimen/default_widget_padding" android:right="@dimen/default_widget_padding" android:top="@dimen/default_widget_padding"></padding>
</shape>
</item>
<item>
<shape>
<stroke android:width="1.1dp" android:color="@color/btn_blue_solid" />
<solid android:color="@color/transp" />
<padding android:bottom="@dimen/default_widget_padding" android:left="@dimen/default_widget_padding" android:right="@dimen/default_widget_padding" android:top="@dimen/default_widget_padding"></padding>
</shape>
</item>
</selector>

View File

@ -2,17 +2,17 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.activity.LoginActivity">
tools:context=".ui.activity.login.LoginActivity">
<data>
<variable
name="activity"
type="com.navinfo.omqs.ui.activity.LoginActivity" />
type="com.navinfo.omqs.ui.activity.login.LoginActivity" />
<variable
name="loginUserModel"
type="com.navinfo.omqs.ui.activity.LoginViewModel" />
type="com.navinfo.omqs.ui.activity.login.LoginViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout

View File

@ -2,17 +2,17 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.activity.MainActivity">
tools:context=".ui.activity.map.MainActivity">
<data>
<variable
name="mainActivity"
type="com.navinfo.omqs.ui.activity.MainActivity" />
type="com.navinfo.omqs.ui.activity.map.MainActivity" />
<variable
name="viewModel"
type="com.navinfo.omqs.ui.activity.MainViewModel" />
type="com.navinfo.omqs.ui.activity.map.MainViewModel" />
</data>
<androidx.drawerlayout.widget.DrawerLayout
@ -27,15 +27,14 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
app:mainActivity="@{mainActivity}"
app:viewModel="@{viewModel}"/>
app:viewModel="@{viewModel}" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
<fragment
android:id="@+id/main_activity_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_gravity="left"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
app:navGraph="@navigation/nav_graph" />
</androidx.drawerlayout.widget.DrawerLayout>
</layout>

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.navinfo.omqs.ui.fragment.offlinemap.OfflineMapCityListAdapter">
<data>
<import type="com.navinfo.omqs.R" />
<variable
name="cityBean"
type="com.navinfo.omqs.bean.OfflineMapCityBean" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:background="@color/cv_bg_color">
<TextView
android:id="@+id/offline_map_city_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{cityBean.name}"
android:textColor="@color/white"
android:textSize="@dimen/default_font_size"
app:layout_constraintLeft_toLeftOf="@id/offline_map_city_size"
app:layout_constraintRight_toRightOf="@id/offline_map_city_size"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/offline_map_city_size"
style="@style/map_size_font_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@mipmap/point_blue"
android:textSize="@dimen/card_title_font_3size"
android:text="@{cityBean.getFileSizeText()}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/offline_map_city_name" />
<TextView
android:id="@+id/tv_city_list_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="false"
android:shadowColor="@android:color/transparent"
android:textColor="@color/white"
android:textSize="@dimen/card_title_font_2size"
app:layout_constraintBottom_toBottomOf="@id/offline_map_download_btn"
app:layout_constraintRight_toLeftOf="@id/offline_map_download_btn"
app:layout_constraintTop_toTopOf="@id/offline_map_download_btn" />
<TextView
android:id="@+id/offline_map_download_btn"
style="@style/map_download_style_btn"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:shadowColor="@android:color/transparent"
android:text="下载"
android:textColor="@color/btn_blue_solid"
android:textSize="@dimen/card_title_font_2size"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/offline_map_progress"
app:layout_constraintTop_toTopOf="parent" />
<com.navinfo.omqs.ui.widget.MyProgressBar
android:layout_marginTop="5dp"
android:visibility="gone"
android:id="@+id/offline_map_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<fragment
android:id="@+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.FirstFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<Button
android:id="@+id/button_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next"
app:layout_constraintBottom_toTopOf="@id/textview_first"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
/>
<TextView
android:id="@+id/textview_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/lorem_ipsum"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_first" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:orientation="vertical">
<RelativeLayout
style="@style/title_default_style"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#553C3F41">
<ImageView
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:paddingBottom="@dimen/default_widget_padding"
android:paddingLeft="@dimen/default_widget_padding"
android:paddingRight="@dimen/default_widget_padding"
android:paddingTop="@dimen/default_widget_padding"
android:src="@drawable/btn_back_xml" />
<RadioGroup
android:id="@+id/rg_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:gravity="center"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_manager"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/fm_card_map_btn_bg_line"
android:button="@null"
android:drawableLeft="@null"
android:gravity="center"
android:paddingBottom="@dimen/default_widget_padding"
android:paddingLeft="@dimen/default_widget_padding"
android:paddingRight="@dimen/default_widget_padding"
android:paddingTop="@dimen/default_widget_padding"
android:text="下载管理"
android:textColor="@color/btn_blue_white"
android:textSize="18sp" />
<RadioButton
android:id="@+id/rb_city_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/fm_card_map_btn_bg_line"
android:button="@null"
android:checked="true"
android:drawableLeft="@null"
android:gravity="center"
android:paddingBottom="@dimen/default_widget_padding"
android:paddingLeft="@dimen/default_widget_padding"
android:paddingRight="@dimen/default_widget_padding"
android:paddingTop="@dimen/default_widget_padding"
android:text="城市列表"
android:textColor="@color/btn_blue_white"
android:textSize="18sp" />
</RadioGroup>
</RelativeLayout>
<EditText
android:id="@+id/edt_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/input_blue_type"
android:hint="搜索"></EditText>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_map_download_network_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textColor="#FFFFFF"
android:visibility="gone"
android:text="网络错误,请检查网络后重试"></TextView>
<!-- <com.yanzhenjie.recyclerview.SwipeRecyclerView-->
<!-- android:id="@+id/lv_map_manager"-->
<!-- android:layout_width="match_parent"-->
<!-- android:visibility="invisible"-->
<!-- android:layout_height="match_parent">-->
<!-- </com.yanzhenjie.recyclerview.SwipeRecyclerView>-->
<!-- <com.yanzhenjie.recyclerview.SwipeRecyclerView-->
<!-- android:id="@+id/lv_map_city"-->
<!-- android:visibility="visible"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent">-->
<!-- </com.yanzhenjie.recyclerview.SwipeRecyclerView>-->
</FrameLayout>
</LinearLayout>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/design_default_color_background"
tools:context=".ui.fragment.offlinemap.OfflineMapFragment">
<com.google.android.material.tabs.TabLayout
android:id="@+id/offline_map_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/default_blue"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载管理" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="城市列表" />
</com.google.android.material.tabs.TabLayout>
<EditText
android:id="@+id/offline_map_search"
style="@style/input_blue_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="搜索"
app:layout_constraintTop_toBottomOf="@id/offline_map_tab_layout" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/offline_map_viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/offline_map_search" />
<ImageView
android:id="@+id/offline_map_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:padding="10dp"
android:src="@mipmap/icon_back_n"
app:layout_constraintBottom_toBottomOf="@id/offline_map_tab_layout"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@id/offline_map_tab_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.offlinemap.OfflineMapCityListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/offline_map_city_list_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/default_red"
tools:context=".ui.fragment.offlinemap.OfflineMapStateListFragment">
<TextView
android:text="第一页"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.navigation.NavigationView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="left"
android:fitsSystemWindows="true"
app:headerLayout="@layout/personal_center_head"
app:menu="@menu/personal_center_menu" />

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.SecondFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<Button
android:id="@+id/button_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/previous"
app:layout_constraintBottom_toTopOf="@id/textview_second"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textview_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/lorem_ipsum"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_second" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -6,17 +6,17 @@
<data>
<variable
name="mainActivity"
type="com.navinfo.omqs.ui.activity.MainActivity" />
type="com.navinfo.omqs.ui.activity.map.MainActivity" />
<variable
name="viewModel"
type="com.navinfo.omqs.ui.activity.MainViewModel" />
type="com.navinfo.omqs.ui.activity.map.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.activity.MainActivity">
tools:context=".ui.activity.map.MainActivity">
<com.navinfo.collect.library.map.NIMapView
android:id="@+id/main_activity_map"

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_home"
android:icon="@drawable/icon_map_zoom_in"
android:title="menu_home" />
<item
android:id="@+id/nav_gallery"
android:icon="@drawable/icon_map_zoom_in"
android:title="menu_gallery" />
<item
android:id="@+id/nav_slideshow"
android:icon="@drawable/icon_map_zoom_in"
android:title="menu_slideshow" />
</group>
</menu>

View File

@ -1,10 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.navinfo.omqs.ui.activity.MainActivity">
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"
app:showAsAction="never" />
</menu>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group
android:id="@+id/group1"
android:checkableBehavior="single">
<item
android:id="@+id/personal_center_menu_offline_map"
android:icon="@drawable/baseline_map_24"
android:title="离线地图" />
<item
android:id="@+id/personal_center_menu_offline_map1"
android:icon="@drawable/baseline_person_24"
android:title="menu_gallery" />
<item
android:id="@+id/personal_center_menu_offline_map2"
android:icon="@drawable/baseline_person_24"
android:title="menu_slideshow" />
</group>
<group
android:id="@+id/group2"
android:checkableBehavior="single">
<item android:title="小标题">
<menu>
<item
android:id="@+id/personal_center_menu_offline_map3"
android:icon="@drawable/baseline_person_24"
android:title="menu_home" />
<item
android:id="@+id/personal_center_menu_offline_map4"
android:icon="@drawable/baseline_person_24"
android:title="menu_gallery" />
<item
android:id="@+id/personal_center_menu_offline_map5"
android:icon="@drawable/baseline_person_24"
android:title="menu_slideshow" />
</menu>
</item>
</group>
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

View File

@ -3,13 +3,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/FirstFragment">
app:startDestination="@id/PersonalCenterFragment">
<fragment
android:id="@+id/FirstFragment"
android:name="com.navinfo.omqs.ui.fragment.FirstFragment"
android:label="@string/first_fragment_label"
tools:layout="@layout/fragment_first">
android:id="@+id/PersonalCenterFragment"
android:name="com.navinfo.omqs.ui.fragment.personalcenter.PersonalCenterFragment"
android:label="@string/personal_center"
tools:layout="@layout/fragment_personal_center">
<action
android:id="@+id/action_FirstFragment_to_SecondFragment"
@ -17,12 +17,12 @@
</fragment>
<fragment
android:id="@+id/SecondFragment"
android:name="com.navinfo.omqs.ui.fragment.SecondFragment"
android:name="com.navinfo.omqs.ui.fragment.offlinemap.OfflineMapFragment"
android:label="@string/second_fragment_label"
tools:layout="@layout/fragment_second">
tools:layout="@layout/fragment_offline_map">
<action
android:id="@+id/action_SecondFragment_to_FirstFragment"
app:destination="@id/FirstFragment" />
app:destination="@id/PersonalCenterFragment" />
</fragment>
</navigation>

View File

@ -2,4 +2,8 @@
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="cv_gray_153">#999999</color>
<color name="cvm_red">#FF3B30</color>
<color name="cv_bg_color">#553C3F41</color>
<color name="btn_blue_solid">#108ee9</color>
</resources>

View File

@ -5,4 +5,8 @@
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen>
<dimen name="default_widget_padding">10dp</dimen>
<dimen name="default_font_size" comment="默认字体大小style中父最顶层">15dp</dimen>
<dimen name="card_title_font_2size">13sp</dimen>
<dimen name="card_title_font_3size">10sp</dimen>
</resources>

View File

@ -2,47 +2,11 @@
<string name="app_name">OMQualityInspection</string>
<string name="action_settings">Settings</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">First Fragment</string>
<string name="personal_center">个人中心</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="lorem_ipsum">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris
volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus
dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad
litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse blandit eleifend
diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, imperdiet nisl a,
ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.\n\n
Suspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus
egestas, est a condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed
neque. Morbi tellus erat, dapibus ut sem a, iaculis tincidunt dui. Interdum et malesuada
fames ac ante ipsum primis in faucibus. Curabitur et eros porttitor, ultricies urna vitae,
molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl nec dolor
bibendum, vel congue leo egestas.\n\n
Sed interdum tortor nibh, in sagittis risus mollis quis. Curabitur mi odio, condimentum sit
amet auctor at, mollis non turpis. Nullam pretium libero vestibulum, finibus orci vel,
molestie quam. Fusce blandit tincidunt nulla, quis sollicitudin libero facilisis et. Integer
interdum nunc ligula, et fermentum metus hendrerit id. Vestibulum lectus felis, dictum at
lacinia sit amet, tristique id quam. Cras eu consequat dui. Suspendisse sodales nunc ligula,
in lobortis sem porta sed. Integer id ultrices magna, in luctus elit. Sed a pellentesque
est.\n\n
Aenean nunc velit, lacinia sed dolor sed, ultrices viverra nulla. Etiam a venenatis nibh.
Morbi laoreet, tortor sed facilisis varius, nibh orci rhoncus nulla, id elementum leo dui
non lorem. Nam mollis ipsum quis auctor varius. Quisque elementum eu libero sed commodo. In
eros nisl, imperdiet vel imperdiet et, scelerisque a mauris. Pellentesque varius ex nunc,
quis imperdiet eros placerat ac. Duis finibus orci et est auctor tincidunt. Sed non viverra
ipsum. Nunc quis augue egestas, cursus lorem at, molestie sem. Morbi a consectetur ipsum, a
placerat diam. Etiam vulputate dignissim convallis. Integer faucibus mauris sit amet finibus
convallis.\n\n
Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. In volutpat arcu ut felis sagittis, in finibus massa
gravida. Pellentesque id tellus orci. Integer dictum, lorem sed efficitur ullamcorper,
libero justo consectetur ipsum, in mollis nisl ex sed nisl. Donec maximus ullamcorper
sodales. Praesent bibendum rhoncus tellus nec feugiat. In a ornare nulla. Donec rhoncus
libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus
vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim.
</string>
<string name="input_user_name">请输入用户名</string>
<string name="input_password">请输入密码</string>
<string name="login">登录</string>

View File

@ -0,0 +1,57 @@
<resources>
<style name="title_default_style" comment="默认顶标题样式">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">20dp</item>
<item name="android:clickable">true</item>
<item name="android:focusable">true</item>
<item name="android:layout_alignParentTop">true</item>
<item name="android:background">@color/default_blue</item>
<item name="android:paddingBottom">6dp</item>
<item name="android:paddingLeft">14dp</item>
<item name="android:paddingRight">14dp</item>
<item name="android:paddingTop">6dp</item>
<item name="android:gravity">center_vertical</item>
</style>
<!-- 默认字体 -->
<style name="content_font_default">
<item name="android:gravity">center_vertical</item>
<item name="android:textSize">15dp</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
</style>
<!-- 输入框基本样式 -->
<style name="input_blue_type" parent="content_font_default">
<item name="android:textSize">16sp</item>
<item name="android:background">#FFFFFF</item>
<item name="android:textColor">#1890ff</item>
<item name="android:padding">10dp</item>
<item name="android:imeOptions">actionDone</item>
<item name="android:singleLine">true</item>
</style>
<style name="map_size_font_style" comment="离线地图gdb/nds大小字体样式">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:drawablePadding">4dp</item>
<item name="android:textColor">@color/white</item>
<item name="android:textSize">9sp</item>
<item name="android:focusable">false</item>
<item name="android:clickable">false</item>
</style>
<style name="map_download_style_btn" comment="离线地图下载的按钮样式">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">center_vertical</item>
<item name="android:padding">@dimen/default_widget_padding</item>
<item name="android:background">@drawable/fm_card_map_down_status_bg</item>
<item name="android:gravity">center_horizontal</item>
<item name="android:textColor">@color/btn_blue_white</item>
<item name="android:textSize">15sp</item>
</style>
</resources>

View File

@ -10,4 +10,5 @@ plugins {
id 'com.android.library' version '7.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
id 'io.realm.kotlin' version '0.10.0' apply false
id 'com.google.dagger.hilt.android' version '2.44' apply false
}

View File

@ -1,121 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* This type adapter supports three subclasses of date: Date, Timestamp, and
* java.sql.Date.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
final class DefaultDateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
// TODO: migrate to streaming adapter
private final DateFormat enUsFormat;
private final DateFormat localFormat;
private final DateFormat iso8601Format;
DefaultDateTypeAdapter() {
this(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US),
DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT));
}
DefaultDateTypeAdapter(String datePattern) {
this(new SimpleDateFormat(datePattern, Locale.US), new SimpleDateFormat(datePattern));
}
DefaultDateTypeAdapter(int style) {
this(DateFormat.getDateInstance(style, Locale.US), DateFormat.getDateInstance(style));
}
public DefaultDateTypeAdapter(int dateStyle, int timeStyle) {
this(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US),
DateFormat.getDateTimeInstance(dateStyle, timeStyle));
}
DefaultDateTypeAdapter(DateFormat enUsFormat, DateFormat localFormat) {
this.enUsFormat = enUsFormat;
this.localFormat = localFormat;
this.iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
this.iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
}
// These methods need to be synchronized since JDK DateFormat classes are not thread-safe
// See issue 162
@Override
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
synchronized (localFormat) {
String dateFormatAsString = enUsFormat.format(src);
return new JsonPrimitive(dateFormatAsString);
}
}
@Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
if (!(json instanceof JsonPrimitive)) {
throw new JsonParseException("The date should be a string value");
}
Date date = deserializeToDate(json);
if (typeOfT == Date.class) {
return date;
} else if (typeOfT == Timestamp.class) {
return new Timestamp(date.getTime());
} else if (typeOfT == java.sql.Date.class) {
return new java.sql.Date(date.getTime());
} else {
throw new IllegalArgumentException(getClass() + " cannot deserialize to " + typeOfT);
}
}
private Date deserializeToDate(JsonElement json) {
synchronized (localFormat) {
try {
return localFormat.parse(json.getAsString());
} catch (ParseException ignored) {
}
try {
return enUsFormat.parse(json.getAsString());
} catch (ParseException ignored) {
}
try {
return iso8601Format.parse(json.getAsString());
} catch (ParseException e) {
throw new JsonSyntaxException(json.getAsString(), e);
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(DefaultDateTypeAdapter.class.getSimpleName());
sb.append('(').append(localFormat.getClass().getSimpleName()).append(')');
return sb.toString();
}
}

View File

@ -1,109 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
/**
* A strategy (or policy) definition that is used to decide whether or not a field or top-level
* class should be serialized or deserialized as part of the JSON output/input. For serialization,
* if the {@link #shouldSkipClass(Class)} method returns false then that class or field type
* will not be part of the JSON output. For deserialization, if {@link #shouldSkipClass(Class)}
* returns false, then it will not be set as part of the Java object structure.
*
* <p>The following are a few examples that shows how you can use this exclusion mechanism.
*
* <p><strong>Exclude fields and objects based on a particular class type:</strong>
* <pre class="code">
* private static class SpecificClassExclusionStrategy implements ExclusionStrategy {
* private final Class&lt;?&gt; excludedThisClass;
*
* public SpecificClassExclusionStrategy(Class&lt;?&gt; excludedThisClass) {
* this.excludedThisClass = excludedThisClass;
* }
*
* public boolean shouldSkipClass(Class&lt;?&gt; clazz) {
* return excludedThisClass.equals(clazz);
* }
*
* public boolean shouldSkipField(FieldAttributes f) {
* return excludedThisClass.equals(f.getDeclaredClass());
* }
* }
* </pre>
*
* <p><strong>Excludes fields and objects based on a particular annotation:</strong>
* <pre class="code">
* public &#64interface FooAnnotation {
* // some implementation here
* }
*
* // Excludes any field (or class) that is tagged with an "&#64FooAnnotation"
* private static class FooAnnotationExclusionStrategy implements ExclusionStrategy {
* public boolean shouldSkipClass(Class&lt;?&gt; clazz) {
* return clazz.getAnnotation(FooAnnotation.class) != null;
* }
*
* public boolean shouldSkipField(FieldAttributes f) {
* return f.getAnnotation(FooAnnotation.class) != null;
* }
* }
* </pre>
*
* <p>Now if you want to configure {@code Gson} to use a user defined exclusion strategy, then
* the {@code GsonBuilder} is required. The following is an example of how you can use the
* {@code GsonBuilder} to configure Gson to use one of the above sample:
* <pre class="code">
* ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
* Gson gson = new GsonBuilder()
* .setExclusionStrategies(excludeStrings)
* .create();
* </pre>
*
* <p>For certain model classes, you may only want to serialize a field, but exclude it for
* deserialization. To do that, you can write an {@code ExclusionStrategy} as per normal;
* however, you would register it with the
* {@link GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)} method.
* For example:
* <pre class="code">
* ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
* Gson gson = new GsonBuilder()
* .addDeserializationExclusionStrategy(excludeStrings)
* .create();
* </pre>
*
* @author Inderjeet Singh
* @author Joel Leitch
*
* @see GsonBuilder#setExclusionStrategies(ExclusionStrategy...)
* @see GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)
* @see GsonBuilder#addSerializationExclusionStrategy(ExclusionStrategy)
*
* @since 1.4
*/
public interface ExclusionStrategy {
/**
* @param f the field object that is under test
* @return true if the field should be ignored; otherwise false
*/
public boolean shouldSkipField(FieldAttributes f);
/**
* @param clazz the class object that is under test
* @return true if the class should be ignored; otherwise false
*/
public boolean shouldSkipClass(Class<?> clazz);
}

View File

@ -1,157 +0,0 @@
/*
* Copyright (C) 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import com.google.gson.internal.$Gson$Preconditions;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
/**
* A data object that stores attributes of a field.
*
* <p>This class is immutable; therefore, it can be safely shared across threads.
*
* @author Inderjeet Singh
* @author Joel Leitch
*
* @since 1.4
*/
public final class FieldAttributes {
private final Field field;
/**
* Constructs a Field Attributes object from the {@code f}.
*
* @param f the field to pull attributes from
*/
public FieldAttributes(Field f) {
$Gson$Preconditions.checkNotNull(f);
this.field = f;
}
/**
* @return the declaring class that contains this field
*/
public Class<?> getDeclaringClass() {
return field.getDeclaringClass();
}
/**
* @return the name of the field
*/
public String getName() {
return field.getName();
}
/**
* <p>For example, assume the following class definition:
* <pre class="code">
* public class Foo {
* private String bar;
* private List&lt;String&gt; red;
* }
*
* Type listParmeterizedType = new TypeToken&lt;List&lt;String&gt;&gt;() {}.getType();
* </pre>
*
* <p>This method would return {@code String.class} for the {@code bar} field and
* {@code listParameterizedType} for the {@code red} field.
*
* @return the specific type declared for this field
*/
public Type getDeclaredType() {
return field.getGenericType();
}
/**
* Returns the {@code Class} object that was declared for this field.
*
* <p>For example, assume the following class definition:
* <pre class="code">
* public class Foo {
* private String bar;
* private List&lt;String&gt; red;
* }
* </pre>
*
* <p>This method would return {@code String.class} for the {@code bar} field and
* {@code List.class} for the {@code red} field.
*
* @return the specific class object that was declared for the field
*/
public Class<?> getDeclaredClass() {
return field.getType();
}
/**
* Return the {@code T} annotation object from this field if it exist; otherwise returns
* {@code null}.
*
* @param annotation the class of the annotation that will be retrieved
* @return the annotation instance if it is bound to the field; otherwise {@code null}
*/
public <T extends Annotation> T getAnnotation(Class<T> annotation) {
return field.getAnnotation(annotation);
}
/**
* Return the annotations that are present on this field.
*
* @return an array of all the annotations set on the field
* @since 1.4
*/
public Collection<Annotation> getAnnotations() {
return Arrays.asList(field.getAnnotations());
}
/**
* Returns {@code true} if the field is defined with the {@code modifier}.
*
* <p>This method is meant to be called as:
* <pre class="code">
* boolean hasPublicModifier = fieldAttribute.hasModifier(java.lang.reflect.Modifier.PUBLIC);
* </pre>
*
* @see java.lang.reflect.Modifier
*/
public boolean hasModifier(int modifier) {
return (field.getModifiers() & modifier) != 0;
}
/**
* This is exposed internally only for the removing synthetic fields from the JSON output.
*
* @return true if the field is synthetic; otherwise false
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
Object get(Object instance) throws IllegalAccessException {
return field.get(instance);
}
/**
* This is exposed internally only for the removing synthetic fields from the JSON output.
*
* @return true if the field is synthetic; otherwise false
*/
boolean isSynthetic() {
return field.isSynthetic();
}
}

View File

@ -1,173 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.lang.reflect.Field;
/**
* An enumeration that defines a few standard naming conventions for JSON field names.
* This enumeration should be used in conjunction with {@link com.google.gson.GsonBuilder}
* to configure a {@link com.google.gson.Gson} instance to properly translate Java field
* names into the desired JSON field names.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public enum FieldNamingPolicy implements FieldNamingStrategy {
/**
* Using this naming policy with Gson will ensure that the field name is
* unchanged.
*/
IDENTITY() {
@Override
public String translateName(Field f) {
return f.getName();
}
},
/**
* Using this naming policy with Gson will ensure that the first "letter" of the Java
* field name is capitalized when serialized to its JSON form.
*
* <p>Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
* <ul>
* <li>someFieldName ---> SomeFieldName</li>
* <li>_someFieldName ---> _SomeFieldName</li>
* </ul>
*/
UPPER_CAMEL_CASE() {
@Override
public String translateName(Field f) {
return upperCaseFirstLetter(f.getName());
}
},
/**
* Using this naming policy with Gson will ensure that the first "letter" of the Java
* field name is capitalized when serialized to its JSON form and the words will be
* separated by a space.
*
* <p>Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
* <ul>
* <li>someFieldName ---> Some Field Name</li>
* <li>_someFieldName ---> _Some Field Name</li>
* </ul>
*
* @since 1.4
*/
UPPER_CAMEL_CASE_WITH_SPACES() {
@Override
public String translateName(Field f) {
return upperCaseFirstLetter(separateCamelCase(f.getName(), " "));
}
},
/**
* Using this naming policy with Gson will modify the Java Field name from its camel cased
* form to a lower case field name where each word is separated by an underscore (_).
*
* <p>Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
* <ul>
* <li>someFieldName ---> some_field_name</li>
* <li>_someFieldName ---> _some_field_name</li>
* <li>aStringField ---> a_string_field</li>
* <li>aURL ---> a_u_r_l</li>
* </ul>
*/
LOWER_CASE_WITH_UNDERSCORES() {
@Override
public String translateName(Field f) {
return separateCamelCase(f.getName(), "_").toLowerCase();
}
},
/**
* Using this naming policy with Gson will modify the Java Field name from its camel cased
* form to a lower case field name where each word is separated by a dash (-).
*
* <p>Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
* <ul>
* <li>someFieldName ---> some-field-name</li>
* <li>_someFieldName ---> _some-field-name</li>
* <li>aStringField ---> a-string-field</li>
* <li>aURL ---> a-u-r-l</li>
* </ul>
* Using dashes in JavaScript is not recommended since dash is also used for a minus sign in
* expressions. This requires that a field named with dashes is always accessed as a quoted
* property like {@code myobject['my-field']}. Accessing it as an object field
* {@code myobject.my-field} will result in an unintended javascript expression.
* @since 1.4
*/
LOWER_CASE_WITH_DASHES() {
@Override
public String translateName(Field f) {
return separateCamelCase(f.getName(), "-").toLowerCase();
}
};
/**
* Converts the field name that uses camel-case define word separation into
* separate words that are separated by the provided {@code separatorString}.
*/
private static String separateCamelCase(String name, String separator) {
StringBuilder translation = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
char character = name.charAt(i);
if (Character.isUpperCase(character) && translation.length() != 0) {
translation.append(separator);
}
translation.append(character);
}
return translation.toString();
}
/**
* Ensures the JSON field names begins with an upper case letter.
*/
private static String upperCaseFirstLetter(String name) {
StringBuilder fieldNameBuilder = new StringBuilder();
int index = 0;
char firstCharacter = name.charAt(index);
while (index < name.length() - 1) {
if (Character.isLetter(firstCharacter)) {
break;
}
fieldNameBuilder.append(firstCharacter);
firstCharacter = name.charAt(++index);
}
if (index == name.length()) {
return fieldNameBuilder.toString();
}
if (!Character.isUpperCase(firstCharacter)) {
String modifiedTarget = modifyString(Character.toUpperCase(firstCharacter), name, ++index);
return fieldNameBuilder.append(modifiedTarget).toString();
} else {
return name;
}
}
private static String modifyString(char firstCharacter, String srcString, int indexOfSubstring) {
return (indexOfSubstring < srcString.length())
? firstCharacter + srcString.substring(indexOfSubstring)
: String.valueOf(firstCharacter);
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.lang.reflect.Field;
/**
* A mechanism for providing custom field naming in Gson. This allows the client code to translate
* field names into a particular convention that is not supported as a normal Java field
* declaration rules. For example, Java does not support "-" characters in a field name.
*
* @author Inderjeet Singh
* @author Joel Leitch
* @since 1.3
*/
public interface FieldNamingStrategy {
/**
* Translates the field name into its JSON field name representation.
*
* @param f the field object that we are translating
* @return the translated field name.
* @since 1.3
*/
public String translateName(Field f);
}

View File

@ -1,947 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.Primitives;
import com.google.gson.internal.Streams;
import com.google.gson.internal.bind.ArrayTypeAdapter;
import com.google.gson.internal.bind.CollectionTypeAdapterFactory;
import com.google.gson.internal.bind.DateTypeAdapter;
import com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory;
import com.google.gson.internal.bind.JsonTreeReader;
import com.google.gson.internal.bind.JsonTreeWriter;
import com.google.gson.internal.bind.MapTypeAdapterFactory;
import com.google.gson.internal.bind.ObjectTypeAdapter;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
import com.google.gson.internal.bind.SqlDateTypeAdapter;
import com.google.gson.internal.bind.TimeTypeAdapter;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.google.gson.stream.MalformedJsonException;
/**
* This is the main class for using Gson. Gson is typically used by first constructing a
* Gson instance and then invoking {@link #toJson(Object)} or {@link #fromJson(String, Class)}
* methods on it.
*
* <p>You can create a Gson instance by invoking {@code new Gson()} if the default configuration
* is all you need. You can also use {@link GsonBuilder} to build a Gson instance with various
* configuration options such as versioning support, pretty printing, custom
* {@link JsonSerializer}s, {@link JsonDeserializer}s, and {@link InstanceCreator}s.</p>
*
* <p>Here is an example of how Gson is used for a simple Class:
*
* <pre>
* Gson gson = new Gson(); // Or use new GsonBuilder().create();
* MyType target = new MyType();
* String json = gson.toJson(target); // serializes target to Json
* MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2
* </pre></p>
*
* <p>If the object that your are serializing/deserializing is a {@code ParameterizedType}
* (i.e. contains at least one type parameter and may be an array) then you must use the
* {@link #toJson(Object, Type)} or {@link #fromJson(String, Type)} method. Here is an
* example for serializing and deserialing a {@code ParameterizedType}:
*
* <pre>
* Type listType = new TypeToken&lt;List&lt;String&gt;&gt;() {}.getType();
* List&lt;String&gt; target = new LinkedList&lt;String&gt;();
* target.add("blah");
*
* Gson gson = new Gson();
* String json = gson.toJson(target, listType);
* List&lt;String&gt; target2 = gson.fromJson(json, listType);
* </pre></p>
*
* <p>See the <a href="https://sites.google.com/site/gson/gson-user-guide">Gson User Guide</a>
* for a more complete set of examples.</p>
*
* @see com.google.gson.reflect.TypeToken
*
* @author Inderjeet Singh
* @author Joel Leitch
* @author Jesse Wilson
*/
public final class Gson {
static final boolean DEFAULT_JSON_NON_EXECUTABLE = false;
private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n";
/**
* This thread local guards against reentrant calls to getAdapter(). In
* certain object graphs, creating an adapter for a type may recursively
* require an adapter for the same type! Without intervention, the recursive
* lookup would stack overflow. We cheat by returning a proxy type adapter.
* The proxy is wired up once the initial adapter has been created.
*/
private final ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>> calls
= new ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>>();
private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache
= Collections.synchronizedMap(new HashMap<TypeToken<?>, TypeAdapter<?>>());
/** Indicates whether Gson is in the phase of constructor invocation. It is used to determine
* whether to add a constructor in preconfiguredGeneratedTypeAdapter set or not. */
private boolean inConstructorPhase = true;
/** List of type adapters that are generated by Gson during its constructor */
private Set<TypeAdapter<?>> preconfiguredGeneratedTypeAdapters = new HashSet<TypeAdapter<?>>();
/** List of type adapters that are generated by Gson during toJson/fromJson. */
private final ThreadLocal<Set<TypeAdapter<?>>> runtimeGeneratedTypeAdapters =
new ThreadLocal<Set<TypeAdapter<?>>>();
private final List<TypeAdapterFactory> factories;
private final ConstructorConstructor constructorConstructor;
private final boolean serializeNulls;
private final boolean htmlSafe;
private final boolean generateNonExecutableJson;
private final boolean prettyPrinting;
final JsonDeserializationContext deserializationContext = new JsonDeserializationContext() {
@Override
@SuppressWarnings("unchecked")
public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException {
return (T) fromJson(json, typeOfT);
}
};
final JsonSerializationContext serializationContext = new JsonSerializationContext() {
@Override
public JsonElement serialize(Object src) {
return toJsonTree(src);
}
@Override
public JsonElement serialize(Object src, Type typeOfSrc) {
return toJsonTree(src, typeOfSrc);
}
};
/**
* Constructs a Gson object with default configuration. The default configuration has the
* following settings:
* <ul>
* <li>The JSON generated by <code>toJson</code> methods is in compact representation. This
* means that all the unneeded white-space is removed. You can change this behavior with
* {@link GsonBuilder#setPrettyPrinting()}. </li>
* <li>The generated JSON omits all the fields that are null. Note that nulls in arrays are
* kept as is since an array is an ordered list. Moreover, if a field is not null, but its
* generated JSON is empty, the field is kept. You can configure Gson to serialize null values
* by setting {@link GsonBuilder#serializeNulls()}.</li>
* <li>Gson provides default serialization and deserialization for Enums, {@link Map},
* {@link java.net.URL}, {@link java.net.URI}, {@link java.util.Locale}, {@link java.util.Date},
* {@link java.math.BigDecimal}, and {@link java.math.BigInteger} classes. If you would prefer
* to change the default representation, you can do so by registering a type adapter through
* {@link GsonBuilder#registerTypeAdapter(Type, Object)}. </li>
* <li>The default Date format is same as {@link java.text.DateFormat#DEFAULT}. This format
* ignores the millisecond portion of the date during serialization. You can change
* this by invoking {@link GsonBuilder#setDateFormat(int)} or
* {@link GsonBuilder#setDateFormat(String)}. </li>
* <li>By default, Gson ignores the {@link com.google.gson.annotations.Expose} annotation.
* You can enable Gson to serialize/deserialize only those fields marked with this annotation
* through {@link GsonBuilder#excludeFieldsWithoutExposeAnnotation()}. </li>
* <li>By default, Gson ignores the {@link com.google.gson.annotations.Since} annotation. You
* can enable Gson to use this annotation through {@link GsonBuilder#setVersion(double)}.</li>
* <li>The default field naming policy for the output Json is same as in Java. So, a Java class
* field <code>versionNumber</code> will be output as <code>&quot;versionNumber&quot;</code> in
* Json. The same rules are applied for mapping incoming Json to the Java classes. You can
* change this policy through {@link GsonBuilder#setFieldNamingPolicy(FieldNamingPolicy)}.</li>
* <li>By default, Gson excludes <code>transient</code> or <code>static</code> fields from
* consideration for serialization and deserialization. You can change this behavior through
* {@link GsonBuilder#excludeFieldsWithModifiers(int...)}.</li>
* </ul>
*/
public Gson() {
this(Excluder.DEFAULT, FieldNamingPolicy.IDENTITY,
Collections.<Type, InstanceCreator<?>>emptyMap(), false, false, DEFAULT_JSON_NON_EXECUTABLE,
true, false, false, LongSerializationPolicy.DEFAULT,
Collections.<TypeAdapterFactory>emptyList());
}
Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingPolicy,
final Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean prettyPrinting, boolean serializeSpecialFloatingPointValues,
LongSerializationPolicy longSerializationPolicy,
List<TypeAdapterFactory> typeAdapterFactories) {
this.constructorConstructor = new ConstructorConstructor(instanceCreators);
this.serializeNulls = serializeNulls;
this.generateNonExecutableJson = generateNonExecutableGson;
this.htmlSafe = htmlSafe;
this.prettyPrinting = prettyPrinting;
List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
// the excluder must precede all adapters that handle user-defined types
factories.add(excluder);
// user's type adapters
factories.addAll(typeAdapterFactories);
// type adapters for basic platform types
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);
factories.add(TypeAdapters.BOOLEAN_FACTORY);
factories.add(TypeAdapters.BYTE_FACTORY);
factories.add(TypeAdapters.SHORT_FACTORY);
factories.add(TypeAdapters.newFactory(long.class, Long.class,
longAdapter(longSerializationPolicy)));
factories.add(TypeAdapters.newFactory(double.class, Double.class,
doubleAdapter(serializeSpecialFloatingPointValues)));
factories.add(TypeAdapters.newFactory(float.class, Float.class,
floatAdapter(serializeSpecialFloatingPointValues)));
factories.add(TypeAdapters.NUMBER_FACTORY);
factories.add(TypeAdapters.CHARACTER_FACTORY);
factories.add(TypeAdapters.STRING_BUILDER_FACTORY);
factories.add(TypeAdapters.STRING_BUFFER_FACTORY);
factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));
factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));
factories.add(TypeAdapters.URL_FACTORY);
factories.add(TypeAdapters.URI_FACTORY);
factories.add(TypeAdapters.UUID_FACTORY);
factories.add(TypeAdapters.LOCALE_FACTORY);
factories.add(TypeAdapters.INET_ADDRESS_FACTORY);
factories.add(TypeAdapters.BIT_SET_FACTORY);
factories.add(DateTypeAdapter.FACTORY);
factories.add(TypeAdapters.CALENDAR_FACTORY);
factories.add(TimeTypeAdapter.FACTORY);
factories.add(SqlDateTypeAdapter.FACTORY);
factories.add(TypeAdapters.TIMESTAMP_FACTORY);
factories.add(ArrayTypeAdapter.FACTORY);
factories.add(TypeAdapters.ENUM_FACTORY);
factories.add(TypeAdapters.CLASS_FACTORY);
// type adapters for composite and user-defined types
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
factories.add(new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor));
factories.add(new ReflectiveTypeAdapterFactory(
constructorConstructor, fieldNamingPolicy, excluder));
this.factories = Collections.unmodifiableList(factories);
this.preconfiguredGeneratedTypeAdapters = Collections.unmodifiableSet(preconfiguredGeneratedTypeAdapters);
inConstructorPhase = false;
}
private TypeAdapter<Number> doubleAdapter(boolean serializeSpecialFloatingPointValues) {
if (serializeSpecialFloatingPointValues) {
return TypeAdapters.DOUBLE;
}
return new TypeAdapter<Number>() {
@Override public Double read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return in.nextDouble();
}
@Override public void write(JsonWriter out, Number value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
double doubleValue = value.doubleValue();
checkValidFloatingPoint(doubleValue);
out.value(value);
}
};
}
private TypeAdapter<Number> floatAdapter(boolean serializeSpecialFloatingPointValues) {
if (serializeSpecialFloatingPointValues) {
return TypeAdapters.FLOAT;
}
return new TypeAdapter<Number>() {
@Override public Float read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return (float) in.nextDouble();
}
@Override public void write(JsonWriter out, Number value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
float floatValue = value.floatValue();
checkValidFloatingPoint(floatValue);
out.value(value);
}
};
}
private void checkValidFloatingPoint(double value) {
if (Double.isNaN(value) || Double.isInfinite(value)) {
throw new IllegalArgumentException(value
+ " is not a valid double value as per JSON specification. To override this"
+ " behavior, use GsonBuilder.serializeSpecialFloatingPointValues() method.");
}
}
private TypeAdapter<Number> longAdapter(LongSerializationPolicy longSerializationPolicy) {
if (longSerializationPolicy == LongSerializationPolicy.DEFAULT) {
return TypeAdapters.LONG;
}
return new TypeAdapter<Number>() {
@Override public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return in.nextLong();
}
@Override public void write(JsonWriter out, Number value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
out.value(value.toString());
}
};
}
/**
* Returns the type adapter for {@code} type.
*
* @throws IllegalArgumentException if this GSON cannot serialize and
* deserialize {@code type}.
*/
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
TypeAdapter<?> cached = typeTokenCache.get(type);
if (cached != null) {
return (TypeAdapter<T>) cached;
}
Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get();
boolean requiresThreadLocalCleanup = false;
if (threadCalls == null) {
threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>();
calls.set(threadCalls);
requiresThreadLocalCleanup = true;
}
// the key and value type parameters always agree
FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);
if (ongoingCall != null) {
return ongoingCall;
}
try {
FutureTypeAdapter<T> call = new FutureTypeAdapter<T>();
threadCalls.put(type, call);
for (TypeAdapterFactory factory : factories) {
TypeAdapter<T> candidate = factory.create(this, type);
if (candidate != null) {
call.setDelegate(candidate);
typeTokenCache.put(type, candidate);
return candidate;
}
}
throw new IllegalArgumentException("GSON cannot handle " + type);
} finally {
threadCalls.remove(type);
if (requiresThreadLocalCleanup) {
calls.remove();
}
}
}
/**
* This method is used to get an alternate type adapter for the specified type. This is used
* to access a type adapter that is overridden by a {@link TypeAdapterFactory} that you
* may have registered. This features is typically used when you want to register a type
* adapter that does a little bit of work but then delegates further processing to the Gson
* default type adapter. Here is an example:
* <p>Let's say we want to write a type adapter that counts the number of objects being read
* from or written to JSON. We can achieve this by writing a type adapter factory that uses
* the <code>getDelegateAdapter</code> method:
* <pre> {@code
* class StatsTypeAdapterFactory implements TypeAdapterFactory {
* public int numReads = 0;
* public int numWrites = 0;
* public &lt;T&gt; TypeAdapter&lt;T&gt; create(Gson gson, TypeToken&lt;T&gt; type) {
* final TypeAdapter&lt;T&gt; delegate = gson.getDelegateAdapter(this, type);
* return new TypeAdapter&lt;T&gt;() {
* public void write(JsonWriter out, T value) throws IOException {
* ++numWrites;
* delegate.write(out, value);
* }
* public T read(JsonReader in) throws IOException {
* ++numReads;
* return delegate.read(in);
* }
* };
* }
* }
* } </pre>
* This factory can now be used like this:
* <pre> {@code
* StatsTypeAdapterFactory stats = new StatsTypeAdapterFactory();
* Gson gson = new GsonBuilder().registerTypeAdapterFactory(stats).create();
* // Call gson.toJson() and fromJson methods on objects
* System.out.println("Num JSON reads" + stats.numReads);
* System.out.println("Num JSON writes" + stats.numWrites);
* }</pre>
* Note that since you can not override type adapter factories for String and Java primitive
* types, our stats factory will not count the number of String or primitives that will be
* read or written.
* @param skipPast The type adapter factory that needs to be skipped while searching for
* a matching type adapter. In most cases, you should just pass <i>this</i> (the type adapter
* factory from where {@link #getDelegateAdapter} method is being invoked).
* @param type Type for which the delegate adapter is being searched for.
*
* @since 2.2
*/
public <T> TypeAdapter<T> getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken<T> type) {
boolean skipPastFound = false;
for (TypeAdapterFactory factory : factories) {
if (!skipPastFound) {
if (factory == skipPast) {
skipPastFound = true;
}
continue;
}
TypeAdapter<T> candidate = factory.create(this, type);
if (candidate != null) {
return candidate;
}
}
throw new IllegalArgumentException("GSON cannot serialize " + type);
}
/**
* Returns the type adapter for {@code} type.
*
* @throws IllegalArgumentException if this GSON cannot serialize and
* deserialize {@code type}.
*/
public <T> TypeAdapter<T> getAdapter(Class<T> type) {
return getAdapter(TypeToken.get(type));
}
/**
* This method serializes the specified object into its equivalent representation as a tree of
* {@link JsonElement}s. This method should be used when the specified object is not a generic
* type. This method uses {@link Class#getClass()} to get the type for the specified object, but
* the {@code getClass()} loses the generic type information because of the Type Erasure feature
* of Java. Note that this method works fine if the any of the object fields are of generic type,
* just the object itself should not be of a generic type. If the object is of generic type, use
* {@link #toJsonTree(Object, Type)} instead.
*
* @param src the object for which Json representation is to be created setting for Gson
* @return Json representation of {@code src}.
* @since 1.4
*/
public JsonElement toJsonTree(Object src) {
if (src == null) {
return JsonNull.INSTANCE;
}
return toJsonTree(src, src.getClass());
}
/**
* This method serializes the specified object, including those of generic types, into its
* equivalent representation as a tree of {@link JsonElement}s. This method must be used if the
* specified object is a generic type. For non-generic objects, use {@link #toJsonTree(Object)}
* instead.
*
* @param src the object for which JSON representation is to be created
* @param typeOfSrc The specific genericized type of src. You can obtain
* this type by using the {@link com.google.gson.reflect.TypeToken} class. For example,
* to get the type for {@code Collection<Foo>}, you should use:
* <pre>
* Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
* </pre>
* @return Json representation of {@code src}
* @since 1.4
*/
public JsonElement toJsonTree(Object src, Type typeOfSrc) {
JsonTreeWriter writer = new JsonTreeWriter();
toJson(src, typeOfSrc, writer);
return writer.get();
}
/**
* This method serializes the specified object into its equivalent Json representation.
* This method should be used when the specified object is not a generic type. This method uses
* {@link Class#getClass()} to get the type for the specified object, but the
* {@code getClass()} loses the generic type information because of the Type Erasure feature
* of Java. Note that this method works fine if the any of the object fields are of generic type,
* just the object itself should not be of a generic type. If the object is of generic type, use
* {@link #toJson(Object, Type)} instead. If you want to write out the object to a
* {@link Writer}, use {@link #toJson(Object, Appendable)} instead.
*
* @param src the object for which Json representation is to be created setting for Gson
* @return Json representation of {@code src}.
*/
public String toJson(Object src) {
if (src == null) {
return toJson(JsonNull.INSTANCE);
}
return toJson(src, src.getClass());
}
/**
* This method serializes the specified object, including those of generic types, into its
* equivalent Json representation. This method must be used if the specified object is a generic
* type. For non-generic objects, use {@link #toJson(Object)} instead. If you want to write out
* the object to a {@link Appendable}, use {@link #toJson(Object, Type, Appendable)} instead.
*
* @param src the object for which JSON representation is to be created
* @param typeOfSrc The specific genericized type of src. You can obtain
* this type by using the {@link com.google.gson.reflect.TypeToken} class. For example,
* to get the type for {@code Collection<Foo>}, you should use:
* <pre>
* Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
* </pre>
* @return Json representation of {@code src}
*/
public String toJson(Object src, Type typeOfSrc) {
StringWriter writer = new StringWriter();
toJson(src, typeOfSrc, writer);
return writer.toString();
}
/**
* This method serializes the specified object into its equivalent Json representation.
* This method should be used when the specified object is not a generic type. This method uses
* {@link Class#getClass()} to get the type for the specified object, but the
* {@code getClass()} loses the generic type information because of the Type Erasure feature
* of Java. Note that this method works fine if the any of the object fields are of generic type,
* just the object itself should not be of a generic type. If the object is of generic type, use
* {@link #toJson(Object, Type, Appendable)} instead.
*
* @param src the object for which Json representation is to be created setting for Gson
* @param writer Writer to which the Json representation needs to be written
* @throws JsonIOException if there was a problem writing to the writer
* @since 1.2
*/
public void toJson(Object src, Appendable writer) throws JsonIOException {
if (src != null) {
toJson(src, src.getClass(), writer);
} else {
toJson(JsonNull.INSTANCE, writer);
}
}
/**
* This method serializes the specified object, including those of generic types, into its
* equivalent Json representation. This method must be used if the specified object is a generic
* type. For non-generic objects, use {@link #toJson(Object, Appendable)} instead.
*
* @param src the object for which JSON representation is to be created
* @param typeOfSrc The specific genericized type of src. You can obtain
* this type by using the {@link com.google.gson.reflect.TypeToken} class. For example,
* to get the type for {@code Collection<Foo>}, you should use:
* <pre>
* Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
* </pre>
* @param writer Writer to which the Json representation of src needs to be written.
* @throws JsonIOException if there was a problem writing to the writer
* @since 1.2
*/
public void toJson(Object src, Type typeOfSrc, Appendable writer) throws JsonIOException {
try {
JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer));
toJson(src, typeOfSrc, jsonWriter);
} catch (IOException e) {
throw new JsonIOException(e);
}
}
/**
* Writes the JSON representation of {@code src} of type {@code typeOfSrc} to
* {@code writer}.
* @throws JsonIOException if there was a problem writing to the writer
*/
@SuppressWarnings("unchecked")
public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
TypeAdapter<?> adapter = getAdapter(TypeToken.get(typeOfSrc));
boolean oldLenient = writer.isLenient();
writer.setLenient(true);
boolean oldHtmlSafe = writer.isHtmlSafe();
writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
// Log.i("json", oldSerializeNulls+"==============");
writer.setSerializeNulls(true);//齐济修改生成key值value为空也生成
try {
((TypeAdapter<Object>) adapter).write(writer, src);
} catch (IOException e) {
throw new JsonIOException(e);
} finally {
writer.setLenient(oldLenient);
writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls);
}
}
/**
* Converts a tree of {@link JsonElement}s into its equivalent JSON representation.
*
* @param jsonElement root of a tree of {@link JsonElement}s
* @return JSON String representation of the tree
* @since 1.4
*/
public String toJson(JsonElement jsonElement) {
StringWriter writer = new StringWriter();
toJson(jsonElement, writer);
return writer.toString();
}
/**
* Writes out the equivalent JSON for a tree of {@link JsonElement}s.
*
* @param jsonElement root of a tree of {@link JsonElement}s
* @param writer Writer to which the Json representation needs to be written
* @throws JsonIOException if there was a problem writing to the writer
* @since 1.4
*/
public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOException {
try {
JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer));
toJson(jsonElement, jsonWriter);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Returns a new JSON writer configured for this GSON and with the non-execute
* prefix if that is configured.
*/
private JsonWriter newJsonWriter(Writer writer) throws IOException {
if (generateNonExecutableJson) {
writer.write(JSON_NON_EXECUTABLE_PREFIX);
}
JsonWriter jsonWriter = new JsonWriter(writer);
if (prettyPrinting) {
jsonWriter.setIndent(" ");
}
jsonWriter.setSerializeNulls(serializeNulls);
return jsonWriter;
}
/**
* Writes the JSON for {@code jsonElement} to {@code writer}.
* @throws JsonIOException if there was a problem writing to the writer
*/
public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException {
boolean oldLenient = writer.isLenient();
writer.setLenient(true);
boolean oldHtmlSafe = writer.isHtmlSafe();
writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
writer.setSerializeNulls(serializeNulls);
try {
Streams.write(jsonElement, writer);
} catch (IOException e) {
throw new JsonIOException(e);
} finally {
writer.setLenient(oldLenient);
writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls);
}
}
/**
* This method deserializes the specified Json into an object of the specified class. It is not
* suitable to use if the specified class is a generic type since it will not have the generic
* type information because of the Type Erasure feature of Java. Therefore, this method should not
* be used if the desired type is a generic type. Note that this method works fine if the any of
* the fields of the specified object are generics, just the object itself should not be a
* generic type. For the cases when the object is of generic type, invoke
* {@link #fromJson(String, Type)}. If you have the Json in a {@link Reader} instead of
* a String, use {@link #fromJson(Reader, Class)} instead.
*
* @param <T> the type of the desired object
* @param json the string from which the object is to be deserialized
* @param classOfT the class of T
* @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null}.
* @throws JsonSyntaxException if json is not a valid representation for an object of type
* classOfT
*/
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
Object object = fromJson(json, (Type) classOfT);
return Primitives.wrap(classOfT).cast(object);
}
/**
* This method deserializes the specified Json into an object of the specified type. This method
* is useful if the specified object is a generic type. For non-generic objects, use
* {@link #fromJson(String, Class)} instead. If you have the Json in a {@link Reader} instead of
* a String, use {@link #fromJson(Reader, Type)} instead.
*
* @param <T> the type of the desired object
* @param json the string from which the object is to be deserialized
* @param typeOfT The specific genericized type of src. You can obtain this type by using the
* {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for
* {@code Collection<Foo>}, you should use:
* <pre>
* Type typeOfT = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
* </pre>
* @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null}.
* @throws JsonParseException if json is not a valid representation for an object of type typeOfT
* @throws JsonSyntaxException if json is not a valid representation for an object of type
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
if (json == null) {
return null;
}
StringReader reader = new StringReader(json);
T target = (T) fromJson(reader, typeOfT);
return target;
}
/**
* This method deserializes the Json read from the specified reader into an object of the
* specified class. It is not suitable to use if the specified class is a generic type since it
* will not have the generic type information because of the Type Erasure feature of Java.
* Therefore, this method should not be used if the desired type is a generic type. Note that
* this method works fine if the any of the fields of the specified object are generics, just the
* object itself should not be a generic type. For the cases when the object is of generic type,
* invoke {@link #fromJson(Reader, Type)}. If you have the Json in a String form instead of a
* {@link Reader}, use {@link #fromJson(String, Class)} instead.
*
* @param <T> the type of the desired object
* @param json the reader producing the Json from which the object is to be deserialized.
* @param classOfT the class of T
* @return an object of type T from the string. Returns {@code null} if {@code json} is at EOF.
* @throws JsonIOException if there was a problem reading from the Reader
* @throws JsonSyntaxException if json is not a valid representation for an object of type
* @since 1.2
*/
public <T> T fromJson(Reader json, Class<T> classOfT) throws JsonSyntaxException, JsonIOException {
JsonReader jsonReader = new JsonReader(json);
Object object = fromJson(jsonReader, classOfT);
assertFullConsumption(object, jsonReader);
return Primitives.wrap(classOfT).cast(object);
}
/**
* This method deserializes the Json read from the specified reader into an object of the
* specified type. This method is useful if the specified object is a generic type. For
* non-generic objects, use {@link #fromJson(Reader, Class)} instead. If you have the Json in a
* String form instead of a {@link Reader}, use {@link #fromJson(String, Type)} instead.
*
* @param <T> the type of the desired object
* @param json the reader producing Json from which the object is to be deserialized
* @param typeOfT The specific genericized type of src. You can obtain this type by using the
* {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for
* {@code Collection<Foo>}, you should use:
* <pre>
* Type typeOfT = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
* </pre>
* @return an object of type T from the json. Returns {@code null} if {@code json} is at EOF.
* @throws JsonIOException if there was a problem reading from the Reader
* @throws JsonSyntaxException if json is not a valid representation for an object of type
* @since 1.2
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
JsonReader jsonReader = new JsonReader(json);
T object = (T) fromJson(jsonReader, typeOfT);
assertFullConsumption(object, jsonReader);
return object;
}
private static void assertFullConsumption(Object obj, JsonReader reader) {
try {
if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
}
} catch (MalformedJsonException e) {
throw new JsonSyntaxException(e);
} catch (IOException e) {
throw new JsonIOException(e);
}
}
/**
* Reads the next JSON value from {@code reader} and convert it to an object
* of type {@code typeOfT}. Returns {@code null}, if the {@code reader} is at EOF.
* Since Type is not parameterized by T, this method is type unsafe and should be used carefully
*
* @throws JsonIOException if there was a problem writing to the Reader
* @throws JsonSyntaxException if json is not a valid representation for an object of type
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true;
boolean oldLenient = reader.isLenient();
reader.setLenient(true);
try {
reader.peek();
isEmpty = false;
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);
return object;
} catch (EOFException e) {
/*
* For compatibility with JSON 1.5 and earlier, we return null for empty
* documents instead of throwing.
*/
if (isEmpty) {
return null;
}
throw new JsonSyntaxException(e);
} catch (IllegalStateException e) {
throw new JsonSyntaxException(e);
} catch (IOException e) {
// TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxException
throw new JsonSyntaxException(e);
} finally {
reader.setLenient(oldLenient);
}
}
/**
* This method deserializes the Json read from the specified parse tree into an object of the
* specified type. It is not suitable to use if the specified class is a generic type since it
* will not have the generic type information because of the Type Erasure feature of Java.
* Therefore, this method should not be used if the desired type is a generic type. Note that
* this method works fine if the any of the fields of the specified object are generics, just the
* object itself should not be a generic type. For the cases when the object is of generic type,
* invoke {@link #fromJson(JsonElement, Type)}.
* @param <T> the type of the desired object
* @param json the root of the parse tree of {@link JsonElement}s from which the object is to
* be deserialized
* @param classOfT The class of T
* @return an object of type T from the json. Returns {@code null} if {@code json} is {@code null}.
* @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT
* @since 1.3
*/
public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
Object object = fromJson(json, (Type) classOfT);
return Primitives.wrap(classOfT).cast(object);
}
/**
* This method deserializes the Json read from the specified parse tree into an object of the
* specified type. This method is useful if the specified object is a generic type. For
* non-generic objects, use {@link #fromJson(JsonElement, Class)} instead.
*
* @param <T> the type of the desired object
* @param json the root of the parse tree of {@link JsonElement}s from which the object is to
* be deserialized
* @param typeOfT The specific genericized type of src. You can obtain this type by using the
* {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for
* {@code Collection<Foo>}, you should use:
* <pre>
* Type typeOfT = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
* </pre>
* @return an object of type T from the json. Returns {@code null} if {@code json} is {@code null}.
* @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT
* @since 1.3
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException {
if (json == null) {
return null;
}
return (T) fromJson(new JsonTreeReader(json), typeOfT);
}
static class FutureTypeAdapter<T> extends TypeAdapter<T> {
private TypeAdapter<T> delegate;
public void setDelegate(TypeAdapter<T> typeAdapter) {
if (delegate != null) {
throw new AssertionError();
}
delegate = typeAdapter;
}
@Override public T read(JsonReader in) throws IOException {
if (delegate == null) {
throw new IllegalStateException();
}
return delegate.read(in);
}
@Override public void write(JsonWriter out, T value) throws IOException {
if (delegate == null) {
throw new IllegalStateException();
}
delegate.write(out, value);
}
}
@Override
public String toString() {
return new StringBuilder("{serializeNulls:")
.append(serializeNulls)
.append("factories:").append(factories)
.append(",instanceCreators:").append(constructorConstructor)
.append("}")
.toString();
}
public static final class $$Internal {
public static void addGeneratedTypeAdapter(Gson gson, TypeAdapter<?> typeAdapter) {
if (gson.inConstructorPhase) {
gson.preconfiguredGeneratedTypeAdapters.add(typeAdapter);
} else {
Set<TypeAdapter<?>> adapters = getRuntimeGeneratedTypeAdapters(gson);
adapters.add(typeAdapter);
}
}
public static boolean isGeneratedTypeAdapter(Gson gson, TypeAdapter<?> typeAdapter) {
boolean generated = gson.preconfiguredGeneratedTypeAdapters.contains(typeAdapter);
if (!generated) generated = getRuntimeGeneratedTypeAdapters(gson).contains(typeAdapter);
return generated;
}
private static Set<TypeAdapter<?>> getRuntimeGeneratedTypeAdapters(Gson gson) {
Set<TypeAdapter<?>> adapters = gson.runtimeGeneratedTypeAdapters.get();
if (adapters == null) adapters = new HashSet<TypeAdapter<?>>();
gson.runtimeGeneratedTypeAdapters.set(adapters);
return adapters;
}
}
}

View File

@ -1,566 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.reflect.TypeToken;
/**
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration
* options other than the default. For {@link Gson} with default configuration, it is simpler to
* use {@code new Gson()}. {@code GsonBuilder} is best used by creating it, and then invoking its
* various configuration methods, and finally calling create.</p>
*
* <p>The following is an example shows how to use the {@code GsonBuilder} to construct a Gson
* instance:
*
* <pre>
* Gson gson = new GsonBuilder()
* .registerTypeAdapter(Id.class, new IdTypeAdapter())
* .enableComplexMapKeySerialization()
* .serializeNulls()
* .setDateFormat(DateFormat.LONG)
* .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
* .setPrettyPrinting()
* .setVersion(1.0)
* .create();
* </pre></p>
*
* <p>NOTES:
* <ul>
* <li> the order of invocation of configuration methods does not matter.</li>
* <li> The default serialization of {@link Date} and its subclasses in Gson does
* not contain time-zone information. So, if you are using date/time instances,
* use {@code GsonBuilder} and its {@code setDateFormat} methods.</li>
* </ul>
* </p>
*
* @author Inderjeet Singh
* @author Joel Leitch
* @author Jesse Wilson
*/
public final class GsonBuilder {
private Excluder excluder = Excluder.DEFAULT;
private LongSerializationPolicy longSerializationPolicy = LongSerializationPolicy.DEFAULT;
private FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
private final Map<Type, InstanceCreator<?>> instanceCreators
= new HashMap<Type, InstanceCreator<?>>();
private final List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
/** tree-style hierarchy factories. These come after factories for backwards compatibility. */
private final List<TypeAdapterFactory> hierarchyFactories = new ArrayList<TypeAdapterFactory>();
private boolean serializeNulls;
private String datePattern;
private int dateStyle = DateFormat.DEFAULT;
private int timeStyle = DateFormat.DEFAULT;
private boolean complexMapKeySerialization;
private boolean serializeSpecialFloatingPointValues;
private boolean escapeHtmlChars = true;
private boolean prettyPrinting;
private boolean generateNonExecutableJson;
/**
* Creates a GsonBuilder instance that can be used to build Gson with various configuration
* settings. GsonBuilder follows the builder pattern, and it is typically used by first
* invoking various configuration methods to set desired options, and finally calling
* {@link #create()}.
*/
public GsonBuilder() {
}
/**
* Configures Gson to enable versioning support.
*
* @param ignoreVersionsAfter any field or type marked with a version higher than this value
* are ignored during serialization or deserialization.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
public GsonBuilder setVersion(double ignoreVersionsAfter) {
excluder = excluder.withVersion(ignoreVersionsAfter);
return this;
}
/**
* Configures Gson to excludes all class fields that have the specified modifiers. By default,
* Gson will exclude all fields marked transient or static. This method will override that
* behavior.
*
* @param modifiers the field modifiers. You must use the modifiers specified in the
* {@link java.lang.reflect.Modifier} class. For example,
* {@link java.lang.reflect.Modifier#TRANSIENT},
* {@link java.lang.reflect.Modifier#STATIC}.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
public GsonBuilder excludeFieldsWithModifiers(int... modifiers) {
excluder = excluder.withModifiers(modifiers);
return this;
}
/**
* Makes the output JSON non-executable in Javascript by prefixing the generated JSON with some
* special text. This prevents attacks from third-party sites through script sourcing. See
* <a href="http://code.google.com/p/google-gson/issues/detail?id=42">Gson Issue 42</a>
* for details.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public GsonBuilder generateNonExecutableJson() {
this.generateNonExecutableJson = true;
return this;
}
/**
* Configures Gson to exclude all fields from consideration for serialization or deserialization
* that do not have the {@link com.google.gson.annotations.Expose} annotation.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
public GsonBuilder excludeFieldsWithoutExposeAnnotation() {
excluder = excluder.excludeFieldsWithoutExposeAnnotation();
return this;
}
/**
* Configure Gson to serialize null fields. By default, Gson omits all fields that are null
* during serialization.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.2
*/
public GsonBuilder serializeNulls() {
this.serializeNulls = true;
return this;
}
/**
* Enabling this feature will only change the serialized form if the map key is
* a complex type (i.e. non-primitive) in its <strong>serialized</strong> JSON
* form. The default implementation of map serialization uses {@code toString()}
* on the key; however, when this is called then one of the following cases
* apply:
*
* <h3>Maps as JSON objects</h3>
* For this case, assume that a type adapter is registered to serialize and
* deserialize some {@code Point} class, which contains an x and y coordinate,
* to/from the JSON Primitive string value {@code "(x,y)"}. The Java map would
* then be serialized as a {@link JsonObject}.
*
* <p>Below is an example:
* <pre> {@code
* Gson gson = new GsonBuilder()
* .register(Point.class, new MyPointTypeAdapter())
* .enableComplexMapKeySerialization()
* .create();
*
* Map<Point, String> original = new LinkedHashMap<Point, String>();
* original.put(new Point(5, 6), "a");
* original.put(new Point(8, 8), "b");
* System.out.println(gson.toJson(original, type));
* }</pre>
* The above code prints this JSON object:<pre> {@code
* {
* "(5,6)": "a",
* "(8,8)": "b"
* }
* }</pre>
*
* <h3>Maps as JSON arrays</h3>
* For this case, assume that a type adapter was NOT registered for some
* {@code Point} class, but rather the default Gson serialization is applied.
* In this case, some {@code new Point(2,3)} would serialize as {@code
* {"x":2,"y":5}}.
*
* <p>Given the assumption above, a {@code Map<Point, String>} will be
* serialize as an array of arrays (can be viewed as an entry set of pairs).
*
* <p>Below is an example of serializing complex types as JSON arrays:
* <pre> {@code
* Gson gson = new GsonBuilder()
* .enableComplexMapKeySerialization()
* .create();
*
* Map<Point, String> original = new LinkedHashMap<Point, String>();
* original.put(new Point(5, 6), "a");
* original.put(new Point(8, 8), "b");
* System.out.println(gson.toJson(original, type));
* }
*
* The JSON output would look as follows:
* <pre> {@code
* [
* [
* {
* "x": 5,
* "y": 6
* },
* "a"
* ],
* [
* {
* "x": 8,
* "y": 8
* },
* "b"
* ]
* ]
* }</pre>
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.7
*/
public GsonBuilder enableComplexMapKeySerialization() {
complexMapKeySerialization = true;
return this;
}
/**
* Configures Gson to exclude inner classes during serialization.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public GsonBuilder disableInnerClassSerialization() {
excluder = excluder.disableInnerClassSerialization();
return this;
}
/**
* Configures Gson to apply a specific serialization policy for {@code Long} and {@code long}
* objects.
*
* @param serializationPolicy the particular policy to use for serializing longs.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public GsonBuilder setLongSerializationPolicy(LongSerializationPolicy serializationPolicy) {
this.longSerializationPolicy = serializationPolicy;
return this;
}
/**
* Configures Gson to apply a specific naming policy to an object's field during serialization
* and deserialization.
*
* @param namingConvention the JSON field naming convention to use for serialization and
* deserialization.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
public GsonBuilder setFieldNamingPolicy(FieldNamingPolicy namingConvention) {
this.fieldNamingPolicy = namingConvention;
return this;
}
/**
* Configures Gson to apply a specific naming policy strategy to an object's field during
* serialization and deserialization.
*
* @param fieldNamingStrategy the actual naming strategy to apply to the fields
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public GsonBuilder setFieldNamingStrategy(FieldNamingStrategy fieldNamingStrategy) {
this.fieldNamingPolicy = fieldNamingStrategy;
return this;
}
/**
* Configures Gson to apply a set of exclusion strategies during both serialization and
* deserialization. Each of the {@code strategies} will be applied as a disjunction rule.
* This means that if one of the {@code strategies} suggests that a field (or class) should be
* skipped then that field (or object) is skipped during serializaiton/deserialization.
*
* @param strategies the set of strategy object to apply during object (de)serialization.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.4
*/
public GsonBuilder setExclusionStrategies(ExclusionStrategy... strategies) {
for (ExclusionStrategy strategy : strategies) {
excluder = excluder.withExclusionStrategy(strategy, true, true);
}
return this;
}
/**
* Configures Gson to apply the passed in exclusion strategy during serialization.
* If this method is invoked numerous times with different exclusion strategy objects
* then the exclusion strategies that were added will be applied as a disjunction rule.
* This means that if one of the added exclusion strategies suggests that a field (or
* class) should be skipped then that field (or object) is skipped during its
* serialization.
*
* @param strategy an exclusion strategy to apply during serialization.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.7
*/
public GsonBuilder addSerializationExclusionStrategy(ExclusionStrategy strategy) {
excluder = excluder.withExclusionStrategy(strategy, true, false);
return this;
}
/**
* Configures Gson to apply the passed in exclusion strategy during deserialization.
* If this method is invoked numerous times with different exclusion strategy objects
* then the exclusion strategies that were added will be applied as a disjunction rule.
* This means that if one of the added exclusion strategies suggests that a field (or
* class) should be skipped then that field (or object) is skipped during its
* deserialization.
*
* @param strategy an exclusion strategy to apply during deserialization.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.7
*/
public GsonBuilder addDeserializationExclusionStrategy(ExclusionStrategy strategy) {
excluder = excluder.withExclusionStrategy(strategy, false, true);
return this;
}
/**
* Configures Gson to output Json that fits in a page for pretty printing. This option only
* affects Json serialization.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
public GsonBuilder setPrettyPrinting() {
prettyPrinting = true;
return this;
}
/**
* By default, Gson escapes HTML characters such as &lt; &gt; etc. Use this option to configure
* Gson to pass-through HTML characters as is.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public GsonBuilder disableHtmlEscaping() {
this.escapeHtmlChars = false;
return this;
}
/**
* Configures Gson to serialize {@code Date} objects according to the pattern provided. You can
* call this method or {@link #setDateFormat(int)} multiple times, but only the last invocation
* will be used to decide the serialization format.
*
* <p>The date format will be used to serialize and deserialize {@link java.util.Date}, {@link
* java.sql.Timestamp} and {@link java.sql.Date}.
*
* <p>Note that this pattern must abide by the convention provided by {@code SimpleDateFormat}
* class. See the documentation in {@link java.text.SimpleDateFormat} for more information on
* valid date and time patterns.</p>
*
* @param pattern the pattern that dates will be serialized/deserialized to/from
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.2
*/
public GsonBuilder setDateFormat(String pattern) {
// TODO(Joel): Make this fail fast if it is an invalid date format
this.datePattern = pattern;
return this;
}
/**
* Configures Gson to to serialize {@code Date} objects according to the style value provided.
* You can call this method or {@link #setDateFormat(String)} multiple times, but only the last
* invocation will be used to decide the serialization format.
*
* <p>Note that this style value should be one of the predefined constants in the
* {@code DateFormat} class. See the documentation in {@link java.text.DateFormat} for more
* information on the valid style constants.</p>
*
* @param style the predefined date style that date objects will be serialized/deserialized
* to/from
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.2
*/
public GsonBuilder setDateFormat(int style) {
this.dateStyle = style;
this.datePattern = null;
return this;
}
/**
* Configures Gson to to serialize {@code Date} objects according to the style value provided.
* You can call this method or {@link #setDateFormat(String)} multiple times, but only the last
* invocation will be used to decide the serialization format.
*
* <p>Note that this style value should be one of the predefined constants in the
* {@code DateFormat} class. See the documentation in {@link java.text.DateFormat} for more
* information on the valid style constants.</p>
*
* @param dateStyle the predefined date style that date objects will be serialized/deserialized
* to/from
* @param timeStyle the predefined style for the time portion of the date objects
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.2
*/
public GsonBuilder setDateFormat(int dateStyle, int timeStyle) {
this.dateStyle = dateStyle;
this.timeStyle = timeStyle;
this.datePattern = null;
return this;
}
/**
* Configures Gson for custom serialization or deserialization. This method combines the
* registration of an {@link TypeAdapter}, {@link InstanceCreator}, {@link JsonSerializer}, and a
* {@link JsonDeserializer}. It is best used when a single object {@code typeAdapter} implements
* all the required interfaces for custom serialization with Gson. If a type adapter was
* previously registered for the specified {@code type}, it is overwritten.
*
* <p>This registers the type specified and no other types: you must manually register related
* types! For example, applications registering {@code boolean.class} should also register {@code
* Boolean.class}.
*
* @param type the type definition for the type adapter being registered
* @param typeAdapter This object must implement at least one of the {@link TypeAdapter},
* {@link InstanceCreator}, {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
|| typeAdapter instanceof JsonDeserializer<?>
|| typeAdapter instanceof InstanceCreator<?>
|| typeAdapter instanceof TypeAdapter<?>);
if (typeAdapter instanceof InstanceCreator<?>) {
instanceCreators.put(type, (InstanceCreator) typeAdapter);
}
if (typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?>) {
TypeToken<?> typeToken = TypeToken.get(type);
factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter));
}
if (typeAdapter instanceof TypeAdapter<?>) {
factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter));
}
return this;
}
/**
* Register a factory for type adapters. Registering a factory is useful when the type
* adapter needs to be configured based on the type of the field being processed. Gson
* is designed to handle a large number of factories, so you should consider registering
* them to be at par with registering an individual type adapter.
*
* @since 2.1
*/
public GsonBuilder registerTypeAdapterFactory(TypeAdapterFactory factory) {
factories.add(factory);
return this;
}
/**
* Configures Gson for custom serialization or deserialization for an inheritance type hierarchy.
* This method combines the registration of a {@link TypeAdapter}, {@link JsonSerializer} and
* a {@link JsonDeserializer}. If a type adapter was previously registered for the specified
* type hierarchy, it is overridden. If a type adapter is registered for a specific type in
* the type hierarchy, it will be invoked instead of the one registered for the type hierarchy.
*
* @param baseType the class definition for the type adapter being registered for the base class
* or interface
* @param typeAdapter This object must implement at least one of {@link TypeAdapter},
* {@link JsonSerializer} or {@link JsonDeserializer} interfaces.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.7
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public GsonBuilder registerTypeHierarchyAdapter(Class<?> baseType, Object typeAdapter) {
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
|| typeAdapter instanceof JsonDeserializer<?>
|| typeAdapter instanceof TypeAdapter<?>);
if (typeAdapter instanceof JsonDeserializer || typeAdapter instanceof JsonSerializer) {
hierarchyFactories.add(0,
TreeTypeAdapter.newTypeHierarchyFactory(baseType, typeAdapter));
}
if (typeAdapter instanceof TypeAdapter<?>) {
factories.add(TypeAdapters.newTypeHierarchyFactory(baseType, (TypeAdapter)typeAdapter));
}
return this;
}
/**
* Section 2.4 of <a href="http://www.ietf.org/rfc/rfc4627.txt">JSON specification</a> disallows
* special double values (NaN, Infinity, -Infinity). However,
* <a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf">Javascript
* specification</a> (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript
* values. Moreover, most JavaScript engines will accept these special values in JSON without
* problem. So, at a practical level, it makes sense to accept these values as valid JSON even
* though JSON specification disallows them.
*
* <p>Gson always accepts these special values during deserialization. However, it outputs
* strictly compliant JSON. Hence, if it encounters a float value {@link Float#NaN},
* {@link Float#POSITIVE_INFINITY}, {@link Float#NEGATIVE_INFINITY}, or a double value
* {@link Double#NaN}, {@link Double#POSITIVE_INFINITY}, {@link Double#NEGATIVE_INFINITY}, it
* will throw an {@link IllegalArgumentException}. This method provides a way to override the
* default behavior when you know that the JSON receiver will be able to handle these special
* values.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
*/
public GsonBuilder serializeSpecialFloatingPointValues() {
this.serializeSpecialFloatingPointValues = true;
return this;
}
/**
* Creates a {@link Gson} instance based on the current configuration. This method is free of
* side-effects to this {@code GsonBuilder} instance and hence can be called multiple times.
*
* @return an instance of Gson configured with the options currently set in this builder
*/
public Gson create() {
List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
factories.addAll(this.factories);
Collections.reverse(factories);
factories.addAll(this.hierarchyFactories);
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
return new Gson(excluder, fieldNamingPolicy, instanceCreators,
serializeNulls, complexMapKeySerialization,
generateNonExecutableJson, escapeHtmlChars, prettyPrinting,
serializeSpecialFloatingPointValues, longSerializationPolicy, factories);
}
private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
List<TypeAdapterFactory> factories) {
DefaultDateTypeAdapter dateTypeAdapter;
if (datePattern != null && !"".equals(datePattern.trim())) {
dateTypeAdapter = new DefaultDateTypeAdapter(datePattern);
} else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) {
dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle, timeStyle);
} else {
return;
}
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Date.class), dateTypeAdapter));
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Timestamp.class), dateTypeAdapter));
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(java.sql.Date.class), dateTypeAdapter));
}
}

View File

@ -1,92 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.lang.reflect.Type;
/**
* This interface is implemented to create instances of a class that does not define a no-args
* constructor. If you can modify the class, you should instead add a private, or public
* no-args constructor. However, that is not possible for library classes, such as JDK classes, or
* a third-party library that you do not have source-code of. In such cases, you should define an
* instance creator for the class. Implementations of this interface should be registered with
* {@link GsonBuilder#registerTypeAdapter(Type, Object)} method before Gson will be able to use
* them.
* <p>Let us look at an example where defining an InstanceCreator might be useful. The
* {@code Id} class defined below does not have a default no-args constructor.</p>
*
* <pre>
* public class Id&lt;T&gt; {
* private final Class&lt;T&gt; clazz;
* private final long value;
* public Id(Class&lt;T&gt; clazz, long value) {
* this.clazz = clazz;
* this.value = value;
* }
* }
* </pre>
*
* <p>If Gson encounters an object of type {@code Id} during deserialization, it will throw an
* exception. The easiest way to solve this problem will be to add a (public or private) no-args
* constructor as follows:</p>
*
* <pre>
* private Id() {
* this(Object.class, 0L);
* }
* </pre>
*
* <p>However, let us assume that the developer does not have access to the source-code of the
* {@code Id} class, or does not want to define a no-args constructor for it. The developer
* can solve this problem by defining an {@code InstanceCreator} for {@code Id}:</p>
*
* <pre>
* class IdInstanceCreator implements InstanceCreator&lt;Id&gt; {
* public Id createInstance(Type type) {
* return new Id(Object.class, 0L);
* }
* }
* </pre>
*
* <p>Note that it does not matter what the fields of the created instance contain since Gson will
* overwrite them with the deserialized values specified in Json. You should also ensure that a
* <i>new</i> object is returned, not a common object since its fields will be overwritten.
* The developer will need to register {@code IdInstanceCreator} with Gson as follows:</p>
*
* <pre>
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdInstanceCreator()).create();
* </pre>
*
* @param <T> the type of object that will be created by this implementation.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public interface InstanceCreator<T> {
/**
* Gson invokes this call-back method during deserialization to create an instance of the
* specified type. The fields of the returned instance are overwritten with the data present
* in the Json. Since the prior contents of the object are destroyed and overwritten, do not
* return an instance that is useful elsewhere. In particular, do not return a common instance,
* always use {@code new} to create a new instance.
*
* @param type the parameterized T represented as a {@link Type}.
* @return a default object instance of type T.
*/
public T createInstance(Type type);
}

View File

@ -1,292 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* A class representing an array type in Json. An array is a list of {@link JsonElement}s each of
* which can be of a different type. This is an ordered list, meaning that the order in which
* elements are added is preserved.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public final class JsonArray extends JsonElement implements Iterable<JsonElement> {
private final List<JsonElement> elements;
/**
* Creates an empty JsonArray.
*/
public JsonArray() {
elements = new ArrayList<JsonElement>();
}
@Override
JsonArray deepCopy() {
JsonArray result = new JsonArray();
for (JsonElement element : elements) {
result.add(element.deepCopy());
}
return result;
}
/**
* Adds the specified element to self.
*
* @param element the element that needs to be added to the array.
*/
public void add(JsonElement element) {
if (element == null) {
element = JsonNull.INSTANCE;
}
elements.add(element);
}
/**
* Adds all the elements of the specified array to self.
*
* @param array the array whose elements need to be added to the array.
*/
public void addAll(JsonArray array) {
elements.addAll(array.elements);
}
/**
* Returns the number of elements in the array.
*
* @return the number of elements in the array.
*/
public int size() {
return elements.size();
}
/**
* Returns an iterator to navigate the elemetns of the array. Since the array is an ordered list,
* the iterator navigates the elements in the order they were inserted.
*
* @return an iterator to navigate the elements of the array.
*/
@Override
public Iterator<JsonElement> iterator() {
return elements.iterator();
}
/**
* Returns the ith element of the array.
*
* @param i the index of the element that is being sought.
* @return the element present at the ith index.
* @throws IndexOutOfBoundsException if i is negative or greater than or equal to the
* {@link #size()} of the array.
*/
public JsonElement get(int i) {
return elements.get(i);
}
/**
* convenience method to get this array as a {@link Number} if it contains a single element.
*
* @return get this element as a number if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid Number.
* @throws IllegalStateException if the array has more than one element.
*/
@Override
public Number getAsNumber() {
if (elements.size() == 1) {
return elements.get(0).getAsNumber();
}
throw new IllegalStateException();
}
/**
* convenience method to get this array as a {@link String} if it contains a single element.
*
* @return get this element as a String if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid String.
* @throws IllegalStateException if the array has more than one element.
*/
@Override
public String getAsString() {
if (elements.size() == 1) {
return elements.get(0).getAsString();
}
throw new IllegalStateException();
}
/**
* convenience method to get this array as a double if it contains a single element.
*
* @return get this element as a double if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid double.
* @throws IllegalStateException if the array has more than one element.
*/
@Override
public double getAsDouble() {
if (elements.size() == 1) {
return elements.get(0).getAsDouble();
}
throw new IllegalStateException();
}
/**
* convenience method to get this array as a {@link BigDecimal} if it contains a single element.
*
* @return get this element as a {@link BigDecimal} if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive}.
* @throws NumberFormatException if the element at index 0 is not a valid {@link BigDecimal}.
* @throws IllegalStateException if the array has more than one element.
* @since 1.2
*/
@Override
public BigDecimal getAsBigDecimal() {
if (elements.size() == 1) {
return elements.get(0).getAsBigDecimal();
}
throw new IllegalStateException();
}
/**
* convenience method to get this array as a {@link BigInteger} if it contains a single element.
*
* @return get this element as a {@link BigInteger} if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive}.
* @throws NumberFormatException if the element at index 0 is not a valid {@link BigInteger}.
* @throws IllegalStateException if the array has more than one element.
* @since 1.2
*/
@Override
public BigInteger getAsBigInteger() {
if (elements.size() == 1) {
return elements.get(0).getAsBigInteger();
}
throw new IllegalStateException();
}
/**
* convenience method to get this array as a float if it contains a single element.
*
* @return get this element as a float if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid float.
* @throws IllegalStateException if the array has more than one element.
*/
@Override
public float getAsFloat() {
if (elements.size() == 1) {
return elements.get(0).getAsFloat();
}
throw new IllegalStateException();
}
/**
* convenience method to get this array as a long if it contains a single element.
*
* @return get this element as a long if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid long.
* @throws IllegalStateException if the array has more than one element.
*/
@Override
public long getAsLong() {
if (elements.size() == 1) {
return elements.get(0).getAsLong();
}
throw new IllegalStateException();
}
/**
* convenience method to get this array as an integer if it contains a single element.
*
* @return get this element as an integer if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid integer.
* @throws IllegalStateException if the array has more than one element.
*/
@Override
public int getAsInt() {
if (elements.size() == 1) {
return elements.get(0).getAsInt();
}
throw new IllegalStateException();
}
@Override
public byte getAsByte() {
if (elements.size() == 1) {
return elements.get(0).getAsByte();
}
throw new IllegalStateException();
}
@Override
public char getAsCharacter() {
if (elements.size() == 1) {
return elements.get(0).getAsCharacter();
}
throw new IllegalStateException();
}
/**
* convenience method to get this array as a primitive short if it contains a single element.
*
* @return get this element as a primitive short if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid short.
* @throws IllegalStateException if the array has more than one element.
*/
@Override
public short getAsShort() {
if (elements.size() == 1) {
return elements.get(0).getAsShort();
}
throw new IllegalStateException();
}
/**
* convenience method to get this array as a boolean if it contains a single element.
*
* @return get this element as a boolean if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid boolean.
* @throws IllegalStateException if the array has more than one element.
*/
@Override
public boolean getAsBoolean() {
if (elements.size() == 1) {
return elements.get(0).getAsBoolean();
}
throw new IllegalStateException();
}
@Override
public boolean equals(Object o) {
return (o == this) || (o instanceof JsonArray && ((JsonArray) o).elements.equals(elements));
}
@Override
public int hashCode() {
return elements.hashCode();
}
}

View File

@ -1,44 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.lang.reflect.Type;
/**
* Context for deserialization that is passed to a custom deserializer during invocation of its
* {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)}
* method.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public interface JsonDeserializationContext {
/**
* Invokes default deserialization on the specified object. It should never be invoked on
* the element received as a parameter of the
* {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)} method. Doing
* so will result in an infinite loop since Gson will in-turn call the custom deserializer again.
*
* @param json the parse tree.
* @param typeOfT type of the expected return value.
* @param <T> The type of the deserialized object.
* @return An object of type typeOfT.
* @throws JsonParseException if the parse tree does not contain expected data.
*/
public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException;
}

View File

@ -1,91 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.lang.reflect.Type;
/**
* <p>Interface representing a custom deserializer for Json. You should write a custom
* deserializer, if you are not happy with the default deserialization done by Gson. You will
* also need to register this deserializer through
* {@link GsonBuilder#registerTypeAdapter(Type, Object)}.</p>
*
* <p>Let us look at example where defining a deserializer will be useful. The {@code Id} class
* defined below has two fields: {@code clazz} and {@code value}.</p>
*
* <pre>
* public class Id&lt;T&gt; {
* private final Class&lt;T&gt; clazz;
* private final long value;
* public Id(Class&lt;T&gt; clazz, long value) {
* this.clazz = clazz;
* this.value = value;
* }
* public long getValue() {
* return value;
* }
* }
* </pre>
*
* <p>The default deserialization of {@code Id(com.foo.MyObject.class, 20L)} will require the
* Json string to be <code>{"clazz":com.foo.MyObject,"value":20}</code>. Suppose, you already know
* the type of the field that the {@code Id} will be deserialized into, and hence just want to
* deserialize it from a Json string {@code 20}. You can achieve that by writing a custom
* deserializer:</p>
*
* <pre>
* class IdDeserializer implements JsonDeserializer&lt;Id&gt;() {
* public Id deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
* throws JsonParseException {
* return new Id((Class)typeOfT, id.getValue());
* }
* </pre>
*
* <p>You will also need to register {@code IdDeserializer} with Gson as follows:</p>
*
* <pre>
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdDeserializer()).create();
* </pre>
*
* <p>New applications should prefer {@link TypeAdapter}, whose streaming API
* is more efficient than this interface's tree API.
*
* @author Inderjeet Singh
* @author Joel Leitch
*
* @param <T> type for which the deserializer is being registered. It is possible that a
* deserializer may be asked to deserialize a specific generic type of the T.
*/
public interface JsonDeserializer<T> {
/**
* Gson invokes this call-back method during deserialization when it encounters a field of the
* specified type.
* <p>In the implementation of this call-back method, you should consider invoking
* {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects
* for any non-trivial field of the returned object. However, you should never invoke it on the
* the same type passing {@code json} since that will cause an infinite loop (Gson will call your
* call-back method again).
*
* @param json The Json data being deserialized
* @param typeOfT The type of the Object to deserialize to
* @return a deserialized object of the specified type typeOfT which is a subclass of {@code T}
* @throws JsonParseException if json is not in the expected format of {@code typeofT}
*/
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException;
}

View File

@ -1,330 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* A class representing an element of Json. It could either be a {@link JsonObject}, a
* {@link JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public abstract class JsonElement {
/**
* Returns a deep copy of this element. Immutable elements like primitives
* and nulls are not copied.
*/
abstract JsonElement deepCopy();
/**
* provides check for verifying if this element is an array or not.
*
* @return true if this element is of type {@link JsonArray}, false otherwise.
*/
public boolean isJsonArray() {
return this instanceof JsonArray;
}
/**
* provides check for verifying if this element is a Json object or not.
*
* @return true if this element is of type {@link JsonObject}, false otherwise.
*/
public boolean isJsonObject() {
return this instanceof JsonObject;
}
/**
* provides check for verifying if this element is a primitive or not.
*
* @return true if this element is of type {@link JsonPrimitive}, false otherwise.
*/
public boolean isJsonPrimitive() {
return this instanceof JsonPrimitive;
}
/**
* provides check for verifying if this element represents a null value or not.
*
* @return true if this element is of type {@link JsonNull}, false otherwise.
* @since 1.2
*/
public boolean isJsonNull() {
return this instanceof JsonNull;
}
/**
* convenience method to get this element as a {@link JsonObject}. If the element is of some
* other type, a {@link ClassCastException} will result. Hence it is best to use this method
* after ensuring that this element is of the desired type by calling {@link #isJsonObject()}
* first.
*
* @return get this element as a {@link JsonObject}.
* @throws IllegalStateException if the element is of another type.
*/
public JsonObject getAsJsonObject() {
if (isJsonObject()) {
return (JsonObject) this;
}
throw new IllegalStateException("Not a JSON Object: " + this);
}
/**
* convenience method to get this element as a {@link JsonArray}. If the element is of some
* other type, a {@link ClassCastException} will result. Hence it is best to use this method
* after ensuring that this element is of the desired type by calling {@link #isJsonArray()}
* first.
*
* @return get this element as a {@link JsonArray}.
* @throws IllegalStateException if the element is of another type.
*/
public JsonArray getAsJsonArray() {
if (isJsonArray()) {
return (JsonArray) this;
}
throw new IllegalStateException("This is not a JSON Array.");
}
/**
* convenience method to get this element as a {@link JsonPrimitive}. If the element is of some
* other type, a {@link ClassCastException} will result. Hence it is best to use this method
* after ensuring that this element is of the desired type by calling {@link #isJsonPrimitive()}
* first.
*
* @return get this element as a {@link JsonPrimitive}.
* @throws IllegalStateException if the element is of another type.
*/
public JsonPrimitive getAsJsonPrimitive() {
if (isJsonPrimitive()) {
return (JsonPrimitive) this;
}
throw new IllegalStateException("This is not a JSON Primitive.");
}
/**
* convenience method to get this element as a {@link JsonNull}. If the element is of some
* other type, a {@link ClassCastException} will result. Hence it is best to use this method
* after ensuring that this element is of the desired type by calling {@link #isJsonNull()}
* first.
*
* @return get this element as a {@link JsonNull}.
* @throws IllegalStateException if the element is of another type.
* @since 1.2
*/
public JsonNull getAsJsonNull() {
if (isJsonNull()) {
return (JsonNull) this;
}
throw new IllegalStateException("This is not a JSON Null.");
}
/**
* convenience method to get this element as a boolean value.
*
* @return get this element as a primitive boolean value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* boolean value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public boolean getAsBoolean() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a {@link Boolean} value.
*
* @return get this element as a {@link Boolean} value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* boolean value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
*/
Boolean getAsBooleanWrapper() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a {@link Number}.
*
* @return get this element as a {@link Number}.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* number.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public Number getAsNumber() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a string value.
*
* @return get this element as a string value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* string value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public String getAsString() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a primitive double value.
*
* @return get this element as a primitive double value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* double value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public double getAsDouble() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a primitive float value.
*
* @return get this element as a primitive float value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* float value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public float getAsFloat() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a primitive long value.
*
* @return get this element as a primitive long value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* long value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public long getAsLong() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a primitive integer value.
*
* @return get this element as a primitive integer value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* integer value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public int getAsInt() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a primitive byte value.
*
* @return get this element as a primitive byte value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* byte value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
* @since 1.3
*/
public byte getAsByte() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a primitive character value.
*
* @return get this element as a primitive char value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* char value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
* @since 1.3
*/
public char getAsCharacter() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a {@link BigDecimal}.
*
* @return get this element as a {@link BigDecimal}.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive}.
* * @throws NumberFormatException if the element is not a valid {@link BigDecimal}.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
* @since 1.2
*/
public BigDecimal getAsBigDecimal() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a {@link BigInteger}.
*
* @return get this element as a {@link BigInteger}.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive}.
* @throws NumberFormatException if the element is not a valid {@link BigInteger}.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
* @since 1.2
*/
public BigInteger getAsBigInteger() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* convenience method to get this element as a primitive short value.
*
* @return get this element as a primitive short value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* short value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public short getAsShort() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}
/**
* Returns a String representation of this element.
*/
@Override
public String toString() {
try {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true);
Streams.write(this, jsonWriter);
return stringWriter.toString();
} catch (IOException e) {
throw new AssertionError(e);
}
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
/**
* This exception is raised when Gson was unable to read an input stream
* or write to one.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public final class JsonIOException extends JsonParseException {
private static final long serialVersionUID = 1L;
public JsonIOException(String msg) {
super(msg);
}
public JsonIOException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Creates exception with the specified cause. Consider using
* {@link #JsonIOException(String, Throwable)} instead if you can describe what happened.
*
* @param cause root exception that caused this exception to be thrown.
*/
public JsonIOException(Throwable cause) {
super(cause);
}
}

View File

@ -1,63 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
/**
* A class representing a Json {@code null} value.
*
* @author Inderjeet Singh
* @author Joel Leitch
* @since 1.2
*/
public final class JsonNull extends JsonElement {
/**
* singleton for JsonNull
*
* @since 1.8
*/
public static final JsonNull INSTANCE = new JsonNull();
/**
* Creates a new JsonNull object.
* Deprecated since Gson version 1.8. Use {@link #INSTANCE} instead
*/
@Deprecated
public JsonNull() {
// Do nothing
}
@Override
JsonNull deepCopy() {
return INSTANCE;
}
/**
* All instances of JsonNull have the same hash code since they are indistinguishable
*/
@Override
public int hashCode() {
return JsonNull.class.hashCode();
}
/**
* All instances of JsonNull are the same
*/
@Override
public boolean equals(Object other) {
return this == other || other instanceof JsonNull;
}
}

View File

@ -1,197 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import com.google.gson.internal.LinkedTreeMap;
import java.util.Map;
import java.util.Set;
/**
* A class representing an object type in Json. An object consists of name-value pairs where names
* are strings, and values are any other type of {@link JsonElement}. This allows for a creating a
* tree of JsonElements. The member elements of this object are maintained in order they were added.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public final class JsonObject extends JsonElement {
private final LinkedTreeMap<String, JsonElement> members =
new LinkedTreeMap<String, JsonElement>();
@Override
JsonObject deepCopy() {
JsonObject result = new JsonObject();
for (Map.Entry<String, JsonElement> entry : members.entrySet()) {
result.add(entry.getKey(), entry.getValue().deepCopy());
}
return result;
}
/**
* Adds a member, which is a name-value pair, to self. The name must be a String, but the value
* can be an arbitrary JsonElement, thereby allowing you to build a full tree of JsonElements
* rooted at this node.
*
* @param property name of the member.
* @param value the member object.
*/
public void add(String property, JsonElement value) {
if (value == null) {
value = JsonNull.INSTANCE;
}
members.put(property, value);
}
/**
* Removes the {@code property} from this {@link JsonObject}.
*
* @param property name of the member that should be removed.
* @return the {@link JsonElement} object that is being removed.
* @since 1.3
*/
public JsonElement remove(String property) {
return members.remove(property);
}
/**
* Convenience method to add a primitive member. The specified value is converted to a
* JsonPrimitive of String.
*
* @param property name of the member.
* @param value the string value associated with the member.
*/
public void addProperty(String property, String value) {
add(property, createJsonElement(value));
}
/**
* Convenience method to add a primitive member. The specified value is converted to a
* JsonPrimitive of Number.
*
* @param property name of the member.
* @param value the number value associated with the member.
*/
public void addProperty(String property, Number value) {
add(property, createJsonElement(value));
}
/**
* Convenience method to add a boolean member. The specified value is converted to a
* JsonPrimitive of Boolean.
*
* @param property name of the member.
* @param value the number value associated with the member.
*/
public void addProperty(String property, Boolean value) {
add(property, createJsonElement(value));
}
/**
* Convenience method to add a char member. The specified value is converted to a
* JsonPrimitive of Character.
*
* @param property name of the member.
* @param value the number value associated with the member.
*/
public void addProperty(String property, Character value) {
add(property, createJsonElement(value));
}
/**
* Creates the proper {@link JsonElement} object from the given {@code value} object.
*
* @param value the object to generate the {@link JsonElement} for
* @return a {@link JsonPrimitive} if the {@code value} is not null, otherwise a {@link JsonNull}
*/
private JsonElement createJsonElement(Object value) {
return value == null ? JsonNull.INSTANCE : new JsonPrimitive(value);
}
/**
* Returns a set of members of this object. The set is ordered, and the order is in which the
* elements were added.
*
* @return a set of members of this object.
*/
public Set<Map.Entry<String, JsonElement>> entrySet() {
return members.entrySet();
}
/**
* Convenience method to check if a member with the specified name is present in this object.
*
* @param memberName name of the member that is being checked for presence.
* @return true if there is a member with the specified name, false otherwise.
*/
public boolean has(String memberName) {
return members.containsKey(memberName);
}
/**
* Returns the member with the specified name.
*
* @param memberName name of the member that is being requested.
* @return the member matching the name. Null if no such member exists.
*/
public JsonElement get(String memberName) {
return members.get(memberName);
}
/**
* Convenience method to get the specified member as a JsonPrimitive element.
*
* @param memberName name of the member being requested.
* @return the JsonPrimitive corresponding to the specified member.
*/
public JsonPrimitive getAsJsonPrimitive(String memberName) {
return (JsonPrimitive) members.get(memberName);
}
/**
* Convenience method to get the specified member as a JsonArray.
*
* @param memberName name of the member being requested.
* @return the JsonArray corresponding to the specified member.
*/
public JsonArray getAsJsonArray(String memberName) {
return (JsonArray) members.get(memberName);
}
/**
* Convenience method to get the specified member as a JsonObject.
*
* @param memberName name of the member being requested.
* @return the JsonObject corresponding to the specified member.
*/
public JsonObject getAsJsonObject(String memberName) {
return (JsonObject) members.get(memberName);
}
@Override
public boolean equals(Object o) {
return (o == this) || (o instanceof JsonObject
&& ((JsonObject) o).members.equals(members));
}
@Override
public int hashCode() {
return members.hashCode();
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
/**
* This exception is raised if there is a serious issue that occurs during parsing of a Json
* string. One of the main usages for this class is for the Gson infrastructure. If the incoming
* Json is bad/malicious, an instance of this exception is raised.
*
* <p>This exception is a {@link RuntimeException} because it is exposed to the client. Using a
* {@link RuntimeException} avoids bad coding practices on the client side where they catch the
* exception and do nothing. It is often the case that you want to blow up if there is a parsing
* error (i.e. often clients do not know how to recover from a {@link JsonParseException}.</p>
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public class JsonParseException extends RuntimeException {
static final long serialVersionUID = -4086729973971783390L;
/**
* Creates exception with the specified message. If you are wrapping another exception, consider
* using {@link #JsonParseException(String, Throwable)} instead.
*
* @param msg error message describing a possible cause of this exception.
*/
public JsonParseException(String msg) {
super(msg);
}
/**
* Creates exception with the specified message and cause.
*
* @param msg error message describing what happened.
* @param cause root exception that caused this exception to be thrown.
*/
public JsonParseException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Creates exception with the specified cause. Consider using
* {@link #JsonParseException(String, Throwable)} instead if you can describe what happened.
*
* @param cause root exception that caused this exception to be thrown.
*/
public JsonParseException(Throwable cause) {
super(cause);
}
}

View File

@ -1,93 +0,0 @@
/*
* Copyright (C) 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
/**
* A parser to parse Json into a parse tree of {@link JsonElement}s
*
* @author Inderjeet Singh
* @author Joel Leitch
* @since 1.3
*/
public final class JsonParser {
/**
* Parses the specified JSON string into a parse tree
*
* @param json JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
* @throws JsonParseException if the specified text is not valid JSON
* @since 1.3
*/
public JsonElement parse(String json) throws JsonSyntaxException {
return parse(new StringReader(json));
}
/**
* Parses the specified JSON string into a parse tree
*
* @param json JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
* @throws JsonParseException if the specified text is not valid JSON
* @since 1.3
*/
public JsonElement parse(Reader json) throws JsonIOException, JsonSyntaxException {
try {
JsonReader jsonReader = new JsonReader(json);
JsonElement element = parse(jsonReader);
if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonSyntaxException("Did not consume the entire document.");
}
return element;
} catch (MalformedJsonException e) {
throw new JsonSyntaxException(e);
} catch (IOException e) {
throw new JsonIOException(e);
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
/**
* Returns the next value from the JSON stream as a parse tree.
*
* @throws JsonParseException if there is an IOException or if the specified
* text is not valid JSON
* @since 1.6
*/
public JsonElement parse(JsonReader json) throws JsonIOException, JsonSyntaxException {
boolean lenient = json.isLenient();
json.setLenient(true);
try {
return Streams.parse(json);
} catch (StackOverflowError e) {
throw new JsonParseException("Failed parsing JSON source: " + json + " to Json", e);
} catch (OutOfMemoryError e) {
throw new JsonParseException("Failed parsing JSON source: " + json + " to Json", e);
} finally {
json.setLenient(lenient);
}
}
}

View File

@ -1,341 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.math.BigDecimal;
import java.math.BigInteger;
import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.LazilyParsedNumber;
/**
* A class representing a Json primitive value. A primitive value
* is either a String, a Java primitive, or a Java primitive
* wrapper type.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public final class JsonPrimitive extends JsonElement {
private static final Class<?>[] PRIMITIVE_TYPES = { int.class, long.class, short.class,
float.class, double.class, byte.class, boolean.class, char.class, Integer.class, Long.class,
Short.class, Float.class, Double.class, Byte.class, Boolean.class, Character.class };
private Object value;
/**
* Create a primitive containing a boolean value.
*
* @param bool the value to create the primitive with.
*/
public JsonPrimitive(Boolean bool) {
setValue(bool);
}
/**
* Create a primitive containing a {@link Number}.
*
* @param number the value to create the primitive with.
*/
public JsonPrimitive(Number number) {
setValue(number);
}
/**
* Create a primitive containing a String value.
*
* @param string the value to create the primitive with.
*/
public JsonPrimitive(String string) {
setValue(string);
}
/**
* Create a primitive containing a character. The character is turned into a one character String
* since Json only supports String.
*
* @param c the value to create the primitive with.
*/
public JsonPrimitive(Character c) {
setValue(c);
}
/**
* Create a primitive using the specified Object. It must be an instance of {@link Number}, a
* Java primitive type, or a String.
*
* @param primitive the value to create the primitive with.
*/
JsonPrimitive(Object primitive) {
setValue(primitive);
}
@Override
JsonPrimitive deepCopy() {
return this;
}
void setValue(Object primitive) {
if (primitive instanceof Character) {
// convert characters to strings since in JSON, characters are represented as a single
// character string
char c = ((Character) primitive).charValue();
this.value = String.valueOf(c);
} else {
$Gson$Preconditions.checkArgument(primitive instanceof Number
|| isPrimitiveOrString(primitive));
this.value = primitive;
}
}
/**
* Check whether this primitive contains a boolean value.
*
* @return true if this primitive contains a boolean value, false otherwise.
*/
public boolean isBoolean() {
return value instanceof Boolean;
}
/**
* convenience method to get this element as a {@link Boolean}.
*
* @return get this element as a {@link Boolean}.
*/
@Override
Boolean getAsBooleanWrapper() {
return (Boolean) value;
}
/**
* convenience method to get this element as a boolean value.
*
* @return get this element as a primitive boolean value.
*/
@Override
public boolean getAsBoolean() {
if (isBoolean()) {
return getAsBooleanWrapper().booleanValue();
} else {
// Check to see if the value as a String is "true" in any case.
return Boolean.parseBoolean(getAsString());
}
}
/**
* Check whether this primitive contains a Number.
*
* @return true if this primitive contains a Number, false otherwise.
*/
public boolean isNumber() {
return value instanceof Number;
}
/**
* convenience method to get this element as a Number.
*
* @return get this element as a Number.
* @throws NumberFormatException if the value contained is not a valid Number.
*/
@Override
public Number getAsNumber() {
return value instanceof String ? new LazilyParsedNumber((String) value) : (Number) value;
}
/**
* Check whether this primitive contains a String value.
*
* @return true if this primitive contains a String value, false otherwise.
*/
public boolean isString() {
return value instanceof String;
}
/**
* convenience method to get this element as a String.
*
* @return get this element as a String.
*/
@Override
public String getAsString() {
if (isNumber()) {
return getAsNumber().toString();
} else if (isBoolean()) {
return getAsBooleanWrapper().toString();
} else {
return (String) value;
}
}
/**
* convenience method to get this element as a primitive double.
*
* @return get this element as a primitive double.
* @throws NumberFormatException if the value contained is not a valid double.
*/
@Override
public double getAsDouble() {
return isNumber() ? getAsNumber().doubleValue() : Double.parseDouble(getAsString());
}
/**
* convenience method to get this element as a {@link BigDecimal}.
*
* @return get this element as a {@link BigDecimal}.
* @throws NumberFormatException if the value contained is not a valid {@link BigDecimal}.
*/
@Override
public BigDecimal getAsBigDecimal() {
return value instanceof BigDecimal ? (BigDecimal) value : new BigDecimal(value.toString());
}
/**
* convenience method to get this element as a {@link BigInteger}.
*
* @return get this element as a {@link BigInteger}.
* @throws NumberFormatException if the value contained is not a valid {@link BigInteger}.
*/
@Override
public BigInteger getAsBigInteger() {
return value instanceof BigInteger ?
(BigInteger) value : new BigInteger(value.toString());
}
/**
* convenience method to get this element as a float.
*
* @return get this element as a float.
* @throws NumberFormatException if the value contained is not a valid float.
*/
@Override
public float getAsFloat() {
return isNumber() ? getAsNumber().floatValue() : Float.parseFloat(getAsString());
}
/**
* convenience method to get this element as a primitive long.
*
* @return get this element as a primitive long.
* @throws NumberFormatException if the value contained is not a valid long.
*/
@Override
public long getAsLong() {
return isNumber() ? getAsNumber().longValue() : Long.parseLong(getAsString());
}
/**
* convenience method to get this element as a primitive short.
*
* @return get this element as a primitive short.
* @throws NumberFormatException if the value contained is not a valid short value.
*/
@Override
public short getAsShort() {
return isNumber() ? getAsNumber().shortValue() : Short.parseShort(getAsString());
}
/**
* convenience method to get this element as a primitive integer.
*
* @return get this element as a primitive integer.
* @throws NumberFormatException if the value contained is not a valid integer.
*/
@Override
public int getAsInt() {
return isNumber() ? getAsNumber().intValue() : Integer.parseInt(getAsString());
}
@Override
public byte getAsByte() {
return isNumber() ? getAsNumber().byteValue() : Byte.parseByte(getAsString());
}
@Override
public char getAsCharacter() {
return getAsString().charAt(0);
}
private static boolean isPrimitiveOrString(Object target) {
if (target instanceof String) {
return true;
}
Class<?> classOfPrimitive = target.getClass();
for (Class<?> standardPrimitive : PRIMITIVE_TYPES) {
if (standardPrimitive.isAssignableFrom(classOfPrimitive)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
if (value == null) {
return 31;
}
// Using recommended hashing algorithm from Effective Java for longs and doubles
if (isIntegral(this)) {
long value = getAsNumber().longValue();
return (int) (value ^ (value >>> 32));
}
if (value instanceof Number) {
long value = Double.doubleToLongBits(getAsNumber().doubleValue());
return (int) (value ^ (value >>> 32));
}
return value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
JsonPrimitive other = (JsonPrimitive)obj;
if (value == null) {
return other.value == null;
}
if (isIntegral(this) && isIntegral(other)) {
return getAsNumber().longValue() == other.getAsNumber().longValue();
}
if (value instanceof Number && other.value instanceof Number) {
double a = getAsNumber().doubleValue();
// Java standard types other than double return true for two NaN. So, need
// special handling for double.
double b = other.getAsNumber().doubleValue();
return a == b || (Double.isNaN(a) && Double.isNaN(b));
}
return value.equals(other.value);
}
/**
* Returns true if the specified number is an integral type
* (Long, Integer, Short, Byte, BigInteger)
*/
private static boolean isIntegral(JsonPrimitive primitive) {
if (primitive.value instanceof Number) {
Number number = (Number) primitive.value;
return number instanceof BigInteger || number instanceof Long || number instanceof Integer
|| number instanceof Short || number instanceof Byte;
}
return false;
}
}

View File

@ -1,49 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.lang.reflect.Type;
/**
* Context for serialization that is passed to a custom serializer during invocation of its
* {@link JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public interface JsonSerializationContext {
/**
* Invokes default serialization on the specified object.
*
* @param src the object that needs to be serialized.
* @return a tree of {@link JsonElement}s corresponding to the serialized form of {@code src}.
*/
public JsonElement serialize(Object src);
/**
* Invokes default serialization on the specified object passing the specific type information.
* It should never be invoked on the element received as a parameter of the
* {@link JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method. Doing
* so will result in an infinite loop since Gson will in-turn call the custom serializer again.
*
* @param src the object that needs to be serialized.
* @param typeOfSrc the actual genericized type of src object.
* @return a tree of {@link JsonElement}s corresponding to the serialized form of {@code src}.
*/
public JsonElement serialize(Object src, Type typeOfSrc);
}

View File

@ -1,89 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.lang.reflect.Type;
/**
* Interface representing a custom serializer for Json. You should write a custom serializer, if
* you are not happy with the default serialization done by Gson. You will also need to register
* this serializer through {@link com.google.gson.GsonBuilder#registerTypeAdapter(Type, Object)}.
*
* <p>Let us look at example where defining a serializer will be useful. The {@code Id} class
* defined below has two fields: {@code clazz} and {@code value}.</p>
*
* <p><pre>
* public class Id&lt;T&gt; {
* private final Class&lt;T&gt; clazz;
* private final long value;
*
* public Id(Class&lt;T&gt; clazz, long value) {
* this.clazz = clazz;
* this.value = value;
* }
*
* public long getValue() {
* return value;
* }
* }
* </pre></p>
*
* <p>The default serialization of {@code Id(com.foo.MyObject.class, 20L)} will be
* <code>{"clazz":com.foo.MyObject,"value":20}</code>. Suppose, you just want the output to be
* the value instead, which is {@code 20} in this case. You can achieve that by writing a custom
* serializer:</p>
*
* <p><pre>
* class IdSerializer implements JsonSerializer&lt;Id&gt;() {
* public JsonElement serialize(Id id, Type typeOfId, JsonSerializationContext context) {
* return new JsonPrimitive(id.getValue());
* }
* }
* </pre></p>
*
* <p>You will also need to register {@code IdSerializer} with Gson as follows:</p>
* <pre>
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdSerializer()).create();
* </pre>
*
* <p>New applications should prefer {@link TypeAdapter}, whose streaming API
* is more efficient than this interface's tree API.
*
* @author Inderjeet Singh
* @author Joel Leitch
*
* @param <T> type for which the serializer is being registered. It is possible that a serializer
* may be asked to serialize a specific generic type of the T.
*/
public interface JsonSerializer<T> {
/**
* Gson invokes this call-back method during serialization when it encounters a field of the
* specified type.
*
* <p>In the implementation of this call-back method, you should consider invoking
* {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
* non-trivial field of the {@code src} object. However, you should never invoke it on the
* {@code src} object itself since that will cause an infinite loop (Gson will call your
* call-back method again).</p>
*
* @param src the object that needs to be converted to Json.
* @param typeOfSrc the actual type (fully genericized version) of the source object.
* @return a JsonElement corresponding to the specified object.
*/
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context);
}

View File

@ -1,125 +0,0 @@
/*
* Copyright (C) 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Iterator;
import java.util.NoSuchElementException;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
/**
* A streaming parser that allows reading of multiple {@link JsonElement}s from the specified reader
* asynchronously.
*
* <p>This class is conditionally thread-safe (see Item 70, Effective Java second edition). To
* properly use this class across multiple threads, you will need to add some external
* synchronization. For example:
*
* <pre>
* JsonStreamParser parser = new JsonStreamParser("['first'] {'second':10} 'third'");
* JsonElement element;
* synchronized (parser) { // synchronize on an object shared by threads
* if (parser.hasNext()) {
* element = parser.next();
* }
* }
* </pre>
*
* @author Inderjeet Singh
* @author Joel Leitch
* @since 1.4
*/
public final class JsonStreamParser implements Iterator<JsonElement> {
private final JsonReader parser;
private final Object lock;
/**
* @param json The string containing JSON elements concatenated to each other.
* @since 1.4
*/
public JsonStreamParser(String json) {
this(new StringReader(json));
}
/**
* @param reader The data stream containing JSON elements concatenated to each other.
* @since 1.4
*/
public JsonStreamParser(Reader reader) {
parser = new JsonReader(reader);
parser.setLenient(true);
lock = new Object();
}
/**
* Returns the next available {@link JsonElement} on the reader. Null if none available.
*
* @return the next available {@link JsonElement} on the reader. Null if none available.
* @throws JsonParseException if the incoming stream is malformed JSON.
* @since 1.4
*/
@Override
public JsonElement next() throws JsonParseException {
if (!hasNext()) {
throw new NoSuchElementException();
}
try {
return Streams.parse(parser);
} catch (StackOverflowError e) {
throw new JsonParseException("Failed parsing JSON source to Json", e);
} catch (OutOfMemoryError e) {
throw new JsonParseException("Failed parsing JSON source to Json", e);
} catch (JsonParseException e) {
throw e.getCause() instanceof EOFException ? new NoSuchElementException() : e;
}
}
/**
* Returns true if a {@link JsonElement} is available on the input for consumption
* @return true if a {@link JsonElement} is available on the input, false otherwise
* @since 1.4
*/
@Override
public boolean hasNext() {
synchronized (lock) {
try {
return parser.peek() != JsonToken.END_DOCUMENT;
} catch (MalformedJsonException e) {
throw new JsonSyntaxException(e);
} catch (IOException e) {
throw new JsonIOException(e);
}
}
}
/**
* This optional {@link Iterator} method is not relevant for stream parsing and hence is not
* implemented.
* @since 1.4
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
/**
* This exception is raised when Gson attempts to read (or write) a malformed
* JSON element.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public final class JsonSyntaxException extends JsonParseException {
private static final long serialVersionUID = 1L;
public JsonSyntaxException(String msg) {
super(msg);
}
public JsonSyntaxException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Creates exception with the specified cause. Consider using
* {@link #JsonSyntaxException(String, Throwable)} instead if you can
* describe what actually happened.
*
* @param cause root exception that caused this exception to be thrown.
*/
public JsonSyntaxException(Throwable cause) {
super(cause);
}
}

View File

@ -1,60 +0,0 @@
/*
* Copyright (C) 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
/**
* Defines the expected format for a {@code long} or {@code Long} type when its serialized.
*
* @since 1.3
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public enum LongSerializationPolicy {
/**
* This is the "default" serialization policy that will output a {@code long} object as a JSON
* number. For example, assume an object has a long field named "f" then the serialized output
* would be:
* {@code {"f":123}}.
*/
DEFAULT() {
@Override
public JsonElement serialize(Long value) {
return new JsonPrimitive(value);
}
},
/**
* Serializes a long value as a quoted string. For example, assume an object has a long field
* named "f" then the serialized output would be:
* {@code {"f":"123"}}.
*/
STRING() {
@Override
public JsonElement serialize(Long value) {
return new JsonPrimitive(String.valueOf(value));
}
};
/**
* Serialize this {@code value} using this serialization policy.
*
* @param value the long value to be serialized into a {@link JsonElement}
* @return the serialized version of {@code value}
*/
public abstract JsonElement serialize(Long value);
}

View File

@ -1,141 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
/**
* Adapts a Gson 1.x tree-style adapter as a streaming TypeAdapter. Since the
* tree adapter may be serialization-only or deserialization-only, this class
* has a facility to lookup a delegate type adapter on demand.
*/
final class TreeTypeAdapter<T> extends TypeAdapter<T> {
private final JsonSerializer<T> serializer;
private final JsonDeserializer<T> deserializer;
private final Gson gson;
private final TypeToken<T> typeToken;
private final TypeAdapterFactory skipPast;
/** The delegate is lazily created because it may not be needed, and creating it may fail. */
private TypeAdapter<T> delegate;
private TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer,
Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast) {
this.serializer = serializer;
this.deserializer = deserializer;
this.gson = gson;
this.typeToken = typeToken;
this.skipPast = skipPast;
}
@Override public T read(JsonReader in) throws IOException {
if (deserializer == null) {
return delegate().read(in);
}
JsonElement value = Streams.parse(in);
if (value.isJsonNull()) {
return null;
}
return deserializer.deserialize(value, typeToken.getType(), gson.deserializationContext);
}
@Override public void write(JsonWriter out, T value) throws IOException {
if (serializer == null) {
delegate().write(out, value);
return;
}
if (value == null) {
out.nullValue();
return;
}
JsonElement tree = serializer.serialize(value, typeToken.getType(), gson.serializationContext);
Streams.write(tree, out);
}
private TypeAdapter<T> delegate() {
TypeAdapter<T> d = delegate;
return d != null
? d
: (delegate = gson.getDelegateAdapter(skipPast, typeToken));
}
/**
* Returns a new factory that will match each type against {@code exactType}.
*/
public static TypeAdapterFactory newFactory(TypeToken<?> exactType, Object typeAdapter) {
return new SingleTypeFactory(typeAdapter, exactType, false, null);
}
/**
* Returns a new factory that will match each type and its raw type against
* {@code exactType}.
*/
public static TypeAdapterFactory newFactoryWithMatchRawType(
TypeToken<?> exactType, Object typeAdapter) {
// only bother matching raw types if exact type is a raw type
boolean matchRawType = exactType.getType() == exactType.getRawType();
return new SingleTypeFactory(typeAdapter, exactType, matchRawType, null);
}
/**
* Returns a new factory that will match each type's raw type for assignability
* to {@code hierarchyType}.
*/
public static TypeAdapterFactory newTypeHierarchyFactory(
Class<?> hierarchyType, Object typeAdapter) {
return new SingleTypeFactory(typeAdapter, null, false, hierarchyType);
}
private static class SingleTypeFactory implements TypeAdapterFactory {
private final TypeToken<?> exactType;
private final boolean matchRawType;
private final Class<?> hierarchyType;
private final JsonSerializer<?> serializer;
private final JsonDeserializer<?> deserializer;
private SingleTypeFactory(Object typeAdapter, TypeToken<?> exactType, boolean matchRawType,
Class<?> hierarchyType) {
serializer = typeAdapter instanceof JsonSerializer
? (JsonSerializer<?>) typeAdapter
: null;
deserializer = typeAdapter instanceof JsonDeserializer
? (JsonDeserializer<?>) typeAdapter
: null;
$Gson$Preconditions.checkArgument(serializer != null || deserializer != null);
this.exactType = exactType;
this.matchRawType = matchRawType;
this.hierarchyType = hierarchyType;
}
@Override
@SuppressWarnings("unchecked") // guarded by typeToken.equals() call
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
boolean matches = exactType != null
? exactType.equals(type) || matchRawType && exactType.getType() == type.getRawType()
: hierarchyType.isAssignableFrom(type.getRawType());
return matches
? new TreeTypeAdapter<T>((JsonSerializer<T>) serializer,
(JsonDeserializer<T>) deserializer, gson, type, this)
: null;
}
}
}

View File

@ -1,286 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import com.google.gson.internal.bind.JsonTreeWriter;
import com.google.gson.internal.bind.JsonTreeReader;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
/**
* Converts Java objects to and from JSON.
*
* <h3>Defining a type's JSON form</h3>
* By default Gson converts application classes to JSON using its built-in type
* adapters. If Gson's default JSON conversion isn't appropriate for a type,
* extend this class to customize the conversion. Here's an example of a type
* adapter for an (X,Y) coordinate point: <pre> {@code
*
* public class PointAdapter extends TypeAdapter<Point> {
* public Point read(JsonReader reader) throws IOException {
* if (reader.peek() == JsonToken.NULL) {
* reader.nextNull();
* return null;
* }
* String xy = reader.nextString();
* String[] parts = xy.split(",");
* int x = Integer.parseInt(parts[0]);
* int y = Integer.parseInt(parts[1]);
* return new Point(x, y);
* }
* public void write(JsonWriter writer, Point value) throws IOException {
* if (value == null) {
* writer.nullValue();
* return;
* }
* String xy = value.getX() + "," + value.getY();
* writer.value(xy);
* }
* }}</pre>
* With this type adapter installed, Gson will convert {@code Points} to JSON as
* strings like {@code "5,8"} rather than objects like {@code {"x":5,"y":8}}. In
* this case the type adapter binds a rich Java class to a compact JSON value.
*
* <p>The {@link #read(JsonReader) read()} method must read exactly one value
* and {@link #write(JsonWriter,Object) write()} must write exactly one value.
* For primitive types this is means readers should make exactly one call to
* {@code nextBoolean()}, {@code nextDouble()}, {@code nextInt()}, {@code
* nextLong()}, {@code nextString()} or {@code nextNull()}. Writers should make
* exactly one call to one of <code>value()</code> or <code>nullValue()</code>.
* For arrays, type adapters should start with a call to {@code beginArray()},
* convert all elements, and finish with a call to {@code endArray()}. For
* objects, they should start with {@code beginObject()}, convert the object,
* and finish with {@code endObject()}. Failing to convert a value or converting
* too many values may cause the application to crash.
*
* <p>Type adapters should be prepared to read null from the stream and write it
* to the stream. Alternatively, they should use {@link #nullSafe()} method while
* registering the type adapter with Gson. If your {@code Gson} instance
* has been configured to {@link GsonBuilder#serializeNulls()}, these nulls will be
* written to the final document. Otherwise the value (and the corresponding name
* when writing to a JSON object) will be omitted automatically. In either case
* your type adapter must handle null.
*
* <p>To use a custom type adapter with Gson, you must <i>register</i> it with a
* {@link GsonBuilder}: <pre> {@code
*
* GsonBuilder builder = new GsonBuilder();
* builder.registerTypeAdapter(Point.class, new PointAdapter());
* // if PointAdapter didn't check for nulls in its read/write methods, you should instead use
* // builder.registerTypeAdapter(Point.class, new PointAdapter().nullSafe());
* ...
* Gson gson = builder.create();
* }</pre>
*
* @since 2.1
*/
// non-Javadoc:
//
// <h3>JSON Conversion</h3>
// <p>A type adapter registered with Gson is automatically invoked while serializing
// or deserializing JSON. However, you can also use type adapters directly to serialize
// and deserialize JSON. Here is an example for deserialization: <pre> {@code
//
// String json = "{'origin':'0,0','points':['1,2','3,4']}";
// TypeAdapter<Graph> graphAdapter = gson.getAdapter(Graph.class);
// Graph graph = graphAdapter.fromJson(json);
// }</pre>
// And an example for serialization: <pre> {@code
//
// Graph graph = new Graph(...);
// TypeAdapter<Graph> graphAdapter = gson.getAdapter(Graph.class);
// String json = graphAdapter.toJson(graph);
// }</pre>
//
// <p>Type adapters are <strong>type-specific</strong>. For example, a {@code
// TypeAdapter<Date>} can convert {@code Date} instances to JSON and JSON to
// instances of {@code Date}, but cannot convert any other types.
//
public abstract class TypeAdapter<T> {
/**
* Writes one JSON value (an array, object, string, number, boolean or null)
* for {@code value}.
*
* @param value the Java object to write. May be null.
*/
public abstract void write(JsonWriter out, T value) throws IOException;
/**
* Converts {@code value} to a JSON document and writes it to {@code out}.
* Unlike Gson's similar {@link Gson#toJson(JsonElement, Appendable) toJson}
* method, this write is strict. Create a {@link
* JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call
* {@link #write(com.google.gson.stream.JsonWriter, Object)} for lenient
* writing.
*
* @param value the Java object to convert. May be null.
* @since 2.2
*/
public final void toJson(Writer out, T value) throws IOException {
JsonWriter writer = new JsonWriter(out);
write(writer, value);
}
/**
* This wrapper method is used to make a type adapter null tolerant. In general, a
* type adapter is required to handle nulls in write and read methods. Here is how this
* is typically done:<br>
* <pre> {@code
*
* Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
* new TypeAdapter<Foo>() {
* public Foo read(JsonReader in) throws IOException {
* if (in.peek() == JsonToken.NULL) {
* in.nextNull();
* return null;
* }
* // read a Foo from in and return it
* }
* public void write(JsonWriter out, Foo src) throws IOException {
* if (src == null) {
* out.nullValue();
* return;
* }
* // write src as JSON to out
* }
* }).create();
* }</pre>
* You can avoid this boilerplate handling of nulls by wrapping your type adapter with
* this method. Here is how we will rewrite the above example:
* <pre> {@code
*
* Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
* new TypeAdapter<Foo>() {
* public Foo read(JsonReader in) throws IOException {
* // read a Foo from in and return it
* }
* public void write(JsonWriter out, Foo src) throws IOException {
* // write src as JSON to out
* }
* }.nullSafe()).create();
* }</pre>
* Note that we didn't need to check for nulls in our type adapter after we used nullSafe.
*/
public final TypeAdapter<T> nullSafe() {
return new TypeAdapter<T>() {
@Override public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
} else {
TypeAdapter.this.write(out, value);
}
}
@Override public T read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
return TypeAdapter.this.read(reader);
}
};
}
/**
* Converts {@code value} to a JSON document. Unlike Gson's similar {@link
* Gson#toJson(Object) toJson} method, this write is strict. Create a {@link
* JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call
* {@link #write(com.google.gson.stream.JsonWriter, Object)} for lenient
* writing.
*
* @param value the Java object to convert. May be null.
* @since 2.2
*/
public final String toJson(T value) throws IOException {
StringWriter stringWriter = new StringWriter();
toJson(stringWriter, value);
return stringWriter.toString();
}
/**
* Converts {@code value} to a JSON tree.
*
* @param value the Java object to convert. May be null.
* @return the converted JSON tree. May be {@link JsonNull}.
* @since 2.2
*/
public final JsonElement toJsonTree(T value) {
try {
JsonTreeWriter jsonWriter = new JsonTreeWriter();
write(jsonWriter, value);
return jsonWriter.get();
} catch (IOException e) {
throw new JsonIOException(e);
}
}
/**
* Reads one JSON value (an array, object, string, number, boolean or null)
* and converts it to a Java object. Returns the converted object.
*
* @return the converted Java object. May be null.
*/
public abstract T read(JsonReader in) throws IOException;
/**
* Converts the JSON document in {@code in} to a Java object. Unlike Gson's
* similar {@link Gson#fromJson(java.io.Reader, Class) fromJson} method, this
* read is strict. Create a {@link JsonReader#setLenient(boolean) lenient}
* {@code JsonReader} and call {@link #read(JsonReader)} for lenient reading.
*
* @return the converted Java object. May be null.
* @since 2.2
*/
public final T fromJson(Reader in) throws IOException {
JsonReader reader = new JsonReader(in);
return read(reader);
}
/**
* Converts the JSON document in {@code json} to a Java object. Unlike Gson's
* similar {@link Gson#fromJson(String, Class) fromJson} method, this read is
* strict. Create a {@link JsonReader#setLenient(boolean) lenient} {@code
* JsonReader} and call {@link #read(JsonReader)} for lenient reading.
*
* @return the converted Java object. May be null.
* @since 2.2
*/
public final T fromJson(String json) throws IOException {
return fromJson(new StringReader(json));
}
/**
* Converts {@code jsonTree} to a Java object.
*
* @param jsonTree the Java object to convert. May be {@link JsonNull}.
* @since 2.2
*/
public final T fromJsonTree(JsonElement jsonTree) {
try {
JsonReader jsonReader = new JsonTreeReader(jsonTree);
return read(jsonReader);
} catch (IOException e) {
throw new JsonIOException(e);
}
}
}

View File

@ -1,170 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import com.google.gson.reflect.TypeToken;
/**
* Creates type adapters for set of related types. Type adapter factories are
* most useful when several types share similar structure in their JSON form.
*
* <h3>Example: Converting enums to lowercase</h3>
* In this example, we implement a factory that creates type adapters for all
* enums. The type adapters will write enums in lowercase, despite the fact
* that they're defined in {@code CONSTANT_CASE} in the corresponding Java
* model: <pre> {@code
*
* public class LowercaseEnumTypeAdapterFactory implements TypeAdapter.Factory {
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
* Class<T> rawType = (Class<T>) type.getRawType();
* if (!rawType.isEnum()) {
* return null;
* }
*
* final Map<String, T> lowercaseToConstant = new HashMap<String, T>();
* for (T constant : rawType.getEnumConstants()) {
* lowercaseToConstant.put(toLowercase(constant), constant);
* }
*
* return new TypeAdapter<T>() {
* public void write(JsonWriter out, T value) throws IOException {
* if (value == null) {
* out.nullValue();
* } else {
* out.value(toLowercase(value));
* }
* }
*
* public T read(JsonReader reader) throws IOException {
* if (reader.peek() == JsonToken.NULL) {
* reader.nextNull();
* return null;
* } else {
* return lowercaseToConstant.get(reader.nextString());
* }
* }
* };
* }
*
* private String toLowercase(Object o) {
* return o.toString().toLowerCase(Locale.US);
* }
* }
* }</pre>
*
* <p>Type adapter factories select which types they provide type adapters
* for. If a factory cannot support a given type, it must return null when
* that type is passed to {@link #create}. Factories should expect {@code
* create()} to be called on them for many types and should return null for
* most of those types. In the above example the factory returns null for
* calls to {@code create()} where {@code type} is not an enum.
*
* <p>A factory is typically called once per type, but the returned type
* adapter may be used many times. It is most efficient to do expensive work
* like reflection in {@code create()} so that the type adapter's {@code
* read()} and {@code write()} methods can be very fast. In this example the
* mapping from lowercase name to enum value is computed eagerly.
*
* <p>As with type adapters, factories must be <i>registered</i> with a {@link
* com.google.gson.GsonBuilder} for them to take effect: <pre> {@code
*
* GsonBuilder builder = new GsonBuilder();
* builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory());
* ...
* Gson gson = builder.create();
* }</pre>
* If multiple factories support the same type, the factory registered earlier
* takes precedence.
*
* <h3>Example: composing other type adapters</h3>
* In this example we implement a factory for Guava's {@code Multiset}
* collection type. The factory can be used to create type adapters for
* multisets of any element type: the type adapter for {@code
* Multiset<String>} is different from the type adapter for {@code
* Multiset<URL>}.
*
* <p>The type adapter <i>delegates</i> to another type adapter for the
* multiset elements. It figures out the element type by reflecting on the
* multiset's type token. A {@code Gson} is passed in to {@code create} for
* just this purpose: <pre> {@code
*
* public class MultisetTypeAdapterFactory implements TypeAdapter.Factory {
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
* Type type = typeToken.getType();
* if (typeToken.getRawType() != Multiset.class
* || !(type instanceof ParameterizedType)) {
* return null;
* }
*
* Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
* TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
* return (TypeAdapter<T>) newMultisetAdapter(elementAdapter);
* }
*
* private <E> TypeAdapter<Multiset<E>> newMultisetAdapter(
* final TypeAdapter<E> elementAdapter) {
* return new TypeAdapter<Multiset<E>>() {
* public void write(JsonWriter out, Multiset<E> value) throws IOException {
* if (value == null) {
* out.nullValue();
* return;
* }
*
* out.beginArray();
* for (Multiset.Entry<E> entry : value.entrySet()) {
* out.value(entry.getCount());
* elementAdapter.write(out, entry.getElement());
* }
* out.endArray();
* }
*
* public Multiset<E> read(JsonReader in) throws IOException {
* if (in.peek() == JsonToken.NULL) {
* in.nextNull();
* return null;
* }
*
* Multiset<E> result = LinkedHashMultiset.create();
* in.beginArray();
* while (in.hasNext()) {
* int count = in.nextInt();
* E element = elementAdapter.read(in);
* result.add(element, count);
* }
* in.endArray();
* return result;
* }
* };
* }
* }
* }</pre>
* Delegating from one type adapter to another is extremely powerful; it's
* the foundation of how Gson converts Java objects and collections. Whenever
* possible your factory should retrieve its delegate type adapter in the
* {@code create()} method; this ensures potentially-expensive type adapter
* creation happens only once.
*
* @since 2.1
*/
public interface TypeAdapterFactory {
/**
* Returns a type adapter for {@code type}, or null if this factory doesn't
* support {@code type}.
*/
<T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
}

View File

@ -1,79 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* An annotation that indicates this member should be exposed for JSON
* serialization or deserialization.
*
* <p>This annotation has no effect unless you build {@link com.google.gson.Gson}
* with a {@link com.google.gson.GsonBuilder} and invoke
* {@link com.google.gson.GsonBuilder#excludeFieldsWithoutExposeAnnotation()}
* method.</p>
*
* <p>Here is an example of how this annotation is meant to be used:
* <p><pre>
* public class User {
* &#64Expose private String firstName;
* &#64Expose(serialize = false) private String lastName;
* &#64Expose (serialize = false, deserialize = false) private String emailAddress;
* private String password;
* }
* </pre></p>
* If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
* methods will use the {@code password} field along-with {@code firstName}, {@code lastName},
* and {@code emailAddress} for serialization and deserialization. However, if you created Gson
* with {@code Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()}
* then the {@code toJson()} and {@code fromJson()} methods of Gson will exclude the
* {@code password} field. This is because the {@code password} field is not marked with the
* {@code @Expose} annotation. Gson will also exclude {@code lastName} and {@code emailAddress}
* from serialization since {@code serialize} is set to {@code false}. Similarly, Gson will
* exclude {@code emailAddress} from deserialization since {@code deserialize} is set to false.
*
* <p>Note that another way to achieve the same effect would have been to just mark the
* {@code password} field as {@code transient}, and Gson would have excluded it even with default
* settings. The {@code @Expose} annotation is useful in a style of programming where you want to
* explicitly specify all fields that should get considered for serialization or deserialization.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expose {
/**
* If {@code true}, the field marked with this annotation is written out in the JSON while
* serializing. If {@code false}, the field marked with this annotation is skipped from the
* serialized output. Defaults to {@code true}.
* @since 1.4
*/
public boolean serialize() default true;
/**
* If {@code true}, the field marked with this annotation is deserialized from the JSON.
* If {@code false}, the field marked with this annotation is skipped during deserialization.
* Defaults to {@code true}.
* @since 1.4
*/
public boolean deserialize() default true;
}

View File

@ -1,93 +0,0 @@
/*
* Copyright (C) 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.google.gson.TypeAdapter;
/**
* An annotation that indicates the Gson {@link TypeAdapter} to use with a class or a field.
* Any type adapters registered in {@link com.google.gson.GsonBuilder} supersede the adapter
* specified in this annotation.
*
* <p>Here is an example of how this annotation is used:</p>
* <pre>
* &#64JsonAdapter(UserJsonAdapter.class)
* public class User {
* public final String firstName, lastName;
* private User(String firstName, String lastName) {
* this.firstName = firstName;
* this.lastName = lastName;
* }
* }
* public class UserJsonAdapter extends TypeAdapter&lt;User&gt; {
* &#64Override public void write(JsonWriter out, User user) throws IOException {
* // implement write: combine firstName and lastName into name
* out.beginObject();
* out.name("name");
* out.value(user.firstName + " " + user.lastName);
* out.endObject();
* // implement the write method
* }
* &#64Override public User read(JsonReader in) throws IOException {
* // implement read: split name into firstName and lastName
* in.beginObject();
* in.nextName();
* String[] nameParts = in.nextString().split(" ");
* in.endObject();
* return new User(nameParts[0], nameParts[1]);
* }
* }
* </pre>
*
* Since User class specified UserJsonAdapter.class in &#64JsonAdapter annotation, it
* will automatically be invoked to serialize/deserialize User instances. <br>
*
* If the UserJsonAdapter needs a constructor other than a no-args constructor, you must register
* an {@link com.google.gson.InstanceCreator} for it.
*
* <p> Here is an example of how to apply this annotation to a field.
* <pre>
* private static final class Gadget {
* &#64JsonAdapter(UserJsonAdapter2.class)
* final User user;
* Gadget(User user) {
* this.user = user;
* }
* }
* </pre>
* The above annotation will ensure UserJsonAdapter2 supersedes UserJsonAdapter for the user
* field of the Gadget class.
*
* @since 2.3
*
* @author Inderjeet Singh
* @author Joel Leitch
* @author Jesse Leitch
*/
// Note that the above example is taken from AdaptAnnotationTest.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface JsonAdapter {
Class<? extends TypeAdapter<?>> value();
}

View File

@ -1,74 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* An annotation that indicates this member should be serialized to JSON with
* the provided name value as its field name.
*
* <p>This annotation will override any {@link com.google.gson.FieldNamingPolicy}, including
* the default field naming policy, that may have been set on the {@link com.google.gson.Gson}
* instance. A different naming policy can set using the {@code GsonBuilder} class. See
* {@link com.google.gson.GsonBuilder#setFieldNamingPolicy(com.google.gson.FieldNamingPolicy)}
* for more information.</p>
*
* <p>Here is an example of how this annotation is meant to be used:</p>
* <pre>
* public class SomeClassWithFields {
* &#64SerializedName("name") private final String someField;
* private final String someOtherField;
*
* public SomeClassWithFields(String a, String b) {
* this.someField = a;
* this.someOtherField = b;
* }
* }
* </pre>
*
* <p>The following shows the output that is generated when serializing an instance of the
* above example class:</p>
* <pre>
* SomeClassWithFields objectToSerialize = new SomeClassWithFields("a", "b");
* Gson gson = new Gson();
* String jsonRepresentation = gson.toJson(objectToSerialize);
* System.out.println(jsonRepresentation);
*
* ===== OUTPUT =====
* {"name":"a","someOtherField":"b"}
* </pre>
*
* <p>NOTE: The value you specify in this annotation must be a valid JSON field name.</p>
*
* @see com.google.gson.FieldNamingPolicy
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SerializedName {
/**
* @return the desired name of the field when it is serialized
*/
String value();
}

View File

@ -1,61 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* An annotation that indicates the version number since a member or a type has been present.
* This annotation is useful to manage versioning of your Json classes for a web-service.
*
* <p>
* This annotation has no effect unless you build {@link com.google.gson.Gson} with a
* {@link com.google.gson.GsonBuilder} and invoke
* {@link com.google.gson.GsonBuilder#setVersion(double)} method.
*
* <p>Here is an example of how this annotation is meant to be used:</p>
* <pre>
* public class User {
* private String firstName;
* private String lastName;
* &#64Since(1.0) private String emailAddress;
* &#64Since(1.0) private String password;
* &#64Since(1.1) private Address address;
* }
* </pre>
*
* <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
* methods will use all the fields for serialization and deserialization. However, if you created
* Gson with {@code Gson gson = new GsonBuilder().setVersion(1.0).create()} then the
* {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code address} field
* since it's version number is set to {@code 1.1}.</p>
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Since {
/**
* the value indicating a version number since this member
* or type has been present.
*/
double value();
}

View File

@ -1,66 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* An annotation that indicates the version number until a member or a type should be present.
* Basically, if Gson is created with a version number that exceeds the value stored in the
* {@code Until} annotation then the field will be ignored from the JSON output. This annotation
* is useful to manage versioning of your JSON classes for a web-service.
*
* <p>
* This annotation has no effect unless you build {@link com.google.gson.Gson} with a
* {@link com.google.gson.GsonBuilder} and invoke
* {@link com.google.gson.GsonBuilder#setVersion(double)} method.
*
* <p>Here is an example of how this annotation is meant to be used:</p>
* <pre>
* public class User {
* private String firstName;
* private String lastName;
* &#64Until(1.1) private String emailAddress;
* &#64Until(1.1) private String password;
* }
* </pre>
*
* <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
* methods will use all the fields for serialization and deserialization. However, if you created
* Gson with {@code Gson gson = new GsonBuilder().setVersion(1.2).create()} then the
* {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code emailAddress}
* and {@code password} fields from the example above, because the version number passed to the
* GsonBuilder, {@code 1.2}, exceeds the version number set on the {@code Until} annotation,
* {@code 1.1}, for those fields.
*
* @author Inderjeet Singh
* @author Joel Leitch
* @since 1.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Until {
/**
* the value indicating a version number until this member
* or type should be ignored.
*/
double value();
}

View File

@ -1,6 +0,0 @@
/**
* This package provides annotations that can be used with {@link com.google.gson.Gson}.
*
* @author Inderjeet Singh, Joel Leitch
*/
package com.google.gson.annotations;

View File

@ -1,45 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.internal;
/**
* A simple utility class used to check method Preconditions.
*
* <pre>
* public long divideBy(long value) {
* Preconditions.checkArgument(value != 0);
* return this.value / value;
* }
* </pre>
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public final class $Gson$Preconditions {
public static <T> T checkNotNull(T obj) {
if (obj == null) {
throw new NullPointerException();
}
return obj;
}
public static void checkArgument(boolean condition) {
if (!condition) {
throw new IllegalArgumentException();
}
}
}

View File

@ -1,608 +0,0 @@
/**
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.internal;
import static com.google.gson.internal.$Gson$Preconditions.checkArgument;
import static com.google.gson.internal.$Gson$Preconditions.checkNotNull;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
/**
* Static methods for working with types.
*
* @author Bob Lee
* @author Jesse Wilson
*/
public final class $Gson$Types {
static final Type[] EMPTY_TYPE_ARRAY = new Type[] {};
private $Gson$Types() {}
/**
* Returns a new parameterized type, applying {@code typeArguments} to
* {@code rawType} and enclosed by {@code ownerType}.
*
* @return a {@link java.io.Serializable serializable} parameterized type.
*/
public static ParameterizedType newParameterizedTypeWithOwner(
Type ownerType, Type rawType, Type... typeArguments) {
return new ParameterizedTypeImpl(ownerType, rawType, typeArguments);
}
/**
* Returns an array type whose elements are all instances of
* {@code componentType}.
*
* @return a {@link java.io.Serializable serializable} generic array type.
*/
public static GenericArrayType arrayOf(Type componentType) {
return new GenericArrayTypeImpl(componentType);
}
/**
* Returns a type that represents an unknown type that extends {@code bound}.
* For example, if {@code bound} is {@code CharSequence.class}, this returns
* {@code ? extends CharSequence}. If {@code bound} is {@code Object.class},
* this returns {@code ?}, which is shorthand for {@code ? extends Object}.
*/
public static WildcardType subtypeOf(Type bound) {
return new WildcardTypeImpl(new Type[] { bound }, EMPTY_TYPE_ARRAY);
}
/**
* Returns a type that represents an unknown supertype of {@code bound}. For
* example, if {@code bound} is {@code String.class}, this returns {@code ?
* super String}.
*/
public static WildcardType supertypeOf(Type bound) {
return new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { bound });
}
/**
* Returns a type that is functionally equal but not necessarily equal
* according to {@link Object#equals(Object) Object.equals()}. The returned
* type is {@link java.io.Serializable}.
*/
public static Type canonicalize(Type type) {
if (type instanceof Class) {
Class<?> c = (Class<?>) type;
return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;
} else if (type instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) type;
return new ParameterizedTypeImpl(p.getOwnerType(),
p.getRawType(), p.getActualTypeArguments());
} else if (type instanceof GenericArrayType) {
GenericArrayType g = (GenericArrayType) type;
return new GenericArrayTypeImpl(g.getGenericComponentType());
} else if (type instanceof WildcardType) {
WildcardType w = (WildcardType) type;
return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());
} else {
// type is either serializable as-is or unsupported
return type;
}
}
public static Class<?> getRawType(Type type) {
if (type instanceof Class<?>) {
// type is a normal class.
return (Class<?>) type;
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
// I'm not exactly sure why getRawType() returns Type instead of Class.
// Neal isn't either but suspects some pathological case related
// to nested classes exists.
Type rawType = parameterizedType.getRawType();
checkArgument(rawType instanceof Class);
return (Class<?>) rawType;
} else if (type instanceof GenericArrayType) {
Type componentType = ((GenericArrayType)type).getGenericComponentType();
return Array.newInstance(getRawType(componentType), 0).getClass();
} else if (type instanceof TypeVariable) {
// we could use the variable's bounds, but that won't work if there are multiple.
// having a raw type that's more general than necessary is okay
return Object.class;
} else if (type instanceof WildcardType) {
return getRawType(((WildcardType) type).getUpperBounds()[0]);
} else {
String className = type == null ? "null" : type.getClass().getName();
throw new IllegalArgumentException("Expected a Class, ParameterizedType, or "
+ "GenericArrayType, but <" + type + "> is of type " + className);
}
}
static boolean equal(Object a, Object b) {
return a == b || (a != null && a.equals(b));
}
/**
* Returns true if {@code a} and {@code b} are equal.
*/
public static boolean equals(Type a, Type b) {
if (a == b) {
// also handles (a == null && b == null)
return true;
} else if (a instanceof Class) {
// Class already specifies equals().
return a.equals(b);
} else if (a instanceof ParameterizedType) {
if (!(b instanceof ParameterizedType)) {
return false;
}
// TODO: save a .clone() call
ParameterizedType pa = (ParameterizedType) a;
ParameterizedType pb = (ParameterizedType) b;
return equal(pa.getOwnerType(), pb.getOwnerType())
&& pa.getRawType().equals(pb.getRawType())
&& Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments());
} else if (a instanceof GenericArrayType) {
if (!(b instanceof GenericArrayType)) {
return false;
}
GenericArrayType ga = (GenericArrayType) a;
GenericArrayType gb = (GenericArrayType) b;
return equals(ga.getGenericComponentType(), gb.getGenericComponentType());
} else if (a instanceof WildcardType) {
if (!(b instanceof WildcardType)) {
return false;
}
WildcardType wa = (WildcardType) a;
WildcardType wb = (WildcardType) b;
return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds())
&& Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds());
} else if (a instanceof TypeVariable) {
if (!(b instanceof TypeVariable)) {
return false;
}
TypeVariable<?> va = (TypeVariable<?>) a;
TypeVariable<?> vb = (TypeVariable<?>) b;
return va.getGenericDeclaration() == vb.getGenericDeclaration()
&& va.getName().equals(vb.getName());
} else {
// This isn't a type we support. Could be a generic array type, wildcard type, etc.
return false;
}
}
private static int hashCodeOrZero(Object o) {
return o != null ? o.hashCode() : 0;
}
public static String typeToString(Type type) {
return type instanceof Class ? ((Class<?>) type).getName() : type.toString();
}
/**
* Returns the generic supertype for {@code supertype}. For example, given a class {@code
* IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set<Integer>} and the
* result when the supertype is {@code Collection.class} is {@code Collection<Integer>}.
*/
static Type getGenericSupertype(Type context, Class<?> rawType, Class<?> toResolve) {
if (toResolve == rawType) {
return context;
}
// we skip searching through interfaces if unknown is an interface
if (toResolve.isInterface()) {
Class<?>[] interfaces = rawType.getInterfaces();
for (int i = 0, length = interfaces.length; i < length; i++) {
if (interfaces[i] == toResolve) {
return rawType.getGenericInterfaces()[i];
} else if (toResolve.isAssignableFrom(interfaces[i])) {
return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve);
}
}
}
// check our supertypes
if (!rawType.isInterface()) {
while (rawType != Object.class) {
Class<?> rawSupertype = rawType.getSuperclass();
if (rawSupertype == toResolve) {
return rawType.getGenericSuperclass();
} else if (toResolve.isAssignableFrom(rawSupertype)) {
return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve);
}
rawType = rawSupertype;
}
}
// we can't resolve this further
return toResolve;
}
/**
* Returns the generic form of {@code supertype}. For example, if this is {@code
* ArrayList<String>}, this returns {@code Iterable<String>} given the input {@code
* Iterable.class}.
*
* @param supertype a superclass of, or interface implemented by, this.
*/
static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
checkArgument(supertype.isAssignableFrom(contextRawType));
return resolve(context, contextRawType,
$Gson$Types.getGenericSupertype(context, contextRawType, supertype));
}
/**
* Returns the component type of this array type.
* @throws ClassCastException if this type is not an array.
*/
public static Type getArrayComponentType(Type array) {
return array instanceof GenericArrayType
? ((GenericArrayType) array).getGenericComponentType()
: ((Class<?>) array).getComponentType();
}
/**
* Returns the element type of this collection type.
* @throws IllegalArgumentException if this type is not a collection.
*/
public static Type getCollectionElementType(Type context, Class<?> contextRawType) {
Type collectionType = getSupertype(context, contextRawType, Collection.class);
if (collectionType instanceof WildcardType) {
collectionType = ((WildcardType)collectionType).getUpperBounds()[0];
}
if (collectionType instanceof ParameterizedType) {
return ((ParameterizedType) collectionType).getActualTypeArguments()[0];
}
return Object.class;
}
/**
* Returns a two element array containing this map's key and value types in
* positions 0 and 1 respectively.
*/
public static Type[] getMapKeyAndValueTypes(Type context, Class<?> contextRawType) {
/*
* Work around a problem with the declaration of java.util.Properties. That
* class should extend Hashtable<String, String>, but it's declared to
* extend Hashtable<Object, Object>.
*/
if (context == Properties.class) {
return new Type[] { String.class, String.class }; // TODO: test subclasses of Properties!
}
Type mapType = getSupertype(context, contextRawType, Map.class);
// TODO: strip wildcards?
if (mapType instanceof ParameterizedType) {
ParameterizedType mapParameterizedType = (ParameterizedType) mapType;
return mapParameterizedType.getActualTypeArguments();
}
return new Type[] { Object.class, Object.class };
}
public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
// this implementation is made a little more complicated in an attempt to avoid object-creation
while (true) {
if (toResolve instanceof TypeVariable) {
TypeVariable<?> typeVariable = (TypeVariable<?>) toResolve;
toResolve = resolveTypeVariable(context, contextRawType, typeVariable);
if (toResolve == typeVariable) {
return toResolve;
}
} else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) {
Class<?> original = (Class<?>) toResolve;
Type componentType = original.getComponentType();
Type newComponentType = resolve(context, contextRawType, componentType);
return componentType == newComponentType
? original
: arrayOf(newComponentType);
} else if (toResolve instanceof GenericArrayType) {
GenericArrayType original = (GenericArrayType) toResolve;
Type componentType = original.getGenericComponentType();
Type newComponentType = resolve(context, contextRawType, componentType);
return componentType == newComponentType
? original
: arrayOf(newComponentType);
} else if (toResolve instanceof ParameterizedType) {
ParameterizedType original = (ParameterizedType) toResolve;
Type ownerType = original.getOwnerType();
Type newOwnerType = resolve(context, contextRawType, ownerType);
boolean changed = newOwnerType != ownerType;
Type[] args = original.getActualTypeArguments();
for (int t = 0, length = args.length; t < length; t++) {
Type resolvedTypeArgument = resolve(context, contextRawType, args[t]);
if (resolvedTypeArgument != args[t]) {
if (!changed) {
args = args.clone();
changed = true;
}
args[t] = resolvedTypeArgument;
}
}
return changed
? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args)
: original;
} else if (toResolve instanceof WildcardType) {
WildcardType original = (WildcardType) toResolve;
Type[] originalLowerBound = original.getLowerBounds();
Type[] originalUpperBound = original.getUpperBounds();
if (originalLowerBound.length == 1) {
Type lowerBound = resolve(context, contextRawType, originalLowerBound[0]);
if (lowerBound != originalLowerBound[0]) {
return supertypeOf(lowerBound);
}
} else if (originalUpperBound.length == 1) {
Type upperBound = resolve(context, contextRawType, originalUpperBound[0]);
if (upperBound != originalUpperBound[0]) {
return subtypeOf(upperBound);
}
}
return original;
} else {
return toResolve;
}
}
}
/**
* Given a parameterized type A&lt;B,C&gt;, returns B. If the specified type is not
* a generic type, returns null.
*/
public static Type getFirstTypeArgument(Type type) {
try {
if (!(type instanceof ParameterizedType)) return null;
ParameterizedType ptype = (ParameterizedType) type;
Type[] actualTypeArguments = ptype.getActualTypeArguments();
if (actualTypeArguments.length == 0) return null;
return canonicalize(actualTypeArguments[0]);
} catch (Exception e) {
return null;
}
}
static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
Class<?> declaredByRaw = declaringClassOf(unknown);
// we can't reduce this further
if (declaredByRaw == null) {
return unknown;
}
Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw);
if (declaredBy instanceof ParameterizedType) {
int index = indexOf(declaredByRaw.getTypeParameters(), unknown);
return ((ParameterizedType) declaredBy).getActualTypeArguments()[index];
}
return unknown;
}
private static int indexOf(Object[] array, Object toFind) {
for (int i = 0; i < array.length; i++) {
if (toFind.equals(array[i])) {
return i;
}
}
throw new NoSuchElementException();
}
/**
* Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by
* a class.
*/
private static Class<?> declaringClassOf(TypeVariable<?> typeVariable) {
GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
return genericDeclaration instanceof Class
? (Class<?>) genericDeclaration
: null;
}
private static void checkNotPrimitive(Type type) {
checkArgument(!(type instanceof Class<?>) || !((Class<?>) type).isPrimitive());
}
private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable {
private final Type ownerType;
private final Type rawType;
private final Type[] typeArguments;
public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
// require an owner type if the raw type needs it
if (rawType instanceof Class<?>) {
Class<?> rawTypeAsClass = (Class<?>) rawType;
boolean isStaticOrTopLevelClass = Modifier.isStatic(rawTypeAsClass.getModifiers())
|| rawTypeAsClass.getEnclosingClass() == null;
checkArgument(ownerType != null || isStaticOrTopLevelClass);
}
this.ownerType = ownerType == null ? null : canonicalize(ownerType);
this.rawType = canonicalize(rawType);
this.typeArguments = typeArguments.clone();
for (int t = 0; t < this.typeArguments.length; t++) {
checkNotNull(this.typeArguments[t]);
checkNotPrimitive(this.typeArguments[t]);
this.typeArguments[t] = canonicalize(this.typeArguments[t]);
}
}
@Override
public Type[] getActualTypeArguments() {
return typeArguments.clone();
}
@Override
public Type getRawType() {
return rawType;
}
@Override
public Type getOwnerType() {
return ownerType;
}
@Override public boolean equals(Object other) {
return other instanceof ParameterizedType
&& $Gson$Types.equals(this, (ParameterizedType) other);
}
@Override public int hashCode() {
return Arrays.hashCode(typeArguments)
^ rawType.hashCode()
^ hashCodeOrZero(ownerType);
}
@Override public String toString() {
StringBuilder stringBuilder = new StringBuilder(30 * (typeArguments.length + 1));
stringBuilder.append(typeToString(rawType));
if (typeArguments.length == 0) {
return stringBuilder.toString();
}
stringBuilder.append("<").append(typeToString(typeArguments[0]));
for (int i = 1; i < typeArguments.length; i++) {
stringBuilder.append(", ").append(typeToString(typeArguments[i]));
}
return stringBuilder.append(">").toString();
}
private static final long serialVersionUID = 0;
}
private static final class GenericArrayTypeImpl implements GenericArrayType, Serializable {
private final Type componentType;
public GenericArrayTypeImpl(Type componentType) {
this.componentType = canonicalize(componentType);
}
@Override
public Type getGenericComponentType() {
return componentType;
}
@Override public boolean equals(Object o) {
return o instanceof GenericArrayType
&& $Gson$Types.equals(this, (GenericArrayType) o);
}
@Override public int hashCode() {
return componentType.hashCode();
}
@Override public String toString() {
return typeToString(componentType) + "[]";
}
private static final long serialVersionUID = 0;
}
/**
* The WildcardType interface supports multiple upper bounds and multiple
* lower bounds. We only support what the Java 6 language needs - at most one
* bound. If a lower bound is set, the upper bound must be Object.class.
*/
private static final class WildcardTypeImpl implements WildcardType, Serializable {
private final Type upperBound;
private final Type lowerBound;
public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
checkArgument(lowerBounds.length <= 1);
checkArgument(upperBounds.length == 1);
if (lowerBounds.length == 1) {
checkNotNull(lowerBounds[0]);
checkNotPrimitive(lowerBounds[0]);
checkArgument(upperBounds[0] == Object.class);
this.lowerBound = canonicalize(lowerBounds[0]);
this.upperBound = Object.class;
} else {
checkNotNull(upperBounds[0]);
checkNotPrimitive(upperBounds[0]);
this.lowerBound = null;
this.upperBound = canonicalize(upperBounds[0]);
}
}
@Override
public Type[] getUpperBounds() {
return new Type[] { upperBound };
}
@Override
public Type[] getLowerBounds() {
return lowerBound != null ? new Type[] { lowerBound } : EMPTY_TYPE_ARRAY;
}
@Override public boolean equals(Object other) {
return other instanceof WildcardType
&& $Gson$Types.equals(this, (WildcardType) other);
}
@Override public int hashCode() {
// this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds());
return (lowerBound != null ? 31 + lowerBound.hashCode() : 1)
^ (31 + upperBound.hashCode());
}
@Override public String toString() {
if (lowerBound != null) {
return "? super " + typeToString(lowerBound);
} else if (upperBound == Object.class) {
return "?";
} else {
return "? extends " + typeToString(upperBound);
}
}
private static final long serialVersionUID = 0;
}
}

View File

@ -1,232 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.internal;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonIOException;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* Returns a function that can construct an instance of a requested type.
*/
public final class ConstructorConstructor {
private final Map<Type, InstanceCreator<?>> instanceCreators;
public ConstructorConstructor(Map<Type, InstanceCreator<?>> instanceCreators) {
this.instanceCreators = instanceCreators;
}
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
final Type type = typeToken.getType();
final Class<? super T> rawType = typeToken.getRawType();
// first try an instance creator
@SuppressWarnings("unchecked") // types must agree
final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
if (typeCreator != null) {
return new ObjectConstructor<T>() {
@Override
public T construct() {
return typeCreator.createInstance(type);
}
};
}
// Next try raw type match for instance creators
@SuppressWarnings("unchecked") // types must agree
final InstanceCreator<T> rawTypeCreator =
(InstanceCreator<T>) instanceCreators.get(rawType);
if (rawTypeCreator != null) {
return new ObjectConstructor<T>() {
@Override
public T construct() {
return rawTypeCreator.createInstance(type);
}
};
}
ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);
if (defaultConstructor != null) {
return defaultConstructor;
}
ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);
if (defaultImplementation != null) {
return defaultImplementation;
}
// finally try unsafe
return newUnsafeAllocator(type, rawType);
}
private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {
try {
final Constructor<? super T> constructor = rawType.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return new ObjectConstructor<T>() {
@Override
@SuppressWarnings("unchecked") // T is the same raw type as is requested
public T construct() {
try {
Object[] args = null;
return (T) constructor.newInstance(args);
} catch (InstantiationException e) {
// TODO: JsonParseException ?
throw new RuntimeException("Failed to invoke " + constructor + " with no args", e);
} catch (InvocationTargetException e) {
// TODO: don't wrap if cause is unchecked!
// TODO: JsonParseException ?
throw new RuntimeException("Failed to invoke " + constructor + " with no args",
e.getTargetException());
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
};
} catch (NoSuchMethodException e) {
return null;
}
}
/**
* Constructors for common interface types like Map and List and their
* subytpes.
*/
@SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is
private <T> ObjectConstructor<T> newDefaultImplementationConstructor(
final Type type, Class<? super T> rawType) {
if (Collection.class.isAssignableFrom(rawType)) {
if (SortedSet.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override
public T construct() {
return (T) new TreeSet<Object>();
}
};
} else if (EnumSet.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override
@SuppressWarnings("rawtypes")
public T construct() {
if (type instanceof ParameterizedType) {
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
if (elementType instanceof Class) {
return (T) EnumSet.noneOf((Class)elementType);
} else {
throw new JsonIOException("Invalid EnumSet type: " + type.toString());
}
} else {
throw new JsonIOException("Invalid EnumSet type: " + type.toString());
}
}
};
} else if (Set.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override
public T construct() {
return (T) new LinkedHashSet<Object>();
}
};
} else if (Queue.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override
public T construct() {
return (T) new LinkedList<Object>();
}
};
} else {
return new ObjectConstructor<T>() {
@Override
public T construct() {
return (T) new ArrayList<Object>();
}
};
}
}
if (Map.class.isAssignableFrom(rawType)) {
if (SortedMap.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override
public T construct() {
return (T) new TreeMap<Object, Object>();
}
};
} else if (type instanceof ParameterizedType && !(String.class.isAssignableFrom(
TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType()))) {
return new ObjectConstructor<T>() {
@Override
public T construct() {
return (T) new LinkedHashMap<Object, Object>();
}
};
} else {
return new ObjectConstructor<T>() {
@Override
public T construct() {
return (T) new LinkedTreeMap<String, Object>();
}
};
}
}
return null;
}
private <T> ObjectConstructor<T> newUnsafeAllocator(
final Type type, final Class<? super T> rawType) {
return new ObjectConstructor<T>() {
private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
@Override
@SuppressWarnings("unchecked")
public T construct() {
try {
Object newInstance = unsafeAllocator.newInstance(rawType);
return (T) newInstance;
} catch (Exception e) {
throw new RuntimeException(("Unable to invoke no-args constructor for " + type + ". "
+ "Register an InstanceCreator with Gson for this type may fix this problem."), e);
}
}
};
}
@Override public String toString() {
return instanceCreators.toString();
}
}

View File

@ -1,252 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.internal;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.Since;
import com.google.gson.annotations.Until;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* This class selects which fields and types to omit. It is configurable,
* supporting version attributes {@link Since} and {@link Until}, modifiers,
* synthetic fields, anonymous and local classes, inner classes, and fields with
* the {@link Expose} annotation.
*
* <p>This class is a type adapter factory; types that are excluded will be
* adapted to null. It may delegate to another type adapter if only one
* direction is excluded.
*
* @author Joel Leitch
* @author Jesse Wilson
*/
public final class Excluder implements TypeAdapterFactory, Cloneable {
private static final double IGNORE_VERSIONS = -1.0d;
public static final Excluder DEFAULT = new Excluder();
private double version = IGNORE_VERSIONS;
private int modifiers = Modifier.TRANSIENT | Modifier.STATIC;
private boolean serializeInnerClasses = true;
private boolean requireExpose;
private List<ExclusionStrategy> serializationStrategies = Collections.emptyList();
private List<ExclusionStrategy> deserializationStrategies = Collections.emptyList();
@Override protected Excluder clone() {
try {
return (Excluder) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public Excluder withVersion(double ignoreVersionsAfter) {
Excluder result = clone();
result.version = ignoreVersionsAfter;
return result;
}
public Excluder withModifiers(int... modifiers) {
Excluder result = clone();
result.modifiers = 0;
for (int modifier : modifiers) {
result.modifiers |= modifier;
}
return result;
}
public Excluder disableInnerClassSerialization() {
Excluder result = clone();
result.serializeInnerClasses = false;
return result;
}
public Excluder excludeFieldsWithoutExposeAnnotation() {
Excluder result = clone();
result.requireExpose = true;
return result;
}
public Excluder withExclusionStrategy(ExclusionStrategy exclusionStrategy,
boolean serialization, boolean deserialization) {
Excluder result = clone();
if (serialization) {
result.serializationStrategies = new ArrayList<ExclusionStrategy>(serializationStrategies);
result.serializationStrategies.add(exclusionStrategy);
}
if (deserialization) {
result.deserializationStrategies
= new ArrayList<ExclusionStrategy>(deserializationStrategies);
result.deserializationStrategies.add(exclusionStrategy);
}
return result;
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
Class<?> rawType = type.getRawType();
final boolean skipSerialize = excludeClass(rawType, true);
final boolean skipDeserialize = excludeClass(rawType, false);
if (!skipSerialize && !skipDeserialize) {
return null;
}
return new TypeAdapter<T>() {
/** The delegate is lazily created because it may not be needed, and creating it may fail. */
private TypeAdapter<T> delegate;
@Override public T read(JsonReader in) throws IOException {
if (skipDeserialize) {
in.skipValue();
return null;
}
return delegate().read(in);
}
@Override public void write(JsonWriter out, T value) throws IOException {
if (skipSerialize) {
out.nullValue();
return;
}
delegate().write(out, value);
}
private TypeAdapter<T> delegate() {
TypeAdapter<T> d = delegate;
return d != null
? d
: (delegate = gson.getDelegateAdapter(Excluder.this, type));
}
};
}
public boolean excludeField(Field field, boolean serialize) {
if ((modifiers & field.getModifiers()) != 0) {
return true;
}
if (version != Excluder.IGNORE_VERSIONS
&& !isValidVersion(field.getAnnotation(Since.class), field.getAnnotation(Until.class))) {
return true;
}
if (field.isSynthetic()) {
return true;
}
if (requireExpose) {
Expose annotation = field.getAnnotation(Expose.class);
if (annotation == null || (serialize ? !annotation.serialize() : !annotation.deserialize())) {
return true;
}
}
if (!serializeInnerClasses && isInnerClass(field.getType())) {
return true;
}
if (isAnonymousOrLocal(field.getType())) {
return true;
}
List<ExclusionStrategy> list = serialize ? serializationStrategies : deserializationStrategies;
if (!list.isEmpty()) {
FieldAttributes fieldAttributes = new FieldAttributes(field);
for (ExclusionStrategy exclusionStrategy : list) {
if (exclusionStrategy.shouldSkipField(fieldAttributes)) {
return true;
}
}
}
return false;
}
public boolean excludeClass(Class<?> clazz, boolean serialize) {
if (version != Excluder.IGNORE_VERSIONS
&& !isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class))) {
return true;
}
if (!serializeInnerClasses && isInnerClass(clazz)) {
return true;
}
if (isAnonymousOrLocal(clazz)) {
return true;
}
List<ExclusionStrategy> list = serialize ? serializationStrategies : deserializationStrategies;
for (ExclusionStrategy exclusionStrategy : list) {
if (exclusionStrategy.shouldSkipClass(clazz)) {
return true;
}
}
return false;
}
private boolean isAnonymousOrLocal(Class<?> clazz) {
return !Enum.class.isAssignableFrom(clazz)
&& (clazz.isAnonymousClass() || clazz.isLocalClass());
}
private boolean isInnerClass(Class<?> clazz) {
return clazz.isMemberClass() && !isStatic(clazz);
}
private boolean isStatic(Class<?> clazz) {
return (clazz.getModifiers() & Modifier.STATIC) != 0;
}
private boolean isValidVersion(Since since, Until until) {
return isValidSince(since) && isValidUntil(until);
}
private boolean isValidSince(Since annotation) {
if (annotation != null) {
double annotationVersion = annotation.value();
if (annotationVersion > version) {
return false;
}
}
return true;
}
private boolean isValidUntil(Until annotation) {
if (annotation != null) {
double annotationVersion = annotation.value();
if (annotationVersion <= version) {
return false;
}
}
return true;
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.internal;
import com.google.gson.stream.JsonReader;
import java.io.IOException;
/**
* Internal-only APIs of JsonReader available only to other classes in Gson.
*/
public abstract class JsonReaderInternalAccess {
public static JsonReaderInternalAccess INSTANCE;
/**
* Changes the type of the current property name token to a string value.
*/
public abstract void promoteNameToValue(JsonReader reader) throws IOException;
}

View File

@ -1,78 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.internal;
import java.io.ObjectStreamException;
import java.math.BigDecimal;
/**
* This class holds a number value that is lazily converted to a specific number type
*
* @author Inderjeet Singh
*/
public final class LazilyParsedNumber extends Number {
private final String value;
public LazilyParsedNumber(String value) {
this.value = value;
}
@Override
public int intValue() {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
try {
return (int) Long.parseLong(value);
} catch (NumberFormatException nfe) {
return new BigDecimal(value).intValue();
}
}
}
@Override
public long longValue() {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
return new BigDecimal(value).longValue();
}
}
@Override
public float floatValue() {
return Float.parseFloat(value);
}
@Override
public double doubleValue() {
return Double.parseDouble(value);
}
@Override
public String toString() {
return value;
}
/**
* If somebody is unlucky enough to have to serialize one of these, serialize
* it as a BigDecimal so that they won't need Gson on the other side to
* deserialize it.
*/
private Object writeReplace() throws ObjectStreamException {
return new BigDecimal(value);
}
}

View File

@ -1,869 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
* Copyright (C) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.internal;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A map of comparable keys to values. Unlike {@code TreeMap}, this class uses
* insertion order for iteration order. Comparison order is only used as an
* optimization for efficient insertion and removal.
*
* <p>This implementation was derived from Android 4.1's TreeMap and
* LinkedHashMap classes.
*/
public final class LinkedHashTreeMap<K, V> extends AbstractMap<K, V> implements Serializable {
@SuppressWarnings({ "unchecked", "rawtypes" }) // to avoid Comparable<Comparable<Comparable<...>>>
private static final Comparator<Comparable> NATURAL_ORDER = new Comparator<Comparable>() {
@Override
public int compare(Comparable a, Comparable b) {
return a.compareTo(b);
}
};
Comparator<? super K> comparator;
Node<K, V>[] table;
final Node<K, V> header;
int size = 0;
int modCount = 0;
int threshold;
/**
* Create a natural order, empty tree map whose keys must be mutually
* comparable and non-null.
*/
@SuppressWarnings("unchecked") // unsafe! this assumes K is comparable
public LinkedHashTreeMap() {
this((Comparator<? super K>) NATURAL_ORDER);
}
/**
* Create a tree map ordered by {@code comparator}. This map's keys may only
* be null if {@code comparator} permits.
*
* @param comparator the comparator to order elements with, or {@code null} to
* use the natural ordering.
*/
@SuppressWarnings({ "unchecked", "rawtypes" }) // unsafe! if comparator is null, this assumes K is comparable
public LinkedHashTreeMap(Comparator<? super K> comparator) {
this.comparator = comparator != null
? comparator
: (Comparator) NATURAL_ORDER;
this.header = new Node<K, V>();
this.table = new Node[16]; // TODO: sizing/resizing policies
this.threshold = (table.length / 2) + (table.length / 4); // 3/4 capacity
}
@Override public int size() {
return size;
}
@Override public V get(Object key) {
Node<K, V> node = findByObject(key);
return node != null ? node.value : null;
}
@Override public boolean containsKey(Object key) {
return findByObject(key) != null;
}
@Override public V put(K key, V value) {
if (key == null) {
throw new NullPointerException("key == null");
}
Node<K, V> created = find(key, true);
V result = created.value;
created.value = value;
return result;
}
@Override public void clear() {
Arrays.fill(table, null);
size = 0;
modCount++;
// Clear all links to help GC
Node<K, V> header = this.header;
for (Node<K, V> e = header.next; e != header; ) {
Node<K, V> next = e.next;
e.next = e.prev = null;
e = next;
}
header.next = header.prev = header;
}
@Override public V remove(Object key) {
Node<K, V> node = removeInternalByKey(key);
return node != null ? node.value : null;
}
/**
* Returns the node at or adjacent to the given key, creating it if requested.
*
* @throws ClassCastException if {@code key} and the tree's keys aren't
* mutually comparable.
*/
Node<K, V> find(K key, boolean create) {
Comparator<? super K> comparator = this.comparator;
Node<K, V>[] table = this.table;
int hash = secondaryHash(key.hashCode());
int index = hash & (table.length - 1);
Node<K, V> nearest = table[index];
int comparison = 0;
if (nearest != null) {
// Micro-optimization: avoid polymorphic calls to Comparator.compare().
@SuppressWarnings("unchecked") // Throws a ClassCastException below if there's trouble.
Comparable<Object> comparableKey = (comparator == NATURAL_ORDER)
? (Comparable<Object>) key
: null;
while (true) {
comparison = (comparableKey != null)
? comparableKey.compareTo(nearest.key)
: comparator.compare(key, nearest.key);
// We found the requested key.
if (comparison == 0) {
return nearest;
}
// If it exists, the key is in a subtree. Go deeper.
Node<K, V> child = (comparison < 0) ? nearest.left : nearest.right;
if (child == null) {
break;
}
nearest = child;
}
}
// The key doesn't exist in this tree.
if (!create) {
return null;
}
// Create the node and add it to the tree or the table.
Node<K, V> header = this.header;
Node<K, V> created;
if (nearest == null) {
// Check that the value is comparable if we didn't do any comparisons.
if (comparator == NATURAL_ORDER && !(key instanceof Comparable)) {
throw new ClassCastException(key.getClass().getName() + " is not Comparable");
}
created = new Node<K, V>(nearest, key, hash, header, header.prev);
table[index] = created;
} else {
created = new Node<K, V>(nearest, key, hash, header, header.prev);
if (comparison < 0) { // nearest.key is higher
nearest.left = created;
} else { // comparison > 0, nearest.key is lower
nearest.right = created;
}
rebalance(nearest, true);
}
if (size++ > threshold) {
doubleCapacity();
}
modCount++;
return created;
}
@SuppressWarnings("unchecked")
Node<K, V> findByObject(Object key) {
try {
return key != null ? find((K) key, false) : null;
} catch (ClassCastException e) {
return null;
}
}
/**
* Returns this map's entry that has the same key and value as {@code
* entry}, or null if this map has no such entry.
*
* <p>This method uses the comparator for key equality rather than {@code
* equals}. If this map's comparator isn't consistent with equals (such as
* {@code String.CASE_INSENSITIVE_ORDER}), then {@code remove()} and {@code
* contains()} will violate the collections API.
*/
Node<K, V> findByEntry(Entry<?, ?> entry) {
Node<K, V> mine = findByObject(entry.getKey());
boolean valuesEqual = mine != null && equal(mine.value, entry.getValue());
return valuesEqual ? mine : null;
}
private boolean equal(Object a, Object b) {
return a == b || (a != null && a.equals(b));
}
/**
* Applies a supplemental hash function to a given hashCode, which defends
* against poor quality hash functions. This is critical because HashMap
* uses power-of-two length hash tables, that otherwise encounter collisions
* for hashCodes that do not differ in lower or upper bits.
*/
private static int secondaryHash(int h) {
// Doug Lea's supplemental hash function
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
/**
* Removes {@code node} from this tree, rearranging the tree's structure as
* necessary.
*
* @param unlink true to also unlink this node from the iteration linked list.
*/
void removeInternal(Node<K, V> node, boolean unlink) {
if (unlink) {
node.prev.next = node.next;
node.next.prev = node.prev;
node.next = node.prev = null; // Help the GC (for performance)
}
Node<K, V> left = node.left;
Node<K, V> right = node.right;
Node<K, V> originalParent = node.parent;
if (left != null && right != null) {
/*
* To remove a node with both left and right subtrees, move an
* adjacent node from one of those subtrees into this node's place.
*
* Removing the adjacent node may change this node's subtrees. This
* node may no longer have two subtrees once the adjacent node is
* gone!
*/
Node<K, V> adjacent = (left.height > right.height) ? left.last() : right.first();
removeInternal(adjacent, false); // takes care of rebalance and size--
int leftHeight = 0;
left = node.left;
if (left != null) {
leftHeight = left.height;
adjacent.left = left;
left.parent = adjacent;
node.left = null;
}
int rightHeight = 0;
right = node.right;
if (right != null) {
rightHeight = right.height;
adjacent.right = right;
right.parent = adjacent;
node.right = null;
}
adjacent.height = Math.max(leftHeight, rightHeight) + 1;
replaceInParent(node, adjacent);
return;
} else if (left != null) {
replaceInParent(node, left);
node.left = null;
} else if (right != null) {
replaceInParent(node, right);
node.right = null;
} else {
replaceInParent(node, null);
}
rebalance(originalParent, false);
size--;
modCount++;
}
Node<K, V> removeInternalByKey(Object key) {
Node<K, V> node = findByObject(key);
if (node != null) {
removeInternal(node, true);
}
return node;
}
private void replaceInParent(Node<K, V> node, Node<K, V> replacement) {
Node<K, V> parent = node.parent;
node.parent = null;
if (replacement != null) {
replacement.parent = parent;
}
if (parent != null) {
if (parent.left == node) {
parent.left = replacement;
} else {
assert (parent.right == node);
parent.right = replacement;
}
} else {
int index = node.hash & (table.length - 1);
table[index] = replacement;
}
}
/**
* Rebalances the tree by making any AVL rotations necessary between the
* newly-unbalanced node and the tree's root.
*
* @param insert true if the node was unbalanced by an insert; false if it
* was by a removal.
*/
private void rebalance(Node<K, V> unbalanced, boolean insert) {
for (Node<K, V> node = unbalanced; node != null; node = node.parent) {
Node<K, V> left = node.left;
Node<K, V> right = node.right;
int leftHeight = left != null ? left.height : 0;
int rightHeight = right != null ? right.height : 0;
int delta = leftHeight - rightHeight;
if (delta == -2) {
Node<K, V> rightLeft = right.left;
Node<K, V> rightRight = right.right;
int rightRightHeight = rightRight != null ? rightRight.height : 0;
int rightLeftHeight = rightLeft != null ? rightLeft.height : 0;
int rightDelta = rightLeftHeight - rightRightHeight;
if (rightDelta == -1 || (rightDelta == 0 && !insert)) {
rotateLeft(node); // AVL right right
} else {
assert (rightDelta == 1);
rotateRight(right); // AVL right left
rotateLeft(node);
}
if (insert) {
break; // no further rotations will be necessary
}
} else if (delta == 2) {
Node<K, V> leftLeft = left.left;
Node<K, V> leftRight = left.right;
int leftRightHeight = leftRight != null ? leftRight.height : 0;
int leftLeftHeight = leftLeft != null ? leftLeft.height : 0;
int leftDelta = leftLeftHeight - leftRightHeight;
if (leftDelta == 1 || (leftDelta == 0 && !insert)) {
rotateRight(node); // AVL left left
} else {
assert (leftDelta == -1);
rotateLeft(left); // AVL left right
rotateRight(node);
}
if (insert) {
break; // no further rotations will be necessary
}
} else if (delta == 0) {
node.height = leftHeight + 1; // leftHeight == rightHeight
if (insert) {
break; // the insert caused balance, so rebalancing is done!
}
} else {
assert (delta == -1 || delta == 1);
node.height = Math.max(leftHeight, rightHeight) + 1;
if (!insert) {
break; // the height hasn't changed, so rebalancing is done!
}
}
}
}
/**
* Rotates the subtree so that its root's right child is the new root.
*/
private void rotateLeft(Node<K, V> root) {
Node<K, V> left = root.left;
Node<K, V> pivot = root.right;
Node<K, V> pivotLeft = pivot.left;
Node<K, V> pivotRight = pivot.right;
// move the pivot's left child to the root's right
root.right = pivotLeft;
if (pivotLeft != null) {
pivotLeft.parent = root;
}
replaceInParent(root, pivot);
// move the root to the pivot's left
pivot.left = root;
root.parent = pivot;
// fix heights
root.height = Math.max(left != null ? left.height : 0,
pivotLeft != null ? pivotLeft.height : 0) + 1;
pivot.height = Math.max(root.height,
pivotRight != null ? pivotRight.height : 0) + 1;
}
/**
* Rotates the subtree so that its root's left child is the new root.
*/
private void rotateRight(Node<K, V> root) {
Node<K, V> pivot = root.left;
Node<K, V> right = root.right;
Node<K, V> pivotLeft = pivot.left;
Node<K, V> pivotRight = pivot.right;
// move the pivot's right child to the root's left
root.left = pivotRight;
if (pivotRight != null) {
pivotRight.parent = root;
}
replaceInParent(root, pivot);
// move the root to the pivot's right
pivot.right = root;
root.parent = pivot;
// fixup heights
root.height = Math.max(right != null ? right.height : 0,
pivotRight != null ? pivotRight.height : 0) + 1;
pivot.height = Math.max(root.height,
pivotLeft != null ? pivotLeft.height : 0) + 1;
}
private EntrySet entrySet;
private KeySet keySet;
@Override public Set<Entry<K, V>> entrySet() {
EntrySet result = entrySet;
return result != null ? result : (entrySet = new EntrySet());
}
@Override public Set<K> keySet() {
KeySet result = keySet;
return result != null ? result : (keySet = new KeySet());
}
static final class Node<K, V> implements Entry<K, V> {
Node<K, V> parent;
Node<K, V> left;
Node<K, V> right;
Node<K, V> next;
Node<K, V> prev;
final K key;
final int hash;
V value;
int height;
/** Create the header entry */
Node() {
key = null;
hash = -1;
next = prev = this;
}
/** Create a regular entry */
Node(Node<K, V> parent, K key, int hash, Node<K, V> next, Node<K, V> prev) {
this.parent = parent;
this.key = key;
this.hash = hash;
this.height = 1;
this.next = next;
this.prev = prev;
prev.next = this;
next.prev = this;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
@SuppressWarnings("rawtypes")
@Override public boolean equals(Object o) {
if (o instanceof Entry) {
Entry other = (Entry) o;
return (key == null ? other.getKey() == null : key.equals(other.getKey()))
&& (value == null ? other.getValue() == null : value.equals(other.getValue()));
}
return false;
}
@Override public int hashCode() {
return (key == null ? 0 : key.hashCode())
^ (value == null ? 0 : value.hashCode());
}
@Override public String toString() {
return key + "=" + value;
}
/**
* Returns the first node in this subtree.
*/
public Node<K, V> first() {
Node<K, V> node = this;
Node<K, V> child = node.left;
while (child != null) {
node = child;
child = node.left;
}
return node;
}
/**
* Returns the last node in this subtree.
*/
public Node<K, V> last() {
Node<K, V> node = this;
Node<K, V> child = node.right;
while (child != null) {
node = child;
child = node.right;
}
return node;
}
}
private void doubleCapacity() {
table = doubleCapacity(table);
threshold = (table.length / 2) + (table.length / 4); // 3/4 capacity
}
/**
* Returns a new array containing the same nodes as {@code oldTable}, but with
* twice as many trees, each of (approximately) half the previous size.
*/
static <K, V> Node<K, V>[] doubleCapacity(Node<K, V>[] oldTable) {
// TODO: don't do anything if we're already at MAX_CAPACITY
int oldCapacity = oldTable.length;
@SuppressWarnings("unchecked") // Arrays and generics don't get along.
Node<K, V>[] newTable = new Node[oldCapacity * 2];
AvlIterator<K, V> iterator = new AvlIterator<K, V>();
AvlBuilder<K, V> leftBuilder = new AvlBuilder<K, V>();
AvlBuilder<K, V> rightBuilder = new AvlBuilder<K, V>();
// Split each tree into two trees.
for (int i = 0; i < oldCapacity; i++) {
Node<K, V> root = oldTable[i];
if (root == null) {
continue;
}
// Compute the sizes of the left and right trees.
iterator.reset(root);
int leftSize = 0;
int rightSize = 0;
for (Node<K, V> node; (node = iterator.next()) != null; ) {
if ((node.hash & oldCapacity) == 0) {
leftSize++;
} else {
rightSize++;
}
}
// Split the tree into two.
leftBuilder.reset(leftSize);
rightBuilder.reset(rightSize);
iterator.reset(root);
for (Node<K, V> node; (node = iterator.next()) != null; ) {
if ((node.hash & oldCapacity) == 0) {
leftBuilder.add(node);
} else {
rightBuilder.add(node);
}
}
// Populate the enlarged array with these new roots.
newTable[i] = leftSize > 0 ? leftBuilder.root() : null;
newTable[i + oldCapacity] = rightSize > 0 ? rightBuilder.root() : null;
}
return newTable;
}
/**
* Walks an AVL tree in iteration order. Once a node has been returned, its
* left, right and parent links are <strong>no longer used</strong>. For this
* reason it is safe to transform these links as you walk a tree.
*
* <p><strong>Warning:</strong> this iterator is destructive. It clears the
* parent node of all nodes in the tree. It is an error to make a partial
* iteration of a tree.
*/
static class AvlIterator<K, V> {
/** This stack is a singly linked list, linked by the 'parent' field. */
private Node<K, V> stackTop;
void reset(Node<K, V> root) {
Node<K, V> stackTop = null;
for (Node<K, V> n = root; n != null; n = n.left) {
n.parent = stackTop;
stackTop = n; // Stack push.
}
this.stackTop = stackTop;
}
public Node<K, V> next() {
Node<K, V> stackTop = this.stackTop;
if (stackTop == null) {
return null;
}
Node<K, V> result = stackTop;
stackTop = result.parent;
result.parent = null;
for (Node<K, V> n = result.right; n != null; n = n.left) {
n.parent = stackTop;
stackTop = n; // Stack push.
}
this.stackTop = stackTop;
return result;
}
}
/**
* Builds AVL trees of a predetermined size by accepting nodes of increasing
* value. To use:
* <ol>
* <li>Call {@link #reset} to initialize the target size <i>size</i>.
* <li>Call {@link #add} <i>size</i> times with increasing values.
* <li>Call {@link #root} to get the root of the balanced tree.
* </ol>
*
* <p>The returned tree will satisfy the AVL constraint: for every node
* <i>N</i>, the height of <i>N.left</i> and <i>N.right</i> is different by at
* most 1. It accomplishes this by omitting deepest-level leaf nodes when
* building trees whose size isn't a power of 2 minus 1.
*
* <p>Unlike rebuilding a tree from scratch, this approach requires no value
* comparisons. Using this class to create a tree of size <i>S</i> is
* {@code O(S)}.
*/
final static class AvlBuilder<K, V> {
/** This stack is a singly linked list, linked by the 'parent' field. */
private Node<K, V> stack;
private int leavesToSkip;
private int leavesSkipped;
private int size;
void reset(int targetSize) {
// compute the target tree size. This is a power of 2 minus one, like 15 or 31.
int treeCapacity = Integer.highestOneBit(targetSize) * 2 - 1;
leavesToSkip = treeCapacity - targetSize;
size = 0;
leavesSkipped = 0;
stack = null;
}
void add(Node<K, V> node) {
node.left = node.parent = node.right = null;
node.height = 1;
// Skip a leaf if necessary.
if (leavesToSkip > 0 && (size & 1) == 0) {
size++;
leavesToSkip--;
leavesSkipped++;
}
node.parent = stack;
stack = node; // Stack push.
size++;
// Skip a leaf if necessary.
if (leavesToSkip > 0 && (size & 1) == 0) {
size++;
leavesToSkip--;
leavesSkipped++;
}
/*
* Combine 3 nodes into subtrees whenever the size is one less than a
* multiple of 4. For example we combine the nodes A, B, C into a
* 3-element tree with B as the root.
*
* Combine two subtrees and a spare single value whenever the size is one
* less than a multiple of 8. For example at 8 we may combine subtrees
* (A B C) and (E F G) with D as the root to form ((A B C) D (E F G)).
*
* Just as we combine single nodes when size nears a multiple of 4, and
* 3-element trees when size nears a multiple of 8, we combine subtrees of
* size (N-1) whenever the total size is 2N-1 whenever N is a power of 2.
*/
for (int scale = 4; (size & scale - 1) == scale - 1; scale *= 2) {
if (leavesSkipped == 0) {
// Pop right, center and left, then make center the top of the stack.
Node<K, V> right = stack;
Node<K, V> center = right.parent;
Node<K, V> left = center.parent;
center.parent = left.parent;
stack = center;
// Construct a tree.
center.left = left;
center.right = right;
center.height = right.height + 1;
left.parent = center;
right.parent = center;
} else if (leavesSkipped == 1) {
// Pop right and center, then make center the top of the stack.
Node<K, V> right = stack;
Node<K, V> center = right.parent;
stack = center;
// Construct a tree with no left child.
center.right = right;
center.height = right.height + 1;
right.parent = center;
leavesSkipped = 0;
} else if (leavesSkipped == 2) {
leavesSkipped = 0;
}
}
}
Node<K, V> root() {
Node<K, V> stackTop = this.stack;
if (stackTop.parent != null) {
throw new IllegalStateException();
}
return stackTop;
}
}
private abstract class LinkedTreeMapIterator<T> implements Iterator<T> {
Node<K, V> next = header.next;
Node<K, V> lastReturned = null;
int expectedModCount = modCount;
@Override
public final boolean hasNext() {
return next != header;
}
final Node<K, V> nextNode() {
Node<K, V> e = next;
if (e == header) {
throw new NoSuchElementException();
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
next = e.next;
return lastReturned = e;
}
@Override
public final void remove() {
if (lastReturned == null) {
throw new IllegalStateException();
}
removeInternal(lastReturned, true);
lastReturned = null;
expectedModCount = modCount;
}
}
class EntrySet extends AbstractSet<Entry<K, V>> {
@Override public int size() {
return size;
}
@Override public Iterator<Entry<K, V>> iterator() {
return new LinkedTreeMapIterator<Entry<K, V>>() {
@Override
public Entry<K, V> next() {
return nextNode();
}
};
}
@Override public boolean contains(Object o) {
return o instanceof Entry && findByEntry((Entry<?, ?>) o) != null;
}
@Override public boolean remove(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Node<K, V> node = findByEntry((Entry<?, ?>) o);
if (node == null) {
return false;
}
removeInternal(node, true);
return true;
}
@Override public void clear() {
LinkedHashTreeMap.this.clear();
}
}
class KeySet extends AbstractSet<K> {
@Override public int size() {
return size;
}
@Override public Iterator<K> iterator() {
return new LinkedTreeMapIterator<K>() {
@Override
public K next() {
return nextNode().key;
}
};
}
@Override public boolean contains(Object o) {
return containsKey(o);
}
@Override public boolean remove(Object key) {
return removeInternalByKey(key) != null;
}
@Override public void clear() {
LinkedHashTreeMap.this.clear();
}
}
/**
* If somebody is unlucky enough to have to serialize one of these, serialize
* it as a LinkedHashMap so that they won't need Gson on the other side to
* deserialize it. Using serialization defeats our DoS defence, so most apps
* shouldn't use it.
*/
private Object writeReplace() throws ObjectStreamException {
return new LinkedHashMap<K, V>(this);
}
}

View File

@ -1,635 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
* Copyright (C) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.internal;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A map of comparable keys to values. Unlike {@code TreeMap}, this class uses
* insertion order for iteration order. Comparison order is only used as an
* optimization for efficient insertion and removal.
*
* <p>This implementation was derived from Android 4.1's TreeMap class.
*/
public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Serializable {
@SuppressWarnings({ "unchecked", "rawtypes" }) // to avoid Comparable<Comparable<Comparable<...>>>
private static final Comparator<Comparable> NATURAL_ORDER = new Comparator<Comparable>() {
@Override
public int compare(Comparable a, Comparable b) {
return a.compareTo(b);
}
};
Comparator<? super K> comparator;
Node<K, V> root;
int size = 0;
int modCount = 0;
// Used to preserve iteration order
final Node<K, V> header = new Node<K, V>();
/**
* Create a natural order, empty tree map whose keys must be mutually
* comparable and non-null.
*/
@SuppressWarnings("unchecked") // unsafe! this assumes K is comparable
public LinkedTreeMap() {
this((Comparator<? super K>) NATURAL_ORDER);
}
/**
* Create a tree map ordered by {@code comparator}. This map's keys may only
* be null if {@code comparator} permits.
*
* @param comparator the comparator to order elements with, or {@code null} to
* use the natural ordering.
*/
@SuppressWarnings({ "unchecked", "rawtypes" }) // unsafe! if comparator is null, this assumes K is comparable
public LinkedTreeMap(Comparator<? super K> comparator) {
this.comparator = comparator != null
? comparator
: (Comparator) NATURAL_ORDER;
}
@Override public int size() {
return size;
}
@Override public V get(Object key) {
Node<K, V> node = findByObject(key);
return node != null ? node.value : null;
}
@Override public boolean containsKey(Object key) {
return findByObject(key) != null;
}
@Override public V put(K key, V value) {
if (key == null) {
throw new NullPointerException("key == null");
}
Node<K, V> created = find(key, true);
V result = created.value;
created.value = value;
return result;
}
@Override public void clear() {
root = null;
size = 0;
modCount++;
// Clear iteration order
Node<K, V> header = this.header;
header.next = header.prev = header;
}
@Override public V remove(Object key) {
Node<K, V> node = removeInternalByKey(key);
return node != null ? node.value : null;
}
/**
* Returns the node at or adjacent to the given key, creating it if requested.
*
* @throws ClassCastException if {@code key} and the tree's keys aren't
* mutually comparable.
*/
Node<K, V> find(K key, boolean create) {
Comparator<? super K> comparator = this.comparator;
Node<K, V> nearest = root;
int comparison = 0;
if (nearest != null) {
// Micro-optimization: avoid polymorphic calls to Comparator.compare().
@SuppressWarnings("unchecked") // Throws a ClassCastException below if there's trouble.
Comparable<Object> comparableKey = (comparator == NATURAL_ORDER)
? (Comparable<Object>) key
: null;
while (true) {
comparison = (comparableKey != null)
? comparableKey.compareTo(nearest.key)
: comparator.compare(key, nearest.key);
// We found the requested key.
if (comparison == 0) {
return nearest;
}
// If it exists, the key is in a subtree. Go deeper.
Node<K, V> child = (comparison < 0) ? nearest.left : nearest.right;
if (child == null) {
break;
}
nearest = child;
}
}
// The key doesn't exist in this tree.
if (!create) {
return null;
}
// Create the node and add it to the tree or the table.
Node<K, V> header = this.header;
Node<K, V> created;
if (nearest == null) {
// Check that the value is comparable if we didn't do any comparisons.
if (comparator == NATURAL_ORDER && !(key instanceof Comparable)) {
throw new ClassCastException(key.getClass().getName() + " is not Comparable");
}
created = new Node<K, V>(nearest, key, header, header.prev);
root = created;
} else {
created = new Node<K, V>(nearest, key, header, header.prev);
if (comparison < 0) { // nearest.key is higher
nearest.left = created;
} else { // comparison > 0, nearest.key is lower
nearest.right = created;
}
rebalance(nearest, true);
}
size++;
modCount++;
return created;
}
@SuppressWarnings("unchecked")
Node<K, V> findByObject(Object key) {
try {
return key != null ? find((K) key, false) : null;
} catch (ClassCastException e) {
return null;
}
}
/**
* Returns this map's entry that has the same key and value as {@code
* entry}, or null if this map has no such entry.
*
* <p>This method uses the comparator for key equality rather than {@code
* equals}. If this map's comparator isn't consistent with equals (such as
* {@code String.CASE_INSENSITIVE_ORDER}), then {@code remove()} and {@code
* contains()} will violate the collections API.
*/
Node<K, V> findByEntry(Entry<?, ?> entry) {
Node<K, V> mine = findByObject(entry.getKey());
boolean valuesEqual = mine != null && equal(mine.value, entry.getValue());
return valuesEqual ? mine : null;
}
private boolean equal(Object a, Object b) {
return a == b || (a != null && a.equals(b));
}
/**
* Removes {@code node} from this tree, rearranging the tree's structure as
* necessary.
*
* @param unlink true to also unlink this node from the iteration linked list.
*/
void removeInternal(Node<K, V> node, boolean unlink) {
if (unlink) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
Node<K, V> left = node.left;
Node<K, V> right = node.right;
Node<K, V> originalParent = node.parent;
if (left != null && right != null) {
/*
* To remove a node with both left and right subtrees, move an
* adjacent node from one of those subtrees into this node's place.
*
* Removing the adjacent node may change this node's subtrees. This
* node may no longer have two subtrees once the adjacent node is
* gone!
*/
Node<K, V> adjacent = (left.height > right.height) ? left.last() : right.first();
removeInternal(adjacent, false); // takes care of rebalance and size--
int leftHeight = 0;
left = node.left;
if (left != null) {
leftHeight = left.height;
adjacent.left = left;
left.parent = adjacent;
node.left = null;
}
int rightHeight = 0;
right = node.right;
if (right != null) {
rightHeight = right.height;
adjacent.right = right;
right.parent = adjacent;
node.right = null;
}
adjacent.height = Math.max(leftHeight, rightHeight) + 1;
replaceInParent(node, adjacent);
return;
} else if (left != null) {
replaceInParent(node, left);
node.left = null;
} else if (right != null) {
replaceInParent(node, right);
node.right = null;
} else {
replaceInParent(node, null);
}
rebalance(originalParent, false);
size--;
modCount++;
}
Node<K, V> removeInternalByKey(Object key) {
Node<K, V> node = findByObject(key);
if (node != null) {
removeInternal(node, true);
}
return node;
}
private void replaceInParent(Node<K, V> node, Node<K, V> replacement) {
Node<K, V> parent = node.parent;
node.parent = null;
if (replacement != null) {
replacement.parent = parent;
}
if (parent != null) {
if (parent.left == node) {
parent.left = replacement;
} else {
assert (parent.right == node);
parent.right = replacement;
}
} else {
root = replacement;
}
}
/**
* Rebalances the tree by making any AVL rotations necessary between the
* newly-unbalanced node and the tree's root.
*
* @param insert true if the node was unbalanced by an insert; false if it
* was by a removal.
*/
private void rebalance(Node<K, V> unbalanced, boolean insert) {
for (Node<K, V> node = unbalanced; node != null; node = node.parent) {
Node<K, V> left = node.left;
Node<K, V> right = node.right;
int leftHeight = left != null ? left.height : 0;
int rightHeight = right != null ? right.height : 0;
int delta = leftHeight - rightHeight;
if (delta == -2) {
Node<K, V> rightLeft = right.left;
Node<K, V> rightRight = right.right;
int rightRightHeight = rightRight != null ? rightRight.height : 0;
int rightLeftHeight = rightLeft != null ? rightLeft.height : 0;
int rightDelta = rightLeftHeight - rightRightHeight;
if (rightDelta == -1 || (rightDelta == 0 && !insert)) {
rotateLeft(node); // AVL right right
} else {
assert (rightDelta == 1);
rotateRight(right); // AVL right left
rotateLeft(node);
}
if (insert) {
break; // no further rotations will be necessary
}
} else if (delta == 2) {
Node<K, V> leftLeft = left.left;
Node<K, V> leftRight = left.right;
int leftRightHeight = leftRight != null ? leftRight.height : 0;
int leftLeftHeight = leftLeft != null ? leftLeft.height : 0;
int leftDelta = leftLeftHeight - leftRightHeight;
if (leftDelta == 1 || (leftDelta == 0 && !insert)) {
rotateRight(node); // AVL left left
} else {
assert (leftDelta == -1);
rotateLeft(left); // AVL left right
rotateRight(node);
}
if (insert) {
break; // no further rotations will be necessary
}
} else if (delta == 0) {
node.height = leftHeight + 1; // leftHeight == rightHeight
if (insert) {
break; // the insert caused balance, so rebalancing is done!
}
} else {
assert (delta == -1 || delta == 1);
node.height = Math.max(leftHeight, rightHeight) + 1;
if (!insert) {
break; // the height hasn't changed, so rebalancing is done!
}
}
}
}
/**
* Rotates the subtree so that its root's right child is the new root.
*/
private void rotateLeft(Node<K, V> root) {
Node<K, V> left = root.left;
Node<K, V> pivot = root.right;
Node<K, V> pivotLeft = pivot.left;
Node<K, V> pivotRight = pivot.right;
// move the pivot's left child to the root's right
root.right = pivotLeft;
if (pivotLeft != null) {
pivotLeft.parent = root;
}
replaceInParent(root, pivot);
// move the root to the pivot's left
pivot.left = root;
root.parent = pivot;
// fix heights
root.height = Math.max(left != null ? left.height : 0,
pivotLeft != null ? pivotLeft.height : 0) + 1;
pivot.height = Math.max(root.height,
pivotRight != null ? pivotRight.height : 0) + 1;
}
/**
* Rotates the subtree so that its root's left child is the new root.
*/
private void rotateRight(Node<K, V> root) {
Node<K, V> pivot = root.left;
Node<K, V> right = root.right;
Node<K, V> pivotLeft = pivot.left;
Node<K, V> pivotRight = pivot.right;
// move the pivot's right child to the root's left
root.left = pivotRight;
if (pivotRight != null) {
pivotRight.parent = root;
}
replaceInParent(root, pivot);
// move the root to the pivot's right
pivot.right = root;
root.parent = pivot;
// fixup heights
root.height = Math.max(right != null ? right.height : 0,
pivotRight != null ? pivotRight.height : 0) + 1;
pivot.height = Math.max(root.height,
pivotLeft != null ? pivotLeft.height : 0) + 1;
}
private EntrySet entrySet;
private KeySet keySet;
@Override public Set<Entry<K, V>> entrySet() {
EntrySet result = entrySet;
return result != null ? result : (entrySet = new EntrySet());
}
@Override public Set<K> keySet() {
KeySet result = keySet;
return result != null ? result : (keySet = new KeySet());
}
static final class Node<K, V> implements Entry<K, V> {
Node<K, V> parent;
Node<K, V> left;
Node<K, V> right;
Node<K, V> next;
Node<K, V> prev;
final K key;
V value;
int height;
/** Create the header entry */
Node() {
key = null;
next = prev = this;
}
/** Create a regular entry */
Node(Node<K, V> parent, K key, Node<K, V> next, Node<K, V> prev) {
this.parent = parent;
this.key = key;
this.height = 1;
this.next = next;
this.prev = prev;
prev.next = this;
next.prev = this;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
@SuppressWarnings("rawtypes")
@Override public boolean equals(Object o) {
if (o instanceof Entry) {
Entry other = (Entry) o;
return (key == null ? other.getKey() == null : key.equals(other.getKey()))
&& (value == null ? other.getValue() == null : value.equals(other.getValue()));
}
return false;
}
@Override public int hashCode() {
return (key == null ? 0 : key.hashCode())
^ (value == null ? 0 : value.hashCode());
}
@Override public String toString() {
return key + "=" + value;
}
/**
* Returns the first node in this subtree.
*/
public Node<K, V> first() {
Node<K, V> node = this;
Node<K, V> child = node.left;
while (child != null) {
node = child;
child = node.left;
}
return node;
}
/**
* Returns the last node in this subtree.
*/
public Node<K, V> last() {
Node<K, V> node = this;
Node<K, V> child = node.right;
while (child != null) {
node = child;
child = node.right;
}
return node;
}
}
private abstract class LinkedTreeMapIterator<T> implements Iterator<T> {
Node<K, V> next = header.next;
Node<K, V> lastReturned = null;
int expectedModCount = modCount;
@Override
public final boolean hasNext() {
return next != header;
}
final Node<K, V> nextNode() {
Node<K, V> e = next;
if (e == header) {
throw new NoSuchElementException();
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
next = e.next;
return lastReturned = e;
}
@Override
public final void remove() {
if (lastReturned == null) {
throw new IllegalStateException();
}
removeInternal(lastReturned, true);
lastReturned = null;
expectedModCount = modCount;
}
}
class EntrySet extends AbstractSet<Entry<K, V>> {
@Override public int size() {
return size;
}
@Override public Iterator<Entry<K, V>> iterator() {
return new LinkedTreeMapIterator<Entry<K, V>>() {
@Override
public Entry<K, V> next() {
return nextNode();
}
};
}
@Override public boolean contains(Object o) {
return o instanceof Entry && findByEntry((Entry<?, ?>) o) != null;
}
@Override public boolean remove(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Node<K, V> node = findByEntry((Entry<?, ?>) o);
if (node == null) {
return false;
}
removeInternal(node, true);
return true;
}
@Override public void clear() {
LinkedTreeMap.this.clear();
}
}
class KeySet extends AbstractSet<K> {
@Override public int size() {
return size;
}
@Override public Iterator<K> iterator() {
return new LinkedTreeMapIterator<K>() {
@Override
public K next() {
return nextNode().key;
}
};
}
@Override public boolean contains(Object o) {
return containsKey(o);
}
@Override public boolean remove(Object key) {
return removeInternalByKey(key) != null;
}
@Override public void clear() {
LinkedTreeMap.this.clear();
}
}
/**
* If somebody is unlucky enough to have to serialize one of these, serialize
* it as a LinkedHashMap so that they won't need Gson on the other side to
* deserialize it. Using serialization defeats our DoS defence, so most apps
* shouldn't use it.
*/
private Object writeReplace() throws ObjectStreamException {
return new LinkedHashMap<K, V>(this);
}
}

Some files were not shown because too many files have changed in this diff Show More