Quellcode durchsuchen

本周内容:
1、网络库封装
2、参数配置
3、初始化逻辑

fluty vor 5 Jahren
Ursprung
Commit
6f3648b1ab
100 geänderte Dateien mit 12641 neuen und 200 gelöschten Zeilen
  1. 2 2
      app/build.gradle
  2. BIN
      app/libs/android-support-v4.jar
  3. 5 2
      app/src/main/AndroidManifest.xml
  4. 107 50
      app/src/main/java/com/funcheer/channel/sdk/MainActivity.java
  5. 1 1
      app/src/main/java/com/funcheer/channel/sdk/MainApp.java
  6. 0 34
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  7. 0 5
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  8. 0 5
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  9. 1 1
      app/src/main/res/values/strings.xml
  10. 57 57
      fq_channel_oppo/src/main/java/com/fq/channel/sdk/SdkPluginOppo.java
  11. 1 1
      fq_plugin_api/build.gradle
  12. 6 0
      fq_plugin_api/src/main/assets/fqcfg
  13. 24 24
      fq_plugin_api/src/main/java/com/fq/channel/sdk/api/FqGame.java
  14. 124 0
      fq_plugin_api/src/main/java/com/fq/channel/sdk/api/FqGameHander.java
  15. 13 0
      fq_plugin_api/src/main/java/com/fq/channel/sdk/api/IResult.java
  16. 3 0
      fq_plugin_base/build.gradle
  17. 118 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Constants/AppConfig.java
  18. 9 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Constants/FqCache.java
  19. 182 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Constants/FqConfig.java
  20. 3 1
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/ResourceCfg.java
  21. 40 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Exception/BaseException.java
  22. 7 7
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Exception/NoSuchFunctionException.java
  23. 21 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Exception/NullStringToEmptyAdapterFactory.java
  24. 34 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Exception/StringNullAdapter.java
  25. 0 9
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Test.java
  26. 49 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/FqNetRequest.java
  27. 20 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/HttpConstant.java
  28. 140 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/HttpManager.java
  29. 108 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/callback/BaseCallback.java
  30. 168 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ParamsUtils.java
  31. 31 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ReqContent.java
  32. 182 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ReqHeader.java
  33. 27 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ReqInitBody.java
  34. 39 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ResultContent.java
  35. 41 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ResultHeader.java
  36. 176 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ResultInitBody.java
  37. 126 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/AppInfoUtils.java
  38. 58 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/Base64.java
  39. 19 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/DES.java
  40. 139 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/DeviceUtils.java
  41. 75 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/EncryptUtils.java
  42. 170 0
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/FqLog.java
  43. 65 1
      fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/Utils.java
  44. 165 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/DefaultDateTypeAdapter.java
  45. 109 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/ExclusionStrategy.java
  46. 161 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/FieldAttributes.java
  47. 178 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/FieldNamingPolicy.java
  48. 40 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/FieldNamingStrategy.java
  49. 1041 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/Gson.java
  50. 627 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/GsonBuilder.java
  51. 92 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/InstanceCreator.java
  52. 384 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonArray.java
  53. 44 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonDeserializationContext.java
  54. 91 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonDeserializer.java
  55. 322 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonElement.java
  56. 45 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonIOException.java
  57. 67 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonNull.java
  58. 205 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonObject.java
  59. 64 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonParseException.java
  60. 112 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonParser.java
  61. 295 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonPrimitive.java
  62. 49 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonSerializationContext.java
  63. 89 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonSerializer.java
  64. 122 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonStreamParser.java
  65. 47 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonSyntaxException.java
  66. 58 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/LongSerializationPolicy.java
  67. 290 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/TypeAdapter.java
  68. 170 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/TypeAdapterFactory.java
  69. 81 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/Expose.java
  70. 104 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/JsonAdapter.java
  71. 93 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/SerializedName.java
  72. 63 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/Since.java
  73. 68 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/Until.java
  74. 6 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/package-info.java
  75. 49 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/$Gson$Preconditions.java
  76. 610 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/$Gson$Types.java
  77. 238 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/ConstructorConstructor.java
  78. 260 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/Excluder.java
  79. 32 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/GsonBuildConfig.java
  80. 92 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/JavaVersion.java
  81. 32 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/JsonReaderInternalAccess.java
  82. 96 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/LazilyParsedNumber.java
  83. 864 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/LinkedHashTreeMap.java
  84. 630 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/LinkedTreeMap.java
  85. 33 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/ObjectConstructor.java
  86. 86 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/PreJava9DateFormatProvider.java
  87. 100 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/Primitives.java
  88. 120 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/Streams.java
  89. 123 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/UnsafeAllocator.java
  90. 99 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/ArrayTypeAdapter.java
  91. 102 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/CollectionTypeAdapterFactory.java
  92. 101 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/DateTypeAdapter.java
  93. 83 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java
  94. 316 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/JsonTreeReader.java
  95. 208 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/JsonTreeWriter.java
  96. 264 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/MapTypeAdapterFactory.java
  97. 109 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/ObjectTypeAdapter.java
  98. 254 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/ReflectiveTypeAdapterFactory.java
  99. 67 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/SqlDateTypeAdapter.java
  100. 0 0
      fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/TimeTypeAdapter.java

+ 2 - 2
app/build.gradle

@@ -5,11 +5,11 @@ apply plugin: 'kotlin-android'
 apply plugin: 'kotlin-android-extensions'
 
 android {
-    compileSdkVersion 29
+    compileSdkVersion 26
     defaultConfig {
         applicationId "com.funcheer.channel.sdk"
         minSdkVersion 15
-        targetSdkVersion 29
+        targetSdkVersion 26
         versionCode 1
         versionName "1.0"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

BIN
app/libs/android-support-v4.jar


+ 5 - 2
app/src/main/AndroidManifest.xml

@@ -1,11 +1,15 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.fqchannelsdk">
+    <!-- 访问网络 -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 
     <application
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
-        android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
         android:name="com.funcheer.channel.sdk.MainApp"
         android:theme="@style/fq_AppTheme" >
@@ -24,6 +28,5 @@
     </activity>
 
 
-
     </application>
 </manifest>

+ 107 - 50
app/src/main/java/com/funcheer/channel/sdk/MainActivity.java

@@ -14,8 +14,19 @@ import android.widget.Toast;
 
 import com.example.fqchannelsdk.R;
 import com.fq.channel.sdk.api.FqGame;
-import com.fq.channel.sdk.api.bean.LoginInfo;
-import com.fq.channel.sdk.api.bean.PaymentInfo;
+import com.fq.channel.sdk.api.IResult;
+import com.fq.channel.sdk.base.net.HttpManager;
+import com.fq.channel.sdk.base.net.callback.BaseCallback;
+import com.fq.channel.sdk.base.Exception.BaseException;
+import com.fq.channel.sdk.base.utils.FqLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+//import com.fq.channel.sdk.api.FqGame;
+//import com.fq.channel.sdk.api.bean.LoginInfo;
+//import com.fq.channel.sdk.api.bean.PaymentInfo;
 
 /**
  * @Description: 描述
@@ -23,14 +34,14 @@ import com.fq.channel.sdk.api.bean.PaymentInfo;
  * @CreateDate: 2020/3/24 09:49
  */
 public class MainActivity extends Activity implements View.OnClickListener {
-    private static final String TAG = "FQSDK_LOG"+MainActivity.class.getSimpleName();
+    private static final String TAG = MainActivity.class.getSimpleName();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.fq_activity_main);
+        FqLog.i(TAG, "onCreate: ");
         initUI();
-
         initSDK();
     }
 
@@ -54,33 +65,38 @@ public class MainActivity extends Activity implements View.OnClickListener {
     @Override
     protected void onRestart() {
         super.onRestart();
+        FqLog.i(TAG, "onRestart: ");
     }
 
     @Override
     protected void onStart() {
         super.onStart();
+        FqLog.i(TAG, "onStart: ");
     }
 
     @Override
     protected void onResume() {
         super.onResume();
-
+        FqLog.i(TAG, "onResume: ");
     }
 
     @Override
     protected void onPause() {
         super.onPause();
+        FqLog.i(TAG, "onPause: ");
 
     }
 
     @Override
     protected void onStop() {
         super.onStop();
+        FqLog.i(TAG, "onStop: ");
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
+        FqLog.i(TAG, "onDestroy: ");
     }
 
     private void initUI(){
@@ -90,6 +106,7 @@ public class MainActivity extends Activity implements View.OnClickListener {
         findViewById(getResourceId("fq_charge", "id")).setOnClickListener(this);
         findViewById(getResourceId("fq_report_data", "id")).setOnClickListener(this);
         findViewById(getResourceId("fq_exit", "id")).setOnClickListener(this);
+
     }
 
     @Override
@@ -101,7 +118,12 @@ public class MainActivity extends Activity implements View.OnClickListener {
                 break;
 
             case R.id.fq_switch_account:
-                switch_account();
+                try {
+                    switch_account();
+                }catch (JSONException e){
+                    e.printStackTrace();
+                }
+
                 break;
 
             case R.id.fq_logout:
@@ -130,71 +152,84 @@ public class MainActivity extends Activity implements View.OnClickListener {
     }
 
     private void initSDK(){
-        FqGame.init(MainActivity.this, new FqGame.IResult<String>() {
+        FqGame.init(MainActivity.this, new IResult<String>() {
             @Override
             public void onSuccess(String s) {
-                Log.i(TAG,"init success: "+s);
+                FqLog.i(TAG,"init success: "+s);
             }
 
             @Override
             public void onFail(String failMsg) {
-                Log.i(TAG,"init fail:"+failMsg);
+                FqLog.i(TAG,"init fail:"+failMsg);
             }
         });
     }
 
 
     private void login(){
-        FqGame.login(MainActivity.this, new FqGame.IResult<LoginInfo>() {
+//        FqGame.login(MainActivity.this, new FqGame.IResult<LoginInfo>() {
+//            @Override
+//            public void onSuccess(LoginInfo s) {
+//                Log.i(TAG,"login success: "+s.getUid());
+//            }
+//
+//            @Override
+//            public void onFail(String failMsg) {
+//                Log.i(TAG,"login fail:"+failMsg);
+//            }
+//        });
+        HttpManager.getInstance().get("http://wwww.baidu.com", new BaseCallback<String>() {
             @Override
-            public void onSuccess(LoginInfo s) {
-                Log.i(TAG,"login success: "+s.getUid());
+            public void onFailure(BaseException msg) {
+                FqLog.e(TAG, "onFailure: ");
             }
 
             @Override
-            public void onFail(String failMsg) {
-                Log.i(TAG,"login fail:"+failMsg);
+            public void onSuccess(int code, String msg, String data) {
+                FqLog.i(TAG, "onResponse: " + data);
             }
         });
+
     }
 
     public void charge(){
-        //get price
-        String priceString = ((EditText)findViewById(getResourceId("fq_charge_price",
-                "id"))).getText().toString();
-        if (TextUtils.isEmpty(priceString)){
-            showResult("请先填入价格");
-            return;
-        }
 
-        float price = Float.parseFloat(priceString);
-        Log.d(TAG,"price : "+price);
-        PaymentInfo paymentInfo = new PaymentInfo();
-        paymentInfo.setOrderAmount("1");            //订单金额           必须字段
-        paymentInfo.setSubject("1元宝");              //商品名             必须字段
-        paymentInfo.setRoleName("Hello");           //角色名             必须字段
-        paymentInfo.setCpBillNo("cp_order001");    //CP订单号           必须字段
-//        paymentInfo.setUid(uid);                    //登录时成功拿到的UID  必须字段
-        paymentInfo.setServerId("1");               //区服ID             必须字段
-        paymentInfo.setExtraInfo("FqGameSdk");      //拓展信息            必须字段
-        paymentInfo.setRemark("remark");            //订单备注            非必须字段
-        paymentInfo.setRoleLevel("5");              //角色等级            非必须字段
-        paymentInfo.setRoleId("Role_001");          //订单备注            非必须字段
-        paymentInfo.setPartyName("帮派");            //帮派               非必须字段
-        paymentInfo.setServerName("区服名");         //区服名              非必须字段
-
-
-        FqGame.pay(MainActivity.this,paymentInfo, new FqGame.IResult<String>() {
-            @Override
-            public void onSuccess(String s) {
-                Log.i(TAG,"pay success: "+s);
-            }
-
-            @Override
-            public void onFail(String failMsg) {
-                Log.i(TAG,"pay fail:"+failMsg);
-            }
-        });
+        //get price
+//        String priceString = ((EditText)findViewById(getResourceId("fq_charge_price",
+//                "id"))).getText().toString();
+//        if (TextUtils.isEmpty(priceString)){
+//            showResult("请先填入价格");
+//            return;
+//        }
+//
+//        float price = Float.parseFloat(priceString);
+//        Log.d(TAG,"price : "+price);
+//        PaymentInfo paymentInfo = new PaymentInfo();
+//        paymentInfo.setOrderAmount("1");            //订单金额           必须字段
+//        paymentInfo.setSubject("1元宝");              //商品名             必须字段
+//        paymentInfo.setRoleName("Hello");           //角色名             必须字段
+//        paymentInfo.setCpBillNo("cp_order001");    //CP订单号           必须字段
+////        paymentInfo.setUid(uid);                    //登录时成功拿到的UID  必须字段
+//        paymentInfo.setServerId("1");               //区服ID             必须字段
+//        paymentInfo.setExtraInfo("FqGameSdk");      //拓展信息            必须字段
+//        paymentInfo.setRemark("remark");            //订单备注            非必须字段
+//        paymentInfo.setRoleLevel("5");              //角色等级            非必须字段
+//        paymentInfo.setRoleId("Role_001");          //订单备注            非必须字段
+//        paymentInfo.setPartyName("帮派");            //帮派               非必须字段
+//        paymentInfo.setServerName("区服名");         //区服名              非必须字段
+//
+//
+//        FqGame.pay(MainActivity.this,paymentInfo, new FqGame.IResult<String>() {
+//            @Override
+//            public void onSuccess(String s) {
+//                Log.i(TAG,"pay success: "+s);
+//            }
+//
+//            @Override
+//            public void onFail(String failMsg) {
+//                Log.i(TAG,"pay fail:"+failMsg);
+//            }
+//        });
     }
 
     private void getChannelId() {
@@ -283,7 +318,28 @@ public class MainActivity extends Activity implements View.OnClickListener {
     private void logout() {
     }
 
-    private void switch_account() {
+    private void switch_account() throws JSONException {
+        HashMap hashMap = new HashMap<String,String>();
+        JSONObject custom = new JSONObject();
+        custom.put("ssoid","ssoid");
+        custom.put("token", "token");
+        hashMap.put("channelType","ansen");
+        hashMap.put("gameId","123");
+        hashMap.put("imei","123");
+        hashMap.put("channelId","123");
+        hashMap.put("channelParams",custom.toString());
+
+        HttpManager.getInstance().post("http://192.168.1.152:9099/api/sdk/channel/v1/login",hashMap, new BaseCallback<String>() {
+            @Override
+            public void onFailure(BaseException msg) {
+                Log.i(TAG, "onFailure: ");
+            }
+
+            @Override
+            public void onSuccess(int code, String msg, String data) {
+                Log.i(TAG, "onResponse: " + data);
+            }
+        });
     }
 
     private void showResult(String message){
@@ -320,5 +376,6 @@ public class MainActivity extends Activity implements View.OnClickListener {
     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[]
             grantResults) {
         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        FqGame.onRequestPermissionsResult(requestCode,permissions,grantResults);
     }
 }

+ 1 - 1
app/src/main/java/com/funcheer/channel/sdk/MainApp.java

@@ -1,10 +1,10 @@
 package com.funcheer.channel.sdk;
 
-import android.app.Application;
 import android.content.Context;
 
 import com.fq.channel.sdk.FQApplication;
 
+
 public class MainApp extends FQApplication {
 
     @Override

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 34
app/src/main/res/drawable-v24/ic_launcher_foreground.xml


+ 0 - 5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>

+ 0 - 5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>

+ 1 - 1
app/src/main/res/values/strings.xml

@@ -1,5 +1,5 @@
 <resources>
-    <string name="app_name">FQChannelSDK</string>
+    <string name="app_name">FQChannelMubao</string>
     <string name="fq_result">result...</string>
 
     <string name="fq_user">用户</string>

+ 57 - 57
fq_channel_oppo/src/main/java/com/fq/channel/sdk/SdkPluginOppo.java

@@ -16,26 +16,26 @@ import android.widget.Toast;
 import com.fq.channel.sdk.base.interfaces.FQPluginApi;
 import com.fq.channel.sdk.base.interfaces.PluginResult;
 import com.fq.channel.sdk.base.interfaces.PluginResultHandler;
+import com.fq.channel.sdk.base.utils.FqLog;
 
 import java.util.Map;
 
 public class SdkPluginOppo extends FQPluginApi {
 
 	private static final String TAG = "FQSDK_LOGSdkPluginOppo";
- 
+
 	/**
 	 * 数据统计
 	 */
 	private int dataType;
-	private String roleId,roleName,roleLevel,zoneId,zoneName,serverId,serverName,balance,vipLevel,partyName; 
+	private String roleId,roleName,roleLevel,zoneId,zoneName,serverId,serverName,balance,vipLevel,partyName;
 
 	/**
 	 * 初始化
 	 */
 	@Override
-	public void init(Context activity, Map<String, Object> map,
-			final PluginResultHandler handler) {
-		Log.i(TAG, "init");
+	public void init(Context activity,final PluginResultHandler handler) {
+		FqLog.i(TAG, "init");
 		handler.onHandlePluginResult(new PluginResult(
 				PluginResult.Status.OK));
 	}
@@ -45,15 +45,15 @@ public class SdkPluginOppo extends FQPluginApi {
 	 */
 	@Override
 	public void login(final Context activity, final PluginResultHandler handler) {
-		Log.i(TAG, "login ");
-		
+		FqLog.i(TAG, "login ");
+
 		Builder builder = new Builder(activity);
-        builder.setTitle("登录");  
-        builder.setMessage("请选择登录操作");  
+        builder.setTitle("登录");
+        builder.setMessage("请选择登录操作");
         builder.setCancelable(false);
-        builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {  
+        builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int which) {
-            	
+
 //            	//登陆乐逗服务器:
 //        		String path = "sns/ChannelLogin?channel_id=" + IdsLingdoCache.get().getChannelId()  //渠道ID
 //        				+ "&channel_en_name="+ "test"   //测试参数【必须】
@@ -69,24 +69,24 @@ public class SdkPluginOppo extends FQPluginApi {
 				handler.onHandlePluginResult(new PluginResult(
 						PluginResult.Status.OK,"登录成功"));
         		dialog.dismiss();
-            }  
-        });  
-        builder.setNeutralButton("失败", new DialogInterface.OnClickListener() {  
+            }
+        });
+        builder.setNeutralButton("失败", new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int which) {
 				handler.onHandlePluginResult(new PluginResult(
 						PluginResult.Status.ERROR,"登录失败"));
             	dialog.dismiss();
-            }  
-        });  
-        builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {  
-            public void onClick(DialogInterface dialog, int which) {  
+            }
+        });
+        builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int which) {
 				handler.onHandlePluginResult(new PluginResult(
 						PluginResult.Status.CANCEL));
             	dialog.dismiss();
-            }  
-        });  
+            }
+        });
         builder.setOnKeyListener(new OnKeyListener() {
-			
+
 			@Override
 			public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
 				if (keyCode == KeyEvent.KEYCODE_BACK) {
@@ -94,7 +94,7 @@ public class SdkPluginOppo extends FQPluginApi {
 							PluginResult.Status.CANCEL));
 	            	dialog.dismiss();
 	            	return true;
-		        } 
+		        }
             	return false;
 			}
 		});
@@ -104,35 +104,35 @@ public class SdkPluginOppo extends FQPluginApi {
 	@Override
 	public void pay(final Context activity, final Map<String, Object> map,
 			final PluginResultHandler handler) {
-		Log.i(TAG, "pay map:"+ (map != null?map.toString():"")); 
+		FqLog.i(TAG, "pay map:"+ (map != null?map.toString():""));
 
 		Builder builder = new Builder(activity);
-        builder.setTitle("支付");  
-        builder.setMessage("请选择支付操作");  
+        builder.setTitle("支付");
+        builder.setMessage("请选择支付操作");
         builder.setCancelable(false);
-        builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {  
-            public void onClick(DialogInterface dialog, int which) { 
+        builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int which) {
 				handler.onHandlePluginResult(new PluginResult(
 						PluginResult.Status.OK));
         		dialog.dismiss();
-            }  
-        });  
-        builder.setNeutralButton("失败", new DialogInterface.OnClickListener() {  
-            public void onClick(DialogInterface dialog, int which) {   
+            }
+        });
+        builder.setNeutralButton("失败", new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int which) {
 				handler.onHandlePluginResult(new PluginResult(
 						PluginResult.Status.ERROR));
             	dialog.dismiss();
-            }  
-        });  
-        builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {  
-            public void onClick(DialogInterface dialog, int which) {  
+            }
+        });
+        builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int which) {
 				handler.onHandlePluginResult(new PluginResult(
 						PluginResult.Status.CANCEL));
             	dialog.dismiss();
-            }  
-        });  
+            }
+        });
         builder.setOnKeyListener(new OnKeyListener() {
-			
+
 			@Override
 			public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
 				if (keyCode == KeyEvent.KEYCODE_BACK) {
@@ -140,7 +140,7 @@ public class SdkPluginOppo extends FQPluginApi {
 							PluginResult.Status.CANCEL));
 	            	dialog.dismiss();
 	            	return true;
-		        } 
+		        }
             	return false;
 			}
 		});
@@ -164,7 +164,7 @@ public class SdkPluginOppo extends FQPluginApi {
 	 */
 	public void logout(final Context context,final PluginResultHandler cb){
 		Log.d(TAG, "logout");
- 
+
 		Builder builder = new Builder(context);
 		builder.setTitle("注销");
 		builder.setMessage("是否注销?");
@@ -176,7 +176,7 @@ public class SdkPluginOppo extends FQPluginApi {
 				cb.onHandlePluginResult(new PluginResult(
 						PluginResult.Status.OK));
 			}
-		}); 
+		});
 		builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
 
 			@Override
@@ -186,7 +186,7 @@ public class SdkPluginOppo extends FQPluginApi {
 						PluginResult.Status.ERROR));
 			}
 		});
-		builder.show(); 
+		builder.show();
 	}
 
 	@Override
@@ -230,12 +230,12 @@ public class SdkPluginOppo extends FQPluginApi {
 	}
 
 
-	private void showTips(final String message){ 
+	private void showTips(final String message){
 		Handler handler = new Handler(Looper.getMainLooper());
 		handler.post(new Runnable() {
-			
+
 			@Override
-			public void run() { 
+			public void run() {
 //				Activity activity = IdsLingdoCache.get().getCurrentActivity();
 //				if (null != activity){
 //					Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
@@ -248,7 +248,7 @@ public class SdkPluginOppo extends FQPluginApi {
 	 * 游戏角色信息
 	 * @param data
 	 */
-	public void setExtraBundle(Bundle data){ 
+	public void setExtraBundle(Bundle data){
 		if(null == data){
 			data = new Bundle();
 		}
@@ -277,11 +277,11 @@ public class SdkPluginOppo extends FQPluginApi {
 		reportData.append(" vipLevel:"+vipLevel);
 		reportData.append(" partyName:"+zoneId);
 		reportData.append(" zoneName:"+zoneName);
-		reportData.append(" dataType:"+dataType); 
+		reportData.append(" dataType:"+dataType);
 		reportData.append("]");
-		
+
 		switch(dataType){
-			case 0 ://创建角色 
+			case 0 ://创建角色
 				showTips("数据上报:创建角色 ");
 				Log.i(TAG, "reportData"+reportData);
 				break;
@@ -296,7 +296,7 @@ public class SdkPluginOppo extends FQPluginApi {
 				Log.i(TAG, "reportData"+reportData);
 				break;
 
-			case 3 ://登录 
+			case 3 ://登录
 				showTips("数据上报:登录");
 				Log.i(TAG, "reportData"+reportData);
 				break;
@@ -304,8 +304,8 @@ public class SdkPluginOppo extends FQPluginApi {
 			case 4 ://支付
 				showTips("数据上报:进入游戏");
 				Log.i(TAG, "reportData"+reportData);
-				break; 
-				
+				break;
+
 			case 5 :// 选择服务器
 				showTips("数据上报:支付");
 				Log.i(TAG, "reportData"+reportData);
@@ -339,9 +339,9 @@ public class SdkPluginOppo extends FQPluginApi {
 	 */
 	public void releaseSdkResource(Context context){
 		Log.i(TAG, "releaseSdkResource");
-		
-		Toast.makeText(context, "释放SDK资源", Toast.LENGTH_SHORT).show();  
-		
+
+		Toast.makeText(context, "释放SDK资源", Toast.LENGTH_SHORT).show();
+
 		try {
 			/**渠道存在退出界面,此处游戏必须调用,begin*/
 		    Intent startMain = new Intent(Intent.ACTION_MAIN);
@@ -352,11 +352,11 @@ public class SdkPluginOppo extends FQPluginApi {
 		} catch (Exception e) {
 			Log.e(TAG, "releaseSdkResource 抛出异常!") ;
 		}
-	} 
+	}
 
 	/**
 	 * 复写生命周期方法,里面调用
-	 */ 
+	 */
 	public void onStart(Context context){
 		Log.i(TAG, "onStart");
 	}

+ 1 - 1
fq_plugin_api/build.gradle

@@ -28,5 +28,5 @@ dependencies {
 
     api project(':fq_plugin_core')
     api project(':fq_plugin_base')
-    api project(':fq_channel_mubao')
+    api project(':fq_channel_oppo')
 }

+ 6 - 0
fq_plugin_api/src/main/assets/fqcfg

@@ -0,0 +1,6 @@
+{
+"appId": "20213",
+"channelId": "1110",
+"adId": "844922",
+"adFlag": "sglsbw_oppo_0001"
+}

+ 24 - 24
fq_plugin_api/src/main/java/com/fq/channel/sdk/api/FqGame.java

@@ -1,16 +1,31 @@
 package com.fq.channel.sdk.api;
 
+import android.Manifest;
 import android.app.Activity;
+import android.content.pm.PackageManager;
 import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.fq.channel.sdk.api.bean.LoginInfo;
 import com.fq.channel.sdk.api.bean.PaymentInfo;
+import com.fq.channel.sdk.base.Constants.AppConfig;
+import com.fq.channel.sdk.base.Constants.FqConfig;
+import com.fq.channel.sdk.base.Exception.BaseException;
 import com.fq.channel.sdk.base.interfaces.PluginResult.Status;
 import com.fq.channel.sdk.base.interfaces.PluginResult;
 import com.fq.channel.sdk.base.interfaces.PluginResultHandler;
+import com.fq.channel.sdk.base.net.FqNetRequest;
+import com.fq.channel.sdk.base.net.HttpManager;
+import com.fq.channel.sdk.base.net.callback.BaseCallback;
+import com.fq.channel.sdk.base.utils.DeviceUtils;
+import com.fq.channel.sdk.base.utils.FqLog;
 import com.fq.channel.sdk.base.utils.Utils;
 import com.fq.channel.sdk.core.JointManager;
+import com.fq.channel.sdk.core.invoke.ApiPlugin;
+import com.fq.threelib.okhttp3.internal.Util;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -24,7 +39,7 @@ import java.util.Map;
  */
 public class FqGame {
 
-    private static final String TAG = "FQSDK_LOGFqGame";
+    private static final String TAG = "FqGame";
 
     /**
      * 提过给CP调用的初始化接口
@@ -33,21 +48,8 @@ public class FqGame {
      * @param resultInit
      */
     public static void init(@NonNull Activity activity, final IResult<String> resultInit) {
-        Log.i(TAG, "init");
-        JointManager.getInstance().init(activity, new PluginResultHandler() {
-            @Override
-            public void onHandlePluginResult(PluginResult result) {
-                Status mStatus = result.getStatus();
-                Log.i(TAG, "init mStatus :" + mStatus);
-                if (Status.OK == mStatus) {
-                    resultInit.onSuccess(result.getMessage());
-                } else if (Status.CANCEL == mStatus) {
-                    resultInit.onFail(result.getMessage());
-                } else if (Status.ERROR == mStatus) {
-                    resultInit.onFail(result.getMessage());
-                }
-            }
-        });
+        FqLog.i(TAG, "init");
+        FqGameHander.init(activity,resultInit);
     }
 
     /**
@@ -116,16 +118,14 @@ public class FqGame {
         }
     }
 
-
     /**
-     * 提供给CP接收回调的接口
+     * onRequestPermissionsResult
+     *
      */
-
-    public interface IResult<T> {
-
-        void onSuccess(T t);
-
-        void onFail(String failMsg);
+    public static void onRequestPermissionsResult(int requestCode,
+                                                  String[] permissions, int[] grantResults){
+        FqGameHander.onRequestPermissionsResult(requestCode,permissions,grantResults);
+        ApiPlugin.getInstace().onRequestPermissionsResult(requestCode, permissions, grantResults);
     }
 
 }

+ 124 - 0
fq_plugin_api/src/main/java/com/fq/channel/sdk/api/FqGameHander.java

@@ -0,0 +1,124 @@
+package com.fq.channel.sdk.api;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.fq.channel.sdk.base.Constants.AppConfig;
+import com.fq.channel.sdk.base.Constants.FqConfig;
+import com.fq.channel.sdk.base.Exception.BaseException;
+import com.fq.channel.sdk.base.interfaces.PluginResult;
+import com.fq.channel.sdk.base.interfaces.PluginResultHandler;
+import com.fq.channel.sdk.base.net.FqNetRequest;
+import com.fq.channel.sdk.base.net.HttpManager;
+import com.fq.channel.sdk.base.net.callback.BaseCallback;
+import com.fq.channel.sdk.base.net.req.ResultContent;
+import com.fq.channel.sdk.base.net.req.ResultInitBody;
+import com.fq.channel.sdk.base.utils.DeviceUtils;
+import com.fq.channel.sdk.base.utils.FqLog;
+import com.fq.channel.sdk.base.utils.Utils;
+import com.fq.channel.sdk.core.JointManager;
+
+/**
+ * @Description: 描述
+ * @Author: FLuty
+ * @CreateDate: 2020/4/3 15:59
+ */
+public class FqGameHander {
+
+    private static final String TAG = "FqGameHander";
+
+    //READ_PHONE_STATE 权限请求码
+    public static final int REQUEST_CODE_READ_PHONE_STATE = 1;
+
+    private static IResult<String> resultInit;
+
+    public static void init(Activity activity, IResult<String> result) {
+        resultInit = result;
+        //初始化配置
+        initSDKConfig(activity);
+        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_PHONE_STATE)
+                != PackageManager.PERMISSION_GRANTED) {
+            //如果没有读取权限的则进行权限的申请
+            ActivityCompat.requestPermissions(activity,
+                    new String[]{Manifest.permission.READ_PHONE_STATE}, REQUEST_CODE_READ_PHONE_STATE);
+        } else {
+            //有权限的情况 直接读取imei并进行网络请求
+            String imei = DeviceUtils.getIMEI();
+            AppConfig.sImei = TextUtils.isEmpty(imei) ? "" : imei;
+            initSDKNet();
+        }
+    }
+
+    /**
+     * 网络初始化
+     */
+    private static void initSDKNet() {
+        FqNetRequest.init(new BaseCallback<ResultInitBody>() {
+            @Override
+            public void onFailure(BaseException msg) {
+                FqLog.e(TAG, "initSDKNet onFailure :"+msg.getMsg());
+                resultInit.onFail(msg.getMsg());
+            }
+
+            @Override
+            public void onSuccess(int code, String msg, ResultInitBody data) {
+                FqLog.i(TAG, "initSDKNet onSuccess :" + data.toString());
+                initChannel(resultInit);
+            }
+        });
+    }
+
+    /**
+     * sdk配置初始化
+     */
+    private static void initSDKConfig(Activity activity) {
+        //初始化
+        FqConfig fqConfig = FqConfig.get(activity);
+        fqConfig.setCurrentActivity(activity);
+        fqConfig.init(activity);
+        HttpManager.getInstance().initHttp();
+        JointManager.getInstance().initCfgConfigFile(activity);
+    }
+
+    /**
+     * 渠道初始化
+     */
+    private static void initChannel(final IResult<String> resultInit) {
+        JointManager.getInstance().init(FqConfig.get().getApplicationContext(), new PluginResultHandler() {
+            @Override
+            public void onHandlePluginResult(PluginResult result) {
+                PluginResult.Status mStatus = result.getStatus();
+                Log.i(TAG, "init mStatus :" + mStatus);
+                if (PluginResult.Status.OK == mStatus) {
+                    resultInit.onSuccess(result.getMessage());
+                } else if (PluginResult.Status.CANCEL == mStatus) {
+                    resultInit.onFail(result.getMessage());
+                } else if (PluginResult.Status.ERROR == mStatus) {
+                    resultInit.onFail(result.getMessage());
+                }
+            }
+        });
+    }
+
+    protected static void onRequestPermissionsResult(int requestCode,
+                                                     String[] permissions, int[] grantResults) {
+        if (REQUEST_CODE_READ_PHONE_STATE == requestCode
+                && grantResults.length > 0
+                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+            //处理获取到读取IMEI权限的
+            String imei = DeviceUtils.getIMEI();
+            AppConfig.sImei = TextUtils.isEmpty(imei) ? "" : imei;
+        } else {
+            //获取不到读取IMEI权限
+            AppConfig.sImei = "";
+        }
+        //无论是否获得IMEI读取权限都要进行初始化接口的调用
+        initSDKNet();
+    }
+
+}

+ 13 - 0
fq_plugin_api/src/main/java/com/fq/channel/sdk/api/IResult.java

@@ -0,0 +1,13 @@
+package com.fq.channel.sdk.api;
+
+/**
+ * @Description: 提供给CP接收回调的接口
+ * @Author: FLuty
+ * @CreateDate: 2020/4/3 16:10
+ */
+public interface IResult<T> {
+
+    void onSuccess(T t);
+
+    void onFail(String failMsg);
+}

+ 3 - 0
fq_plugin_base/build.gradle

@@ -25,4 +25,7 @@ android {
 
 dependencies {
     api fileTree(dir: 'libs', include: ['*.jar'])
+    api 'com.google.code.findbugs:jsr305:3.0.2'
+    api 'org.codehaus.mojo:animal-sniffer-annotations:1.16'
+    implementation 'org.conscrypt:conscrypt-openjdk-uber:1.4.0'
 }

+ 118 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Constants/AppConfig.java

@@ -0,0 +1,118 @@
+package com.fq.channel.sdk.base.Constants;
+
+
+import com.fq.channel.sdk.base.utils.AppInfoUtils;
+
+import java.util.List;
+
+/**
+ * Created by Ethan on 2017/8/4.
+ * 配置类
+ */
+
+public class AppConfig {
+
+
+    /**
+     * AppId
+     */
+    public static final String APP_ID = AppInfoUtils.getInstance().getAppId();
+
+
+
+    /**
+     * 渠道ID
+     */
+    public static final String CHANNEL_ID = AppInfoUtils.getInstance().getChannelId();
+
+    /**
+     * adId
+     */
+    public static final String AD_ID = AppInfoUtils.getInstance().getAdId();
+
+    /**
+     * adFlag
+     */
+    public static final String AD_FLAG = AppInfoUtils.getInstance().getAdFlag();
+
+    /**
+     * 凤起SDK的版本号
+     */
+    public static final String FQ_GAME_SDK_VERSION_CODE = AppInfoUtils.getInstance().getFqGameSdkVersionCode();
+
+    /**
+     * 游戏的版本信息
+     */
+    public static final String GAME_VERSION = AppInfoUtils.getInstance().getVersionCode() + "_" + AppInfoUtils.getInstance().getVersionName();
+
+
+    /**
+     * IMEI
+     */
+    public static String sImei;
+
+    /**
+     * 当前的uid
+     */
+    public static String sCurrentUid;
+
+    /**
+     * 当前token
+     */
+    public static String sCurrentToken;
+
+    /**
+     * 当前的凤起账号
+     */
+    public static String sCurrentAccount;
+
+    /**
+     * 客服电话号码
+     */
+    public static String sCustomerServerPhone = "";
+
+    /**
+     * 客服QQ
+     */
+    public static String sCustomerServerQq = "";
+
+    /**
+     * 是否禁用小球
+     */
+    public static boolean sIsFloatDisable = false;
+
+    /**
+     * 当前设备之前登录过的凤起账号
+     */
+    public static List<String> sLoggedinFqAccounts = null;
+
+    /**
+     * H5游戏的链接
+     */
+    public static String sH5GameUrl = "";
+
+    /**
+     * Td_AD_AppId
+     */
+    public static String sTdADAppId = "";
+
+    /**
+     * Td_GA_AppId
+     */
+    public static String sTdGAAppId = "";
+
+    /**
+     * Td_AD_Tdid
+     */
+    public static String sTdADTdid = "";
+
+    /**
+     * Mac
+     */
+    public static final String Mac = AppInfoUtils.getInstance().tryGetWifiMac();
+
+    /**
+     * AndroidId
+     */
+    public static final String AndroidId = "";
+}

+ 9 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Constants/FqCache.java

@@ -0,0 +1,9 @@
+package com.fq.channel.sdk.base.Constants;
+
+/**
+ * @Description: 描述
+ * @Author: FLuty
+ * @CreateDate: 2020/4/3 14:44
+ */
+public class FqCache {
+}

+ 182 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Constants/FqConfig.java

@@ -0,0 +1,182 @@
+package com.fq.channel.sdk.base.Constants;
+
+import android.app.Activity;
+import android.content.Context;
+
+import com.fq.channel.sdk.base.utils.Utils;
+
+import org.json.JSONObject;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * @Description: 描述
+ * @Author: FLuty
+ * @CreateDate: 2020/4/2 17:27
+ */
+public class FqConfig {
+
+    public static boolean DEBUG_VERSION = true;
+
+    public static final String KEY_APP_ID = "appId";
+
+    public static final String KEY_CHANNEL_ID = "channelId";
+
+    public static final String KEY_AD_ID = "adId";
+
+    public static final String KEY_AD_FLAG = "adFlag";
+
+
+
+//    private static final String BASE_URL = "http://ta.funcheergame.com";  //测试环境
+//    private static final String BASE_URL = "https://app.funcheergame.com";  //正试环境
+    private static final String BASE_URL = "http://192.168.1.152:9099";  //本地环境
+
+    /**
+     * 初始化
+     */
+    public static String KEY_URL_INT = BASE_URL + "/api/game/v1/active";
+
+
+    private String mFilePath = "fqcfg";
+
+    private Map<String, Object> mData = new HashMap <String, Object>();
+
+    private static FqConfig sCache;
+
+    private Context mAppContext;
+
+    private FqConfig(Context appContext) {
+        mAppContext = appContext;
+    }
+
+    public static FqConfig get() {
+        if (sCache == null) {
+            throw new RuntimeException("get(Context) never called");
+        }
+        return sCache;
+    }
+
+    private static final byte[] SYNC = new byte[0];
+
+    public static FqConfig get(Context cxt) {
+        if (sCache == null) {
+            synchronized (SYNC) {
+                if (sCache == null) {
+                    sCache = new FqConfig(cxt.getApplicationContext());
+                }
+            }
+        }
+        return sCache;
+    }
+
+    public void init(Context appContext) {
+        initCfg(appContext);
+        Utils.init(appContext);
+    }
+
+    public Context getApplicationContext() {
+        return mAppContext;
+    }
+
+    private void initCfg(Context context) {
+        try {
+            String st = Utils.readSimplelyFile(context.getAssets().open(mFilePath));
+            JSONObject json = new JSONObject(st);
+            Iterator<String> it = json.keys();
+            while (it.hasNext()) {
+                String key = it.next();
+                mData.put(key, json.get(key));
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException();
+        }
+    }
+
+    private WeakReference<Activity> mCurrActivityRef;
+    private LinkedList<WeakReference <Activity>> mActivities;
+
+    /**
+     * 设置top activity
+     *
+     * @param activity
+     */
+    public void setCurrentActivity(Activity activity) {
+        if (activity == null) {
+            throw new NullPointerException("null passed to setCurrentActivity(Activity)");
+        }
+        // ----------------保存当前Activity实例---------------//
+        if (mCurrActivityRef != null) {
+            mCurrActivityRef.clear();
+        }
+        mCurrActivityRef = new WeakReference <Activity>(activity);
+        // ----------------end ----------------//
+
+        // -----------------保存所有开发者设置的Activity--------------------//
+        // 用于在退出时,所有activity都退出
+        if (mActivities == null) {
+            mActivities = new LinkedList <WeakReference <Activity>>();
+        }
+        mActivities.add(new WeakReference <Activity>(activity));
+        // -----------------------end---------------------------//
+
+    }
+
+    public void onPause(Activity activity) {
+
+    }
+
+    /**
+     * 获取当前top activity, 依赖于开发者调用,可能返回空
+     *
+     * @return
+     */
+    public Activity getCurrentActivity() {
+        return mCurrActivityRef != null ? mCurrActivityRef.get() : null;
+    }
+
+    public Object get(String key) {
+        return mData.get(key);
+    }
+
+    public String getString(String key) {
+        return String.valueOf(mData.get(key));
+    }
+
+    /**
+     * 获取adId
+     * 如果获取不到 则使用默认的adId
+     */
+    public String getAppId() {return getString(KEY_APP_ID);
+    }
+
+    /**
+     * 获取渠道号 ChannelId
+     * 如果获取不到 则使用默认的channelId
+     */
+    public String getChannelId() {
+        return getString(KEY_CHANNEL_ID);
+    }
+
+    /**
+     * 获取adId
+     * 如果获取不到 则使用默认的adId
+     */
+    public String getAdId() {return getString(KEY_AD_ID);
+    }
+
+    /**
+     * 获取adFlag
+     *
+     * @return
+     */
+    public String getAdFlag() {
+        return getString(KEY_AD_FLAG);
+    }
+
+}

+ 3 - 1
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/ResourceCfg.java

@@ -1,8 +1,10 @@
-package com.fq.channel.sdk.base.utils;
+package com.fq.channel.sdk.base.Constants;
 
 import android.content.Context;
 
 
+import com.fq.channel.sdk.base.utils.Utils;
+
 import org.json.JSONObject;
 
 import java.util.HashMap;

+ 40 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Exception/BaseException.java

@@ -0,0 +1,40 @@
+package com.fq.channel.sdk.base.Exception;
+
+/**
+ * @Description: okhttp 网络异常
+ * @Author: FLuty
+ * @CreateDate: 2020/3/30 17:43
+ */
+public class BaseException extends Exception {
+    private   int code ;  //错误码
+    private   String msg;    //错误信息
+
+    public BaseException(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    @Override
+    public String toString() {
+        return "BaseException{" +
+                "code='" + code + '\'' +
+                ", msg='" + msg + '\'' +
+                '}';
+    }
+}

+ 7 - 7
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Exception/NoSuchFunctionException.java

@@ -1,16 +1,16 @@
 package com.fq.channel.sdk.base.Exception;
 
 /**
- * @Description:  异常类
+ * @Description: 异常类
  * @Author: FLuty
  * @CreateDate: 2020/3/24 15:18
  */
-public class NoSuchFunctionException extends Exception{
+public class NoSuchFunctionException extends Exception {
 
-	private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = 1L;
+
+    public NoSuchFunctionException(String message) {
+        super(message);
+    }
 
-		public NoSuchFunctionException(String message) {
-			super(message);
-		}
-	
 }

+ 21 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Exception/NullStringToEmptyAdapterFactory.java

@@ -0,0 +1,21 @@
+package com.fq.channel.sdk.base.Exception;
+
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.TypeAdapterFactory;
+import com.fq.threelib.gson.reflect.TypeToken;
+
+/**
+ * Created by Ethan on 2017/7/6.
+ * 生成Json处理null 转换成 “” 相关类
+ */
+
+public class NullStringToEmptyAdapterFactory<T> implements TypeAdapterFactory {
+    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+        Class<T> rawType = (Class<T>) type.getRawType();
+        if (rawType != String.class) {
+            return null;
+        }
+        return (TypeAdapter<T>) new StringNullAdapter();
+    }
+}

+ 34 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Exception/StringNullAdapter.java

@@ -0,0 +1,34 @@
+package com.fq.channel.sdk.base.Exception;
+
+
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.gson.stream.JsonWriter;
+
+import java.io.IOException;
+
+/**
+ * Created by Ethan on 2017/7/6.
+ * 生成Json处理null 转换成 “” 相关类
+ */
+
+public class StringNullAdapter extends TypeAdapter<String> {
+    @Override
+    public String read(JsonReader reader) throws IOException {
+        if (reader.peek() == JsonToken.NULL) {
+            reader.nextNull();
+            return "";
+        }
+        return reader.nextString();
+    }
+
+    @Override
+    public void write(JsonWriter writer, String value) throws IOException {
+        if (value == null) {
+            writer.value("");
+            return;
+        }
+        writer.value(value);
+    }
+}

+ 0 - 9
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/Test.java

@@ -1,9 +0,0 @@
-package com.fq.channel.sdk.base;
-
-/**
- * @Description: 描述
- * @Author: FLuty
- * @CreateDate: 2020/3/24 15:18
- */
-public class Test {
-}

+ 49 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/FqNetRequest.java

@@ -0,0 +1,49 @@
+package com.fq.channel.sdk.base.net;
+
+import com.fq.channel.sdk.base.Constants.AppConfig;
+import com.fq.channel.sdk.base.Constants.FqConfig;
+import com.fq.channel.sdk.base.Exception.NullStringToEmptyAdapterFactory;
+import com.fq.channel.sdk.base.net.callback.BaseCallback;
+import com.fq.channel.sdk.base.net.req.ParamsUtils;
+import com.fq.channel.sdk.base.net.req.ReqContent;
+import com.fq.channel.sdk.base.net.req.ReqHeader;
+import com.fq.channel.sdk.base.net.req.ReqInitBody;
+import com.fq.channel.sdk.base.utils.Utils;
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.GsonBuilder;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * @Description: 业务网络请求封装类
+ * @Author: FLuty
+ * @CreateDate: 2020/4/3 10:45
+ */
+public class FqNetRequest {
+
+    /**
+     * 初始化请求
+     *
+     * @param callback
+     */
+    public static void init(BaseCallback callback) {
+        String request = ParamsUtils.getInstance().generateInitParams();
+        HttpManager.getInstance().post(FqConfig.KEY_URL_INT,Utils.decodeText(request) , callback);
+    }
+
+    /**
+     * 对Json字符串进行URLEncoder
+     *
+     * @param requestJson Json字符串
+     * @return
+     */
+    private String encoderJson(String requestJson) {
+        try {
+            return URLEncoder.encode(requestJson, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+}

+ 20 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/HttpConstant.java

@@ -0,0 +1,20 @@
+package com.fq.channel.sdk.base.net;
+
+/**
+ * @Description: http请求常量
+ * @Author: FLuty
+ * @CreateDate: 2020/4/1 15:23
+ */
+public class HttpConstant {
+    /** 通用版服务器返回JSON数据最外层包装*/
+    public static final String KEY_RESPONSE_HEAD = "head";    //状态信息
+    public static final String KEY_RESPONSE_BODY = "body"; //返回数据
+
+
+    public static final int HTTP_CODE_SUCCEED = 0;//网络请求成功
+    public static final int HTTP_CODE_ERROR = -1;//网络请求异常错误
+    public static final int HTTP_DATA_ERROR = -100;//内部数据错误(包括解析json错误)
+    public static final int HTTP_NET_ERROR = -200;//网络请求异常错误
+
+    public static final String HTTP_MSG_PARSE_ERROR = "服务器返回数据解析错误";
+}

+ 140 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/HttpManager.java

@@ -0,0 +1,140 @@
+package com.fq.channel.sdk.base.net;
+
+import com.fq.channel.sdk.base.net.callback.BaseCallback;
+import com.fq.threelib.okhttp3.FormBody;
+import com.fq.threelib.okhttp3.MediaType;
+import com.fq.threelib.okhttp3.OkHttpClient;
+import com.fq.threelib.okhttp3.Request;
+import com.fq.threelib.okhttp3.RequestBody;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @Description: 用来发送get, post请求的工具类
+ * @Author: FLuty
+ * @CreateDate: 2020/3/30 16:49
+ */
+public class HttpManager {
+
+    private static HttpManager instance = null;
+    //okhttp对象
+    private OkHttpClient client;
+
+    private static final int TIMEOUT = 10; //连接/写入/读取超时时间 单位:秒
+
+    private HttpManager() {
+    }
+
+    public static HttpManager getInstance() {
+        if (instance == null) {
+            instance = new HttpManager();
+        }
+        return instance;
+    }
+
+    /**
+     * 初始化OkHttpClient
+     * 这个方法必需要调用一次
+     */
+    public void initHttp() {
+        client = new OkHttpClient.Builder()
+                .connectTimeout(TIMEOUT, TimeUnit.SECONDS)
+                .writeTimeout(TIMEOUT, TimeUnit.SECONDS)
+                .readTimeout(TIMEOUT, TimeUnit.SECONDS)
+                .build();
+    }
+
+    public void request(final Request request, final BaseCallback callback) {
+        client.newCall(request).enqueue(callback);
+    }
+
+    /**
+     * 对外公开的get方法
+     *
+     * @param url
+     * @param callback
+     */
+    public void get(String url, BaseCallback callback) {
+        Request request = buildRequest(url, null,null, HttpMethodType.GET);
+        request(request, callback);
+    }
+
+    /**
+     * 对外公开的post方法
+     *
+     * @param url
+     * @param params
+     * @param callback
+     */
+    public void post(String url, Map<String, String> params, BaseCallback callback) {
+        Request request = buildRequest(url,null, params, HttpMethodType.POST);
+        request(request, callback);
+    }
+
+    /**
+     * 对外公开的post方法
+     *
+     * @param url
+     * @param requestStr
+     * @param callback
+     */
+    public void post(String url, String requestStr, BaseCallback callback) {
+        RequestBody requestBody = RequestBody
+                .create(MediaType.parse("application/json; charset=utf-8"), requestStr);
+        Request.Builder builder = new Request.Builder();
+        builder.url(url);
+        builder.post(requestBody);
+        request(builder.build(), callback);
+    }
+
+    /**
+     * 构建请求对象
+     *
+     * @param url
+     * @param params
+     * @param type
+     * @return
+     */
+    private Request buildRequest(String url, Map<String, String> headers, Map<String, String> params, HttpMethodType type) {
+        Request.Builder builder = new Request.Builder();
+        builder.url(url);
+        //请求头信息
+        if (headers != null && headers.size() > 0) {
+            for (Map.Entry<String, String> entry : headers.entrySet()) {
+                builder.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        if (type == HttpMethodType.GET) {
+            builder.get();
+        } else if (type == HttpMethodType.POST) {
+            builder.post(buildRequestBody(params));
+        }
+        return builder.build();
+    }
+
+    /**
+     * 通过Map的键值对构建请求对象的body
+     *
+     * @param params
+     * @return
+     */
+    private RequestBody buildRequestBody(Map<String, String> params) {
+        FormBody.Builder builder = new FormBody.Builder();
+        if (params != null) {
+            for (Map.Entry<String, String> entity : params.entrySet()) {
+                builder.add(entity.getKey(), entity.getValue());
+            }
+        }
+        return builder.build();
+    }
+
+    /**
+     * 这个枚举用于指明是哪一种提交方式
+     */
+    enum HttpMethodType {
+        GET,
+        POST
+    }
+
+}

+ 108 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/callback/BaseCallback.java

@@ -0,0 +1,108 @@
+package com.fq.channel.sdk.base.net.callback;
+
+import android.text.TextUtils;
+
+import com.fq.channel.sdk.base.Exception.BaseException;
+import com.fq.channel.sdk.base.net.HttpConstant;
+import com.fq.channel.sdk.base.net.req.ResultHeader;
+import com.fq.channel.sdk.base.utils.FqLog;
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.okhttp3.Call;
+import com.fq.threelib.okhttp3.Callback;
+import com.fq.threelib.okhttp3.Response;
+
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * @Description:
+ * @Author: FLuty
+ * @CreateDate: 2020/4/2 11:30
+ */
+public abstract class BaseCallback<T> implements Callback {
+    private static final String TAG = "FQSDKBaseCallback";
+
+    @Override
+    public void onFailure(Call call, IOException e) {
+        FqLog.e(TAG, "请求失败:" + "i=" + e.getMessage());
+        onFailure(new BaseException(HttpConstant.HTTP_CODE_ERROR,e.getMessage()));
+    }
+
+    @Override
+    public void onResponse(Call call, Response response) throws IOException {
+        String body = response.body().string();
+        if (response.isSuccessful()){
+            FqLog.i(TAG, "请求成功:" + "i=" +body );
+            try {
+                JSONObject jsonObject = new JSONObject(body);
+                String head = jsonObject.optString(HttpConstant.KEY_RESPONSE_HEAD);
+                ResultHeader resultHeader =  new Gson().fromJson(head, ResultHeader.class);
+                if (resultHeader!=null&&resultHeader.getResponseCode().equals("00000")){
+                    successParseJson(jsonObject.optString(HttpConstant.KEY_RESPONSE_BODY));
+                }else {
+                    onFailure(new BaseException(HttpConstant.HTTP_NET_ERROR,HttpConstant.HTTP_MSG_PARSE_ERROR));
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+                onFailure(new BaseException(HttpConstant.HTTP_NET_ERROR,HttpConstant.HTTP_MSG_PARSE_ERROR));
+            }
+        }else{
+            FqLog.e(TAG, "请求失败:" + "i=" +body );
+            onFailure(new BaseException(HttpConstant.HTTP_NET_ERROR,HttpConstant.HTTP_MSG_PARSE_ERROR));
+        }
+
+    }
+
+    /**
+     * 请求成功解析数据
+     *
+     * @param resultData
+     */
+    protected void successParseJson(String resultData) {
+        T data = null;
+        try {
+            //获取泛型类型
+            Type genericSuperclass = this.getClass().getGenericSuperclass();
+            Type type = null;
+            //有泛型参数时,genericSuperclass 是 ParameterizedType类型; 否则为 class
+            if (genericSuperclass instanceof ParameterizedType){
+                type = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
+            }
+            if (type == null) {
+                data = (T) resultData;
+            } else {
+                String typeName = type.toString();
+                if (!TextUtils.isEmpty(typeName)) {
+                    String[] name = typeName.split(" ");
+                    if (name.length > 1) {
+                        typeName = name[1];
+                    }
+                }
+                if (Byte.class.getName().equals(typeName) || Short.class.getName().equals
+                        (typeName) || Integer.class.getName().equals(typeName) || Long.class.getName
+                        ().equals(typeName) || Float.class.getName().equals(typeName) || Double
+                        .class.getName().equals(typeName) || Character.class.getName().equals
+                        (typeName) || Boolean.class.getName().equals(typeName) || String.class
+                        .getName().equals(typeName)) {
+                    data = (T) resultData;
+                } else {
+                    data = (T) new Gson().fromJson(resultData, type);
+                }
+            }
+            FqLog.d(TAG, " data.className: " + (data != null ? data.getClass().getSimpleName(): "null"));
+        } catch (Exception e) {
+            onFailure(new BaseException(HttpConstant.HTTP_DATA_ERROR, HttpConstant.HTTP_MSG_PARSE_ERROR));
+            return;
+        }
+        //解析映射成功,返回成功
+        onSuccess(HttpConstant.HTTP_CODE_SUCCEED, "OK", data);
+    }
+
+
+    public abstract void onFailure(BaseException msg);
+
+    public abstract void onSuccess(int code, String msg, T data);
+}

+ 168 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ParamsUtils.java

@@ -0,0 +1,168 @@
+package com.fq.channel.sdk.base.net.req;
+
+import com.fq.channel.sdk.base.Constants.AppConfig;
+import com.fq.channel.sdk.base.Exception.NullStringToEmptyAdapterFactory;
+import com.fq.channel.sdk.base.utils.DES;
+import com.fq.channel.sdk.base.utils.DeviceUtils;
+import com.fq.channel.sdk.base.utils.EncryptUtils;
+import com.fq.channel.sdk.base.utils.Utils;
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.GsonBuilder;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Iterator;
+import java.util.TreeMap;
+
+/**
+ * Created by Ethan on 2017/7/5.
+ * 生成请求参数的单例工具类
+ */
+
+public class ParamsUtils {
+
+    private ParamsUtils() {
+    }
+
+    private volatile static ParamsUtils sInstance;
+
+    public static ParamsUtils getInstance() {
+        if (sInstance == null) {
+            synchronized (ParamsUtils.class) {
+                if (sInstance == null) {
+                    sInstance = new ParamsUtils();
+                }
+            }
+        }
+        return sInstance;
+    }
+
+
+    /**
+     * 生成初始化请求的 对应的Json
+     *
+     * @return
+     */
+    public String generateInitParams() {
+
+        ReqContent<ReqInitBody> reqContent = new ReqContent<>();
+        //生成init操作的boby
+        ReqInitBody body = new ReqInitBody(AppConfig.AD_ID);
+        //生成init操作的head
+        ReqHeader head = generateReqHeader(body);
+
+        //转换成最终请求用的Json
+        reqContent.setBody(body);
+        reqContent.setHead(head);
+
+        return gson2Json(reqContent);
+    }
+
+
+    /**
+     * 根据对应请求的body生成 对应Json中的head
+     *
+     * @param reqBody 对应请求的body
+     * @param <T>
+     * @return 对应请求Json中的head
+     */
+    private <T> ReqHeader generateReqHeader(T reqBody) {
+
+        ReqHeader reqHeader = new ReqHeader();
+        reqHeader.setOsInfo(DeviceUtils.getAndroidVersion() + "_"
+                + DeviceUtils.getSDKVersion());  //系统信息
+
+        reqHeader.setModel(DeviceUtils.getModel()); //手机型号
+        reqHeader.setNetworkType(Utils.isWifi() ?"1" : "2"); //网络类型 1 wifi   2 其他
+
+        reqHeader.setRequestIp(Utils.getIPAddress(true)); //请求ip
+        reqHeader.setImei(AppConfig.sImei); //Android 手机唯一识别码
+        reqHeader.setIdfa(""); //ios唯一标识 没有传空字符传
+        reqHeader.setOsType("1"); //系统类型 1 安卓 2 IOS 3 web 4 其他
+
+        reqHeader.setAppId(AppConfig.APP_ID); //appId
+        reqHeader.setChannelId(AppConfig.CHANNEL_ID); //渠道号
+        reqHeader.setAdId(AppConfig.AD_ID); //广告编号
+        reqHeader.setAdFlag(AppConfig.AD_FLAG); //广告名
+        reqHeader.setSdkVersion(AppConfig.FQ_GAME_SDK_VERSION_CODE);//凤起SDK版本
+        reqHeader.setGameVersion(AppConfig.GAME_VERSION);//游戏版本
+        reqHeader.setToken(AppConfig.sCurrentToken); //token
+
+        reqHeader.setTdid(AppConfig.sTdADTdid);//Tdid
+
+        String timestamp = System.currentTimeMillis() + "";//时间戳
+        reqHeader.setTimestamp(timestamp);
+
+        reqHeader.setVerificationCode(generateSign(reqBody, timestamp));//MD5校验值
+        return reqHeader;
+    }
+
+    /**
+     * 根据相应的ReqBody生成对应的sign
+     *
+     * @param t         对应的ReqBody
+     * @param timestamp 时间戳
+     * @param <T>
+     * @return
+     */
+    private <T> String generateSign(T t, String timestamp) {
+        try {
+            TreeMap<String, String> treeMap = new TreeMap<>();
+            Field[] fields = t.getClass().getDeclaredFields();
+
+            //防止加载类
+            for (Field field : fields) {
+                if (Modifier.isFinal(field.getModifiers()) || Modifier.isStatic(field.getModifiers())){
+                    continue;
+                }
+                field.setAccessible(true);
+                String key = field.getName();
+                String value = "";
+                //不为空的值拼接原来的值
+                if (field.get(t) != null) {
+//                    value = (String) field.get(t);
+                    value = String.valueOf(field.get(t)) ;
+                }
+                treeMap.put(key, value);
+            }
+
+            //拼接有序的数据进行加密
+            Iterator<String> iterator = treeMap.keySet().iterator();
+            StringBuilder sb = new StringBuilder();
+            while (iterator.hasNext()) {
+                String key = iterator.next();
+                sb.append(key);
+                sb.append(treeMap.get(key));
+            }
+
+            //加上时间戳
+            sb.append(timestamp);
+            //生成MD5之后再进行加密
+            String md5 = EncryptUtils.encryptMD5ToString(sb.toString());
+            String result = DES.encryptDES(md5, md5.substring(md5.length() - 8, md5.length()));
+            return result;
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 生成最终请求的json
+     *
+     * @param reqContent
+     * @param <T>
+     * @return
+     */
+    private <T> String gson2Json(ReqContent<T> reqContent) {
+
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapterFactory(new NullStringToEmptyAdapterFactory())//这里生成Gson时注册自定义处理String NULL值的TypeAdapter 将null换成""
+                .disableHtmlEscaping() //禁止转义JSON等号大括号字符串
+                .create();
+        return gson.toJson(reqContent);
+    }
+
+}

+ 31 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ReqContent.java

@@ -0,0 +1,31 @@
+package com.fq.channel.sdk.base.net.req;
+
+/**
+ * Created by Ethan on 2017/7/5.
+ * 请求的JSON
+ */
+
+public class ReqContent<T> {
+
+    //请求JSON中的head
+    private ReqHeader head;
+
+    //请求JSON中的body
+    private T body;
+
+    public ReqHeader getHead() {
+        return head;
+    }
+
+    public void setHead(ReqHeader head) {
+        this.head = head;
+    }
+
+    public T getBody() {
+        return body;
+    }
+
+    public void setBody(T body) {
+        this.body = body;
+    }
+}

+ 182 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ReqHeader.java

@@ -0,0 +1,182 @@
+package com.fq.channel.sdk.base.net.req;
+
+/**
+ * Created by Ethan on 2017/7/5.
+ * 请求JSON中的head
+ */
+
+public class ReqHeader {
+
+    /**
+     * osInfo : null
+     * model : null
+     * networkType : null
+     * requestIp : null
+     * sImei : null
+     * idfa : null
+     * osType : null
+     * appId : null
+     * channelId : null
+     * adId : null
+     * adFlag : null
+     * sdkVersion : null
+     * gameVersion : null
+     * sCurrentToken : null
+     * verificationCode : null
+     * timestamp : null
+     */
+
+    private String osInfo;
+    private String model;
+    private String networkType;
+    private String requestIp;
+    private String imei;
+    private String idfa;
+    private String osType;
+    private String appId;
+    private String channelId;
+    private String adId;
+    private String adFlag;
+    private String sdkVersion;
+    private String gameVersion;
+    private String token;
+    private String verificationCode;
+    private String timestamp;
+    private String tdid;
+
+    public String getOsInfo() {
+        return osInfo;
+    }
+
+    public void setOsInfo(String osInfo) {
+        this.osInfo = osInfo;
+    }
+
+    public String getModel() {
+        return model;
+    }
+
+    public void setModel(String model) {
+        this.model = model;
+    }
+
+    public String getNetworkType() {
+        return networkType;
+    }
+
+    public void setNetworkType(String networkType) {
+        this.networkType = networkType;
+    }
+
+    public String getRequestIp() {
+        return requestIp;
+    }
+
+    public void setRequestIp(String requestIp) {
+        this.requestIp = requestIp;
+    }
+
+    public String getImei() {
+        return imei;
+    }
+
+    public void setImei(String imei) {
+        this.imei = imei;
+    }
+
+    public String getIdfa() {
+        return idfa;
+    }
+
+    public void setIdfa(String idfa) {
+        this.idfa = idfa;
+    }
+
+    public String getOsType() {
+        return osType;
+    }
+
+    public void setOsType(String osType) {
+        this.osType = osType;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getChannelId() {
+        return channelId;
+    }
+
+    public void setChannelId(String channelId) {
+        this.channelId = channelId;
+    }
+
+    public String getAdId() {
+        return adId;
+    }
+
+    public void setAdId(String adId) {
+        this.adId = adId;
+    }
+
+    public String getAdFlag() {
+        return adFlag;
+    }
+
+    public void setAdFlag(String adFlag) {
+        this.adFlag = adFlag;
+    }
+
+    public String getSdkVersion() {
+        return sdkVersion;
+    }
+
+    public void setSdkVersion(String sdkVersion) {
+        this.sdkVersion = sdkVersion;
+    }
+
+    public String getGameVersion() {
+        return gameVersion;
+    }
+
+    public void setGameVersion(String gameVersion) {
+        this.gameVersion = gameVersion;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getVerificationCode() {
+        return verificationCode;
+    }
+
+    public void setVerificationCode(String verificationCode) {
+        this.verificationCode = verificationCode;
+    }
+
+    public String getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(String timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public String getTdid() {
+        return tdid;
+    }
+
+    public void setTdid(String tdid) {
+        this.tdid = tdid;
+    }
+}

+ 27 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ReqInitBody.java

@@ -0,0 +1,27 @@
+package com.fq.channel.sdk.base.net.req;
+
+/**
+ * Created by Ethan on 2017/7/5.
+ * 初始化请求JSON中的Body
+ */
+
+public class ReqInitBody {
+
+    /**
+     * adId : test123
+     */
+
+    private String adId;
+
+    public String getAdId() {
+        return adId;
+    }
+
+    public void setAdId(String adId) {
+        this.adId = adId;
+    }
+
+    public ReqInitBody(String adId) {
+        this.adId = adId;
+    }
+}

+ 39 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ResultContent.java

@@ -0,0 +1,39 @@
+package com.fq.channel.sdk.base.net.req;
+
+/**
+ * Created by Ethan on 2017/7/5.
+ * 请求返回的JSON
+ */
+
+public class ResultContent<T> {
+
+    //返回的Json数据中的head
+    private ResultHeader head;
+
+    //返回的Json数据中的body
+    private T body;
+
+    public ResultHeader getHead() {
+        return head;
+    }
+
+    public void setHead(ResultHeader head) {
+        this.head = head;
+    }
+
+    public T getBody() {
+        return body;
+    }
+
+    public void setBody(T body) {
+        this.body = body;
+    }
+
+    @Override
+    public String toString() {
+        return "ResultContent{" +
+                "head=" + head +
+                ", body=" + body +
+                '}';
+    }
+}

+ 41 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ResultHeader.java

@@ -0,0 +1,41 @@
+package com.fq.channel.sdk.base.net.req;
+
+/**
+ * Created by Ethan on 2017/7/5.
+ */
+
+public class ResultHeader {
+
+    /**
+     * responseCode : 00000
+     * responseMsg : success
+     */
+
+    private String responseCode;
+    private String responseMsg;
+
+    public String getResponseCode() {
+        return responseCode;
+    }
+
+    public void setResponseCode(String responseCode) {
+        this.responseCode = responseCode;
+    }
+
+    public String getResponseMsg() {
+        return responseMsg;
+    }
+
+    public void setResponseMsg(String responseMsg) {
+        this.responseMsg = responseMsg;
+    }
+
+
+    @Override
+    public String toString() {
+        return "ResultHeader{" +
+                "responseCode='" + responseCode + '\'' +
+                ", responseMsg='" + responseMsg + '\'' +
+                '}';
+    }
+}

+ 176 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/net/req/ResultInitBody.java

@@ -0,0 +1,176 @@
+package com.fq.channel.sdk.base.net.req;
+
+
+/**
+ * Created by Ethan on 2017/7/5.
+ * 初始化接口返回的参数
+ */
+
+public class ResultInitBody {
+
+    /**
+     * 先判断closeState再判断updateState
+     */
+
+
+    /**
+     * 游戏是否关闭 0不关闭 1关闭  关闭了如有colseUrl则转到closeUrl,没有的话回调初始化失败
+     */
+    private String closeState;
+
+    /**
+     * 客服QQ
+     */
+    private String linkQq;
+
+    /**
+     * 是否更新 0不更新 1非强制更新 2强制更新 updateUrl为更新的链接
+     */
+    private String updateState;
+
+    /**
+     * 更新的链接
+     */
+    private String updateUrl;
+
+    /**
+     * 是否显示悬浮小球
+     */
+    private String isFloatDisable;
+
+    /**
+     * 游戏关闭时跳转的url
+     */
+    private String closeUrl;
+
+    /**
+     * 广告参数
+     */
+    private String adPrams;
+
+    /**
+     *
+     */
+    private String thirdPartyPrams;
+
+    /**
+     * 客服电话
+     */
+    private String linkTel;
+
+    /**
+     * H5游戏的链接
+     */
+    private String h5GameUrl;
+
+    /**
+     * 是否是审核服 1审核 0正式  IOS所需的,Android暂时没有用到
+     */
+    private String serverStatus;
+
+
+    public String getCloseState() {
+        return closeState;
+    }
+
+    public void setCloseState(String closeState) {
+        this.closeState = closeState;
+    }
+
+    public String getLinkQq() {
+        return linkQq;
+    }
+
+    public void setLinkQq(String linkQq) {
+        this.linkQq = linkQq;
+    }
+
+    public String getUpdateState() {
+        return updateState;
+    }
+
+    public void setUpdateState(String updateState) {
+        this.updateState = updateState;
+    }
+
+    public String getUpdateUrl() {
+        return updateUrl;
+    }
+
+    public void setUpdateUrl(String updateUrl) {
+        this.updateUrl = updateUrl;
+    }
+
+    public String getIsFloatDisable() {
+        return isFloatDisable;
+    }
+
+    public void setIsFloatDisable(String isFloatDisable) {
+        this.isFloatDisable = isFloatDisable;
+    }
+
+    public String getCloseUrl() {
+        return closeUrl;
+    }
+
+    public void setCloseUrl(String closeUrl) {
+        this.closeUrl = closeUrl;
+    }
+
+    public String getAdPrams() {
+        return adPrams;
+    }
+
+    public void setAdPrams(String adPrams) {
+        this.adPrams = adPrams;
+    }
+
+    public String getThirdPartyPrams() {
+        return thirdPartyPrams;
+    }
+
+    public void setThirdPartyPrams(String thirdPartyPrams) {
+        this.thirdPartyPrams = thirdPartyPrams;
+    }
+
+    public String getLinkTel() {
+        return linkTel;
+    }
+
+    public void setLinkTel(String linkTel) {
+        this.linkTel = linkTel;
+    }
+
+    public String getH5GameUrl() {
+        return h5GameUrl;
+    }
+
+    public void setH5GameUrl(String h5GameUrl) {
+        this.h5GameUrl = h5GameUrl;
+    }
+
+    public String getServerStatus() {
+        return serverStatus;
+    }
+
+    public void setServerStatus(String serverStatus) {
+        this.serverStatus = serverStatus;
+    }
+
+    @Override
+    public String toString() {
+        return "ResultInitBody{" +
+                "closeState='" + closeState + '\'' +
+                ", linkQq='" + linkQq + '\'' +
+                ", updateState='" + updateState + '\'' +
+                ", updateUrl='" + updateUrl + '\'' +
+                ", isFloatDisable='" + isFloatDisable + '\'' +
+                ", closeUrl='" + closeUrl + '\'' +
+                ", adPrams='" + adPrams + '\'' +
+                ", thirdPartyPrams='" + thirdPartyPrams + '\'' +
+                ", linkTel='" + linkTel + '\'' +
+                ", h5GameUrl='" + h5GameUrl + '\'' +
+                ", serverStatus='" + serverStatus + '\'' +
+                '}';
+    }
+}

+ 126 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/AppInfoUtils.java

@@ -0,0 +1,126 @@
+package com.fq.channel.sdk.base.utils;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import com.fq.channel.sdk.base.Constants.FqConfig;
+
+/**
+ * Created by Ethan on 2017/9/2.
+ * 读取配置在AndroidManifest中的配置信息
+ *
+ * @author Ethan
+ */
+
+public class AppInfoUtils {
+
+    /**
+     * 声明成volatile
+     */
+    private volatile static AppInfoUtils sInstance;
+
+    private AppInfoUtils() {
+    }
+
+    public static AppInfoUtils getInstance() {
+
+        if (sInstance == null) {
+            synchronized (AppInfoUtils.class) {
+                if (sInstance == null) {
+                    sInstance = new AppInfoUtils();
+                }
+            }
+        }
+        return sInstance;
+    }
+
+
+    /**
+     * 获取AppId
+     */
+    public String getAppId() {
+
+        return FqConfig.get().getAppId();
+    }
+
+    /**
+     * 获取配置写在strings.xml中的版本号信息
+     */
+    public String getFqGameSdkVersionCode() {
+        return Utils.FQSDK_VERSION;
+    }
+
+
+    /**
+     * 获取渠道号 ChannelId
+     * 如果获取不到 则使用默认的channelId
+     */
+    public String getChannelId() {
+        return FqConfig.get().getChannelId();
+    }
+
+    /**
+     * 获取adId
+     * 如果获取不到 则使用默认的adId
+     */
+    public String getAdId() {
+        return FqConfig.get().getAdId();
+    }
+
+    /**
+     * 获取adFlag
+     *
+     * @return
+     */
+    public String getAdFlag() {
+        return FqConfig.get().getAdFlag();
+    }
+
+    /**
+     * 获取VersionCode
+     *
+     * @return
+     */
+    public String getVersionCode() {
+
+        String versionCode = "";
+        try {
+            PackageManager pm = Utils.getContext().getPackageManager();
+            PackageInfo pi = pm.getPackageInfo(Utils.getContext().getPackageName(), 0);
+            versionCode = pi.versionCode + "";
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+
+        return versionCode;
+    }
+
+    /**
+     * 获取VersionName
+     *
+     * @return
+     */
+    public String getVersionName() {
+
+        String versionName = "";
+        try {
+            PackageManager pm = Utils.getContext().getPackageManager();
+            PackageInfo pi = pm.getPackageInfo(Utils.getContext().getPackageName(), 0);
+            versionName = pi.versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+
+        return versionName;
+    }
+
+    /**
+     * 通过WiFiManager获取mac地址
+     *
+     * @return
+     */
+    public String tryGetWifiMac() {
+        return DeviceUtils.tryGetWifiMac();
+    }
+
+}

+ 58 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/Base64.java

@@ -0,0 +1,58 @@
+package com.fq.channel.sdk.base.utils;
+
+public class Base64 {
+    private static final char[] legalChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+            .toCharArray();
+
+    /**
+     * data[]进行编码
+     *
+     * @param data
+     * @return
+     */
+    public static String encode(byte[] data) {
+        int start = 0;
+        int len = data.length;
+        StringBuffer buf = new StringBuffer(data.length * 3 / 2);
+
+        int end = len - 3;
+        int i = start;
+        int n = 0;
+
+        while (i <= end) {
+            int d = ((((int) data[i]) & 0x0ff) << 16)
+                    | ((((int) data[i + 1]) & 0x0ff) << 8)
+                    | (((int) data[i + 2]) & 0x0ff);
+
+            buf.append(legalChars[(d >> 18) & 63]);
+            buf.append(legalChars[(d >> 12) & 63]);
+            buf.append(legalChars[(d >> 6) & 63]);
+            buf.append(legalChars[d & 63]);
+
+            i += 3;
+
+            if (n++ >= 14) {
+                n = 0;
+                buf.append(" ");
+            }
+        }
+
+        if (i == start + len - 2) {
+            int d = ((((int) data[i]) & 0x0ff) << 16)
+                    | ((((int) data[i + 1]) & 255) << 8);
+
+            buf.append(legalChars[(d >> 18) & 63]);
+            buf.append(legalChars[(d >> 12) & 63]);
+            buf.append(legalChars[(d >> 6) & 63]);
+            buf.append("=");
+        } else if (i == start + len - 1) {
+            int d = (((int) data[i]) & 0x0ff) << 16;
+
+            buf.append(legalChars[(d >> 18) & 63]);
+            buf.append(legalChars[(d >> 12) & 63]);
+            buf.append("==");
+        }
+
+        return buf.toString();
+    }
+}

+ 19 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/DES.java

@@ -0,0 +1,19 @@
+package com.fq.channel.sdk.base.utils;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class DES {
+    private static byte[] iv = {1, 2, 3, 4, 5, 6, 7, 8};
+
+    public static String encryptDES(String encryptString, String encryptKey)
+            throws Exception {
+        IvParameterSpec zeroIv = new IvParameterSpec(iv);
+        SecretKeySpec key = new SecretKeySpec(encryptKey.getBytes(), "DES");
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        cipher.init(Cipher.ENCRYPT_MODE, key, zeroIv);
+        byte[] encryptedData = cipher.doFinal(encryptString.getBytes());
+        return Base64.encode(encryptedData);
+    }
+}

+ 139 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/DeviceUtils.java

@@ -0,0 +1,139 @@
+package com.fq.channel.sdk.base.utils;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * 用于获取设备信息的工具类
+ * Created by Ethan on 2017/6/14.
+ */
+
+public class DeviceUtils {
+
+    /**
+     * 获取第一个IMEI码
+     * <p>需添加权限 {@code <uses-permission android:name="android.permission.READ_PHONE_STATE"/>}</p>
+     *
+     * @return IMEI码
+     */
+    @SuppressLint("HardwareIds")
+    public static String getIMEI() {
+        TelephonyManager tm = (TelephonyManager) Utils.getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        return tm != null ? tm.getDeviceId() : null;
+    }
+
+    /**
+     * 获取IMSI码
+     * <p>需添加权限 {@code <uses-permission android:name="android.permission.READ_PHONE_STATE"/>}</p>
+     *
+     * @return IMSI码
+     */
+    @SuppressLint("HardwareIds")
+    public static String getIMSI() {
+
+        TelephonyManager tm = (TelephonyManager) Utils.getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        String imei = tm != null ? tm.getSubscriberId() : null;
+        if (TextUtils.isEmpty(imei)){
+            return "";
+        }
+        return imei;
+    }
+
+    /**
+     * 获取屏幕的宽度(单位:px)
+     *
+     * @return 屏幕宽px
+     */
+    public static int getScreenWidth() {
+        WindowManager windowManager = (WindowManager) Utils.getContext().getSystemService(Context.WINDOW_SERVICE);
+        DisplayMetrics dm = new DisplayMetrics();// 创建了一张白纸
+        windowManager.getDefaultDisplay().getMetrics(dm);// 给白纸设置宽高
+        return dm.widthPixels;
+    }
+
+    /**
+     * 获取屏幕的高度(单位:px)
+     *
+     * @return 屏幕高px
+     */
+    public static int getScreenHeight() {
+        WindowManager windowManager = (WindowManager) Utils.getContext().getSystemService(Context.WINDOW_SERVICE);
+        DisplayMetrics dm = new DisplayMetrics();// 创建了一张白纸
+        windowManager.getDefaultDisplay().getMetrics(dm);// 给白纸设置宽高
+        return dm.heightPixels;
+    }
+
+    /**
+     * 获取设备系统版本号
+     * 如 Android6.0 返回 23
+     *
+     * @return 设备系统版本号
+     */
+    public static int getSDKVersion() {
+        return Build.VERSION.SDK_INT;
+    }
+
+    /**
+     * 获取设备型号
+     * 如 NEM-AL10
+     *
+     * @return 返回设备型号
+     */
+    public static String getModel() {
+
+        String model = "";
+        try {
+            model = URLEncoder.encode(Build.MODEL, "UTF-8");
+            return model;
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+        }
+        return model;
+    }
+
+    /**
+     * 获取Android系统的版本号 如 2.34.07.0
+     *
+     * @return 安卓系统的版本
+     */
+    public static String getAndroidVersion() {
+        return Build.VERSION.RELEASE;
+    }
+
+    /**
+     * 获取sim卡的序列号
+     *
+     * @return sim卡序列号
+     */
+    public static String getSimSerialNumber() {
+        TelephonyManager tm = (TelephonyManager) Utils.getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        return tm.getSimSerialNumber();
+    }
+
+    /**
+     * 通过WiFiManager获取mac地址
+     * @return
+     */
+    public static String tryGetWifiMac() {
+        WifiManager wm = (WifiManager) Utils.getContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+        WifiInfo wi = wm.getConnectionInfo();
+        if (wi == null || wi.getMacAddress() == null) {
+            return null;
+        }
+        if ("02:00:00:00:00:00".equals(wi.getMacAddress().trim())) {
+            return null;
+        } else {
+            return wi.getMacAddress().trim();
+        }
+    }
+}

+ 75 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/EncryptUtils.java

@@ -0,0 +1,75 @@
+package com.fq.channel.sdk.base.utils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Created by Ethan on 2017/7/5.
+ * 加密工具
+ */
+
+public class EncryptUtils {
+
+    private static final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+    /**
+     * MD5加密
+     *
+     * @param data 明文字符串
+     * @return 16进制密文
+     */
+    public static String encryptMD5ToString(final String data) {
+        return encryptMD5ToString(data.getBytes());
+    }
+
+    /**
+     * MD5加密
+     *
+     * @param data 明文字节数组
+     * @return 16进制密文
+     */
+    public static String encryptMD5ToString(final byte[] data) {
+        return bytes2HexString(encryptMD5(data));
+    }
+
+    /**
+     * MD5加密
+     *
+     * @param data 明文字节数组
+     * @return 密文字节数组
+     */
+    public static byte[] encryptMD5(final byte[] data) {
+        return hashTemplate(data, "MD5");
+    }
+
+    /**
+     * hash加密模板
+     *
+     * @param data      数据
+     * @param algorithm 加密算法
+     * @return 密文字节数组
+     */
+    private static byte[] hashTemplate(final byte[] data, final String algorithm) {
+        if (data == null || data.length <= 0) return null;
+        try {
+            MessageDigest md = MessageDigest.getInstance(algorithm);
+            md.update(data);
+            return md.digest();
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private static String bytes2HexString(final byte[] bytes) {
+        if (bytes == null) return null;
+        int len = bytes.length;
+        if (len <= 0) return null;
+        char[] ret = new char[len << 1];
+        for (int i = 0, j = 0; i < len; i++) {
+            ret[j++] = hexDigits[bytes[i] >>> 4 & 0x0f];
+            ret[j++] = hexDigits[bytes[i] & 0x0f];
+        }
+        return new String(ret);
+    }
+}

+ 170 - 0
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/FqLog.java

@@ -0,0 +1,170 @@
+package com.fq.channel.sdk.base.utils;
+
+import android.util.Log;
+
+import com.fq.channel.sdk.base.Constants.FqConfig;
+
+/**
+ * @Description: 日志工具类
+ * @Author: FLuty
+ * @CreateDate: 2020/4/2 17:24
+ */
+public class FqLog {
+    private static final String TAG = "FGLOG--";
+
+    public static void i(String tag, CharSequence c) {
+        if (FqConfig.DEBUG_VERSION && c != null) {
+            Log.i(TAG + tag, c.toString());
+        }
+    }
+
+    public static void i(String tag, Character c) {
+        if (FqConfig.DEBUG_VERSION && c != null) {
+            Log.i(TAG + tag, c.toString());
+        }
+    }
+
+    public static void i(String tag, boolean c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.i(TAG + tag, "" + c);
+        }
+    }
+
+    public static void i(String tag, char c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.i(TAG + tag, "" + c);
+        }
+    }
+
+    public static void i(String tag, byte[] b) {
+        if (FqConfig.DEBUG_VERSION && b != null) {
+            Log.i(TAG + tag, new String(b));
+        }
+    }
+
+    public static void i(String tag, int c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.i(TAG + tag, "" + c);
+        }
+    }
+
+    public static void d(String tag, CharSequence c) {
+        if (FqConfig.DEBUG_VERSION && c != null) {
+            Log.d(TAG + tag, c.toString());
+        }
+    }
+
+    public static void d(String tag, Character c) {
+        if (FqConfig.DEBUG_VERSION && c != null) {
+            Log.d(TAG + tag, c.toString());
+        }
+    }
+
+    public static void d(String tag, boolean c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.d(TAG + tag, "" + c);
+        }
+    }
+
+    public static void d(String tag, char c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.d(tag, "" + c);
+        }
+    }
+
+    public static void d(String tag, byte[] b) {
+        if (FqConfig.DEBUG_VERSION && b != null) {
+            Log.d(TAG + tag, new String(b));
+        }
+    }
+
+    public static void d(String tag, int c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.d(TAG + tag, "" + c);
+        }
+    }
+
+    public static void w(String tag, CharSequence c) {
+        if (FqConfig.DEBUG_VERSION && c != null) {
+            Log.w(TAG + tag, c.toString());
+        }
+    }
+
+    public static void w(String tag, Character c) {
+        if (FqConfig.DEBUG_VERSION && c != null) {
+            Log.w(TAG + tag, c.toString());
+        }
+    }
+
+    public static void w(String tag, boolean c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.w(TAG + tag, "" + c);
+        }
+    }
+
+    public static void w(String tag, char c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.w(tag, "" + c);
+        }
+    }
+
+    public static void w(String tag, byte[] b) {
+        if (FqConfig.DEBUG_VERSION && b != null) {
+            Log.w(TAG + tag, new String(b));
+        }
+    }
+
+    public static void w(String tag, int c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.w(tag, "" + c);
+        }
+    }
+
+    public static void e(String tag, Character c) {
+        if (FqConfig.DEBUG_VERSION && c != null) {
+            Log.e(TAG + tag, c.toString());
+        }
+    }
+
+    public static void e(String tag, CharSequence c) {
+        if (FqConfig.DEBUG_VERSION && c != null) {
+            Log.e(TAG + tag, c.toString());
+        }
+    }
+
+    public static void e(String tag, boolean c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.e(TAG + tag, "" + c);
+        }
+    }
+
+    public static void e(String tag, char c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.e(TAG + tag, "" + c);
+        }
+    }
+
+    public static void e(String tag, byte[] b) {
+        if (FqConfig.DEBUG_VERSION && b != null) {
+            Log.e(TAG + tag, new String(b));
+        }
+    }
+
+    public static void e(String tag, int c) {
+        if (FqConfig.DEBUG_VERSION) {
+            Log.e(TAG + tag, "" + c);
+        }
+    }
+
+    public static void e(String tag, String msg, Throwable tr) {
+        if (FqConfig.DEBUG_VERSION && msg != null) {
+            Log.e(TAG + tag, msg, tr);
+        }
+    }
+
+    public static void printStackTrace(Exception e) {
+        if (FqConfig.DEBUG_VERSION && e != null) {
+            e.printStackTrace();
+        }
+    }
+}

+ 65 - 1
fq_plugin_base/src/main/java/com/fq/channel/sdk/base/utils/Utils.java

@@ -5,6 +5,8 @@ import android.app.ActivityManager;
 import android.app.Application;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
 import android.net.Uri;
 import android.support.annotation.ColorRes;
 import android.support.annotation.DimenRes;
@@ -14,6 +16,11 @@ import android.support.annotation.StringRes;
 import android.support.v4.content.ContextCompat;
 import android.text.TextUtils;
 
+import com.fq.channel.sdk.base.Exception.NullStringToEmptyAdapterFactory;
+import com.fq.channel.sdk.base.net.req.ReqContent;
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.GsonBuilder;
+
 import java.io.ByteArrayOutputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -21,8 +28,12 @@ import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -40,7 +51,7 @@ public class Utils {
     private static Context sContext;
 
     //SDK版本号
-    public static String FQSDK_VERSION = "1.0.8";
+    public static String FQSDK_VERSION = "2.0.0";
 
     /**
      * 初始化 Utils
@@ -337,4 +348,57 @@ public class Utils {
         return obj;
     }
 
+    /**
+     * 获取IP地址
+     * <p>需添加权限 {@code <uses-permission android:name="android.permission.INTERNET"/>}</p>
+     *
+     * @param useIPv4 是否用IPv4
+     * @return IP地址
+     */
+    public static String getIPAddress(final boolean useIPv4) {
+        try {
+            for (Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces(); nis.hasMoreElements(); ) {
+                NetworkInterface ni = nis.nextElement();
+                // 防止小米手机返回10.0.2.15
+                if (!ni.isUp()) continue;
+                for (Enumeration<InetAddress> addresses = ni.getInetAddresses(); addresses.hasMoreElements(); ) {
+                    InetAddress inetAddress = addresses.nextElement();
+                    if (!inetAddress.isLoopbackAddress()) {
+                        String hostAddress = inetAddress.getHostAddress();
+                        boolean isIPv4 = hostAddress.indexOf(':') < 0;
+                        if (useIPv4) {
+                            if (isIPv4) return hostAddress;
+                        } else {
+                            if (!isIPv4) {
+                                int index = hostAddress.indexOf('%');
+                                return index < 0 ? hostAddress.toUpperCase() : hostAddress.substring(0, index).toUpperCase();
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (SocketException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+    /**
+     * 判断是否是WiFi链接
+     * <p>需添加权限 {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>}</p>
+     *
+     * @return
+     */
+    public static boolean isWifi() {
+
+        ConnectivityManager manager = (ConnectivityManager) Utils.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo info = manager.getActiveNetworkInfo();
+        if (info != null
+                && ConnectivityManager.TYPE_WIFI == info.getType()) {
+            return true;
+        }
+        return false;
+    }
+
 }

+ 165 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/DefaultDateTypeAdapter.java

@@ -0,0 +1,165 @@
+/*
+ * 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.fq.threelib.gson;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import com.fq.threelib.gson.internal.JavaVersion;
+import com.fq.threelib.gson.internal.PreJava9DateFormatProvider;
+import com.fq.threelib.gson.internal.bind.util.ISO8601Utils;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.gson.stream.JsonWriter;
+
+/**
+ * This type adapter supports three subclasses of date: Date, Timestamp, and
+ * java.sql.Date.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+final class DefaultDateTypeAdapter extends TypeAdapter<Date> {
+
+  private static final String SIMPLE_NAME = "DefaultDateTypeAdapter";
+
+  private final Class<? extends Date> dateType;
+
+  /**
+   * List of 1 or more different date formats used for de-serialization attempts.
+   * The first of them is used for serialization as well.
+   */
+  private final List<DateFormat> dateFormats = new ArrayList<DateFormat>();
+
+  DefaultDateTypeAdapter(Class<? extends Date> dateType) {
+    this.dateType = verifyDateType(dateType);
+    dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US));
+    if (!Locale.getDefault().equals(Locale.US)) {
+      dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT));
+    }
+    if (JavaVersion.isJava9OrLater()) {
+      dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(DateFormat.DEFAULT, DateFormat.DEFAULT));
+    }
+  }
+
+  DefaultDateTypeAdapter(Class<? extends Date> dateType, String datePattern) {
+    this.dateType = verifyDateType(dateType);
+    dateFormats.add(new SimpleDateFormat(datePattern, Locale.US));
+    if (!Locale.getDefault().equals(Locale.US)) {
+      dateFormats.add(new SimpleDateFormat(datePattern));
+    }
+  }
+
+  DefaultDateTypeAdapter(Class<? extends Date> dateType, int style) {
+    this.dateType = verifyDateType(dateType);
+    dateFormats.add(DateFormat.getDateInstance(style, Locale.US));
+    if (!Locale.getDefault().equals(Locale.US)) {
+      dateFormats.add(DateFormat.getDateInstance(style));
+    }
+    if (JavaVersion.isJava9OrLater()) {
+      dateFormats.add(PreJava9DateFormatProvider.getUSDateFormat(style));
+    }
+  }
+
+  public DefaultDateTypeAdapter(int dateStyle, int timeStyle) {
+    this(Date.class, dateStyle, timeStyle);
+  }
+
+  public DefaultDateTypeAdapter(Class<? extends Date> dateType, int dateStyle, int timeStyle) {
+    this.dateType = verifyDateType(dateType);
+    dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US));
+    if (!Locale.getDefault().equals(Locale.US)) {
+      dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle));
+    }
+    if (JavaVersion.isJava9OrLater()) {
+      dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(dateStyle, timeStyle));
+    }
+  }
+
+  private static Class<? extends Date> verifyDateType(Class<? extends Date> dateType) {
+    if ( dateType != Date.class && dateType != java.sql.Date.class && dateType != Timestamp.class ) {
+      throw new IllegalArgumentException("Date type must be one of " + Date.class + ", " + Timestamp.class + ", or " + java.sql.Date.class + " but was " + dateType);
+    }
+    return dateType;
+  }
+
+  // These methods need to be synchronized since JDK DateFormat classes are not thread-safe
+  // See issue 162
+  @Override
+  public void write(JsonWriter out, Date value) throws IOException {
+    if (value == null) {
+      out.nullValue();
+      return;
+    }
+    synchronized(dateFormats) {
+      String dateFormatAsString = dateFormats.get(0).format(value);
+      out.value(dateFormatAsString);
+    }
+  }
+
+  @Override
+  public Date read(JsonReader in) throws IOException {
+    if (in.peek() == JsonToken.NULL) {
+      in.nextNull();
+      return null;
+    }
+    Date date = deserializeToDate(in.nextString());
+    if (dateType == Date.class) {
+      return date;
+    } else if (dateType == Timestamp.class) {
+      return new Timestamp(date.getTime());
+    } else if (dateType == java.sql.Date.class) {
+      return new java.sql.Date(date.getTime());
+    } else {
+      // This must never happen: dateType is guarded in the primary constructor
+      throw new AssertionError();
+    }
+  }
+
+  private Date deserializeToDate(String s) {
+    synchronized (dateFormats) {
+      for (DateFormat dateFormat : dateFormats) {
+        try {
+          return dateFormat.parse(s);
+        } catch (ParseException ignored) {}
+      }
+      try {
+        return ISO8601Utils.parse(s, new ParsePosition(0));
+      } catch (ParseException e) {
+        throw new JsonSyntaxException(s, e);
+      }
+    }
+  }
+
+  @Override
+  public String toString() {
+    DateFormat defaultFormat = dateFormats.get(0);
+    if (defaultFormat instanceof SimpleDateFormat) {
+      return SIMPLE_NAME + '(' + ((SimpleDateFormat) defaultFormat).toPattern() + ')';
+    } else {
+      return SIMPLE_NAME + '(' + defaultFormat.getClass().getSimpleName() + ')';
+    }
+  }
+}

+ 109 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/ExclusionStrategy.java

@@ -0,0 +1,109 @@
+/*
+ * 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.fq.threelib.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 true then that class or field type
+ * will not be part of the JSON output. For deserialization, if {@link #shouldSkipClass(Class)}
+ * returns true, 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);
+}

+ 161 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/FieldAttributes.java

@@ -0,0 +1,161 @@
+/*
+ * 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.fq.threelib.gson;
+
+import com.fq.threelib.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 listParameterizedType = 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;
+  }
+
+  /**
+   * Returns the value of the field represented by this {@code Field}, on
+   * the specified object. The value is automatically wrapped in an
+   * object if it has a primitive type.
+   *
+   * @return the value of the represented field in object
+   * {@code obj}; primitive values are wrapped in an appropriate
+   * object before being returned
+   * @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();
+  }
+}

+ 178 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/FieldNamingPolicy.java

@@ -0,0 +1,178 @@
+/*
+ * 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.fq.threelib.gson;
+
+import java.lang.reflect.Field;
+import java.util.Locale;
+
+/**
+ * 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(Locale.ENGLISH);
+    }
+  },
+
+  /**
+   * 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(Locale.ENGLISH);
+    }
+  },
+
+  /**
+   * 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 dot (.).
+   *
+   * <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 dots in JavaScript is not recommended since dot is also used for a member sign in
+   * expressions. This requires that a field named with dots 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 2.8
+   */
+  LOWER_CASE_WITH_DOTS() {
+    @Override public String translateName(Field f) {
+      return separateCamelCase(f.getName(), ".").toLowerCase(Locale.ENGLISH);
+    }
+  };
+
+  /**
+   * Converts the field name that uses camel-case define word separation into
+   * separate words that are separated by the provided {@code separatorString}.
+   */
+  static String separateCamelCase(String name, String separator) {
+    StringBuilder translation = new StringBuilder();
+    for (int i = 0, length = name.length(); i < 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.
+   */
+  static String upperCaseFirstLetter(String name) {
+    int firstLetterIndex = 0;
+    int limit = name.length() - 1;
+    for(; !Character.isLetter(name.charAt(firstLetterIndex)) && firstLetterIndex < limit; ++firstLetterIndex);
+
+    char firstLetter = name.charAt(firstLetterIndex);
+    if(Character.isUpperCase(firstLetter)) { //The letter is already uppercased, return the original
+      return name;
+    }
+
+    char uppercased = Character.toUpperCase(firstLetter);
+    if(firstLetterIndex == 0) { //First character in the string is the first letter, saves 1 substring
+      return uppercased + name.substring(1);
+    }
+
+    return name.substring(0, firstLetterIndex) + uppercased + name.substring(firstLetterIndex + 1);
+  }
+}

+ 40 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/FieldNamingStrategy.java

@@ -0,0 +1,40 @@
+/*
+ * 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.fq.threelib.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);
+}

Datei-Diff unterdrückt, da er zu groß ist
+ 1041 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/Gson.java


+ 627 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/GsonBuilder.java

@@ -0,0 +1,627 @@
+/*
+ * 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.fq.threelib.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.fq.threelib.gson.internal.$Gson$Preconditions;
+import com.fq.threelib.gson.internal.Excluder;
+import com.fq.threelib.gson.internal.bind.TreeTypeAdapter;
+import com.fq.threelib.gson.internal.bind.TypeAdapters;
+import com.fq.threelib.gson.reflect.TypeToken;
+import com.fq.threelib.gson.stream.JsonReader;
+
+import static com.fq.threelib.gson.Gson.DEFAULT_COMPLEX_MAP_KEYS;
+import static com.fq.threelib.gson.Gson.DEFAULT_ESCAPE_HTML;
+import static com.fq.threelib.gson.Gson.DEFAULT_JSON_NON_EXECUTABLE;
+import static com.fq.threelib.gson.Gson.DEFAULT_LENIENT;
+import static com.fq.threelib.gson.Gson.DEFAULT_PRETTY_PRINT;
+import static com.fq.threelib.gson.Gson.DEFAULT_SERIALIZE_NULLS;
+import static com.fq.threelib.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;
+
+/**
+ * <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 = DEFAULT_SERIALIZE_NULLS;
+  private String datePattern;
+  private int dateStyle = DateFormat.DEFAULT;
+  private int timeStyle = DateFormat.DEFAULT;
+  private boolean complexMapKeySerialization = DEFAULT_COMPLEX_MAP_KEYS;
+  private boolean serializeSpecialFloatingPointValues = DEFAULT_SPECIALIZE_FLOAT_VALUES;
+  private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML;
+  private boolean prettyPrinting = DEFAULT_PRETTY_PRINT;
+  private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE;
+  private boolean lenient = DEFAULT_LENIENT;
+
+  /**
+   * 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() {
+  }
+
+  /**
+   * Constructs a GsonBuilder instance from a Gson instance. The newly constructed GsonBuilder
+   * has the same configuration as the previously built Gson instance.
+   *
+   * @param gson the gson instance whose configuration should by applied to a new GsonBuilder.
+   */
+  GsonBuilder(Gson gson) {
+    this.excluder = gson.excluder;
+    this.fieldNamingPolicy = gson.fieldNamingStrategy;
+    this.instanceCreators.putAll(gson.instanceCreators);
+    this.serializeNulls = gson.serializeNulls;
+    this.complexMapKeySerialization = gson.complexMapKeySerialization;
+    this.generateNonExecutableJson = gson.generateNonExecutableJson;
+    this.escapeHtmlChars = gson.htmlSafe;
+    this.prettyPrinting = gson.prettyPrinting;
+    this.lenient = gson.lenient;
+    this.serializeSpecialFloatingPointValues = gson.serializeSpecialFloatingPointValues;
+    this.longSerializationPolicy = gson.longSerializationPolicy;
+    this.datePattern = gson.datePattern;
+    this.dateStyle = gson.dateStyle;
+    this.timeStyle = gson.timeStyle;
+    this.factories.addAll(gson.builderFactories);
+    this.hierarchyFactories.addAll(gson.builderHierarchyFactories);
+  }
+
+  /**
+   * 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.fq.threelib.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 serialization/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 is strict and only accepts JSON as specified by
+   * <a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. This option makes the parser
+   * liberal in what it accepts.
+   *
+   * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+   * @see JsonReader#setLenient(boolean)
+   */
+  public GsonBuilder setLenient() {
+    lenient = 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 Date}, {@link
+   * 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 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 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(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>(this.factories.size() + this.hierarchyFactories.size() + 3);
+    factories.addAll(this.factories);
+    Collections.reverse(factories);
+
+    List<TypeAdapterFactory> hierarchyFactories = new ArrayList<TypeAdapterFactory>(this.hierarchyFactories);
+    Collections.reverse(hierarchyFactories);
+    factories.addAll(hierarchyFactories);
+
+    addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
+
+    return new Gson(excluder, fieldNamingPolicy, instanceCreators,
+        serializeNulls, complexMapKeySerialization,
+        generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
+        serializeSpecialFloatingPointValues, longSerializationPolicy,
+        datePattern, dateStyle, timeStyle,
+        this.factories, this.hierarchyFactories, factories);
+  }
+
+  @SuppressWarnings("unchecked")
+  private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
+      List<TypeAdapterFactory> factories) {
+    DefaultDateTypeAdapter dateTypeAdapter;
+    TypeAdapter<Timestamp> timestampTypeAdapter;
+    TypeAdapter<java.sql.Date> javaSqlDateTypeAdapter;
+    if (datePattern != null && !"".equals(datePattern.trim())) {
+      dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, datePattern);
+      timestampTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(Timestamp.class, datePattern);
+      javaSqlDateTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(java.sql.Date.class, datePattern);
+    } else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) {
+      dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, dateStyle, timeStyle);
+      timestampTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(Timestamp.class, dateStyle, timeStyle);
+      javaSqlDateTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(java.sql.Date.class, dateStyle, timeStyle);
+    } else {
+      return;
+    }
+
+    factories.add(TypeAdapters.newFactory(Date.class, dateTypeAdapter));
+    factories.add(TypeAdapters.newFactory(Timestamp.class, timestampTypeAdapter));
+    factories.add(TypeAdapters.newFactory(java.sql.Date.class, javaSqlDateTypeAdapter));
+  }
+}

+ 92 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/InstanceCreator.java

@@ -0,0 +1,92 @@
+/*
+ * 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.fq.threelib.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);
+}

+ 384 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonArray.java

@@ -0,0 +1,384 @@
+/*
+ * 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.fq.threelib.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>();
+  }
+  
+  public JsonArray(int capacity) {
+    elements = new ArrayList<JsonElement>(capacity);
+  }
+
+  /**
+   * Creates a deep copy of this element and all its children
+   * @since 2.8.2
+   */
+  @Override
+  public JsonArray deepCopy() {
+    if (!elements.isEmpty()) {
+      JsonArray result = new JsonArray(elements.size());
+      for (JsonElement element : elements) {
+        result.add(element.deepCopy());
+      }
+      return result;
+    }
+    return new JsonArray();
+  }
+
+  /**
+   * Adds the specified boolean to self.
+   *
+   * @param bool the boolean that needs to be added to the array.
+   */
+  public void add(Boolean bool) {
+    elements.add(bool == null ? JsonNull.INSTANCE : new JsonPrimitive(bool));
+  }
+
+  /**
+   * Adds the specified character to self.
+   *
+   * @param character the character that needs to be added to the array.
+   */
+  public void add(Character character) {
+    elements.add(character == null ? JsonNull.INSTANCE : new JsonPrimitive(character));
+  }
+
+  /**
+   * Adds the specified number to self.
+   *
+   * @param number the number that needs to be added to the array.
+   */
+  public void add(Number number) {
+    elements.add(number == null ? JsonNull.INSTANCE : new JsonPrimitive(number));
+  }
+
+  /**
+   * Adds the specified string to self.
+   *
+   * @param string the string that needs to be added to the array.
+   */
+  public void add(String string) {
+    elements.add(string == null ? JsonNull.INSTANCE : new JsonPrimitive(string));
+  }
+
+  /**
+   * 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);
+  }
+
+  /**
+   * Replaces the element at the specified position in this array with the specified element.
+   *   Element can be null.
+   * @param index index of the element to replace
+   * @param element element to be stored at the specified position
+   * @return the element previously at the specified position
+   * @throws IndexOutOfBoundsException if the specified index is outside the array bounds
+   */
+  public JsonElement set(int index, JsonElement element) {
+    return elements.set(index, element);
+  }
+
+  /**
+   * Removes the first occurrence of the specified element from this array, if it is present.
+   * If the array does not contain the element, it is unchanged.
+   * @param element element to be removed from this array, if present
+   * @return true if this array contained the specified element, false otherwise
+   * @since 2.3
+   */
+  public boolean remove(JsonElement element) {
+    return elements.remove(element);
+  }
+
+  /**
+   * Removes the element at the specified position in this array. Shifts any subsequent elements
+   * to the left (subtracts one from their indices). Returns the element that was removed from
+   * the array.
+   * @param index index the index of the element to be removed
+   * @return the element previously at the specified position
+   * @throws IndexOutOfBoundsException if the specified index is outside the array bounds
+   * @since 2.3
+   */
+  public JsonElement remove(int index) {
+    return elements.remove(index);
+  }
+
+  /**
+   * Returns true if this array contains the specified element.
+   * @return true if this array contains the specified element.
+   * @param element whose presence in this array is to be tested
+   * @since 2.3
+   */
+  public boolean contains(JsonElement element) {
+    return elements.contains(element);
+  }
+
+  /**
+   * 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 elements 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.
+   */
+  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();
+  }
+}

+ 44 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonDeserializationContext.java

@@ -0,0 +1,44 @@
+/*
+ * 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.fq.threelib.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;
+}

+ 91 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonDeserializer.java

@@ -0,0 +1,91 @@
+/*
+ * 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.fq.threelib.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;
+}

+ 322 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonElement.java

@@ -0,0 +1,322 @@
+/*
+ * 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.fq.threelib.gson;
+
+import com.fq.threelib.gson.internal.Streams;
+import com.fq.threelib.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.
+   * @since 2.8.2
+   */
+  public 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 IllegalStateException} 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 IllegalStateException} 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("Not a JSON Array: " + this);
+  }
+
+  /**
+   * convenience method to get this element as a {@link JsonPrimitive}. If the element is of some
+   * other type, a {@link IllegalStateException} 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("Not a JSON Primitive: " + this);
+  }
+
+  /**
+   * convenience method to get this element as a {@link JsonNull}. If the element is of some
+   * other type, a {@link IllegalStateException} 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("Not a JSON Null: " + this);
+  }
+
+  /**
+   * 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 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 the first character of this element as a string or the first
+   * character of this array's first element as a string.
+   *
+   * @return the first character of the string.
+   * @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.
+   * @since 1.3
+   * @deprecated This method is misleading, as it does not get this element as a char but rather as
+   * a string's first character.
+   */
+  @Deprecated
+  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);
+    }
+  }
+}

+ 45 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonIOException.java

@@ -0,0 +1,45 @@
+/*
+ * 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.fq.threelib.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);
+  }
+}

+ 67 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonNull.java

@@ -0,0 +1,67 @@
+/*
+ * 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.fq.threelib.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
+  }
+
+  /**
+   * Returns the same instance since it is an immutable value
+   * @since 2.8.2
+   */
+  @Override
+  public 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;
+  }
+}

+ 205 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonObject.java

@@ -0,0 +1,205 @@
+/*
+ * 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.fq.threelib.gson;
+
+import com.fq.threelib.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>();
+
+  /**
+   * Creates a deep copy of this element and all its children
+   * @since 2.8.2
+   */
+  @Override
+  public 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) {
+    members.put(property, value == null ? JsonNull.INSTANCE : 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, value == null ? JsonNull.INSTANCE : new JsonPrimitive(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, value == null ? JsonNull.INSTANCE : new JsonPrimitive(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, value == null ? JsonNull.INSTANCE : new JsonPrimitive(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, 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();
+  }
+
+  /**
+   * Returns a set of members key values.
+   *
+   * @return a set of member keys as Strings
+   * @since 2.8.1
+   */
+  public Set<String> keySet() {
+    return members.keySet();
+  }
+
+  /**
+   * Returns the number of key/value pairs in the object.
+   *
+   * @return the number of key/value pairs in the object.
+   */
+  public int size() {
+    return members.size();
+  }
+
+  /**
+   * 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();
+  }
+}

+ 64 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonParseException.java

@@ -0,0 +1,64 @@
+/*
+ * 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.fq.threelib.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);
+  }
+}

+ 112 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonParser.java

@@ -0,0 +1,112 @@
+/*
+ * 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.fq.threelib.gson;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+import com.fq.threelib.gson.internal.Streams;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.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 {
+  /** @deprecated No need to instantiate this class, use the static methods instead. */
+  @Deprecated
+  public 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
+   */
+  public static JsonElement parseString(String json) throws JsonSyntaxException {
+    return parseReader(new StringReader(json));
+  }
+
+  /**
+   * Parses the specified JSON string into a parse tree
+   *
+   * @param reader 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
+   */
+  public static JsonElement parseReader(Reader reader) throws JsonIOException, JsonSyntaxException {
+    try {
+      JsonReader jsonReader = new JsonReader(reader);
+      JsonElement element = parseReader(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
+   */
+  public static JsonElement parseReader(JsonReader reader)
+      throws JsonIOException, JsonSyntaxException {
+    boolean lenient = reader.isLenient();
+    reader.setLenient(true);
+    try {
+      return Streams.parse(reader);
+    } catch (StackOverflowError e) {
+      throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
+    } catch (OutOfMemoryError e) {
+      throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
+    } finally {
+      reader.setLenient(lenient);
+    }
+  }
+
+  /** @deprecated Use {@link JsonParser#parseString} */
+  @Deprecated
+  public JsonElement parse(String json) throws JsonSyntaxException {
+    return parseString(json);
+  }
+
+  /** @deprecated Use {@link JsonParser#parseReader(Reader)} */
+  @Deprecated
+  public JsonElement parse(Reader json) throws JsonIOException, JsonSyntaxException {
+    return parseReader(json);
+  }
+
+  /** @deprecated Use {@link JsonParser#parseReader(JsonReader)} */
+  @Deprecated
+  public JsonElement parse(JsonReader json) throws JsonIOException, JsonSyntaxException {
+    return parseReader(json);
+  }
+}

+ 295 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonPrimitive.java

@@ -0,0 +1,295 @@
+/*
+ * 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.fq.threelib.gson;
+
+import com.fq.threelib.gson.internal.$Gson$Preconditions;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.fq.threelib.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 final Object value;
+
+  /**
+   * Create a primitive containing a boolean value.
+   *
+   * @param bool the value to create the primitive with.
+   */
+  public JsonPrimitive(Boolean bool) {
+    value = $Gson$Preconditions.checkNotNull(bool);
+  }
+
+  /**
+   * Create a primitive containing a {@link Number}.
+   *
+   * @param number the value to create the primitive with.
+   */
+  public JsonPrimitive(Number number) {
+    value = $Gson$Preconditions.checkNotNull(number);
+  }
+
+  /**
+   * Create a primitive containing a String value.
+   *
+   * @param string the value to create the primitive with.
+   */
+  public JsonPrimitive(String string) {
+    value = $Gson$Preconditions.checkNotNull(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) {
+    // convert characters to strings since in JSON, characters are represented as a single
+    // character string
+    value = $Gson$Preconditions.checkNotNull(c).toString();
+  }
+
+  /**
+   * Returns the same value as primitives are immutable.
+   * @since 2.8.2
+   */
+  @Override
+  public JsonPrimitive deepCopy() {
+    return this;
+  }
+
+  /**
+   * 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 boolean value.
+   *
+   * @return get this element as a primitive boolean value.
+   */
+  @Override
+  public boolean getAsBoolean() {
+    if (isBoolean()) {
+      return ((Boolean) value).booleanValue();
+    }
+	// 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 ((Boolean) value).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);
+  }
+
+  @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;
+  }
+}

+ 49 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonSerializationContext.java

@@ -0,0 +1,49 @@
+/*
+ * 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.fq.threelib.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);
+}

+ 89 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonSerializer.java

@@ -0,0 +1,89 @@
+/*
+ * 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.fq.threelib.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);
+}

+ 122 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonStreamParser.java

@@ -0,0 +1,122 @@
+/*
+ * 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.fq.threelib.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.fq.threelib.gson.internal.Streams;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.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
+   */
+  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
+   */
+  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
+   */
+  public void remove() {
+    throw new UnsupportedOperationException();
+  }
+}

+ 47 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/JsonSyntaxException.java

@@ -0,0 +1,47 @@
+/*
+ * 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.fq.threelib.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);
+  }
+}

+ 58 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/LongSerializationPolicy.java

@@ -0,0 +1,58 @@
+/*
+ * 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.fq.threelib.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);
+}

+ 290 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/TypeAdapter.java

@@ -0,0 +1,290 @@
+/*
+ * 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.fq.threelib.gson;
+
+import com.fq.threelib.gson.internal.bind.JsonTreeWriter;
+import com.fq.threelib.gson.internal.bind.JsonTreeReader;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.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) {
+    StringWriter stringWriter = new StringWriter();
+    try {
+      toJson(stringWriter, value);
+    } catch (IOException e) {
+      throw new AssertionError(e); // No I/O writing to a StringWriter.
+    }
+    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(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);
+    }
+  }
+}

+ 170 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/TypeAdapterFactory.java

@@ -0,0 +1,170 @@
+/*
+ * 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.fq.threelib.gson;
+
+import com.fq.threelib.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 TypeAdapterFactory {
+ *     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 TypeAdapterFactory {
+ *     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);
+}

+ 81 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/Expose.java

@@ -0,0 +1,81 @@
+/*
+ * 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.fq.threelib.gson.annotations;
+
+import java.lang.annotation.Documented;
+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
+ */
+@Documented
+@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;
+}

+ 104 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/JsonAdapter.java

@@ -0,0 +1,104 @@
+/*
+ * 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.fq.threelib.gson.annotations;
+
+import com.fq.threelib.gson.JsonDeserializer;
+import com.fq.threelib.gson.JsonSerializer;
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.TypeAdapterFactory;
+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 Gson {@link TypeAdapter} to use with a class
+ * or field.
+ *
+ * <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>
+ *
+ * <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>
+ *
+ * It's possible to specify different type adapters on a field, that
+ * field's type, and in the {@link com.fq.threelib.gson.GsonBuilder}. Field
+ * annotations take precedence over {@code GsonBuilder}-registered type
+ * adapters, which in turn take precedence over annotated types.
+ *
+ * <p>The class referenced by this annotation must be either a {@link
+ * TypeAdapter} or a {@link TypeAdapterFactory}, or must implement one
+ * or both of {@link JsonDeserializer} or {@link JsonSerializer}. 
+ * Using {@link TypeAdapterFactory} makes it possible to delegate 
+ * to the enclosing {@code Gson} instance.
+ *
+ * @since 2.3
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ * @author Jesse Wilson
+ */
+// Note that the above example is taken from AdaptAnnotationTest.
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.FIELD})
+public @interface JsonAdapter {
+
+  /** Either a {@link TypeAdapter} or {@link TypeAdapterFactory}, or one or both of {@link JsonDeserializer} or {@link JsonSerializer}. */
+  Class<?> value();
+
+  /** false, to be able to handle {@code null} values within the adapter, default value is true. */
+  boolean nullSafe() default true;
+
+}

+ 93 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/SerializedName.java

@@ -0,0 +1,93 @@
+/*
+ * 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.fq.threelib.gson.annotations;
+
+import java.lang.annotation.Documented;
+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 MyClass {
+ *   &#64SerializedName("name") String a;
+ *   &#64SerializedName(value="name1", alternate={"name2", "name3"}) String b;
+ *   String c;
+ *
+ *   public MyClass(String a, String b, String c) {
+ *     this.a = a;
+ *     this.b = b;
+ *     this.c = c;
+ *   }
+ * }
+ * </pre>
+ *
+ * <p>The following shows the output that is generated when serializing an instance of the
+ * above example class:</p>
+ * <pre>
+ * MyClass target = new MyClass("v1", "v2", "v3");
+ * Gson gson = new Gson();
+ * String json = gson.toJson(target);
+ * System.out.println(json);
+ *
+ * ===== OUTPUT =====
+ * {"name":"v1","name1":"v2","c":"v3"}
+ * </pre>
+ *
+ * <p>NOTE: The value you specify in this annotation must be a valid JSON field name.</p>
+ * While deserializing, all values specified in the annotation will be deserialized into the field.
+ * For example:
+ * <pre>
+ *   MyClass target = gson.fromJson("{'name1':'v1'}", MyClass.class);
+ *   assertEquals("v1", target.b);
+ *   target = gson.fromJson("{'name2':'v2'}", MyClass.class);
+ *   assertEquals("v2", target.b);
+ *   target = gson.fromJson("{'name3':'v3'}", MyClass.class);
+ *   assertEquals("v3", target.b);
+ * </pre>
+ * Note that MyClass.b is now deserialized from either name1, name2 or name3.
+ *
+ * @see com.google.gson.FieldNamingPolicy
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface SerializedName {
+
+  /**
+   * @return the desired name of the field when it is serialized or deserialized
+   */
+  String value();
+  /**
+   * @return the alternative names of the field when it is deserialized
+   */
+  String[] alternate() default {};
+}

+ 63 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/Since.java

@@ -0,0 +1,63 @@
+/*
+ * 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.fq.threelib.gson.annotations;
+
+import java.lang.annotation.Documented;
+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
+ */
+@Documented
+@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();
+}

+ 68 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/Until.java

@@ -0,0 +1,68 @@
+/*
+ * 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.fq.threelib.gson.annotations;
+
+import java.lang.annotation.Documented;
+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
+ */
+@Documented
+@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();
+}

+ 6 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/annotations/package-info.java

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

+ 49 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/$Gson$Preconditions.java

@@ -0,0 +1,49 @@
+/*
+ * 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.fq.threelib.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 {
+  private $Gson$Preconditions() {
+    throw new UnsupportedOperationException();
+  }
+
+  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();
+    }
+  }
+}

+ 610 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/$Gson$Types.java

@@ -0,0 +1,610 @@
+/**
+ * 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.fq.threelib.gson.internal;
+
+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.*;
+
+import static com.fq.threelib.gson.internal.$Gson$Preconditions.checkArgument;
+import static com.fq.threelib.gson.internal.$Gson$Preconditions.checkNotNull;
+
+/**
+ * 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() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Returns a new parameterized type, applying {@code typeArguments} to
+   * {@code rawType} and enclosed by {@code ownerType}.
+   *
+   * @return a {@link 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 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) {
+    Type[] upperBounds;
+    if (bound instanceof WildcardType) {
+      upperBounds = ((WildcardType) bound).getUpperBounds();
+    } else {
+      upperBounds = new Type[] { bound };
+    }
+    return new WildcardTypeImpl(upperBounds, 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) {
+    Type[] lowerBounds;
+    if (bound instanceof WildcardType) {
+      lowerBounds = ((WildcardType) bound).getLowerBounds();
+    } else {
+      lowerBounds = new Type[] { bound };
+    }
+    return new WildcardTypeImpl(new Type[] { Object.class }, lowerBounds);
+  }
+
+  /**
+   * Returns a type that is functionally equal but not necessarily equal
+   * according to {@link Object#equals(Object) Object.equals()}. The returned
+   * type is {@link 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;
+    }
+  }
+
+  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) {
+    if (context instanceof WildcardType) {
+      // wildcards are useless for resolving supertypes. As the upper bound has the same raw type, use it instead
+      context = ((WildcardType)context).getUpperBounds()[0];
+    }
+    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) {
+    return resolve(context, contextRawType, toResolve, new HashSet<TypeVariable>());
+  }
+
+  private static Type resolve(Type context, Class<?> contextRawType, Type toResolve,
+                              Collection<TypeVariable> visitedTypeVariables) {
+    // 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;
+        if (visitedTypeVariables.contains(typeVariable)) {
+          // cannot reduce due to infinite recursion
+          return toResolve;
+        } else {
+          visitedTypeVariables.add(typeVariable);
+        }
+        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, visitedTypeVariables);
+        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, visitedTypeVariables);
+        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, visitedTypeVariables);
+        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], visitedTypeVariables);
+          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], visitedTypeVariables);
+          if (lowerBound != originalLowerBound[0]) {
+            return supertypeOf(lowerBound);
+          }
+        } else if (originalUpperBound.length == 1) {
+          Type upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);
+          if (upperBound != originalUpperBound[0]) {
+            return subtypeOf(upperBound);
+          }
+        }
+        return original;
+
+      } else {
+        return toResolve;
+      }
+    }
+  }
+
+  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, length = array.length; i < 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;
+  }
+
+  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, length = this.typeArguments.length; t < length; t++) {
+        checkNotNull(this.typeArguments[t]);
+        checkNotPrimitive(this.typeArguments[t]);
+        this.typeArguments[t] = canonicalize(this.typeArguments[t]);
+      }
+    }
+
+    public Type[] getActualTypeArguments() {
+      return typeArguments.clone();
+    }
+
+    public Type getRawType() {
+      return rawType;
+    }
+
+    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() {
+      int length = typeArguments.length;
+      if (length == 0) {
+        return typeToString(rawType);
+      }
+
+      StringBuilder stringBuilder = new StringBuilder(30 * (length + 1));
+      stringBuilder.append(typeToString(rawType)).append("<").append(typeToString(typeArguments[0]));
+      for (int i = 1; i < 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);
+    }
+
+    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]);
+      }
+    }
+
+    public Type[] getUpperBounds() {
+      return new Type[] { upperBound };
+    }
+
+    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;
+  }
+}

+ 238 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/ConstructorConstructor.java

@@ -0,0 +1,238 @@
+/*
+ * 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.fq.threelib.gson.internal;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+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;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+
+import com.fq.threelib.gson.InstanceCreator;
+import com.fq.threelib.gson.JsonIOException;
+import com.fq.threelib.gson.internal.reflect.ReflectionAccessor;
+import com.fq.threelib.gson.reflect.TypeToken;
+
+/**
+ * Returns a function that can construct an instance of a requested type.
+ */
+public final class ConstructorConstructor {
+  private final Map<Type, InstanceCreator<?>> instanceCreators;
+  private final ReflectionAccessor accessor = ReflectionAccessor.getInstance();
+
+  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()) {
+        accessor.makeAccessible(constructor);
+      }
+      return new ObjectConstructor<T>() {
+        @SuppressWarnings("unchecked") // T is the same raw type as is requested
+        @Override 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
+   * subtypes.
+   */
+  @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>() {
+          @SuppressWarnings("rawtypes")
+          @Override 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 ArrayDeque<Object>();
+          }
+        };
+      } else {
+        return new ObjectConstructor<T>() {
+          @Override public T construct() {
+            return (T) new ArrayList<Object>();
+          }
+        };
+      }
+    }
+
+    if (Map.class.isAssignableFrom(rawType)) {
+      if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) {
+        return new ObjectConstructor<T>() {
+          @Override public T construct() {
+            return (T) new ConcurrentSkipListMap<Object, Object>();
+          }
+        };
+      } else if (ConcurrentMap.class.isAssignableFrom(rawType)) {
+        return new ObjectConstructor<T>() {
+          @Override public T construct() {
+            return (T) new ConcurrentHashMap<Object, Object>();
+          }
+        };
+      } else 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();
+      @SuppressWarnings("unchecked")
+      @Override 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 + ". "
+              + "Registering an InstanceCreator with Gson for this type may fix this problem."), e);
+        }
+      }
+    };
+  }
+
+  @Override public String toString() {
+    return instanceCreators.toString();
+  }
+}

+ 260 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/Excluder.java

@@ -0,0 +1,260 @@
+/*
+ * 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.fq.threelib.gson.internal;
+
+import com.fq.threelib.gson.ExclusionStrategy;
+import com.fq.threelib.gson.FieldAttributes;
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.TypeAdapterFactory;
+import com.fq.threelib.gson.annotations.Expose;
+import com.fq.threelib.gson.annotations.Since;
+import com.fq.threelib.gson.annotations.Until;
+import com.fq.threelib.gson.reflect.TypeToken;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.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(e);
+    }
+  }
+
+  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;
+  }
+
+  public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
+    Class<?> rawType = type.getRawType();
+    boolean excludeClass = excludeClassChecks(rawType);
+
+    final boolean skipSerialize = excludeClass || excludeClassInStrategy(rawType, true);
+    final boolean skipDeserialize = excludeClass ||  excludeClassInStrategy(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;
+  }
+
+  private boolean excludeClassChecks(Class<?> clazz) {
+      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;
+      }
+
+      return false;
+  }
+
+  public boolean excludeClass(Class<?> clazz, boolean serialize) {
+      return excludeClassChecks(clazz) ||
+              excludeClassInStrategy(clazz, serialize);
+  }
+
+  private boolean excludeClassInStrategy(Class<?> clazz, boolean serialize) {
+      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;
+  }
+}

+ 32 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/GsonBuildConfig.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Gson authors
+ *
+ * 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.fq.threelib.gson.internal;
+
+/**
+ * Build configuration for Gson. This file is automatically populated by
+ * templating-maven-plugin and .java/.class files are generated for use in Gson.
+ *
+ * @author Inderjeet Singh
+ */
+public final class GsonBuildConfig {
+  // Based on https://stackoverflow.com/questions/2469922/generate-a-version-java-file-in-maven
+
+  /** This field is automatically populated by Maven when a build is triggered */
+  public static final String VERSION = "${project.version}";
+
+  private GsonBuildConfig() { }
+}

+ 92 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/JavaVersion.java

@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Gson authors
+ *
+ * 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.fq.threelib.gson.internal;
+
+/**
+ * Utility to check the major Java version of the current JVM.
+ */
+public final class JavaVersion {
+  // Oracle defines naming conventions at http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html
+  // However, many alternate implementations differ. For example, Debian used 9-debian as the version string
+
+  private static final int majorJavaVersion = determineMajorJavaVersion();
+
+  private static int determineMajorJavaVersion() {
+    String javaVersion = System.getProperty("java.version");
+    return getMajorJavaVersion(javaVersion);
+  }
+
+  // Visible for testing only
+  static int getMajorJavaVersion(String javaVersion) {
+    int version = parseDotted(javaVersion);
+    if (version == -1) {
+      version = extractBeginningInt(javaVersion);
+    }
+    if (version == -1) {
+      return 6;  // Choose minimum supported JDK version as default
+    }
+    return version;
+  }
+
+  // Parses both legacy 1.8 style and newer 9.0.4 style 
+  private static int parseDotted(String javaVersion) {
+    try {
+      String[] parts = javaVersion.split("[._]");
+      int firstVer = Integer.parseInt(parts[0]);
+      if (firstVer == 1 && parts.length > 1) {
+        return Integer.parseInt(parts[1]);
+      } else {
+        return firstVer;
+      }
+    } catch (NumberFormatException e) {
+      return -1;
+    }
+  }
+
+  private static int extractBeginningInt(String javaVersion) {
+    try {
+      StringBuilder num = new StringBuilder();
+      for (int i = 0; i < javaVersion.length(); ++i) {
+        char c = javaVersion.charAt(i);
+        if (Character.isDigit(c)) {
+          num.append(c);
+        } else {
+          break;
+        }
+      }
+      return Integer.parseInt(num.toString());
+    } catch (NumberFormatException e) {
+      return -1;
+    }
+  }
+
+  /**
+   * @return the major Java version, i.e. '8' for Java 1.8, '9' for Java 9 etc.
+   */
+  public static int getMajorJavaVersion() {
+    return majorJavaVersion;
+  }
+
+  /**
+   * @return {@code true} if the application is running on Java 9 or later; and {@code false} otherwise.
+   */
+  public static boolean isJava9OrLater() {
+    return majorJavaVersion >= 9;
+  }
+
+  private JavaVersion() { }
+}

+ 32 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/JsonReaderInternalAccess.java

@@ -0,0 +1,32 @@
+/*
+ * 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.fq.threelib.gson.internal;
+
+import com.fq.threelib.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;
+}

+ 96 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/LazilyParsedNumber.java

@@ -0,0 +1,96 @@
+/*
+ * 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.fq.threelib.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;
+
+  /** @param value must not be null */
+  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);
+  }
+
+  @Override
+  public int hashCode() {
+    return value.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj instanceof LazilyParsedNumber) {
+      LazilyParsedNumber other = (LazilyParsedNumber) obj;
+      return value == other.value || value.equals(other.value);
+    }
+    return false;
+  }
+}

+ 864 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/LinkedHashTreeMap.java

@@ -0,0 +1,864 @@
+/*
+ * 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.fq.threelib.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>() {
+    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;
+    }
+
+    public K getKey() {
+      return key;
+    }
+
+    public V getValue() {
+      return value;
+    }
+
+    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;
+
+    LinkedTreeMapIterator() {
+    }
+
+    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;
+    }
+
+    public final void remove() {
+      if (lastReturned == null) {
+        throw new IllegalStateException();
+      }
+      removeInternal(lastReturned, true);
+      lastReturned = null;
+      expectedModCount = modCount;
+    }
+  }
+
+  final 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>>() {
+        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();
+    }
+  }
+
+  final class KeySet extends AbstractSet<K> {
+    @Override public int size() {
+      return size;
+    }
+
+    @Override public Iterator<K> iterator() {
+      return new LinkedTreeMapIterator<K>() {
+        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);
+  }
+}

+ 630 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/LinkedTreeMap.java

@@ -0,0 +1,630 @@
+/*
+ * 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.fq.threelib.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>() {
+    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;
+    }
+
+    public K getKey() {
+      return key;
+    }
+
+    public V getValue() {
+      return value;
+    }
+
+    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;
+
+    LinkedTreeMapIterator() {
+    }
+
+    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;
+    }
+
+    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>>() {
+        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();
+    }
+  }
+
+  final class KeySet extends AbstractSet<K> {
+    @Override public int size() {
+      return size;
+    }
+
+    @Override public Iterator<K> iterator() {
+      return new LinkedTreeMapIterator<K>() {
+        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);
+  }
+}

+ 33 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/ObjectConstructor.java

@@ -0,0 +1,33 @@
+/*
+ * 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.fq.threelib.gson.internal;
+
+/**
+ * Defines a generic object construction factory.  The purpose of this class
+ * is to construct a default instance of a class that can be used for object
+ * navigation while deserialization from its JSON representation.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public interface ObjectConstructor<T> {
+
+  /**
+   * Returns a new instance.
+   */
+  public T construct();
+}

+ 86 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/PreJava9DateFormatProvider.java

@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Gson authors
+ *
+ * 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.fq.threelib.gson.internal;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+/**
+ * Provides DateFormats for US locale with patterns which were the default ones before Java 9.
+ */
+public class PreJava9DateFormatProvider {
+
+  /**
+   * Returns the same DateFormat as {@code DateFormat.getDateInstance(style, Locale.US)} in Java 8 or below.
+   */
+  public static DateFormat getUSDateFormat(int style) {
+    return new SimpleDateFormat(getDateFormatPattern(style), Locale.US);
+  }
+
+  /**
+   * Returns the same DateFormat as {@code DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US)}
+   * in Java 8 or below.
+   */
+  public static DateFormat getUSDateTimeFormat(int dateStyle, int timeStyle) {
+    String pattern = getDatePartOfDateTimePattern(dateStyle) + " " + getTimePartOfDateTimePattern(timeStyle);
+    return new SimpleDateFormat(pattern, Locale.US);
+  }
+
+  private static String getDateFormatPattern(int style) {
+    switch (style) {
+    case DateFormat.SHORT:
+      return "M/d/yy";
+    case DateFormat.MEDIUM:
+      return "MMM d, y";
+    case DateFormat.LONG:
+      return "MMMM d, y";
+    case DateFormat.FULL:
+      return "EEEE, MMMM d, y";
+    default:
+      throw new IllegalArgumentException("Unknown DateFormat style: " + style);
+    }
+  }
+
+  private static String getDatePartOfDateTimePattern(int dateStyle) {
+    switch (dateStyle) {
+    case DateFormat.SHORT:
+      return "M/d/yy";
+    case DateFormat.MEDIUM:
+      return "MMM d, yyyy";
+    case DateFormat.LONG:
+      return "MMMM d, yyyy";
+    case DateFormat.FULL:
+      return "EEEE, MMMM d, yyyy";
+    default:
+      throw new IllegalArgumentException("Unknown DateFormat style: " + dateStyle);
+    }
+  }
+
+  private static String getTimePartOfDateTimePattern(int timeStyle) {
+    switch (timeStyle) {
+    case DateFormat.SHORT:
+      return "h:mm a";
+    case DateFormat.MEDIUM:
+      return "h:mm:ss a";
+    case DateFormat.FULL:
+    case DateFormat.LONG:
+      return "h:mm:ss a z";
+    default:
+      throw new IllegalArgumentException("Unknown DateFormat style: " + timeStyle);
+    }
+  }
+}

+ 100 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/Primitives.java

@@ -0,0 +1,100 @@
+/*
+ * 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.fq.threelib.gson.internal;
+
+import java.lang.reflect.Type;
+
+/**
+ * Contains static utility methods pertaining to primitive types and their
+ * corresponding wrapper types.
+ *
+ * @author Kevin Bourrillion
+ */
+public final class Primitives {
+  private Primitives() {}
+
+  /**
+   * Returns true if this type is a primitive.
+   */
+  public static boolean isPrimitive(Type type) {
+    return type instanceof Class<?> && ((Class<?>) type).isPrimitive();
+  }
+
+  /**
+   * Returns {@code true} if {@code type} is one of the nine
+   * primitive-wrapper types, such as {@link Integer}.
+   *
+   * @see Class#isPrimitive
+   */
+  public static boolean isWrapperType(Type type) {
+    return type == Integer.class
+        || type == Float.class
+        || type == Byte.class
+        || type == Double.class
+        || type == Long.class
+        || type == Character.class
+        || type == Boolean.class
+        || type == Short.class
+        || type == Void.class;
+  }
+
+  /**
+   * Returns the corresponding wrapper type of {@code type} if it is a primitive
+   * type; otherwise returns {@code type} itself. Idempotent.
+   * <pre>
+   *     wrap(int.class) == Integer.class
+   *     wrap(Integer.class) == Integer.class
+   *     wrap(String.class) == String.class
+   * </pre>
+   */
+  @SuppressWarnings("unchecked")
+  public static <T> Class<T> wrap(Class<T> type) {
+    if (type == int.class) return (Class<T>) Integer.class;
+    if (type == float.class) return (Class<T>) Float.class;
+    if (type == byte.class) return (Class<T>) Byte.class;
+    if (type == double.class) return (Class<T>) Double.class;
+    if (type == long.class) return (Class<T>) Long.class;
+    if (type == char.class) return (Class<T>) Character.class;
+    if (type == boolean.class) return (Class<T>) Boolean.class;
+    if (type == short.class) return (Class<T>) Short.class;
+    if (type == void.class) return (Class<T>) Void.class;
+    return type;
+  }
+
+  /**
+   * Returns the corresponding primitive type of {@code type} if it is a
+   * wrapper type; otherwise returns {@code type} itself. Idempotent.
+   * <pre>
+   *     unwrap(Integer.class) == int.class
+   *     unwrap(int.class) == int.class
+   *     unwrap(String.class) == String.class
+   * </pre>
+   */
+  @SuppressWarnings("unchecked")
+  public static <T> Class<T> unwrap(Class<T> type) {
+    if (type == Integer.class) return (Class<T>) int.class;
+    if (type == Float.class) return (Class<T>) float.class;
+    if (type == Byte.class) return (Class<T>) byte.class;
+    if (type == Double.class) return (Class<T>) double.class;
+    if (type == Long.class) return (Class<T>) long.class;
+    if (type == Character.class) return (Class<T>) char.class;
+    if (type == Boolean.class) return (Class<T>) boolean.class;
+    if (type == Short.class) return (Class<T>) short.class;
+    if (type == Void.class) return (Class<T>) void.class;
+    return type;
+  }
+}

+ 120 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/Streams.java

@@ -0,0 +1,120 @@
+/*
+ * 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.fq.threelib.gson.internal;
+
+import com.fq.threelib.gson.JsonElement;
+import com.fq.threelib.gson.JsonIOException;
+import com.fq.threelib.gson.JsonNull;
+import com.fq.threelib.gson.JsonParseException;
+import com.fq.threelib.gson.JsonSyntaxException;
+import com.fq.threelib.gson.internal.bind.TypeAdapters;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonWriter;
+import com.fq.threelib.gson.stream.MalformedJsonException;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Reads and writes GSON parse trees over streams.
+ */
+public final class Streams {
+  private Streams() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Takes a reader in any state and returns the next value as a JsonElement.
+   */
+  public static JsonElement parse(JsonReader reader) throws JsonParseException {
+    boolean isEmpty = true;
+    try {
+      reader.peek();
+      isEmpty = false;
+      return TypeAdapters.JSON_ELEMENT.read(reader);
+    } catch (EOFException e) {
+      /*
+       * For compatibility with JSON 1.5 and earlier, we return a JsonNull for
+       * empty documents instead of throwing.
+       */
+      if (isEmpty) {
+        return JsonNull.INSTANCE;
+      }
+      // The stream ended prematurely so it is likely a syntax error.
+      throw new JsonSyntaxException(e);
+    } catch (MalformedJsonException e) {
+      throw new JsonSyntaxException(e);
+    } catch (IOException e) {
+      throw new JsonIOException(e);
+    } catch (NumberFormatException e) {
+      throw new JsonSyntaxException(e);
+    }
+  }
+
+  /**
+   * Writes the JSON element to the writer, recursively.
+   */
+  public static void write(JsonElement element, JsonWriter writer) throws IOException {
+    TypeAdapters.JSON_ELEMENT.write(writer, element);
+  }
+
+  public static Writer writerForAppendable(Appendable appendable) {
+    return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable);
+  }
+
+  /**
+   * Adapts an {@link Appendable} so it can be passed anywhere a {@link Writer}
+   * is used.
+   */
+  private static final class AppendableWriter extends Writer {
+    private final Appendable appendable;
+    private final CurrentWrite currentWrite = new CurrentWrite();
+
+    AppendableWriter(Appendable appendable) {
+      this.appendable = appendable;
+    }
+
+    @Override public void write(char[] chars, int offset, int length) throws IOException {
+      currentWrite.chars = chars;
+      appendable.append(currentWrite, offset, offset + length);
+    }
+
+    @Override public void write(int i) throws IOException {
+      appendable.append((char) i);
+    }
+
+    @Override public void flush() {}
+    @Override public void close() {}
+
+    /**
+     * A mutable char sequence pointing at a single char[].
+     */
+    static class CurrentWrite implements CharSequence {
+      char[] chars;
+      public int length() {
+        return chars.length;
+      }
+      public char charAt(int i) {
+        return chars[i];
+      }
+      public CharSequence subSequence(int start, int end) {
+        return new String(chars, start, end - start);
+      }
+    }
+  }
+
+}

+ 123 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/UnsafeAllocator.java

@@ -0,0 +1,123 @@
+/*
+ * 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.fq.threelib.gson.internal;
+
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Do sneaky things to allocate objects without invoking their constructors.
+ *
+ * @author Joel Leitch
+ * @author Jesse Wilson
+ */
+public abstract class UnsafeAllocator {
+  public abstract <T> T newInstance(Class<T> c) throws Exception;
+
+  public static UnsafeAllocator create() {
+    // try JVM
+    // public class Unsafe {
+    //   public Object allocateInstance(Class<?> type);
+    // }
+    try {
+      Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
+      Field f = unsafeClass.getDeclaredField("theUnsafe");
+      f.setAccessible(true);
+      final Object unsafe = f.get(null);
+      final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
+      return new UnsafeAllocator() {
+        @Override
+        @SuppressWarnings("unchecked")
+        public <T> T newInstance(Class<T> c) throws Exception {
+          assertInstantiable(c);
+          return (T) allocateInstance.invoke(unsafe, c);
+        }
+      };
+    } catch (Exception ignored) {
+    }
+
+    // try dalvikvm, post-gingerbread
+    // public class ObjectStreamClass {
+    //   private static native int getConstructorId(Class<?> c);
+    //   private static native Object newInstance(Class<?> instantiationClass, int methodId);
+    // }
+    try {
+      Method getConstructorId = ObjectStreamClass.class
+          .getDeclaredMethod("getConstructorId", Class.class);
+      getConstructorId.setAccessible(true);
+      final int constructorId = (Integer) getConstructorId.invoke(null, Object.class);
+      final Method newInstance = ObjectStreamClass.class
+          .getDeclaredMethod("newInstance", Class.class, int.class);
+      newInstance.setAccessible(true);
+      return new UnsafeAllocator() {
+        @Override
+        @SuppressWarnings("unchecked")
+        public <T> T newInstance(Class<T> c) throws Exception {
+          assertInstantiable(c);
+          return (T) newInstance.invoke(null, c, constructorId);
+        }
+      };
+    } catch (Exception ignored) {
+    }
+
+    // try dalvikvm, pre-gingerbread
+    // public class ObjectInputStream {
+    //   private static native Object newInstance(
+    //     Class<?> instantiationClass, Class<?> constructorClass);
+    // }
+    try {
+      final Method newInstance = ObjectInputStream.class
+          .getDeclaredMethod("newInstance", Class.class, Class.class);
+      newInstance.setAccessible(true);
+      return new UnsafeAllocator() {
+        @Override
+        @SuppressWarnings("unchecked")
+        public <T> T newInstance(Class<T> c) throws Exception {
+          assertInstantiable(c);
+          return (T) newInstance.invoke(null, c, Object.class);
+        }
+      };
+    } catch (Exception ignored) {
+    }
+
+    // give up
+    return new UnsafeAllocator() {
+      @Override
+      public <T> T newInstance(Class<T> c) {
+        throw new UnsupportedOperationException("Cannot allocate " + c);
+      }
+    };
+  }
+
+  /**
+   * Check if the class can be instantiated by unsafe allocator. If the instance has interface or abstract modifiers
+   * throw an {@link UnsupportedOperationException}
+   * @param c instance of the class to be checked
+   */
+  static void assertInstantiable(Class<?> c) {
+    int modifiers = c.getModifiers();
+    if (Modifier.isInterface(modifiers)) {
+      throw new UnsupportedOperationException("Interface can't be instantiated! Interface name: " + c.getName());
+    }
+    if (Modifier.isAbstract(modifiers)) {
+      throw new UnsupportedOperationException("Abstract class can't be instantiated! Class name: " + c.getName());
+    }
+  }
+}

+ 99 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/ArrayTypeAdapter.java

@@ -0,0 +1,99 @@
+/*
+ * 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.fq.threelib.gson.internal.bind;
+
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.TypeAdapterFactory;
+import com.fq.threelib.gson.internal.$Gson$Types;
+import com.fq.threelib.gson.reflect.TypeToken;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.gson.stream.JsonWriter;
+
+/**
+ * Adapt an array of objects.
+ */
+public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
+  public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+      Type type = typeToken.getType();
+      if (!(type instanceof GenericArrayType || type instanceof Class && ((Class<?>) type).isArray())) {
+        return null;
+      }
+
+      Type componentType = $Gson$Types.getArrayComponentType(type);
+      TypeAdapter<?> componentTypeAdapter = gson.getAdapter(TypeToken.get(componentType));
+      return new ArrayTypeAdapter(
+              gson, componentTypeAdapter, $Gson$Types.getRawType(componentType));
+    }
+  };
+
+  private final Class<E> componentType;
+  private final TypeAdapter<E> componentTypeAdapter;
+
+  public ArrayTypeAdapter(Gson context, TypeAdapter<E> componentTypeAdapter, Class<E> componentType) {
+    this.componentTypeAdapter =
+      new TypeAdapterRuntimeTypeWrapper<E>(context, componentTypeAdapter, componentType);
+    this.componentType = componentType;
+  }
+
+  @Override public Object read(JsonReader in) throws IOException {
+    if (in.peek() == JsonToken.NULL) {
+      in.nextNull();
+      return null;
+    }
+
+    List<E> list = new ArrayList<E>();
+    in.beginArray();
+    while (in.hasNext()) {
+      E instance = componentTypeAdapter.read(in);
+      list.add(instance);
+    }
+    in.endArray();
+
+    int size = list.size();
+    Object array = Array.newInstance(componentType, size);
+    for (int i = 0; i < size; i++) {
+      Array.set(array, i, list.get(i));
+    }
+    return array;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override public void write(JsonWriter out, Object array) throws IOException {
+    if (array == null) {
+      out.nullValue();
+      return;
+    }
+
+    out.beginArray();
+    for (int i = 0, length = Array.getLength(array); i < length; i++) {
+      E value = (E) Array.get(array, i);
+      componentTypeAdapter.write(out, value);
+    }
+    out.endArray();
+  }
+}

+ 102 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/CollectionTypeAdapterFactory.java

@@ -0,0 +1,102 @@
+/*
+ * 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.fq.threelib.gson.internal.bind;
+
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.TypeAdapterFactory;
+import com.fq.threelib.gson.internal.$Gson$Types;
+import com.fq.threelib.gson.internal.ConstructorConstructor;
+import com.fq.threelib.gson.internal.ObjectConstructor;
+import com.fq.threelib.gson.reflect.TypeToken;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+/**
+ * Adapt a homogeneous collection of objects.
+ */
+public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
+  private final ConstructorConstructor constructorConstructor;
+
+  public CollectionTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
+    this.constructorConstructor = constructorConstructor;
+  }
+
+  @Override
+  public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+    Type type = typeToken.getType();
+
+    Class<? super T> rawType = typeToken.getRawType();
+    if (!Collection.class.isAssignableFrom(rawType)) {
+      return null;
+    }
+
+    Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
+    TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
+    ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);
+
+    @SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
+    TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter, constructor);
+    return result;
+  }
+
+  private static final class Adapter<E> extends TypeAdapter<Collection<E>> {
+    private final TypeAdapter<E> elementTypeAdapter;
+    private final ObjectConstructor<? extends Collection<E>> constructor;
+
+    public Adapter(Gson context, Type elementType,
+        TypeAdapter<E> elementTypeAdapter,
+        ObjectConstructor<? extends Collection<E>> constructor) {
+      this.elementTypeAdapter =
+          new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
+      this.constructor = constructor;
+    }
+
+    @Override public Collection<E> read(JsonReader in) throws IOException {
+      if (in.peek() == JsonToken.NULL) {
+        in.nextNull();
+        return null;
+      }
+
+      Collection<E> collection = constructor.construct();
+      in.beginArray();
+      while (in.hasNext()) {
+        E instance = elementTypeAdapter.read(in);
+        collection.add(instance);
+      }
+      in.endArray();
+      return collection;
+    }
+
+    @Override public void write(JsonWriter out, Collection<E> collection) throws IOException {
+      if (collection == null) {
+        out.nullValue();
+        return;
+      }
+
+      out.beginArray();
+      for (E element : collection) {
+        elementTypeAdapter.write(out, element);
+      }
+      out.endArray();
+    }
+  }
+}

+ 101 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/DateTypeAdapter.java

@@ -0,0 +1,101 @@
+/*
+ * 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.fq.threelib.gson.internal.bind;
+
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.JsonSyntaxException;
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.TypeAdapterFactory;
+import com.fq.threelib.gson.internal.JavaVersion;
+import com.fq.threelib.gson.internal.PreJava9DateFormatProvider;
+import com.fq.threelib.gson.internal.bind.util.ISO8601Utils;
+import com.fq.threelib.gson.reflect.TypeToken;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Adapter for Date. Although this class appears stateless, it is not.
+ * DateFormat captures its time zone and locale when it is created, which gives
+ * this class state. DateFormat isn't thread safe either, so this class has
+ * to synchronize its read and write methods.
+ */
+public final class DateTypeAdapter extends TypeAdapter<Date> {
+  public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
+    @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
+    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+      return typeToken.getRawType() == Date.class ? (TypeAdapter<T>) new DateTypeAdapter() : null;
+    }
+  };
+
+  /**
+   * List of 1 or more different date formats used for de-serialization attempts.
+   * The first of them (default US format) is used for serialization as well.
+   */
+  private final List<DateFormat> dateFormats = new ArrayList<DateFormat>();
+
+  public DateTypeAdapter() {
+    dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US));
+    if (!Locale.getDefault().equals(Locale.US)) {
+      dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT));
+    }
+    if (JavaVersion.isJava9OrLater()) {
+      dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(DateFormat.DEFAULT, DateFormat.DEFAULT));
+    }
+  }
+
+  @Override public Date read(JsonReader in) throws IOException {
+    if (in.peek() == JsonToken.NULL) {
+      in.nextNull();
+      return null;
+    }
+    return deserializeToDate(in.nextString());
+  }
+
+  private synchronized Date deserializeToDate(String json) {
+    for (DateFormat dateFormat : dateFormats) {
+      try {
+        return dateFormat.parse(json);
+      } catch (ParseException ignored) {}
+    }
+    try {
+    	return ISO8601Utils.parse(json, new ParsePosition(0));
+    } catch (ParseException e) {
+      throw new JsonSyntaxException(json, e);
+    }
+  }
+
+  @Override public synchronized void write(JsonWriter out, Date value) throws IOException {
+    if (value == null) {
+      out.nullValue();
+      return;
+    }
+    String dateFormatAsString = dateFormats.get(0).format(value);
+    out.value(dateFormatAsString);
+  }
+  
+  
+}

+ 83 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java

@@ -0,0 +1,83 @@
+/*
+ * 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.fq.threelib.gson.internal.bind;
+
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.JsonDeserializer;
+import com.fq.threelib.gson.JsonSerializer;
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.TypeAdapterFactory;
+import com.fq.threelib.gson.annotations.JsonAdapter;
+import com.fq.threelib.gson.internal.ConstructorConstructor;
+import com.fq.threelib.gson.reflect.TypeToken;
+
+/**
+ * Given a type T, looks for the annotation {@link JsonAdapter} and uses an instance of the
+ * specified class as the default type adapter.
+ *
+ * @since 2.3
+ */
+public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapterFactory {
+  private final ConstructorConstructor constructorConstructor;
+
+  public JsonAdapterAnnotationTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
+    this.constructorConstructor = constructorConstructor;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
+    Class<? super T> rawType = targetType.getRawType();
+    JsonAdapter annotation = rawType.getAnnotation(JsonAdapter.class);
+    if (annotation == null) {
+      return null;
+    }
+    return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation);
+  }
+
+  @SuppressWarnings({ "unchecked", "rawtypes" }) // Casts guarded by conditionals.
+  TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
+      TypeToken<?> type, JsonAdapter annotation) {
+    Object instance = constructorConstructor.get(TypeToken.get(annotation.value())).construct();
+
+    TypeAdapter<?> typeAdapter;
+    if (instance instanceof TypeAdapter) {
+      typeAdapter = (TypeAdapter<?>) instance;
+    } else if (instance instanceof TypeAdapterFactory) {
+      typeAdapter = ((TypeAdapterFactory) instance).create(gson, type);
+    } else if (instance instanceof JsonSerializer || instance instanceof JsonDeserializer) {
+      JsonSerializer<?> serializer = instance instanceof JsonSerializer
+          ? (JsonSerializer) instance
+          : null;
+      JsonDeserializer<?> deserializer = instance instanceof JsonDeserializer
+          ? (JsonDeserializer) instance
+          : null;
+      typeAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, null);
+    } else {
+      throw new IllegalArgumentException("Invalid attempt to bind an instance of "
+          + instance.getClass().getName() + " as a @JsonAdapter for " + type.toString()
+          + ". @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory,"
+          + " JsonSerializer or JsonDeserializer.");
+    }
+
+    if (typeAdapter != null && annotation.nullSafe()) {
+      typeAdapter = typeAdapter.nullSafe();
+    }
+
+    return typeAdapter;
+  }
+}

+ 316 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/JsonTreeReader.java

@@ -0,0 +1,316 @@
+/*
+ * 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.fq.threelib.gson.internal.bind;
+
+import com.fq.threelib.gson.JsonArray;
+import com.fq.threelib.gson.JsonElement;
+import com.fq.threelib.gson.JsonNull;
+import com.fq.threelib.gson.JsonObject;
+import com.fq.threelib.gson.JsonPrimitive;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Arrays;
+
+/**
+ * This reader walks the elements of a JsonElement as if it was coming from a
+ * character stream.
+ *
+ * @author Jesse Wilson
+ */
+public final class JsonTreeReader extends JsonReader {
+  private static final Reader UNREADABLE_READER = new Reader() {
+    @Override public int read(char[] buffer, int offset, int count) throws IOException {
+      throw new AssertionError();
+    }
+    @Override public void close() throws IOException {
+      throw new AssertionError();
+    }
+  };
+  private static final Object SENTINEL_CLOSED = new Object();
+
+  /*
+   * The nesting stack. Using a manual array rather than an ArrayList saves 20%.
+   */
+  private Object[] stack = new Object[32];
+  private int stackSize = 0;
+
+  /*
+   * The path members. It corresponds directly to stack: At indices where the
+   * stack contains an object (EMPTY_OBJECT, DANGLING_NAME or NONEMPTY_OBJECT),
+   * pathNames contains the name at this scope. Where it contains an array
+   * (EMPTY_ARRAY, NONEMPTY_ARRAY) pathIndices contains the current index in
+   * that array. Otherwise the value is undefined, and we take advantage of that
+   * by incrementing pathIndices when doing so isn't useful.
+   */
+  private String[] pathNames = new String[32];
+  private int[] pathIndices = new int[32];
+
+  public JsonTreeReader(JsonElement element) {
+    super(UNREADABLE_READER);
+    push(element);
+  }
+
+  @Override public void beginArray() throws IOException {
+    expect(JsonToken.BEGIN_ARRAY);
+    JsonArray array = (JsonArray) peekStack();
+    push(array.iterator());
+    pathIndices[stackSize - 1] = 0;
+  }
+
+  @Override public void endArray() throws IOException {
+    expect(JsonToken.END_ARRAY);
+    popStack(); // empty iterator
+    popStack(); // array
+    if (stackSize > 0) {
+      pathIndices[stackSize - 1]++;
+    }
+  }
+
+  @Override public void beginObject() throws IOException {
+    expect(JsonToken.BEGIN_OBJECT);
+    JsonObject object = (JsonObject) peekStack();
+    push(object.entrySet().iterator());
+  }
+
+  @Override public void endObject() throws IOException {
+    expect(JsonToken.END_OBJECT);
+    popStack(); // empty iterator
+    popStack(); // object
+    if (stackSize > 0) {
+      pathIndices[stackSize - 1]++;
+    }
+  }
+
+  @Override public boolean hasNext() throws IOException {
+    JsonToken token = peek();
+    return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
+  }
+
+  @Override public JsonToken peek() throws IOException {
+    if (stackSize == 0) {
+      return JsonToken.END_DOCUMENT;
+    }
+
+    Object o = peekStack();
+    if (o instanceof Iterator) {
+      boolean isObject = stack[stackSize - 2] instanceof JsonObject;
+      Iterator<?> iterator = (Iterator<?>) o;
+      if (iterator.hasNext()) {
+        if (isObject) {
+          return JsonToken.NAME;
+        } else {
+          push(iterator.next());
+          return peek();
+        }
+      } else {
+        return isObject ? JsonToken.END_OBJECT : JsonToken.END_ARRAY;
+      }
+    } else if (o instanceof JsonObject) {
+      return JsonToken.BEGIN_OBJECT;
+    } else if (o instanceof JsonArray) {
+      return JsonToken.BEGIN_ARRAY;
+    } else if (o instanceof JsonPrimitive) {
+      JsonPrimitive primitive = (JsonPrimitive) o;
+      if (primitive.isString()) {
+        return JsonToken.STRING;
+      } else if (primitive.isBoolean()) {
+        return JsonToken.BOOLEAN;
+      } else if (primitive.isNumber()) {
+        return JsonToken.NUMBER;
+      } else {
+        throw new AssertionError();
+      }
+    } else if (o instanceof JsonNull) {
+      return JsonToken.NULL;
+    } else if (o == SENTINEL_CLOSED) {
+      throw new IllegalStateException("JsonReader is closed");
+    } else {
+      throw new AssertionError();
+    }
+  }
+
+  private Object peekStack() {
+    return stack[stackSize - 1];
+  }
+
+  private Object popStack() {
+    Object result = stack[--stackSize];
+    stack[stackSize] = null;
+    return result;
+  }
+
+  private void expect(JsonToken expected) throws IOException {
+    if (peek() != expected) {
+      throw new IllegalStateException(
+          "Expected " + expected + " but was " + peek() + locationString());
+    }
+  }
+
+  @Override public String nextName() throws IOException {
+    expect(JsonToken.NAME);
+    Iterator<?> i = (Iterator<?>) peekStack();
+    Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
+    String result = (String) entry.getKey();
+    pathNames[stackSize - 1] = result;
+    push(entry.getValue());
+    return result;
+  }
+
+  @Override public String nextString() throws IOException {
+    JsonToken token = peek();
+    if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
+      throw new IllegalStateException(
+          "Expected " + JsonToken.STRING + " but was " + token + locationString());
+    }
+    String result = ((JsonPrimitive) popStack()).getAsString();
+    if (stackSize > 0) {
+      pathIndices[stackSize - 1]++;
+    }
+    return result;
+  }
+
+  @Override public boolean nextBoolean() throws IOException {
+    expect(JsonToken.BOOLEAN);
+    boolean result = ((JsonPrimitive) popStack()).getAsBoolean();
+    if (stackSize > 0) {
+      pathIndices[stackSize - 1]++;
+    }
+    return result;
+  }
+
+  @Override public void nextNull() throws IOException {
+    expect(JsonToken.NULL);
+    popStack();
+    if (stackSize > 0) {
+      pathIndices[stackSize - 1]++;
+    }
+  }
+
+  @Override public double nextDouble() throws IOException {
+    JsonToken token = peek();
+    if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
+      throw new IllegalStateException(
+          "Expected " + JsonToken.NUMBER + " but was " + token + locationString());
+    }
+    double result = ((JsonPrimitive) peekStack()).getAsDouble();
+    if (!isLenient() && (Double.isNaN(result) || Double.isInfinite(result))) {
+      throw new NumberFormatException("JSON forbids NaN and infinities: " + result);
+    }
+    popStack();
+    if (stackSize > 0) {
+      pathIndices[stackSize - 1]++;
+    }
+    return result;
+  }
+
+  @Override public long nextLong() throws IOException {
+    JsonToken token = peek();
+    if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
+      throw new IllegalStateException(
+          "Expected " + JsonToken.NUMBER + " but was " + token + locationString());
+    }
+    long result = ((JsonPrimitive) peekStack()).getAsLong();
+    popStack();
+    if (stackSize > 0) {
+      pathIndices[stackSize - 1]++;
+    }
+    return result;
+  }
+
+  @Override public int nextInt() throws IOException {
+    JsonToken token = peek();
+    if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
+      throw new IllegalStateException(
+          "Expected " + JsonToken.NUMBER + " but was " + token + locationString());
+    }
+    int result = ((JsonPrimitive) peekStack()).getAsInt();
+    popStack();
+    if (stackSize > 0) {
+      pathIndices[stackSize - 1]++;
+    }
+    return result;
+  }
+
+  @Override public void close() throws IOException {
+    stack = new Object[] { SENTINEL_CLOSED };
+    stackSize = 1;
+  }
+
+  @Override public void skipValue() throws IOException {
+    if (peek() == JsonToken.NAME) {
+      nextName();
+      pathNames[stackSize - 2] = "null";
+    } else {
+      popStack();
+      if (stackSize > 0) {
+        pathNames[stackSize - 1] = "null";
+      }
+    }
+    if (stackSize > 0) {
+      pathIndices[stackSize - 1]++;
+    }
+  }
+
+  @Override public String toString() {
+    return getClass().getSimpleName();
+  }
+
+  public void promoteNameToValue() throws IOException {
+    expect(JsonToken.NAME);
+    Iterator<?> i = (Iterator<?>) peekStack();
+    Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
+    push(entry.getValue());
+    push(new JsonPrimitive((String) entry.getKey()));
+  }
+
+  private void push(Object newTop) {
+    if (stackSize == stack.length) {
+      int newLength = stackSize * 2;
+      stack = Arrays.copyOf(stack, newLength);
+      pathIndices = Arrays.copyOf(pathIndices, newLength);
+      pathNames = Arrays.copyOf(pathNames, newLength);
+    }
+    stack[stackSize++] = newTop;
+  }
+
+  @Override public String getPath() {
+    StringBuilder result = new StringBuilder().append('$');
+    for (int i = 0; i < stackSize; i++) {
+      if (stack[i] instanceof JsonArray) {
+        if (stack[++i] instanceof Iterator) {
+          result.append('[').append(pathIndices[i]).append(']');
+        }
+      } else if (stack[i] instanceof JsonObject) {
+        if (stack[++i] instanceof Iterator) {
+          result.append('.');
+          if (pathNames[i] != null) {
+            result.append(pathNames[i]);
+          }
+        }
+      }
+    }
+    return result.toString();
+  }
+
+  private String locationString() {
+    return " at path " + getPath();
+  }
+}

+ 208 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/JsonTreeWriter.java

@@ -0,0 +1,208 @@
+/*
+ * 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.fq.threelib.gson.internal.bind;
+
+import com.fq.threelib.gson.JsonArray;
+import com.fq.threelib.gson.JsonElement;
+import com.fq.threelib.gson.JsonNull;
+import com.fq.threelib.gson.JsonObject;
+import com.fq.threelib.gson.JsonPrimitive;
+import com.fq.threelib.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This writer creates a JsonElement.
+ */
+public final class JsonTreeWriter extends JsonWriter {
+  private static final Writer UNWRITABLE_WRITER = new Writer() {
+    @Override public void write(char[] buffer, int offset, int counter) {
+      throw new AssertionError();
+    }
+    @Override public void flush() throws IOException {
+      throw new AssertionError();
+    }
+    @Override public void close() throws IOException {
+      throw new AssertionError();
+    }
+  };
+  /** Added to the top of the stack when this writer is closed to cause following ops to fail. */
+  private static final JsonPrimitive SENTINEL_CLOSED = new JsonPrimitive("closed");
+
+  /** The JsonElements and JsonArrays under modification, outermost to innermost. */
+  private final List<JsonElement> stack = new ArrayList<JsonElement>();
+
+  /** The name for the next JSON object value. If non-null, the top of the stack is a JsonObject. */
+  private String pendingName;
+
+  /** the JSON element constructed by this writer. */
+  private JsonElement product = JsonNull.INSTANCE; // TODO: is this really what we want?;
+
+  public JsonTreeWriter() {
+    super(UNWRITABLE_WRITER);
+  }
+
+  /**
+   * Returns the top level object produced by this writer.
+   */
+  public JsonElement get() {
+    if (!stack.isEmpty()) {
+      throw new IllegalStateException("Expected one JSON element but was " + stack);
+    }
+    return product;
+  }
+
+  private JsonElement peek() {
+    return stack.get(stack.size() - 1);
+  }
+
+  private void put(JsonElement value) {
+    if (pendingName != null) {
+      if (!value.isJsonNull() || getSerializeNulls()) {
+        JsonObject object = (JsonObject) peek();
+        object.add(pendingName, value);
+      }
+      pendingName = null;
+    } else if (stack.isEmpty()) {
+      product = value;
+    } else {
+      JsonElement element = peek();
+      if (element instanceof JsonArray) {
+        ((JsonArray) element).add(value);
+      } else {
+        throw new IllegalStateException();
+      }
+    }
+  }
+
+  @Override public JsonWriter beginArray() throws IOException {
+    JsonArray array = new JsonArray();
+    put(array);
+    stack.add(array);
+    return this;
+  }
+
+  @Override public JsonWriter endArray() throws IOException {
+    if (stack.isEmpty() || pendingName != null) {
+      throw new IllegalStateException();
+    }
+    JsonElement element = peek();
+    if (element instanceof JsonArray) {
+      stack.remove(stack.size() - 1);
+      return this;
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override public JsonWriter beginObject() throws IOException {
+    JsonObject object = new JsonObject();
+    put(object);
+    stack.add(object);
+    return this;
+  }
+
+  @Override public JsonWriter endObject() throws IOException {
+    if (stack.isEmpty() || pendingName != null) {
+      throw new IllegalStateException();
+    }
+    JsonElement element = peek();
+    if (element instanceof JsonObject) {
+      stack.remove(stack.size() - 1);
+      return this;
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override public JsonWriter name(String name) throws IOException {
+    if (stack.isEmpty() || pendingName != null) {
+      throw new IllegalStateException();
+    }
+    JsonElement element = peek();
+    if (element instanceof JsonObject) {
+      pendingName = name;
+      return this;
+    }
+    throw new IllegalStateException();
+  }
+
+  @Override public JsonWriter value(String value) throws IOException {
+    if (value == null) {
+      return nullValue();
+    }
+    put(new JsonPrimitive(value));
+    return this;
+  }
+
+  @Override public JsonWriter nullValue() throws IOException {
+    put(JsonNull.INSTANCE);
+    return this;
+  }
+
+  @Override public JsonWriter value(boolean value) throws IOException {
+    put(new JsonPrimitive(value));
+    return this;
+  }
+
+  @Override public JsonWriter value(Boolean value) throws IOException {
+    if (value == null) {
+      return nullValue();
+    }
+    put(new JsonPrimitive(value));
+    return this;
+  }
+
+  @Override public JsonWriter value(double value) throws IOException {
+    if (!isLenient() && (Double.isNaN(value) || Double.isInfinite(value))) {
+      throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
+    }
+    put(new JsonPrimitive(value));
+    return this;
+  }
+
+  @Override public JsonWriter value(long value) throws IOException {
+    put(new JsonPrimitive(value));
+    return this;
+  }
+
+  @Override public JsonWriter value(Number value) throws IOException {
+    if (value == null) {
+      return nullValue();
+    }
+
+    if (!isLenient()) {
+      double d = value.doubleValue();
+      if (Double.isNaN(d) || Double.isInfinite(d)) {
+        throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
+      }
+    }
+
+    put(new JsonPrimitive(value));
+    return this;
+  }
+
+  @Override public void flush() throws IOException {
+  }
+
+  @Override public void close() throws IOException {
+    if (!stack.isEmpty()) {
+      throw new IOException("Incomplete document");
+    }
+    stack.add(SENTINEL_CLOSED);
+  }
+}

+ 264 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/MapTypeAdapterFactory.java

@@ -0,0 +1,264 @@
+/*
+ * 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.fq.threelib.gson.internal.bind;
+
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.JsonElement;
+import com.fq.threelib.gson.JsonPrimitive;
+import com.fq.threelib.gson.JsonSyntaxException;
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.TypeAdapterFactory;
+import com.fq.threelib.gson.internal.$Gson$Types;
+import com.fq.threelib.gson.internal.ConstructorConstructor;
+import com.fq.threelib.gson.internal.JsonReaderInternalAccess;
+import com.fq.threelib.gson.internal.ObjectConstructor;
+import com.fq.threelib.gson.internal.Streams;
+import com.fq.threelib.gson.reflect.TypeToken;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Adapts maps to either JSON objects or JSON arrays.
+ *
+ * <h3>Maps as JSON objects</h3>
+ * For primitive keys or when complex map key serialization is not enabled, this
+ * converts Java {@link Map Maps} to JSON Objects. This requires that map keys
+ * can be serialized as strings; this is insufficient for some key types. For
+ * example, consider a map whose keys are points on a grid. The default JSON
+ * form encodes reasonably: <pre>   {@code
+ *   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>
+ * But GSON is unable to deserialize this value because the JSON string name is
+ * just the {@link Object#toString() toString()} of the map key. Attempting to
+ * convert the above JSON to an object fails with a parse exception:
+ * <pre>com.fq.threelib.gson.JsonParseException: Expecting object found: "(5,6)"
+ *   at com.fq.threelib.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler
+ *   at com.fq.threelib.gson.ObjectNavigator.navigateClassFields
+ *   ...</pre>
+ *
+ * <h3>Maps as JSON arrays</h3>
+ * An alternative approach taken by this type adapter when it is required and
+ * complex map key serialization is enabled is to encode maps as arrays of map
+ * entries. Each map entry is a two element array containing a key and a value.
+ * This approach is more flexible because any type can be used as the map's key;
+ * not just strings. But it's also less portable because the receiver of such
+ * JSON must be aware of the map entry convention.
+ *
+ * <p>Register this adapter when you are creating your GSON instance.
+ * <pre>   {@code
+ *   Gson gson = new GsonBuilder()
+ *     .registerTypeAdapter(Map.class, new MapAsArrayTypeAdapter())
+ *     .create();
+ * }</pre>
+ * This will change the structure of the JSON emitted by the code above. Now we
+ * get an array. In this case the arrays elements are map entries:
+ * <pre>   {@code
+ *   [
+ *     [
+ *       {
+ *         "x": 5,
+ *         "y": 6
+ *       },
+ *       "a",
+ *     ],
+ *     [
+ *       {
+ *         "x": 8,
+ *         "y": 8
+ *       },
+ *       "b"
+ *     ]
+ *   ]
+ * }</pre>
+ * This format will serialize and deserialize just fine as long as this adapter
+ * is registered.
+ */
+public final class MapTypeAdapterFactory implements TypeAdapterFactory {
+  private final ConstructorConstructor constructorConstructor;
+  final boolean complexMapKeySerialization;
+
+  public MapTypeAdapterFactory(ConstructorConstructor constructorConstructor,
+      boolean complexMapKeySerialization) {
+    this.constructorConstructor = constructorConstructor;
+    this.complexMapKeySerialization = complexMapKeySerialization;
+  }
+
+  @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+    Type type = typeToken.getType();
+
+    Class<? super T> rawType = typeToken.getRawType();
+    if (!Map.class.isAssignableFrom(rawType)) {
+      return null;
+    }
+
+    Class<?> rawTypeOfSrc = $Gson$Types.getRawType(type);
+    Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc);
+    TypeAdapter<?> keyAdapter = getKeyAdapter(gson, keyAndValueTypes[0]);
+    TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(keyAndValueTypes[1]));
+    ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    // we don't define a type parameter for the key or value types
+    TypeAdapter<T> result = new Adapter(gson, keyAndValueTypes[0], keyAdapter,
+        keyAndValueTypes[1], valueAdapter, constructor);
+    return result;
+  }
+
+  /**
+   * Returns a type adapter that writes the value as a string.
+   */
+  private TypeAdapter<?> getKeyAdapter(Gson context, Type keyType) {
+    return (keyType == boolean.class || keyType == Boolean.class)
+        ? TypeAdapters.BOOLEAN_AS_STRING
+        : context.getAdapter(TypeToken.get(keyType));
+  }
+
+  private final class Adapter<K, V> extends TypeAdapter<Map<K, V>> {
+    private final TypeAdapter<K> keyTypeAdapter;
+    private final TypeAdapter<V> valueTypeAdapter;
+    private final ObjectConstructor<? extends Map<K, V>> constructor;
+
+    public Adapter(Gson context, Type keyType, TypeAdapter<K> keyTypeAdapter,
+        Type valueType, TypeAdapter<V> valueTypeAdapter,
+        ObjectConstructor<? extends Map<K, V>> constructor) {
+      this.keyTypeAdapter =
+        new TypeAdapterRuntimeTypeWrapper<K>(context, keyTypeAdapter, keyType);
+      this.valueTypeAdapter =
+        new TypeAdapterRuntimeTypeWrapper<V>(context, valueTypeAdapter, valueType);
+      this.constructor = constructor;
+    }
+
+    @Override public Map<K, V> read(JsonReader in) throws IOException {
+      JsonToken peek = in.peek();
+      if (peek == JsonToken.NULL) {
+        in.nextNull();
+        return null;
+      }
+
+      Map<K, V> map = constructor.construct();
+
+      if (peek == JsonToken.BEGIN_ARRAY) {
+        in.beginArray();
+        while (in.hasNext()) {
+          in.beginArray(); // entry array
+          K key = keyTypeAdapter.read(in);
+          V value = valueTypeAdapter.read(in);
+          V replaced = map.put(key, value);
+          if (replaced != null) {
+            throw new JsonSyntaxException("duplicate key: " + key);
+          }
+          in.endArray();
+        }
+        in.endArray();
+      } else {
+        in.beginObject();
+        while (in.hasNext()) {
+          JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
+          K key = keyTypeAdapter.read(in);
+          V value = valueTypeAdapter.read(in);
+          V replaced = map.put(key, value);
+          if (replaced != null) {
+            throw new JsonSyntaxException("duplicate key: " + key);
+          }
+        }
+        in.endObject();
+      }
+      return map;
+    }
+
+    @Override public void write(JsonWriter out, Map<K, V> map) throws IOException {
+      if (map == null) {
+        out.nullValue();
+        return;
+      }
+
+      if (!complexMapKeySerialization) {
+        out.beginObject();
+        for (Map.Entry<K, V> entry : map.entrySet()) {
+          out.name(String.valueOf(entry.getKey()));
+          valueTypeAdapter.write(out, entry.getValue());
+        }
+        out.endObject();
+        return;
+      }
+
+      boolean hasComplexKeys = false;
+      List<JsonElement> keys = new ArrayList<JsonElement>(map.size());
+
+      List<V> values = new ArrayList<V>(map.size());
+      for (Map.Entry<K, V> entry : map.entrySet()) {
+        JsonElement keyElement = keyTypeAdapter.toJsonTree(entry.getKey());
+        keys.add(keyElement);
+        values.add(entry.getValue());
+        hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject();
+      }
+
+      if (hasComplexKeys) {
+        out.beginArray();
+        for (int i = 0, size = keys.size(); i < size; i++) {
+          out.beginArray(); // entry array
+          Streams.write(keys.get(i), out);
+          valueTypeAdapter.write(out, values.get(i));
+          out.endArray();
+        }
+        out.endArray();
+      } else {
+        out.beginObject();
+        for (int i = 0, size = keys.size(); i < size; i++) {
+          JsonElement keyElement = keys.get(i);
+          out.name(keyToString(keyElement));
+          valueTypeAdapter.write(out, values.get(i));
+        }
+        out.endObject();
+      }
+    }
+
+    private String keyToString(JsonElement keyElement) {
+      if (keyElement.isJsonPrimitive()) {
+        JsonPrimitive primitive = keyElement.getAsJsonPrimitive();
+        if (primitive.isNumber()) {
+          return String.valueOf(primitive.getAsNumber());
+        } else if (primitive.isBoolean()) {
+          return Boolean.toString(primitive.getAsBoolean());
+        } else if (primitive.isString()) {
+          return primitive.getAsString();
+        } else {
+          throw new AssertionError();
+        }
+      } else if (keyElement.isJsonNull()) {
+        return "null";
+      } else {
+        throw new AssertionError();
+      }
+    }
+  }
+}

+ 109 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/ObjectTypeAdapter.java

@@ -0,0 +1,109 @@
+/*
+ * 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.fq.threelib.gson.internal.bind;
+
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.TypeAdapterFactory;
+import com.fq.threelib.gson.internal.LinkedTreeMap;
+import com.fq.threelib.gson.reflect.TypeToken;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Adapts types whose static type is only 'Object'. Uses getClass() on
+ * serialization and a primitive/Map/List on deserialization.
+ */
+public final class ObjectTypeAdapter extends TypeAdapter<Object> {
+  public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
+    @SuppressWarnings("unchecked")
+    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+      if (type.getRawType() == Object.class) {
+        return (TypeAdapter<T>) new ObjectTypeAdapter(gson);
+      }
+      return null;
+    }
+  };
+
+  private final Gson gson;
+
+  ObjectTypeAdapter(Gson gson) {
+    this.gson = gson;
+  }
+
+  @Override public Object read(JsonReader in) throws IOException {
+    JsonToken token = in.peek();
+    switch (token) {
+    case BEGIN_ARRAY:
+      List<Object> list = new ArrayList<Object>();
+      in.beginArray();
+      while (in.hasNext()) {
+        list.add(read(in));
+      }
+      in.endArray();
+      return list;
+
+    case BEGIN_OBJECT:
+      Map<String, Object> map = new LinkedTreeMap<String, Object>();
+      in.beginObject();
+      while (in.hasNext()) {
+        map.put(in.nextName(), read(in));
+      }
+      in.endObject();
+      return map;
+
+    case STRING:
+      return in.nextString();
+
+    case NUMBER:
+      return in.nextDouble();
+
+    case BOOLEAN:
+      return in.nextBoolean();
+
+    case NULL:
+      in.nextNull();
+      return null;
+
+    default:
+      throw new IllegalStateException();
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override public void write(JsonWriter out, Object value) throws IOException {
+    if (value == null) {
+      out.nullValue();
+      return;
+    }
+
+    TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
+    if (typeAdapter instanceof ObjectTypeAdapter) {
+      out.beginObject();
+      out.endObject();
+      return;
+    }
+
+    typeAdapter.write(out, value);
+  }
+}

+ 254 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/ReflectiveTypeAdapterFactory.java

@@ -0,0 +1,254 @@
+/*
+ * 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.fq.threelib.gson.internal.bind;
+
+import com.fq.threelib.gson.FieldNamingStrategy;
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.JsonSyntaxException;
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.TypeAdapterFactory;
+import com.fq.threelib.gson.annotations.JsonAdapter;
+import com.fq.threelib.gson.annotations.SerializedName;
+import com.fq.threelib.gson.internal.$Gson$Types;
+import com.fq.threelib.gson.internal.ConstructorConstructor;
+import com.fq.threelib.gson.internal.Excluder;
+import com.fq.threelib.gson.internal.ObjectConstructor;
+import com.fq.threelib.gson.internal.Primitives;
+import com.fq.threelib.gson.internal.reflect.ReflectionAccessor;
+import com.fq.threelib.gson.reflect.TypeToken;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Type adapter that reflects over the fields and methods of a class.
+ */
+public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
+  private final ConstructorConstructor constructorConstructor;
+  private final FieldNamingStrategy fieldNamingPolicy;
+  private final Excluder excluder;
+  private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
+  private final ReflectionAccessor accessor = ReflectionAccessor.getInstance();
+
+  public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
+      FieldNamingStrategy fieldNamingPolicy, Excluder excluder,
+      JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory) {
+    this.constructorConstructor = constructorConstructor;
+    this.fieldNamingPolicy = fieldNamingPolicy;
+    this.excluder = excluder;
+    this.jsonAdapterFactory = jsonAdapterFactory;
+  }
+
+  public boolean excludeField(Field f, boolean serialize) {
+    return excludeField(f, serialize, excluder);
+  }
+
+  static boolean excludeField(Field f, boolean serialize, Excluder excluder) {
+    return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
+  }
+
+  /** first element holds the default name */
+  private List<String> getFieldNames(Field f) {
+    SerializedName annotation = f.getAnnotation(SerializedName.class);
+    if (annotation == null) {
+      String name = fieldNamingPolicy.translateName(f);
+      return Collections.singletonList(name);
+    }
+
+    String serializedName = annotation.value();
+    String[] alternates = annotation.alternate();
+    if (alternates.length == 0) {
+      return Collections.singletonList(serializedName);
+    }
+
+    List<String> fieldNames = new ArrayList<String>(alternates.length + 1);
+    fieldNames.add(serializedName);
+    for (String alternate : alternates) {
+      fieldNames.add(alternate);
+    }
+    return fieldNames;
+  }
+
+  @Override public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
+    Class<? super T> raw = type.getRawType();
+
+    if (!Object.class.isAssignableFrom(raw)) {
+      return null; // it's a primitive!
+    }
+
+    ObjectConstructor<T> constructor = constructorConstructor.get(type);
+    return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
+  }
+
+  private BoundField createBoundField(
+      final Gson context, final Field field, final String name,
+      final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
+    final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
+    // special casing primitives here saves ~5% on Android...
+    JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
+    TypeAdapter<?> mapped = null;
+    if (annotation != null) {
+      mapped = jsonAdapterFactory.getTypeAdapter(
+          constructorConstructor, context, fieldType, annotation);
+    }
+    final boolean jsonAdapterPresent = mapped != null;
+    if (mapped == null) mapped = context.getAdapter(fieldType);
+
+    final TypeAdapter<?> typeAdapter = mapped;
+    return new BoundField(name, serialize, deserialize) {
+      @SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
+      @Override void write(JsonWriter writer, Object value)
+          throws IOException, IllegalAccessException {
+        Object fieldValue = field.get(value);
+        TypeAdapter t = jsonAdapterPresent ? typeAdapter
+            : new TypeAdapterRuntimeTypeWrapper(context, typeAdapter, fieldType.getType());
+        t.write(writer, fieldValue);
+      }
+      @Override void read(JsonReader reader, Object value)
+          throws IOException, IllegalAccessException {
+        Object fieldValue = typeAdapter.read(reader);
+        if (fieldValue != null || !isPrimitive) {
+          field.set(value, fieldValue);
+        }
+      }
+      @Override public boolean writeField(Object value) throws IOException, IllegalAccessException {
+        if (!serialized) return false;
+        Object fieldValue = field.get(value);
+        return fieldValue != value; // avoid recursion for example for Throwable.cause
+      }
+    };
+  }
+
+  private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
+    Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
+    if (raw.isInterface()) {
+      return result;
+    }
+
+    Type declaredType = type.getType();
+    while (raw != Object.class) {
+      Field[] fields = raw.getDeclaredFields();
+      for (Field field : fields) {
+        boolean serialize = excludeField(field, true);
+        boolean deserialize = excludeField(field, false);
+        if (!serialize && !deserialize) {
+          continue;
+        }
+        accessor.makeAccessible(field);
+        Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
+        List<String> fieldNames = getFieldNames(field);
+        BoundField previous = null;
+        for (int i = 0, size = fieldNames.size(); i < size; ++i) {
+          String name = fieldNames.get(i);
+          if (i != 0) serialize = false; // only serialize the default name
+          BoundField boundField = createBoundField(context, field, name,
+              TypeToken.get(fieldType), serialize, deserialize);
+          BoundField replaced = result.put(name, boundField);
+          if (previous == null) previous = replaced;
+        }
+        if (previous != null) {
+          throw new IllegalArgumentException(declaredType
+              + " declares multiple JSON fields named " + previous.name);
+        }
+      }
+      type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
+      raw = type.getRawType();
+    }
+    return result;
+  }
+
+  static abstract class BoundField {
+    final String name;
+    final boolean serialized;
+    final boolean deserialized;
+
+    protected BoundField(String name, boolean serialized, boolean deserialized) {
+      this.name = name;
+      this.serialized = serialized;
+      this.deserialized = deserialized;
+    }
+    abstract boolean writeField(Object value) throws IOException, IllegalAccessException;
+    abstract void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException;
+    abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException;
+  }
+
+  public static final class Adapter<T> extends TypeAdapter<T> {
+    private final ObjectConstructor<T> constructor;
+    private final Map<String, BoundField> boundFields;
+
+    Adapter(ObjectConstructor<T> constructor, Map<String, BoundField> boundFields) {
+      this.constructor = constructor;
+      this.boundFields = boundFields;
+    }
+
+    @Override public T read(JsonReader in) throws IOException {
+      if (in.peek() == JsonToken.NULL) {
+        in.nextNull();
+        return null;
+      }
+
+      T instance = constructor.construct();
+
+      try {
+        in.beginObject();
+        while (in.hasNext()) {
+          String name = in.nextName();
+          BoundField field = boundFields.get(name);
+          if (field == null || !field.deserialized) {
+            in.skipValue();
+          } else {
+            field.read(in, instance);
+          }
+        }
+      } catch (IllegalStateException e) {
+        throw new JsonSyntaxException(e);
+      } catch (IllegalAccessException e) {
+        throw new AssertionError(e);
+      }
+      in.endObject();
+      return instance;
+    }
+
+    @Override public void write(JsonWriter out, T value) throws IOException {
+      if (value == null) {
+        out.nullValue();
+        return;
+      }
+
+      out.beginObject();
+      try {
+        for (BoundField boundField : boundFields.values()) {
+          if (boundField.writeField(value)) {
+            out.name(boundField.name);
+            boundField.write(out, value);
+          }
+        }
+      } catch (IllegalAccessException e) {
+        throw new AssertionError(e);
+      }
+      out.endObject();
+    }
+  }
+}

+ 67 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/SqlDateTypeAdapter.java

@@ -0,0 +1,67 @@
+/*
+ * 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.fq.threelib.gson.internal.bind;
+
+import com.fq.threelib.gson.Gson;
+import com.fq.threelib.gson.JsonSyntaxException;
+import com.fq.threelib.gson.TypeAdapter;
+import com.fq.threelib.gson.TypeAdapterFactory;
+import com.fq.threelib.gson.reflect.TypeToken;
+import com.fq.threelib.gson.stream.JsonReader;
+import com.fq.threelib.gson.stream.JsonToken;
+import com.fq.threelib.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+/**
+ * Adapter for java.sql.Date. Although this class appears stateless, it is not.
+ * DateFormat captures its time zone and locale when it is created, which gives
+ * this class state. DateFormat isn't thread safe either, so this class has
+ * to synchronize its read and write methods.
+ */
+public final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {
+  public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
+    @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
+    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+      return typeToken.getRawType() == java.sql.Date.class
+          ? (TypeAdapter<T>) new SqlDateTypeAdapter() : null;
+    }
+  };
+
+  private final DateFormat format = new SimpleDateFormat("MMM d, yyyy");
+
+  @Override
+  public synchronized java.sql.Date read(JsonReader in) throws IOException {
+    if (in.peek() == JsonToken.NULL) {
+      in.nextNull();
+      return null;
+    }
+    try {
+      final long utilDate = format.parse(in.nextString()).getTime();
+      return new java.sql.Date(utilDate);
+    } catch (ParseException e) {
+      throw new JsonSyntaxException(e);
+    }
+  }
+
+  @Override
+  public synchronized void write(JsonWriter out, java.sql.Date value) throws IOException {
+    out.value(value == null ? null : format.format(value));
+  }
+}

+ 0 - 0
fq_plugin_base/src/main/java/com/fq/threelib/gson/internal/bind/TimeTypeAdapter.java


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.