commit 73478f0b1f972bc302155bef7655e1dcdf9ceb79 Author: lh <1209405678@qq.com> Date: Fri Nov 24 09:50:00 2017 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39fb081 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..96cc43e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ec344a3 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..54207cd --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..1024777 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..75a08a9 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,43 @@ +apply plugin: 'com.android.application' +apply plugin: 'android-apt' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.0" + defaultConfig { + applicationId "com.qhclh.ytzh" + minSdkVersion 15 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:26.+' + testCompile 'junit:junit:4.12' + compile 'com.jakewharton:butterknife:8.4.0' + apt 'com.jakewharton:butterknife-compiler:8.4.0' + compile 'com.github.bumptech.glide:glide:3.7.0' + compile 'com.android.support:support-v4:26.0.0-alpha1' + compile 'com.android.support:design:26.0.0-alpha1' + compile 'org.greenrobot:eventbus:3.0.0' + compile 'com.umeng.analytics:analytics:latest.integration' + compile 'pub.devrel:easypermissions:0.2.1' + compile 'com.google.code.gson:gson:2.8.1' + compile 'org.litepal.android:core:1.3.0' + compile 'com.journeyapps:zxing-android-embedded:3.5.0' + compile 'com.google.zxing:core:3.3.0' + compile project(':jsonrpc') +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..2f7a964 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in F:\SDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/qhclh/ytzh/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/qhclh/ytzh/ExampleInstrumentedTest.java new file mode 100644 index 0000000..198dcdb --- /dev/null +++ b/app/src/androidTest/java/com/qhclh/ytzh/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.qhclh.ytzh; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.qhclh.ytzh", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..cbd9572 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/litepal.xml b/app/src/main/assets/litepal.xml new file mode 100644 index 0000000..8590a8f --- /dev/null +++ b/app/src/main/assets/litepal.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/qhclh/ytzh/base/BaseActivity.java b/app/src/main/java/com/qhclh/ytzh/base/BaseActivity.java new file mode 100644 index 0000000..a9638be --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/base/BaseActivity.java @@ -0,0 +1,99 @@ +package com.qhclh.ytzh.base; + +import android.os.Bundle; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + + +import com.qhclh.ytzh.R; +import com.qhclh.ytzh.utils.ActivityCollector; +import com.umeng.analytics.MobclickAgent; + +import butterknife.ButterKnife; + + +/** + * Created by 青花瓷 on 2017/7/19. + */ + +public abstract class BaseActivity extends AppCompatActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(setLayoutId()); + ButterKnife.bind(this); + ActivityCollector.addActivity(this); + initView(); + initData(); + initOper(); + } + + protected abstract int setLayoutId(); + protected abstract void initView(); + protected abstract void initData(); + protected abstract void initOper(); + ///< toolbar设置 + public void initToolbar(Toolbar toolbar, String title, View.OnClickListener listener) { + if (toolbar == null) { + return; + } + TextView tvTitle = findById(toolbar, R.id.tv_toolbar_title); + if (tvTitle != null) { + tvTitle.setText(title == null ? "" : title); + } + setSupportActionBar(toolbar); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayShowTitleEnabled(false); + } + if (listener != null) { + toolbar.setNavigationIcon(R.drawable.back); + toolbar.setNavigationOnClickListener(listener); + } + } + + @Override + protected void onResume() { + super.onResume(); + try { + MobclickAgent.onResume(this); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + protected void onPause() { + super.onPause(); + try { + MobclickAgent.onPause(this); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + @Override + protected void onDestroy() { + super.onDestroy(); + ActivityCollector.removeActivity(this); + } + + public void finishAll(){ActivityCollector.finishAll();} + + public void showToast(String str) { + Toast.makeText(this, str, Toast.LENGTH_SHORT).show(); + } + + protected T findById(@NonNull View view, @IdRes int resId) { + return ButterKnife.findById(view, resId); + } +} diff --git a/app/src/main/java/com/qhclh/ytzh/configs/ConfigBase.java b/app/src/main/java/com/qhclh/ytzh/configs/ConfigBase.java new file mode 100644 index 0000000..c872861 --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/configs/ConfigBase.java @@ -0,0 +1,31 @@ +package com.qhclh.ytzh.configs; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import com.qhclh.ytzh.utils.StringUtil; + + +public abstract class ConfigBase { + String mName; + Context mContext; + + public ConfigBase(Context context, String name) { + mName = name; + mContext = context; + } + + public SharedPreferences getStore() { + if (StringUtil.IsNullOrEmpty(mName)) { + return PreferenceManager.getDefaultSharedPreferences(mContext); + } else { + return mContext.getSharedPreferences(mName, Context.MODE_PRIVATE); + } + } + + public abstract void GetValues(SharedPreferences store); + + public abstract void SetValues(SharedPreferences.Editor store); + +} diff --git a/app/src/main/java/com/qhclh/ytzh/configs/ConfigUtil.java b/app/src/main/java/com/qhclh/ytzh/configs/ConfigUtil.java new file mode 100644 index 0000000..b0f6cec --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/configs/ConfigUtil.java @@ -0,0 +1,29 @@ +package com.qhclh.ytzh.configs; + +import android.content.Context; +import android.content.SharedPreferences; + + +public class ConfigUtil { + public static void Fill(T config) { + SharedPreferences store = config.getStore(); + config.GetValues(store); + } + + public static void Save(T config) { + SharedPreferences store = config.getStore(); + SharedPreferences.Editor editor = store.edit(); + editor.clear(); + config.SetValues(editor); + editor.commit(); + } + + public static String getServerUri(Context context) { + ServerConfig config = new ServerConfig(context); + Fill(config); + return config.getServerUri(); + } + + +} + diff --git a/app/src/main/java/com/qhclh/ytzh/configs/LastLoginInfo.java b/app/src/main/java/com/qhclh/ytzh/configs/LastLoginInfo.java new file mode 100644 index 0000000..eadb74f --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/configs/LastLoginInfo.java @@ -0,0 +1,32 @@ +package com.qhclh.ytzh.configs; + +import android.content.Context; +import android.content.SharedPreferences; + + +public class LastLoginInfo extends ConfigBase { + + public LastLoginInfo(Context context) { + super(context, "LastLoginUserInfo"); + // TODO Auto-generated constructor stub + } + + @Override + public void GetValues(SharedPreferences store) { + Username = store.getString("Username", ""); + Password = store.getString("Password", ""); + CurrentTimeMillis = store.getLong("CurrentTimeMillis", 0); + } + + @Override + public void SetValues(SharedPreferences.Editor store) { + store.putString("Username", Username); + store.putString("Password", Password); + store.putLong("CurrentTimeMillis", System.currentTimeMillis()); + } + + public String Username; + public String Password; + public long CurrentTimeMillis; + +} diff --git a/app/src/main/java/com/qhclh/ytzh/configs/LoginUserConfig.java b/app/src/main/java/com/qhclh/ytzh/configs/LoginUserConfig.java new file mode 100644 index 0000000..37ada9f --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/configs/LoginUserConfig.java @@ -0,0 +1,31 @@ +package com.qhclh.ytzh.configs; + +import android.content.Context; +import android.content.SharedPreferences; + + +public class LoginUserConfig extends ConfigBase { + + public LoginUserConfig(Context context) { + super(context, "LoginUserConfig"); + } + + @Override + public void GetValues(SharedPreferences store) { + Username = store.getString("Username", ""); + Password = store.getString("Password", ""); + DisplayName = store.getString("DisplayName", ""); + } + + @Override + public void SetValues(SharedPreferences.Editor store) { + store.putString("Username", Username); + store.putString("Password", Password); + store.putString("DisplayName", DisplayName); + } + + public String Username; + public String Password; + public String DisplayName; + +} diff --git a/app/src/main/java/com/qhclh/ytzh/configs/ServerConfig.java b/app/src/main/java/com/qhclh/ytzh/configs/ServerConfig.java new file mode 100644 index 0000000..05e9ee4 --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/configs/ServerConfig.java @@ -0,0 +1,93 @@ +package com.qhclh.ytzh.configs; + +import android.content.Context; +import android.content.SharedPreferences; + +import org.forks.jsonrpc.RpcFacade; + + +public class ServerConfig extends ConfigBase { + + String JPUSH_TAG_KEY="jpushtagkey"; + String SYSTEMSET_ENABLE_SCAN="systemset_enable_scan"; + String ENABLE_Catogray="systemset_enable_catogray"; + String ENABLE_MoneyTipe="systemset_enable_moneytipe"; + String ENABLE_StoreGoods="systemset_enable_storegoods"; + + public ServerConfig(Context context) { + super(context, ""); + } + + @Override + public void GetValues(SharedPreferences store) { + mServerUri = store.getString(RpcFacade.SERVER_URI_PREFKEY, ""); + mTag = store.getString(JPUSH_TAG_KEY, ""); + mEnableGoodsScan = store.getBoolean(SYSTEMSET_ENABLE_SCAN,false); + mEnableGoodsCatogray = store.getBoolean(ENABLE_Catogray,false); + mEnablMoneyTipe = store.getBoolean(ENABLE_MoneyTipe,false); + mEnablStoreGoods = store.getBoolean(ENABLE_StoreGoods,false); + + } + + @Override + public void SetValues(SharedPreferences.Editor store) { + store.putString(RpcFacade.SERVER_URI_PREFKEY, getServerUri()); + store.putString(JPUSH_TAG_KEY, getTag()); + store.putBoolean(SYSTEMSET_ENABLE_SCAN,getEnableGoodsScan()); + store.putBoolean(ENABLE_Catogray,getEnableGoodsCatogray()); + store.putBoolean(ENABLE_MoneyTipe,getEnableMoneyTipe()); + store.putBoolean(ENABLE_StoreGoods,getEnableStoreGoods()); + } + + private String mServerUri; + private String mTag; + + public String getServerUri() { + return mServerUri; + } + + public void setServerUri(String value) { + mServerUri = value; + } + + + public String getTag() { + return mTag; + } + + public void setTag(String value) { + mTag = value; + } + + private boolean mEnableGoodsScan; + public boolean getEnableGoodsScan() { + return mEnableGoodsScan; + } + public void setEnableGoodsScang(boolean value) { + mEnableGoodsScan = value; + } + + private boolean mEnableGoodsCatogray; + public boolean getEnableGoodsCatogray() { + return mEnableGoodsCatogray; + } + public void setEnableGoodsCatogray(boolean value) { + mEnableGoodsCatogray = value; + } + + private boolean mEnablMoneyTipe; + public boolean getEnableMoneyTipe() { + return mEnablMoneyTipe; + } + public void setEnableMoneyTipe(boolean value) { + mEnablMoneyTipe = value; + } + + private boolean mEnablStoreGoods; + public boolean getEnableStoreGoods() { + return mEnablStoreGoods; + } + public void setEnableStoreGoods(boolean value) { + mEnablStoreGoods = value; + } +} diff --git a/app/src/main/java/com/qhclh/ytzh/home/MainActivity.java b/app/src/main/java/com/qhclh/ytzh/home/MainActivity.java new file mode 100644 index 0000000..2b7d37f --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/home/MainActivity.java @@ -0,0 +1,43 @@ +package com.qhclh.ytzh.home; + +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.widget.TextView; + +import com.qhclh.ytzh.R; +import com.qhclh.ytzh.base.BaseActivity; + +import butterknife.BindView; + +public class MainActivity extends BaseActivity { + @BindView(R.id.textaaa) + TextView textView; + + @BindView(R.id.toolbar) + Toolbar mToolbar; + + @Override + protected int setLayoutId() { + return R.layout.act_main; + } + + @Override + protected void initView() { + initToolbar(mToolbar, "aaaaa", new View.OnClickListener() { + @Override + public void onClick(View view) { + finish(); + } + }); + } + + @Override + protected void initData() { + textView.setText("aaaaaaaaaaa"); + } + + @Override + protected void initOper() { + + } +} diff --git a/app/src/main/java/com/qhclh/ytzh/home/MyApplication.java b/app/src/main/java/com/qhclh/ytzh/home/MyApplication.java new file mode 100644 index 0000000..9e4fbb8 --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/home/MyApplication.java @@ -0,0 +1,61 @@ +package com.qhclh.ytzh.home; + +import android.app.Activity; + +import com.qhclh.ytzh.configs.ConfigUtil; +import com.qhclh.ytzh.configs.LastLoginInfo; +import com.qhclh.ytzh.configs.ServerConfig; + +import org.forks.jsonrpc.RpcFacade; +import org.litepal.LitePalApplication; + +import java.util.LinkedList; +import java.util.List; + +/** + * Created by 青花瓷 on 2017/11/23. + */ + +public class MyApplication extends LitePalApplication { + + private final String NAME ="ytzh"; + static LastLoginInfo mLoginInfo; + private static MyApplication instance; + String url= RpcUrl.BaseAppUrl; + ServerConfig config; + + private List activityList = new LinkedList(); + + public static MyApplication getInstance(){ + if (null == instance) { + instance = new MyApplication(); + } + return instance; + } + @Override + public void onCreate() { + super.onCreate(); + + mLoginInfo = new LastLoginInfo(this); + ConfigUtil.Fill(mLoginInfo); + + ///< 先暂时必须设置URL + config = new ServerConfig(this); + config.setServerUri(url); + ConfigUtil.Save(config); + + RpcFacade.init(this.getApplicationContext(),NAME); + } + + public void exit() { + for (Activity activity : activityList) { + activity.finish(); + } + System.exit(0); + } + + public static void ClearLogin() { + mLoginInfo.Username = ""; + ConfigUtil.Save(mLoginInfo); + } +} diff --git a/app/src/main/java/com/qhclh/ytzh/home/RpcUrl.java b/app/src/main/java/com/qhclh/ytzh/home/RpcUrl.java new file mode 100644 index 0000000..ef3b534 --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/home/RpcUrl.java @@ -0,0 +1,11 @@ +package com.qhclh.ytzh.home; + +public class RpcUrl { + + ///< 域名开发 +// public static String BaseAppUrl = "http://192.168.1.9/MarketPlace/"; + ///< 正式服务器地址 + public static String BaseAppUrl = "http://47.93.175.172/Marketplace/"; + ///< 外网测试 +// public static String BaseAppUrl = "http://111.198.52.49:81/MarketPlace/"; +} diff --git a/app/src/main/java/com/qhclh/ytzh/interfaces/OnPromptListener.java b/app/src/main/java/com/qhclh/ytzh/interfaces/OnPromptListener.java new file mode 100644 index 0000000..109aac2 --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/interfaces/OnPromptListener.java @@ -0,0 +1,7 @@ +package com.qhclh.ytzh.interfaces; + + +public interface OnPromptListener { + public void OnPrompt(String prompt); + +} diff --git a/app/src/main/java/com/qhclh/ytzh/splash/SplashActivity.java b/app/src/main/java/com/qhclh/ytzh/splash/SplashActivity.java new file mode 100644 index 0000000..6850548 --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/splash/SplashActivity.java @@ -0,0 +1,66 @@ +package com.qhclh.ytzh.splash; + +import android.content.Intent; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.widget.LinearLayout; + +import com.qhclh.ytzh.R; +import com.qhclh.ytzh.base.BaseActivity; +import com.qhclh.ytzh.home.MainActivity; + +import butterknife.BindView; + +/** + * Created by 青花瓷 on 2017/11/23. + */ + +public class SplashActivity extends BaseActivity { + @BindView(R.id.ll_splash) + LinearLayout ll_splash; + + @Override + protected int setLayoutId() { + return R.layout.act_splash; + } + + @Override + protected void initView() { + + } + + @Override + protected void initData() { + + } + + @Override + protected void initOper() { + + //渐变展示启动屏 + AlphaAnimation aa = new AlphaAnimation(0.3f, 1.0f); + aa.setDuration(2000); + ll_splash.startAnimation(aa); + aa.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationEnd(Animation arg0) { + redirectTo(); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + + @Override + public void onAnimationStart(Animation animation) { + } + + }); + + } + + private void redirectTo() { + startActivity(new Intent(this, MainActivity.class)); + finish(); + } +} diff --git a/app/src/main/java/com/qhclh/ytzh/tasks/CallableTask.java b/app/src/main/java/com/qhclh/ytzh/tasks/CallableTask.java new file mode 100644 index 0000000..687c922 --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/tasks/CallableTask.java @@ -0,0 +1,115 @@ +package com.qhclh.ytzh.tasks; + +import android.app.ProgressDialog; +import android.content.Context; +import android.os.AsyncTask; +import android.util.Log; + + +import com.qhclh.ytzh.utils.DialogUtil; + +import java.util.concurrent.Callable; + +public abstract class CallableTask implements Callable { + final String tag = "CallableTask"; + + Boolean mIsRunning = false; + + protected abstract void successUI(); + + void failUI(Exception e) { + Log.e(tag, e.getMessage(), e); + if(e.getMessage().contains("Unauthorized")) + { + return; + } + if(e.getMessage().contains("Socket")) + { + return; + } + if(e.getMessage().contains("cannot")) + { + return; + } + + DialogUtil.AutoHiddenAlert(mContext,e.getMessage()); +// DialogUtil.Alert(mContext, e.getMessage()); + } + + Context mContext; + ProgressDialog mDialog; + String mMessage; + + public CallableTask(Context context, String message) { + mContext = context; + mMessage = message; + } + + public final void execute() { + if (mIsRunning) { + Log.w(tag, "任务重复执行"); + return; + } + new Task().execute(); + } + + class Task extends AsyncTask { + Exception mException; + + @Override + protected Void doInBackground(Void... params) { + try { + mIsRunning = true; + call(); + } catch (Exception ex) { + mException = ex; + cancel(false); + } + return null; + } + + @Override + protected void onCancelled() { + super.onCancelled(); + endExecute(); + failUI(mException); + } + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + endExecute(); + successUI(); + } + + + public void endExecute() { +// if (mDialog != null && isValidContext(mContext) && mDialog.isShowing()) { + if (mDialog != null) { + mDialog.dismiss(); + } + mIsRunning = false; + + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); +// if (isValidContext(mContext)) { + mDialog = ProgressDialog.show(mContext, "请稍等", mMessage, true); +// } + } + + } + +// private boolean isValidContext (Context c){ +// Activity a = (Activity)c; +// if (a.isDestroyed() || a.isFinishing()){ +// return false; +// }else{ +// return true; +// } +// } + + +} diff --git a/app/src/main/java/com/qhclh/ytzh/tasks/ViewOnClickTask.java b/app/src/main/java/com/qhclh/ytzh/tasks/ViewOnClickTask.java new file mode 100644 index 0000000..855c10c --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/tasks/ViewOnClickTask.java @@ -0,0 +1,39 @@ +package com.qhclh.ytzh.tasks; + +import android.content.Context; +import android.content.DialogInterface; +import android.view.View; + +import com.qhclh.ytzh.utils.DialogUtil; +import com.qhclh.ytzh.utils.StringUtil; + + +public abstract class ViewOnClickTask extends CallableTask implements + View.OnClickListener { + + private String mPrompt; + + public ViewOnClickTask(Context context, String message) { + super(context, message); + } + + public void setPrompt(String prompt) { + mPrompt = prompt; + } + + public void onClick(View v) { + + if (!StringUtil.IsNullOrEmpty(mPrompt)) { + DialogUtil.Confirm(v.getContext(), mPrompt, "确定", "取消", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + ViewOnClickTask.this.execute(); + } + }); + } else { + super.execute(); + } + + } + +} diff --git a/app/src/main/java/com/qhclh/ytzh/utils/ActivityCollector.java b/app/src/main/java/com/qhclh/ytzh/utils/ActivityCollector.java new file mode 100644 index 0000000..7446e66 --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/utils/ActivityCollector.java @@ -0,0 +1,36 @@ +package com.qhclh.ytzh.utils; + +import android.app.Activity; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by jiangxk on 2016/11/29. + */ + +public class ActivityCollector { + private ActivityCollector() { + + } + + private static List sActivityList = new ArrayList<>(); + + public static void addActivity(Activity activity) { + if (!sActivityList.contains(activity)) { + sActivityList.add(activity); + } + } + + public static void removeActivity(Activity activity) { + sActivityList.remove(activity); + } + + public static void finishAll() { + for (Activity activity : sActivityList) { + if (!activity.isFinishing()) { + activity.finish(); + } + } + } +} diff --git a/app/src/main/java/com/qhclh/ytzh/utils/DateTimeUtil.java b/app/src/main/java/com/qhclh/ytzh/utils/DateTimeUtil.java new file mode 100644 index 0000000..c5cf18e --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/utils/DateTimeUtil.java @@ -0,0 +1,72 @@ +package com.qhclh.ytzh.utils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + + +public final class DateTimeUtil { + static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + + static SimpleDateFormat dateFormatDetail = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + public static Date ParseDate(String dateStr) throws ParseException { + return dateFormat.parse(dateStr); + } + public static Date ParseDateDetail(String dateStr) throws ParseException { + return dateFormatDetail.parse(dateStr); + } + + public static Date SafeParseDate(String dateStr) { + try { + if (StringUtil.IsNullOrEmpty(dateStr)) { + return new Date(); + } + return ParseDate(dateStr); + } catch (ParseException e) { + return new Date(); + } + } + + + public static Date SafeParseDateDetail(String dateStr) { + try { + if (StringUtil.IsNullOrEmpty(dateStr)) { + return new Date(); + } + return ParseDateDetail(dateStr); + } catch (ParseException e) { + return new Date(); + } + } + + public static Calendar SafeParseCalendar(String dateStr) { + Calendar calendar = Calendar.getInstance(Locale.getDefault()); + calendar.setTime(SafeParseDate(dateStr)); + return calendar; + } + + public static String FormatDate(Calendar calendar) { + return dateFormat.format(calendar.getTime()); + } + + public static String FormatDate(Date date) { + if (date == null) { + return ""; + } + return dateFormat.format(date); + } + public static String FormatDateDetail(Date date) { + if (date == null) { + return ""; + } + return dateFormatDetail.format(date); + } + + public static Date RemoveTime(Date time) { + return SafeParseDate(FormatDate(time)); + } + +} diff --git a/app/src/main/java/com/qhclh/ytzh/utils/DialogUtil.java b/app/src/main/java/com/qhclh/ytzh/utils/DialogUtil.java new file mode 100644 index 0000000..cdd3ea0 --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/utils/DialogUtil.java @@ -0,0 +1,189 @@ +package com.qhclh.ytzh.utils; + +import android.app.Activity; +import android.app.DatePickerDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Handler; +import android.support.v7.app.AlertDialog; +import android.view.View; +import android.widget.DatePicker; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + + +import com.qhclh.ytzh.interfaces.OnPromptListener; + +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.Callable; + + +public class DialogUtil { + abstract static class ProgressDialogHelper { + Context mContext; + String mMessage; + Callable mRunnable; + Handler mHandler = new Handler(); + + Callable mCallableAfterSuccess; + + public void setCallableAfterSuccess(Callable callable) { + mCallableAfterSuccess = callable; + } + + public ProgressDialogHelper(Context context, String message, + Callable runnable) { + this.mContext = context; + this.mMessage = message; + this.mRunnable = runnable; + } + + ProgressDialog mDialog; + + public void Start() { + mDialog = ProgressDialog.show(mContext, "请稍等", mMessage, true); + Thread thread = new Thread() { + @Override + public void run() { + try { + mRunnable.call(); + } catch (Exception ex) { + ex.printStackTrace(); + Runnable runnable = new Runnable() { + + public void run() { + DialogUtil.Alert(mContext, mMessage); + + } + + String mMessage; + + public Runnable setMessage(String message) { + mMessage = message; + return this; + } + + }.setMessage(ex.getMessage()); + mHandler.post(runnable); + } finally { + mDialog.dismiss(); + } + } + + }; + thread.start(); + } + } + + public static void Progress(Context context, String message, + Callable callable) { + new ProgressDialogHelper(context, message, callable) { + }.Start(); + } + + public static void Progress(Context context, String message, + Callable callable, Callable callableAfterSuccess) { + ProgressDialogHelper helper = new ProgressDialogHelper(context, + message, callable) { + }; + + helper.Start(); + + } + + public static void Alert(Context context, String message) { + new AlertDialog.Builder(context).setMessage(message) + .setPositiveButton("确定", null).show(); + } + + public static void AlertAndFinish(final Activity activity, String message) { + new AlertDialog.Builder(activity).setMessage(message) + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + activity.finish(); + } + + }).show(); + } + + public static void Alert(View view, String message) { + Alert(view.getContext(), message); + } + + public static void AutoHiddenAlert(Context context, String message) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + } + + public static void AutoHiddenAlert(View view, String message) { + AutoHiddenAlert(view.getContext(), message); + } + + public interface SetDate { + void setDate(Date date); + } + + public static void AddSelectDateDialog(TextView arg0, + final SetDate onsetdate) { + arg0.setOnClickListener(new View.OnClickListener() { + + public void onClick(final View view) { + final TextView textView = (TextView) view; + DatePickerDialog.OnDateSetListener onDateSetListener = new DatePickerDialog.OnDateSetListener() { + + public void onDateSet(DatePicker datePicker, int year, + int month, int day) { + Calendar calendar = Calendar.getInstance(); + calendar.set(year, month, day); + textView.setText(DateTimeUtil.FormatDate(calendar)); + onsetdate.setDate(calendar.getTime()); + } + + }; + + String dateStr = textView.getText().toString(); + Calendar calendar = DateTimeUtil.SafeParseCalendar(dateStr); + + new DatePickerDialog(view.getContext(), onDateSetListener, + calendar.get(Calendar.YEAR), calendar + .get(Calendar.MONTH), calendar + .get(Calendar.DAY_OF_MONTH)).show(); + } + + }); + } + + public static void Prompt(Context context, String title, String message, + String inputValue, int inputType, final OnPromptListener onprompt) { + final EditText input = new EditText(context); + input.setText(inputValue); + input.setInputType(inputType); + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(title).setMessage(message).setView(input) + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface arg0, int arg1) { + String prompt = input.getText().toString(); + onprompt.OnPrompt(prompt); + } + }) + .setNegativeButton("取消", new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + + } + }).show(); + + } + + public static void Confirm(Context context, String title, + String trueString, String falseString, + DialogInterface.OnClickListener onConfirm) { + new AlertDialog.Builder(context).setTitle(title) + .setPositiveButton(trueString, onConfirm) + .setNegativeButton(falseString, null).show(); + } + +} diff --git a/app/src/main/java/com/qhclh/ytzh/utils/StringUtil.java b/app/src/main/java/com/qhclh/ytzh/utils/StringUtil.java new file mode 100644 index 0000000..af88171 --- /dev/null +++ b/app/src/main/java/com/qhclh/ytzh/utils/StringUtil.java @@ -0,0 +1,30 @@ +package com.qhclh.ytzh.utils; + +import android.content.Context; + +import java.math.BigDecimal; + +public class StringUtil { + public static Boolean IsNullOrEmpty(String s) { + return s == null || s.length() == 0; + } + + public String GetResource(Context context, int resId) { + return context.getString(resId); + } + + public static String ToString(Object object) { + if (object == null) { + return ""; + } + return String.format("%s", object); + } + + public static BigDecimal ParseMoney(String strValue) { + if (IsNullOrEmpty(strValue)) { + return null; + } + return BigDecimal.valueOf(Double.valueOf(strValue)); + } + +} diff --git a/app/src/main/res/anim/anim_in_left.xml b/app/src/main/res/anim/anim_in_left.xml new file mode 100644 index 0000000..958fdf9 --- /dev/null +++ b/app/src/main/res/anim/anim_in_left.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_in_right.xml b/app/src/main/res/anim/anim_in_right.xml new file mode 100644 index 0000000..8d1ca6b --- /dev/null +++ b/app/src/main/res/anim/anim_in_right.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/anim/anim_out_left.xml b/app/src/main/res/anim/anim_out_left.xml new file mode 100644 index 0000000..2ba701e --- /dev/null +++ b/app/src/main/res/anim/anim_out_left.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/anim/anim_out_right.xml b/app/src/main/res/anim/anim_out_right.xml new file mode 100644 index 0000000..e462ded --- /dev/null +++ b/app/src/main/res/anim/anim_out_right.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable-xhdpi/back.png b/app/src/main/res/drawable-xhdpi/back.png new file mode 100644 index 0000000..cae70d1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/back.png differ diff --git a/app/src/main/res/layout/act_main.xml b/app/src/main/res/layout/act_main.xml new file mode 100644 index 0000000..ea24d6c --- /dev/null +++ b/app/src/main/res/layout/act_main.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/act_splash.xml b/app/src/main/res/layout/act_splash.xml new file mode 100644 index 0000000..acaa945 --- /dev/null +++ b/app/src/main/res/layout/act_splash.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/include_line.xml b/app/src/main/res/layout/include_line.xml new file mode 100644 index 0000000..645cb3f --- /dev/null +++ b/app/src/main/res/layout/include_line.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/include_tab_bar.xml b/app/src/main/res/layout/include_tab_bar.xml new file mode 100644 index 0000000..e4b9dfb --- /dev/null +++ b/app/src/main/res/layout/include_tab_bar.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/include_tool_bar.xml b/app/src/main/res/layout/include_tool_bar.xml new file mode 100644 index 0000000..313814b --- /dev/null +++ b/app/src/main/res/layout/include_tool_bar.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..9a078e3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..efc028a Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..3af2608 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..9bec2e6 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..aee44e1 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..34947cd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..2947e28 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,24 @@ + + + + #E3383B + + #E3383B + + #E3383B + + + #999999 + #EEEEEE + #E3383B + #E6E3383B + #eeee4242 + #ffffff + #000000 + #767c82 + #282828 + #00000000 + #a3a3a3 + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..86b6c8d --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,174 @@ + + + 16dp + 16dp + + + 6sp + 10sp + 12sp + 14sp + 16sp + 18sp + 20sp + 22sp + 24sp + 26sp + 30sp + 32sp + 34sp + 38sp + 48sp + + 0dp + 0.1dp + 0.2dp + 0.4dp + 0.5dp + 1dp + 2dp + 3dp + 5dp + 6dp + 7dp + 8dp + 10dp + 11dp + 12dp + 13dp + 14dp + 15dp + 16dp + 18dp + 20dp + 22dp + 24dp + 25dp + 26dp + 28dp + 30dp + 32dp + 34dp + 35dp + 36dp + 38dp + 40dp + 42dp + 44dp + 46dp + 48dp + 50dp + 52dp + 53dp + 54dp + 55dp + 56dp + 59dp + 60dp + 62dp + 64dp + 65dp + 66dp + 68dp + 69dp + 70dp + 72dp + 74dp + 75dp + 78dp + 80dp + 82dp + 84dp + 85dp + 86dp + 88dp + 90dp + 94dp + 95dp + 96dp + 98dp + 100dp + 102dp + 103dp + 105dp + 106dp + 108dp + 110dp + 112dp + 114dp + 117dp + 120dp + 122dp + 124dp + 129dp + 130dp + 138dp + 150dp + 154dp + 156dp + 157dp + 158dp + 172dp + 175dp + 176dp + 180dp + 188dp + 200dp + 207dp + 218dp + 220dp + 230dp + 240dp + 250dp + 252dp + 274dp + 275dp + 280dp + 300dp + 350dp + + 1px + 8px + 20px + + @dimen/text_size_12 + 24dp + 24dp + 8dp + + + + 50dp + + + 50dp + 22dp + + + + + 65dp + + + 50dp + + + 38dp + + + 15dp + + + 16dp + + + 60dp + + + 57dp + + + 15dip + 20dip + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..459caa0 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Ytzh + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..9678078 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/test/java/com/qhclh/ytzh/ExampleUnitTest.java b/app/src/test/java/com/qhclh/ytzh/ExampleUnitTest.java new file mode 100644 index 0000000..1e0680d --- /dev/null +++ b/app/src/test/java/com/qhclh/ytzh/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.qhclh.ytzh; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3fb806a --- /dev/null +++ b/build.gradle @@ -0,0 +1,23 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..aac7c9b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..b5f2530 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Nov 23 10:54:26 CST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/jsonrpc/.gitignore b/jsonrpc/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/jsonrpc/.gitignore @@ -0,0 +1 @@ +/build diff --git a/jsonrpc/build.gradle b/jsonrpc/build.gradle new file mode 100644 index 0000000..d1a24d1 --- /dev/null +++ b/jsonrpc/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion '25.0.0' + defaultConfig { + minSdkVersion 15 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:23.3.0' + compile 'org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client:4.1.2' +} diff --git a/jsonrpc/proguard-rules.pro b/jsonrpc/proguard-rules.pro new file mode 100644 index 0000000..45dc58a --- /dev/null +++ b/jsonrpc/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /opt/android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/jsonrpc/src/androidTest/java/org/forks/jsonrpc/ApplicationTest.java b/jsonrpc/src/androidTest/java/org/forks/jsonrpc/ApplicationTest.java new file mode 100644 index 0000000..5e36ac6 --- /dev/null +++ b/jsonrpc/src/androidTest/java/org/forks/jsonrpc/ApplicationTest.java @@ -0,0 +1,13 @@ +package org.forks.jsonrpc; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/jsonrpc/src/main/AndroidManifest.xml b/jsonrpc/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b553aa1 --- /dev/null +++ b/jsonrpc/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/DFDataTable.java b/jsonrpc/src/main/java/org/forks/jsonrpc/DFDataTable.java new file mode 100644 index 0000000..1d9147b --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/DFDataTable.java @@ -0,0 +1,38 @@ +package org.forks.jsonrpc; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class DFDataTable { + private Map mColumns; + private List mRows; + private RpcObject mSumRow; + + DFDataTable() { + this(new HashMap(), + new ArrayList(), null); + } + + DFDataTable(Map columns, List rows, + RpcObject sumRow) { + super(); + mColumns = columns; + mRows = rows; + mSumRow = sumRow; + } + + public Map getColumns() { + return mColumns; + } + + public List getRows() { + return mRows; + } + + public RpcObject getSumRow() { + return mSumRow; + } + +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/Helper.java b/jsonrpc/src/main/java/org/forks/jsonrpc/Helper.java new file mode 100644 index 0000000..fefb104 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/Helper.java @@ -0,0 +1,76 @@ +package org.forks.jsonrpc; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; + +final class Helper { + static String getString(JSONObject obj, String name) + throws JsonRpcException { + try { + return obj.getString(name); + } catch (JSONException e) { + throw new JsonRpcException(e); + } + } + + static int getInt(JSONObject obj, String name) throws JsonRpcException { + try { + return obj.getInt(name); + } catch (JSONException e) { + throw new JsonRpcException(e); + } + } + + static JSONObject parseJsonObject(String json) throws JsonRpcException { + try { + return (JSONObject) new JSONTokener(json).nextValue(); + } catch (JSONException e) { + throw new JsonRpcException(e); + } + } + + static JSONArray getJsonArray(JSONObject obj, String name) + throws JsonRpcException { + try { + return obj.getJSONArray(name); + } catch (JSONException e) { + throw new JsonRpcException(e); + } + } + + public static JSONArray getJsonArray(JSONArray array, int i) + throws JsonRpcException { + try { + return array.getJSONArray(i); + } catch (JSONException e) { + throw new JsonRpcException(e); + } + } + + static JSONObject getJsonObject(JSONArray arr, int idx) + throws JsonRpcException { + try { + return arr.getJSONObject(idx); + } catch (JSONException e) { + throw new JsonRpcException(e); + } + } + + static Object get(JSONObject obj, String name) throws JsonRpcException { + try { + return obj.get(name); + } catch (JSONException e) { + throw new JsonRpcException(e); + } + } + + public static Object get(JSONArray array, int idx) throws JsonRpcException { + try { + return array.get(idx); + } catch (JSONException e) { + throw new JsonRpcException(e); + } + } +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/HttpJsonRpcClient.java b/jsonrpc/src/main/java/org/forks/jsonrpc/HttpJsonRpcClient.java new file mode 100644 index 0000000..f92dc5a --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/HttpJsonRpcClient.java @@ -0,0 +1,84 @@ +package org.forks.jsonrpc; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpVersion; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.CookieStore; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.util.EntityUtils; + +public final class HttpJsonRpcClient extends JsonRpcClient { + + private final String mRequestUri; + final static CookieStore sCookieStore = new BasicCookieStore(); + private static DefaultHttpClient sHttpClient; + private static String sAppName = "[android] "; + + static { + HttpParams params = new BasicHttpParams(); + HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); + HttpConnectionParams.setSoTimeout(params, 45 * 1000); + sHttpClient = new DefaultHttpClient(params); + sHttpClient.setCookieStore(sCookieStore); + } + + public HttpJsonRpcClient(String requestUri, String method) { + super(method); + mRequestUri = requestUri; + } + + @Override + protected String doRequest(String json) throws JsonRpcException { + HttpPost request = new HttpPost(mRequestUri); + request.setEntity(newStringEntity(json)); + request.setHeader("User-Agent", sAppName); + HttpResponse response = getResponse(request); + return getResponseString(response); + } + + static void sSetClientAppName(String appName) { + sAppName = "[android] " + appName; + } + + private static String getResponseString(HttpResponse response) + throws JsonRpcException { + HttpEntity back = response.getEntity(); + try { + return EntityUtils.toString(back); + } catch (IOException e) { + throw new JsonRpcException(e); + } + } + + private static StringEntity newStringEntity(String json) + throws JsonRpcException { + try { + return new StringEntity(json, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new JsonRpcException(e); + } + } + + private static HttpResponse getResponse(HttpPost request) + throws JsonRpcException { + try { + return sHttpClient.execute(request); + } catch (ClientProtocolException e) { + throw new JsonRpcException(e); + } catch (IOException e) { + throw new JsonRpcException(e); + } + } + +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/InvalidPropertyValueException.java b/jsonrpc/src/main/java/org/forks/jsonrpc/InvalidPropertyValueException.java new file mode 100644 index 0000000..08b5911 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/InvalidPropertyValueException.java @@ -0,0 +1,23 @@ +package org.forks.jsonrpc; + +@SuppressWarnings("serial") +public final class InvalidPropertyValueException extends + JsonRpcRuntimeException { + + public InvalidPropertyValueException() { + } + + public InvalidPropertyValueException(String detailMessage) { + super(detailMessage); + } + + public InvalidPropertyValueException(Throwable throwable) { + super(throwable); + } + + public InvalidPropertyValueException(String detailMessage, + Throwable throwable) { + super(detailMessage, throwable); + } + +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/IterHelper.java b/jsonrpc/src/main/java/org/forks/jsonrpc/IterHelper.java new file mode 100644 index 0000000..fa07168 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/IterHelper.java @@ -0,0 +1,69 @@ +package org.forks.jsonrpc; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.NoSuchElementException; + +final class IterHelper { + private IterHelper() { + } + + public static ArrayList toList(Iterable iter) { + ArrayList result = new ArrayList(); + for (T item : iter) { + result.add(item); + } + return result; + } + + public static ArrayList toList(T... items) { + ArrayList result = new ArrayList(items.length); + for (T item : items) { + result.add(item); + } + return result; + } + + public static int count(Iterable iter) { + int result = 0; + for (@SuppressWarnings("unused") + T item : iter) { + result++; + } + return result; + } + + public static Iterable skip(final int n, final Iterable iter) { + return new Iterable() { + + public Iterator iterator() { + + return new Iterator() { + + Iterator mIter = iter.iterator(); + { + try { + for (int i = 0; i < n; i++) { + mIter.next(); + } + } catch (NoSuchElementException e) { + } + } + + public boolean hasNext() { + return mIter.hasNext(); + } + + public T next() { + return mIter.next(); + } + + public void remove() { + mIter.remove(); + } + }; + } + }; + } + +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcClient.java b/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcClient.java new file mode 100644 index 0000000..0504579 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcClient.java @@ -0,0 +1,93 @@ +package org.forks.jsonrpc; + +import org.json.JSONException; +import org.json.JSONObject; + +import android.util.Log; + +public abstract class JsonRpcClient { + private final String mMethod; + private final static String LOG_TAG = "JsonRpcClient"; + private long requestTime; + + protected JsonRpcClient(String method) { + mMethod = method; + } + + public final JsonRpcResult call(Object... args) throws JsonRpcException { + try { + JSONObject req = newRequest(args); + JSONObject res = request(req); + checkResponseId(req, res); + checkError(res); + return new JsonRpcResult(res.get("result")); + } catch (JSONException e) { + Log.w(LOG_TAG, "", e); + throw new JsonRpcException(e); + } + catch (JsonRpcException e){ + throw e; + } + catch (Exception e){ + throw new JsonRpcException(JsonRpcException.NETWORK_ERROR_CODE,e.getMessage()); + } + } + + private static void checkResponseId(JSONObject req, JSONObject res) + throws JSONException, JsonRpcException { + int reqId = req.getInt("id"); + int resId = res.getInt("id"); + if (reqId != resId) { + throw new JsonRpcException(String.format( + "response id (%d) does not match to request id (%d)", + reqId, resId)); + } + } + + private static void checkError(JSONObject res) throws JSONException, + JsonRpcException { + JSONObject err = res.optJSONObject("error"); + if (err != null) { + int code = err.getInt("code"); + String msg = err.getString("message"); + String trace = err.optString("detail"); + throw new JsonRpcException(code, msg, trace); + } + } + + private JSONObject request(JSONObject req) throws JsonRpcException { + String json = req.toString(); + log_request(json); + String jsonBack = doRequest(json); + log_response(jsonBack); + return Helper.parseJsonObject(jsonBack); + } + + private void log_response(String jsonBack) { + long duration = System.currentTimeMillis() - requestTime; + Log.i(LOG_TAG, String.format("get response using %d ms, response: %s", + duration, jsonBack)); + } + + private void log_request(String reqJson) { + requestTime = System.currentTimeMillis(); + Log.v(LOG_TAG, "start request: " + reqJson); + } + + protected abstract String doRequest(String json) throws JsonRpcException; + + private JSONObject newRequest(Object[] args) throws JSONException { + Object params = RpcTypeParser.toJson(args); + return newRequest(params); + } + + private static int mIdSeed; + + private JSONObject newRequest(Object params) throws JSONException { + JSONObject result = new JSONObject(); + result.put("id", mIdSeed++); + result.put("method", mMethod); + result.put("params", params); + return result; + } +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcException.java b/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcException.java new file mode 100644 index 0000000..7643922 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcException.java @@ -0,0 +1,51 @@ +package org.forks.jsonrpc; + +public final class JsonRpcException extends Exception { + public boolean isNetworkError(){ + return mCode == NETWORK_ERROR_CODE; + } + static int NETWORK_ERROR_CODE = -1; + + private static final long serialVersionUID = -3409287525569584913L; + private int mCode = 0; + private String mTrace; + + public JsonRpcException() { + super(); + } + + public JsonRpcException(String message, Throwable throwable) { + super(message, throwable); + } + + public JsonRpcException(String message) { + super(message); + } + + public JsonRpcException(Throwable throwable) { + super(throwable); + } + + public JsonRpcException(int code, String message) { + this(code, message, null); + } + + public JsonRpcException(int code, String message, String trace) { + super(message); + mCode = code; + mTrace = trace; + } + + public int getCode() { + return mCode; + } + + public boolean is5xxError() { + return mCode >= 500 && mCode < 600; + } + + public String getDetailMessage() { + return mTrace; + } + +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcResult.java b/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcResult.java new file mode 100644 index 0000000..47a4d00 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcResult.java @@ -0,0 +1,179 @@ +package org.forks.jsonrpc; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.json.JSONObject; + +public final class JsonRpcResult { + private final Object mResult; + + JsonRpcResult(Object result) { + super(); + mResult = result; + } + + public Object getRaw() { + return mResult; + } + + public Integer getInt() { + return (Integer) RpcTypeParser.sInt.fromJson(mResult); + } + + public String getString() { + return (String) RpcTypeParser.sString.fromJson(mResult); + } + + public Short getShort() { + return (Short) RpcTypeParser.sShort.fromJson(mResult); + } + + public Long getLong() { + return (Long) RpcTypeParser.sLong.fromJson(mResult); + } + + public Short getByte() { + return (Short) RpcTypeParser.sByte.fromJson(mResult); + } + + public Double getDouble() { + return (Double) RpcTypeParser.sDouble.fromJson(mResult); + } + + public Boolean getBoolean() { + return (Boolean) RpcTypeParser.sBoolean.fromJson(mResult); + } + + public BigDecimal getDecimal() { + return (BigDecimal) RpcTypeParser.sDecimal.fromJson(mResult); + } + + public BigDecimal getMoney() { + return getDecimal(); + } + + public boolean isNull() { + return mResult == JSONObject.NULL; + } + + public Date getDate() { + return (Date) RpcTypeParser.sDate.fromJson(mResult); + } + + public RpcObject getRpcObject(String objPath) throws JsonRpcException { + return (RpcObject) RpcTypeParser.fromJson(mResult, objPath); + } + + public DFDataTable getTable() { + return (DFDataTable) RpcTypeParser.sTable.fromJson(mResult); + } + + public ManyList getManyList(String objPath) throws JsonRpcException { + return getByType("onetomany(" + objPath + ")"); + } + + public List getIntList() throws JsonRpcException { + return getByType("[int]"); + } + + public List getShortList() throws JsonRpcException { + return getByType("[short]"); + } + + public List getByteList() throws JsonRpcException { + return getByType("[byte]"); + } + + public List getLongList() throws JsonRpcException { + return getByType("[long]"); + } + + public List getBooleanList() throws JsonRpcException { + return getByType("[bool]"); + } + + public List getDoubleList() throws JsonRpcException { + return getByType("[double]"); + } + + public List getStringList() throws JsonRpcException { + return getByType("[str]"); + } + + public List getDateList() throws JsonRpcException { + return getByType("[datetime]"); + } + + public List getDecimalList() throws JsonRpcException { + return getByType("[decimal]"); + } + + public List getMoneyList() throws JsonRpcException { + return getByType("[money]"); + } + + public List getRpcObjectList(String objPath) + throws JsonRpcException { + return getByType("[" + objPath + "]"); + } + + public Map getIntMap() throws JsonRpcException { + return getByType("{str:int}"); + } + + public Map getByteMap() throws JsonRpcException { + return getByType("{str:byte}"); + } + + public Map getShortMap() throws JsonRpcException { + return getByType("{str:short}"); + } + + public Map getLongMap() throws JsonRpcException { + return getByType("{str:long}"); + } + + public Map getBooleanMap() throws JsonRpcException { + return getByType("{str:bool}"); + } + + public Map getDoubleMap() throws JsonRpcException { + return getByType("{str:double}"); + } + + public Map getStringMap() throws JsonRpcException { + return getByType("{str:str}"); + } + + public Map getDateMap() throws JsonRpcException { + return getByType("{str:datetime}"); + } + + public Map getDecimalMap() throws JsonRpcException { + return getByType("{str:decimal}"); + } + + public Map getMoneyMap() throws JsonRpcException { + return getByType("{str:money}"); + } + + public Map getRpcObjectMap(String objPath) + throws JsonRpcException { + return getByType("{str:" + objPath + "}"); + } + + private T getByType(String rpcType) throws JsonRpcException { + RpcType type = RpcTypeParser.parse(rpcType); + return (T) type.fromJson(mResult); + } + + JSONObject getJsonObject() { + if (isNull()) { + return null; + } + return (JSONObject) mResult; + } +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcRuntimeException.java b/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcRuntimeException.java new file mode 100644 index 0000000..541673b --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/JsonRpcRuntimeException.java @@ -0,0 +1,22 @@ +package org.forks.jsonrpc; + +public class JsonRpcRuntimeException extends RuntimeException { + + private static final long serialVersionUID = 4017156582402768366L; + + public JsonRpcRuntimeException() { + } + + public JsonRpcRuntimeException(String detailMessage) { + super(detailMessage); + } + + public JsonRpcRuntimeException(Throwable throwable) { + super(throwable); + } + + public JsonRpcRuntimeException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/ManyList.java b/jsonrpc/src/main/java/org/forks/jsonrpc/ManyList.java new file mode 100644 index 0000000..6863741 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/ManyList.java @@ -0,0 +1,199 @@ +package org.forks.jsonrpc; + +import java.util.ArrayList; +import java.util.Collection; + +import android.util.Pair; + +import org.json.JSONException; +import org.json.JSONObject; + +@SuppressWarnings("serial") +public final class ManyList extends ArrayList { + private final RpcObjectMeta mItemMeta; + private final ArrayList mNewItems = new ArrayList(); + private final ArrayList mDeletedItems = new ArrayList(); + + public ManyList(RpcObjectMeta itemMeta) { + mItemMeta = itemMeta; + } + + public static ManyList create(String itemObjectPath) + throws JsonRpcException { + return new ManyList(RpcObjectMeta.get(itemObjectPath)); + } + + @Override + public void add(int index, RpcObject object) { + checkMetaType(object); + addNew(object); + super.add(index, object); + } + + @Override + public boolean add(RpcObject object) { + checkMetaType(object); + addNew(object); + return super.add(object); + } + + @Override + public boolean addAll(Collection collection) { + checkMetaTypes(collection); + addNewObjects(collection); + return super.addAll(collection); + } + + @Override + public boolean addAll(int location, + Collection collection) { + checkMetaTypes(collection); + addNewObjects(collection); + return super.addAll(location, collection); + } + + @Override + public void clear() { + for (RpcObject item : this) { + addDeleted(item); + } + super.clear(); + } + + @Override + public RpcObject remove(int index) { + RpcObject result = super.remove(index); + addDeleted(result); + return result; + } + + @Override + protected void removeRange(int fromIndex, int toIndex) { + for (int i = fromIndex; i < toIndex; i++) { + addDeleted(get(i)); + } + super.removeRange(fromIndex, toIndex); + } + + @Override + public boolean removeAll(Collection collection) { + // it is too complex to test modify track, and rarely used + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection collection) { + // it is too complex to test modify track, and rarely used + throw new UnsupportedOperationException(); + } + + @Override + public RpcObject set(int index, RpcObject object) { + RpcObject result = super.set(index, object); + addDeleted(result); + addNew(object); + return result; + } + + private void addNewObjects(Collection collection) { + for (RpcObject item : collection) { + addNew(item); + } + } + + private void addNew(RpcObject object) { + if (!mDeletedItems.remove(object)) { + mNewItems.add(object); + } + } + + private void addDeleted(RpcObject object) { + if (!mNewItems.remove(object)) { + mDeletedItems.add(object); + } + } + + JSONObject toJson() throws JSONException { + JSONObject result = new JSONObject(); + Pair, Collection> changedUnChanged = getChangedUnChangedItems(); + putArrayField(result, "_data", changedUnChanged.second); + putArrayField(result, "_new", getNewItems()); + putArrayField(result, "_deleted", getDeletedItems()); + putArrayField(result, "_modified", changedUnChanged.first); + + return result; + } + + private static void putArrayField(JSONObject result, String fieldName, + Collection list) throws JSONException { + if (!list.isEmpty()) { + result.put(fieldName, RpcTypeParser.toJson(list)); + } + } + + private void checkMetaTypes(Collection collection) { + if (!RpcFacade.sDebug) { + return; + } + + for (RpcObject rpcObject : collection) { + checkMetaType(rpcObject); + } + } + + private void checkMetaType(RpcObject object) { + if (!RpcFacade.sDebug) { + return; + } + + RpcObjectMeta objMeta = object.getMeta(); + if (objMeta != mItemMeta) { + throwMetaMisMatchError(objMeta); + } + } + + private void throwMetaMisMatchError(RpcObjectMeta objMeta) { + String msg = "RpcObject %s can not add to %s ManyList"; + msg = String.format(msg, objMeta.getName(), mItemMeta.getName()); + throw new IllegalArgumentException(msg); + } + + Collection getNewItems() { + return mNewItems; + } + + Collection getDeletedItems() { + return mDeletedItems; + } + + Pair, Collection> + getChangedUnChangedItems() { + ArrayList changed = new ArrayList(); + ArrayList unchanged = new ArrayList(); + + for (RpcObject item : this) { + if (!isNewObject(item)) { + if (!item.isModified()) { + unchanged.add(item); + } else { + changed.add(item); + } + } + } + + return new Pair, Collection>(changed, + unchanged); + } + + void acceptChanges() { + mNewItems.clear(); + mDeletedItems.clear(); + for (RpcObject obj : this) { + obj.acceptChanges(); + } + } + + private boolean isNewObject(RpcObject item) { + return mNewItems.contains(item); + } +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/NamedValue.java b/jsonrpc/src/main/java/org/forks/jsonrpc/NamedValue.java new file mode 100644 index 0000000..123ce44 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/NamedValue.java @@ -0,0 +1,66 @@ +package org.forks.jsonrpc; + +import java.util.HashMap; + +import android.util.SparseArray; + +public final class NamedValue { + + private final short mValue; + private final String mName; + + private NamedValue(short value, String name) { + super(); + mValue = value; + mName = name; + } + + public short getValue() { + return mValue; + } + + public String getName() { + return mName; + } + + @Override + public String toString() { + return getName(); + } + + private static final HashMap> sInstancePool = new HashMap>(); + + public static synchronized NamedValue create(String idtype, short val) { + SparseArray array = sInstancePool.get(idtype); + if (array == null) { + array = createArray(idtype); + sInstancePool.put(idtype, array); + } + NamedValue result = array.get(val); + if (result == null) { + result = new NamedValue(val, Short.toString(val)); + array.put(val, result); + } + return result; + } + + private static SparseArray createArray(String idtype) { + try { + SparseArray pairs = NamedValueMeta.get(idtype); + SparseArray result = new SparseArray( + pairs.size()); + for (int i = 0; i < pairs.size(); i++) { + int key = pairs.keyAt(i); + NamedValue v = new NamedValue((short) key, pairs.valueAt(i)); + result.append(key, v); + } + return result; + } catch (JsonRpcException e) { + throw new JsonRpcRuntimeException(e); + } + } + + static void reset() { + sInstancePool.clear(); + } +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/NamedValueMeta.java b/jsonrpc/src/main/java/org/forks/jsonrpc/NamedValueMeta.java new file mode 100644 index 0000000..f3bdb22 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/NamedValueMeta.java @@ -0,0 +1,84 @@ +package org.forks.jsonrpc; + +import java.util.HashMap; +import java.util.List; + +import android.util.SparseArray; + +import static org.forks.jsonrpc.RpcFacade.toFullErtPathByPrefixedErt; + +public final class NamedValueMeta { + private static final String NAMED_VALUE_PAIR = "/sbin/NamedValuePair"; + private static final String METHOD = "/sbin/NamedValue/GetDefinition"; + private static final HashMap> sCache = new HashMap>(); + + public static final class Pair { + public final int Value; + public final String Name; + + Pair(int value, String name) { + super(); + Value = value; + Name = name; + } + } + + public static final SparseArray get(String idType) + throws JsonRpcException { + synchronized (sCache) { + SparseArray result = sCache.get(idType); + if (result != null) { + return result; + } + } + + SparseArray result = doGet(idType); + synchronized (sCache) { + sCache.put(idType, result); + } + return result; + } + + private static SparseArray doGet(String idType) + throws JsonRpcException { + String methodPath = toFullErtPathByPrefixedErt(idType, METHOD); + String rpcPath = toFullErtPathByPrefixedErt(idType, NAMED_VALUE_PAIR); + + JsonRpcResult res = RpcFacade.rpcCall(methodPath, idType); + List pairs = res.getRpcObjectList(rpcPath); + return toNamedValueMap(pairs); + } + + private static SparseArray toNamedValueMap(List pairs) { + SparseArray result = new SparseArray(pairs.size()); + for (RpcObject pair : pairs) { + Integer key = pair.getInt("Value"); + String value = pair.getString("Name"); + result.put(key, value); + } + return result; + } + + static void reset() { + sCache.clear(); + } + + static void register(String idType, SparseArray names) { + sCache.put(idType, names); + } + + public static String toName(Short val, String namedValueType) + throws JsonRpcException { + if (val == null) { + return ""; + } + String idType = RpcTypeParser.getNamedValueTidType(namedValueType); + SparseArray names = get(idType); + String result = names.get(val); + if (result == null) { + result = val.toString(); + } + return result; + } + +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/NoSuchRpcPropertyException.java b/jsonrpc/src/main/java/org/forks/jsonrpc/NoSuchRpcPropertyException.java new file mode 100644 index 0000000..1de5a31 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/NoSuchRpcPropertyException.java @@ -0,0 +1,22 @@ +package org.forks.jsonrpc; + +@SuppressWarnings("serial") +public final class NoSuchRpcPropertyException extends JsonRpcRuntimeException { + + NoSuchRpcPropertyException() { + super(); + } + + NoSuchRpcPropertyException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + NoSuchRpcPropertyException(String detailMessage) { + super(detailMessage); + } + + NoSuchRpcPropertyException(Throwable throwable) { + super(throwable); + } + +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/RpcFacade.java b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcFacade.java new file mode 100644 index 0000000..d35cc88 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcFacade.java @@ -0,0 +1,269 @@ +package org.forks.jsonrpc; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Build; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.util.Log; +import android.webkit.CookieManager; +import android.webkit.CookieSyncManager; +import android.webkit.WebView; + +import org.apache.http.cookie.Cookie; + +public final class RpcFacade { + private static final PreferenceMonitor PREFERENCE_MONITOR = new PreferenceMonitor(); + public static final String SERVER_URI_PREFKEY = "ServerUri"; + private static final String DEBUG_PREFKEY = "Debug"; + private static final String TAG = "RpcFacade"; + private static final String REST_URI = ""; + private static String sRestUri = REST_URI; + private static Context sContext; + private static String sUsername = ""; + private static String sPasswd; + private static final String LOGOUT_METHOD = "/MainSystem/MainSystem/Auth/Logout"; + private static final String LOGIN_METHOD = "/MainSystem/MainSystem/Auth/Login"; + static boolean sDebug = true; + + // keep reference + @SuppressWarnings("unused") + private static KeepSession sKeepSession; + + private RpcFacade() { + } + + public static void syncCookie(WebView webView){ + CookieSyncManager.createInstance(webView.getContext()); + + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.setAcceptCookie(true); + cookieManager.removeAllCookie(); + for(Cookie cookie:HttpJsonRpcClient.sCookieStore.getCookies()){ + if(cookie.getName().equals("EasyAuth")) { + String cookieString = cookie.getName() + "=" + cookie.getValue() + "; path=" + cookie.getPath(); + cookieManager.setCookie(RpcFacade.getServerUri(), cookieString); + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + cookieManager.flush(); + } + + CookieSyncManager.getInstance().sync(); + + } + + public static void init(Context context, String appName) { + Context appContext = context.getApplicationContext(); + if (sContext == null) { + Log.v(TAG, "init RpcFacade, appname: " + appName); + sContext = appContext; + sKeepSession = new KeepSession(); + HttpJsonRpcClient.sSetClientAppName(appName); + initPreference(); + } else if (sContext != appContext) { + Log.v(TAG, "invalid reinit RpcFacade"); + String message = "RpcFacade is inited, can not init again"; + throw new JsonRpcRuntimeException(message); + } + } + + public static void setServerUri(String uri){ + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(sContext); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(SERVER_URI_PREFKEY,uri); + editor.commit(); + } + + + public static boolean isInited() { + return sContext != null; + } + + private static void initPreference() { + Log.d(TAG, + "restore settings from preference, monitor preference change"); + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(sContext); + restoreDebugFromPref(prefs); + restoreServerUriFromPref(prefs); + + // OnSharedPreferenceChangeListener is a weeked event, + // we must keep the event listener instance ourself + prefs.registerOnSharedPreferenceChangeListener(PREFERENCE_MONITOR); + } + + public static JsonRpcResult rpcCall(String method, Object... args) + throws JsonRpcException { + try { + return doRpcCall(method, args); + } catch (JsonRpcException e) { + if (e.getCode() == 401 && isLogedIn()) { + autoLogin(); + return doRpcCall(method, args); + } + throw e; + } + } + + public static String getUsername() { + return sUsername; + } + + public static void login(String user, String passwd) + throws JsonRpcException { + Log.i(TAG, "Start login: " + user); + callLogin(user, passwd); + sUsername = user; + sPasswd = passwd; + } + + public static void restoreLogined(String username,String password){ + sUsername = username; + sPasswd = password; + } + + public static void logout() { + try { + if (isLogedIn()) { + Log.i(TAG, "logout user: " + sUsername); + sUsername = ""; + doRpcCall(LOGOUT_METHOD); + } + } catch (JsonRpcException e) { + } + } + + private static void autoLogin() throws JsonRpcException { + String user = sUsername; + String pwd = sPasswd; + if (user.length() > 0) { + callLogin(user, pwd); + } + } + + private static JsonRpcResult doRpcCall(String method, Object... args) + throws JsonRpcException { + JsonRpcClient client = new HttpJsonRpcClient(getRestUri(method), method); + return client.call(args); + } + + private static void callLogin(String user, String passwd) + throws JsonRpcException { + doRpcCall(LOGIN_METHOD, user, passwd); + } + + private static String getRestUri(String method) { + StringBuilder sb = new StringBuilder(); + appendServerUri(sb); + + String subSystem = getSystem(method); + if (!IsMainSystem(subSystem)) { + appendSubSystem(sb, subSystem); + } + + sb.append("Rest.aspx"); + return sb.toString(); + } + + private static boolean IsMainSystem(String subSystem) { + return subSystem.equals("MainSystem"); + } + + private static void appendSubSystem(StringBuilder sb, String subSystem) { + sb.append("O/"); + sb.append(subSystem); + sb.append('/'); + } + + private static void appendServerUri(StringBuilder sb) { + String serverUri = getServerUri(); + if (serverUri.charAt(serverUri.length() - 1) != '/') { + sb.append('/'); + } + sb.append(serverUri); + } + + public static String getServerUri() { + return sRestUri; + } + + static String getSystem(String method) { + int endIdx = method.indexOf('/', 1); + return method.substring(1, endIdx); + } + + public static String toFullErtPathByPrefixedErt(String objPath, + String subMethodPath) { + String subSystem = getSystem(objPath); + return "/" + subSystem + subMethodPath; + } + + public static boolean isLogedIn() { + return sUsername.length() > 0; + } + + private static void restoreServerUriFromPref(SharedPreferences pref) { + sRestUri = pref.getString(SERVER_URI_PREFKEY, REST_URI); + Log.d(TAG, "get serverUri from preference: " + sRestUri); + } + + private static void restoreDebugFromPref(SharedPreferences pref) { + sDebug = pref.getBoolean(DEBUG_PREFKEY, true); + Log.d(TAG, "get debug mode from preference: " + sDebug); + } + + private static class KeepSession implements Runnable { + private static Handler handler; + + public KeepSession() { + if (handler != null) { + throw new RuntimeException("More than one instance?"); + } + handler = new Handler(); + startAgain(); + } + + public void run() { + if (isLogedIn()) { + Log.i(TAG, "connecting to server to maintain session"); + try { + heartBeat(); + } catch (Exception e) { + Log.w(TAG, e.toString()); + } + } else { + Log.i(TAG, "no need to connect server, not logged in"); + } + startAgain(); + } + + private static void heartBeat() throws JsonRpcException { + // use auto login, because the cell phone maybe just awake after + // sleep, and server is kick the user out because of not activity + // in 2 minutes. + rpcCall("/MainSystem/MainSystem/Auth/Ping"); + } + + private void startAgain() { + handler.postDelayed(this, 60 * 1000); + } + } + + private static final class PreferenceMonitor implements + OnSharedPreferenceChangeListener { + + public void onSharedPreferenceChanged( + SharedPreferences sharedPreferences, String key) { + Log.d(TAG, "preference changed, key: " + key); + if (key.equals(DEBUG_PREFKEY)) { + restoreDebugFromPref(sharedPreferences); + } else if (key.equals(SERVER_URI_PREFKEY)) { + restoreServerUriFromPref(sharedPreferences); + } + } + + } +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/RpcObject.java b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcObject.java new file mode 100644 index 0000000..4c5e8d5 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcObject.java @@ -0,0 +1,386 @@ +package org.forks.jsonrpc; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +@SuppressWarnings("serial") +public final class RpcObject extends HashMap { + private final RpcObjectMeta mMeta; + private HashMap mBackup; + + RpcObject(RpcObjectMeta meta) throws JsonRpcException { + super(); + + mMeta = meta; + initValueStore(mMeta.getProps()); + acceptChanges(); + } + + public final static RpcObject create(String objPath) + throws JsonRpcException { + return new RpcObject(RpcObjectMeta.get(objPath)); + } + + public RpcObjectMeta getMeta() { + return mMeta; + } + + public Short getByte(String propName) { + return (Short) get(propName, RpcTypeParser.BYTE); + } + + public void setByte(String propName, Short value) { + set(propName, value, RpcTypeParser.BYTE); + } + + public Integer getInt(String propName) { + return (Integer) get(propName, RpcTypeParser.INT); + } + + public void setInt(String propName, Integer value) { + set(propName, value, RpcTypeParser.INT); + } + + public Short getShort(String propName) { + return (Short) get(propName, RpcTypeParser.SHORT); + } + + public void setShort(String propName, Short value) { + set(propName, value, RpcTypeParser.SHORT); + } + + public Long getLong(String propName) { + return (Long) get(propName, RpcTypeParser.LONG); + } + + public void setLong(String propName, Long value) { + set(propName, value, RpcTypeParser.LONG); + } + + public Double getDouble(String propName) { + return (Double) get(propName, RpcTypeParser.DOUBLE); + } + + public void setDouble(String propName, Double value) { + set(propName, value, RpcTypeParser.DOUBLE); + } + + public BigDecimal getDecimal(String propName) { + return (BigDecimal) get(propName, RpcTypeParser.DECIMAL); + } + + public void setDecimal(String propName, BigDecimal value) { + set(propName, value, RpcTypeParser.DECIMAL); + } + + public BigDecimal getMoney(String propName) { + return (BigDecimal) get(propName, RpcTypeParser.MONEY); + } + + public void setMoney(String propName, BigDecimal value) { + set(propName, value, RpcTypeParser.MONEY); + } + + public Date getDate(String propName) { + return (Date) get(propName, RpcTypeParser.DATE); + } + + public void setDate(String propName, Date value) { + set(propName, value, RpcTypeParser.DATE); + } + + public String getString(String propName) { + return (String) get(propName, RpcTypeParser.STRING); + } + + public String getGuid(String propName){ + return (String)get(propName,RpcTypeParser.GUID); + } + + public void setString(String propName, String value) { + set(propName, value, RpcTypeParser.STRING); + } + + public Integer getLookupValue(String propName) { + return (Integer) get(propName, RpcTypeParser.LOOKUPVALUE); + } + + public void setLookupValue(String propName, Integer value) { + set(propName, value, RpcTypeParser.LOOKUPVALUE); + } + + public NamedValue getNamedValue(String propName) { + checkNamedValueProp(propName); + return (NamedValue) get(propName); + } + + public void setNamedValue(String propName, Short value) { + NamedValue val = toNamedValue(propName, value); + setNamedValue(propName, val); + } + + private NamedValue toNamedValue(String propName, Short value) { + if (value == null) { + return null; + } + RpcPropertyMeta prop = getPropertyMeta(propName); + String idtype = RpcTypeParser.getNamedValueTidType(prop.getType()); + return NamedValue.create(idtype, value); + } + + public void setNamedValue(String propName, NamedValue value) { + checkNamedValueProp(propName); + put(propName, value); + } + + public Long getEntityKey(String propName) { + return (Long) get(propName, RpcTypeParser.ENTITYKEY); + } + + public void setEntityKey(String propName, Long value) { + set(propName, value, RpcTypeParser.ENTITYKEY); + } + + private void initValueStore(Map props) + throws JsonRpcException { + for (RpcPropertyMeta prop : props.values()) { + put(prop.getName(), createDefaultValue(prop)); + } + } + + private static Object createDefaultValue(RpcPropertyMeta prop) + throws JsonRpcException { + if (RpcTypeParser.isManyList(prop.getType())) { + return createManyList(prop); + } else if (RpcTypeParser.isList(prop.getType())) { + return new ArrayList(); + } else if (RpcTypeParser.isMap(prop.getType())) { + return new HashMap(); + } + return prop.getDefaultValue(); + } + + private static ManyList createManyList(RpcPropertyMeta prop) + throws JsonRpcException { + return ManyList + .create(RpcTypeParser.getManyListItemType(prop.getType())); + } + + private void set(String propName, Object value, String type) { + isPropDefined(propName, type); + put(propName, value); + } + + private Object get(String propName, String type) { + isPropDefined(propName, type); + return get(propName); + } + + private void isPropDefined(String propName, String type) { + if (RpcFacade.sDebug) { + RpcPropertyMeta prop = getPropertyMeta(propName); + checkPropertyType(type, prop); + } + } + + private RpcPropertyMeta getPropertyMeta(String propName) { + RpcPropertyMeta prop = mMeta.getProps().get(propName); + if (prop == null) { + throw new NoSuchRpcPropertyException(propName); + } + return prop; + } + + public Boolean getBoolean(String propName) { + return (Boolean) get(propName, RpcTypeParser.BOOL); + } + + public void setBoolean(String propName, Boolean value) { + set(propName, value, RpcTypeParser.BOOL); + } + + public ManyList getManyList(String propName) { + if (RpcFacade.sDebug) { + checkManyListProp(propName); + } + return (ManyList) get(propName); + } + + public List getList(String propName) { + if (RpcFacade.sDebug) { + checkListProp(propName); + } + return (List) get(propName); + } + + public Map getMap(String propName) { + if (RpcFacade.sDebug) { + checkMapProp(propName); + } + return (Map) get(propName); + } + + public RpcObject getRpcObject(String propName) throws JsonRpcException { + checkRpcObjectType(propName, null); + return (RpcObject) get(propName); + } + + public void setRpcObject(String propName, RpcObject value) + throws JsonRpcException { + checkRpcObjectType(propName, value); + put(propName, value); + } + + private void checkPropertyType(String type, RpcPropertyMeta prop) { + if (!prop.getType().equals(type)) { + String msg = String.format( + "Type: %s, prop: %s, prop type: %s, access type: %s", + mMeta.getName(), prop.getName(), prop.getType(), type); + throw new InvalidPropertyValueException(msg); + } + } + + private void checkListProp(String propName) { + RpcPropertyMeta propMeta = getPropertyMeta(propName); + if (!RpcTypeParser.isList(propMeta.getType())) { + String pattern = "Property '%s' is '%s', not List"; + String message = String.format(pattern, propName, + propMeta.getType()); + throw new InvalidPropertyValueException(message); + } + } + + private void checkMapProp(String propName) { + RpcPropertyMeta propMeta = getPropertyMeta(propName); + if (!RpcTypeParser.isMap(propMeta.getType())) { + String pattern = "Property '%s' is '%s', not Map"; + String message = String.format(pattern, propName, + propMeta.getType()); + throw new InvalidPropertyValueException(message); + } + } + + private void checkManyListProp(String propName) { + RpcPropertyMeta propMeta = getPropertyMeta(propName); + if (!RpcTypeParser.isManyList(propMeta.getType())) { + String pattern = "Property '%s' is '%s', not ManyList"; + String message = String.format(pattern, propName, + propMeta.getType()); + throw new InvalidPropertyValueException(message); + } + } + + private void checkRpcObjectType(String propName, RpcObject value) + throws JsonRpcException { + if (!RpcFacade.sDebug) { + return; + } + + RpcPropertyMeta prop = getPropertyMeta(propName); + String propPath = prop.getType(); + if (!RpcTypeParser.isRpcObject(propPath)) { + String pattern = "Property '%s' is '%s', not rpc object"; + String message = String.format(pattern, propName, propPath); + throw new InvalidPropertyValueException(message); + } + + RpcObjectMeta propMeta = RpcObjectMeta.get(propPath); + if (value != null && propMeta != value.mMeta) { + String pattern = "Property '%s' only accept object '%s'"; + String message = String.format(pattern, propName, propPath); + throw new InvalidPropertyValueException(message); + } + } + + private void checkNamedValueProp(String propName) { + RpcPropertyMeta prop = getPropertyMeta(propName); + if (!RpcTypeParser.isNamedValue(prop.getType())) { + String msg = String.format( + "Type: %s, prop: %s, prop type: %s, is not namedvalue", + mMeta.getName(), prop.getName(), prop.getType()); + throw new InvalidPropertyValueException(msg); + } + } + + static RpcObject create(RpcObjectMeta meta, JSONArray tableRow) + throws JsonRpcException { + RpcObject result = new RpcObject(meta); + + // must be LinkedHashMap<> because we rely on the value order + LinkedHashMap props = (LinkedHashMap) meta + .getProps(); + int i = 0; + for (RpcPropertyMeta prop : props.values()) { + String name = prop.getName(); + Object value = Helper.get(tableRow, i); + if (value != null) { + value = RpcTypeParser.fromJson(value, prop.getType()); + } + result.put(name, value); + i++; + } + + result.acceptChanges(); + return result; + } + + JSONObject toJson() throws JSONException { + JSONObject result = new JSONObject(); + for (Entry entry : entrySet()) { + RpcPropertyMeta prop = mMeta.getProps().get(entry.getKey()); + Object value = entry.getValue(); + if (!equalToDefaultValue(prop, value)) { + value = RpcTypeParser.toJson(value); + result.put(entry.getKey(), value); + } + } + return result; + } + + void acceptChanges() { + mBackup = new HashMap(this); + for (RpcPropertyMeta prop : mMeta.getProps().values()) { + if (RpcTypeParser.isManyList(prop.getType())) { + getManyList(prop.getName()).acceptChanges(); + } + } + } + + boolean isModified() { + return !super.equals(mBackup); + } + + @Override + public boolean equals(Object object) { + return object == this; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + private static boolean equalToDefaultValue(RpcPropertyMeta prop, + Object value) { + Object defaultValue = prop.getDefaultValue(); + if (defaultValue == value) { + return true; + } + + if (defaultValue != null) { + return defaultValue.equals(value); + } + + return false; + } + +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/RpcObjectMeta.java b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcObjectMeta.java new file mode 100644 index 0000000..1722eda --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcObjectMeta.java @@ -0,0 +1,75 @@ +package org.forks.jsonrpc; + +import java.util.HashMap; +import java.util.Map; + +import org.json.JSONArray; +import org.json.JSONObject; + +import static org.forks.jsonrpc.Helper.*; +import static org.forks.jsonrpc.RpcFacade.rpcCall; + +public final class RpcObjectMeta { + private final String mName; + private final Map mProps; + + public RpcObjectMeta(String name, Map props) { + super(); + mName = name; + mProps = props; + } + + public String getName() { + return mName; + } + + public Map getProps() { + return mProps; + } + + public static RpcObjectMeta parse(JSONObject jsonObj) + throws JsonRpcException { + String name = getString(jsonObj, "name"); + JSONArray propsJson = getJsonArray(jsonObj, "props"); + Map props = RpcPropertyMeta.parse(propsJson); + return new RpcObjectMeta(name, props); + } + + private static Map sCache = new HashMap(); + + public static RpcObjectMeta get(String objPath) throws JsonRpcException { + RpcObjectMeta result; + synchronized (sCache) { + result = sCache.get(objPath); + if (result != null) { + return result; + } + } + + result = getFromServer(objPath); + synchronized (sCache) { + sCache.put(objPath, result); + } + + return result; + } + + private static RpcObjectMeta getFromServer(String objPath) + throws JsonRpcException { + String getMethod = "/sbin/ObjectMeta/Get"; + String method = RpcFacade.toFullErtPathByPrefixedErt(objPath, getMethod); + JsonRpcResult result = rpcCall(method, objPath); + return parse(result.getJsonObject()); + } + + // only used in unit test + static void registerMeta(String objPath, RpcObjectMeta meta) { + if (sCache.put(objPath, meta) != null) { + throw new JsonRpcRuntimeException(objPath); + } + } + + static void reset() { + sCache.clear(); + } +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/RpcPropertyMeta.java b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcPropertyMeta.java new file mode 100644 index 0000000..f110905 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcPropertyMeta.java @@ -0,0 +1,61 @@ +package org.forks.jsonrpc; + +import java.util.HashMap; +import java.util.Map; + +import org.json.JSONArray; +import org.json.JSONObject; + +import static org.forks.jsonrpc.Helper.*; + +public final class RpcPropertyMeta { + private final String mName; + private final String mType; + private final Object mDefaultValue; + + public RpcPropertyMeta(String name, String type, Object defaultValue) { + super(); + mName = name; + mType = type; + mDefaultValue = defaultValue; + } + + public String getName() { + return mName; + } + + public String getType() { + return mType; + } + + public Object getDefaultValue() { + return mDefaultValue; + } + + static Map parse(JSONArray arr) + throws JsonRpcException { + Map result = new HashMap( + arr.length()); + return parse(result, arr); + } + + static Map parse( + Map result, JSONArray arr) + throws JsonRpcException { + for (int i = 0; i < arr.length(); i++) { + JSONObject item = getJsonObject(arr, i); + RpcPropertyMeta prop = parse(item); + result.put(prop.getName(), prop); + } + return result; + } + + private static RpcPropertyMeta parse(JSONObject json) + throws JsonRpcException { + String name = getString(json, "name"); + String type = getString(json, "type"); + Object defaultValue = json.opt("defaultvalue"); + defaultValue = RpcTypeParser.fromJson(defaultValue, type); + return new RpcPropertyMeta(name, type, defaultValue); + } +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/RpcType.java b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcType.java new file mode 100644 index 0000000..f72a193 --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcType.java @@ -0,0 +1,5 @@ +package org.forks.jsonrpc; + +interface RpcType { + Object fromJson(Object jsonValue); +} diff --git a/jsonrpc/src/main/java/org/forks/jsonrpc/RpcTypeParser.java b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcTypeParser.java new file mode 100644 index 0000000..47bef1f --- /dev/null +++ b/jsonrpc/src/main/java/org/forks/jsonrpc/RpcTypeParser.java @@ -0,0 +1,557 @@ +package org.forks.jsonrpc; + +import static org.forks.jsonrpc.Helper.getJsonObject; + +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public final class RpcTypeParser { + static final String BYTE = "byte"; + static final String SHORT = "short"; + static final String INT = "int"; + static final String LONG = "long"; + static final String BOOL = "bool"; + static final String DOUBLE = "double"; + static final String SINGLE = "single"; + static final String DECIMAL = "decimal"; + static final String DATE = "datetime"; + static final String STRING = "str"; + static final String GUID = "guid"; + static final String BIN = "bin"; + static final String MONEY = "money"; + static final String LOOKUPVALUE = "lookup"; + static final String ENTITYKEY = "entitykey"; + static final String TABLE = "table"; + + public static boolean isManyList(String rpcType) { + return rpcType.startsWith("onetomany(") && rpcType.endsWith(")"); + } + + public static String getManyListItemType(String rpcType) { + return rpcType.substring("onetomany(".length(), rpcType.indexOf(')')); + } + + public static boolean isList(String rpcType) { + return startWith(rpcType, '['); + } + + public static boolean isMap(String rpcType) { + return startWith(rpcType, '{'); + } + + public static boolean isRpcObject(String rpcType) { + return startWith(rpcType, '/'); + } + + public static boolean isNamedValue(String rpcType) { + return rpcType.startsWith("namedvalue(") && rpcType.endsWith(")"); + } + + public static String getNamedValueTidType(String rpcType) { + return rpcType.substring("namedvalue(".length(), rpcType.length() - 1); + } + + private static boolean startWith(String s, char ch) { + return s.length() != 0 && s.charAt(0) == ch; + } + + public static String getMapValueType(String rpcType) { + int start = rpcType.indexOf(':'); + return rpcType.substring(start + 1, rpcType.length() - 1); + } + + public static String getListItemType(String rpcType) { + return rpcType.substring(1, rpcType.length() - 1); + } + + public static Object fromJson(Object jsonValue, String rpcType) + throws JsonRpcException { + if (isNull(jsonValue)) { + return null; + } + + RpcType type = parse(rpcType); + return type.fromJson(jsonValue); + } + + public static Object toJson(Object value) throws JSONException { + if (value == null) { + return JSONObject.NULL; + } else if (value instanceof Date) { + return dateToJson((Date) value); + } else if (value instanceof RpcObject) { + return ((RpcObject) value).toJson(); + } else if (value instanceof ManyList) { + return ((ManyList) value).toJson(); + } else if (value instanceof List) { + return listToJson((List) value); + } else if (value.getClass().isArray()) { + return arrayToJson(value); + } else if (value instanceof Map) { + return mapToJson((Map) value); + } else if (value instanceof NamedValue) { + return namedValueToJson((NamedValue) value); + } else { + return value; + } + } + + private static Object namedValueToJson(NamedValue value) { + return value.getValue(); + } + + private static Object mapToJson(Map map) throws JSONException { + JSONObject result = new JSONObject(); + for (Entry entry : map.entrySet()) { + Object value = entry.getValue(); + value = toJson(value); + result.put((String) entry.getKey(), value); + } + return result; + } + + private static Object listToJson(List coll) throws JSONException { + JSONArray result = new JSONArray(); + for (Object item : coll) { + item = toJson(item); + result.put(item); + } + return result; + } + + private static Object arrayToJson(Object array) throws JSONException { + JSONArray result = new JSONArray(); + int len = Array.getLength(array); + for (int i = 0; i < len; i++) { + Object item = Array.get(array, i); + item = toJson(item); + result.put(item); + } + return result; + } + + private static Object dateToJson(Date val) { + return String.format("/Date(%tQ+0800)/", val); + } + + synchronized static final RpcType parse(String rpcType) + throws JsonRpcException { + RpcType result = sRpcTypeCache.get(rpcType); + if (result == null) { + result = doParse(rpcType); + sRpcTypeCache.put(rpcType, result); + } + return result; + } + + static final boolean isNull(Object jsonObj) { + return jsonObj == null || jsonObj == JSONObject.NULL; + } + + private static final Map sRpcTypeCache = new HashMap(); + + private static RpcType doParse(String rpcType) throws JsonRpcException { + if (rpcType.equals(BOOL)) { + return sBoolean; + } else if (rpcType.equals(BYTE)) { + return sByte; + } else if (rpcType.equals(SHORT)) { + return sShort; + } else if (rpcType.equals(INT)) { + return sInt; + } else if (rpcType.equals(LONG)) { + return sLong; + } else if (rpcType.equals(SINGLE)) { + return sDouble; + } else if (rpcType.equals(DOUBLE)) { + return sDouble; + } else if (rpcType.equals(STRING)) { + return sString; + } else if(rpcType.equals(GUID)){ + return sString; + } else if (rpcType.equals(BIN)){ + return sString; + } + else if (rpcType.equals(DECIMAL)) { + return sDecimal; + } else if (rpcType.equals(DATE)) { + return sDate; + } else if (rpcType.equals(MONEY)) { + return sDecimal; + } else if (rpcType.equals(LOOKUPVALUE)) { + return sInt; + } else if (isNamedValue(rpcType)) { + return new RpcTypeNullWrap(new NamedValueRpcType(rpcType)); + } else if (rpcType.equals(ENTITYKEY)) { + return sLong; + } else if (isList(rpcType)) { + return new RpcTypeNullWrap(new ListRpcType(rpcType)); + } else if (isRpcObject(rpcType)) { + RpcObjectMeta meta = RpcObjectMeta.get(rpcType); + return new RpcTypeNullWrap(new RpcObjectRpcType(meta)); + } else if (isMap(rpcType)) { + return new RpcTypeNullWrap(new MapRpcType(rpcType)); + } else if (isManyList(rpcType)) { + return new RpcTypeNullWrap(new ManyListRpcType(rpcType)); + } else if (rpcType.equals(TABLE)) { + return sTable; + } else { + throw new JsonRpcException("Unknown rpc type '" + rpcType + "'"); + } + } + + private static final RpcType sDirect = new RpcTypeNullWrap( + new DirectRpcType()); + static final RpcType sInt = sDirect; + static final RpcType sBoolean = sDirect; + static final RpcType sShort = new RpcTypeNullWrap(new ShortRpcType()); + static final RpcType sByte = new RpcTypeNullWrap(new ByteRpcType()); + static final RpcType sLong = new RpcTypeNullWrap(new LongRpcType()); + static final RpcType sDouble = new RpcTypeNullWrap(new DoubleRpcType()); + static final RpcType sString = sDirect; + static final RpcType sDecimal = new RpcTypeNullWrap(new DecimalRpcType()); + static final RpcType sDate = new RpcTypeNullWrap(new DateRpcType()); + static final RpcType sTable = new RpcTypeNullWrap(new TableRpcType()); + + static final void reset() { + sRpcTypeCache.clear(); + } + + private static final class NamedValueRpcType implements RpcType { + + private final String mIdtype; + + public NamedValueRpcType(String rpcType) { + mIdtype = getNamedValueTidType(rpcType); + } + + public Object fromJson(Object jsonValue) { + Short val = (Short) sShort.fromJson(jsonValue); + return NamedValue.create(mIdtype, val); + } + + } + + private static final class TableRpcType implements RpcType { + + public Object fromJson(Object jsonValue) { + try { + JSONObject obj = (JSONObject) jsonValue; + Map cols = getTableColumns(obj); + RpcObjectMeta meta = new RpcObjectMeta("table", cols); + ArrayList rows = getTableRows(obj, meta); + RpcObject sumRow = getTableSumRow(obj, meta); + return new DFDataTable(cols, rows, sumRow); + } catch (JsonRpcException e) { + throw new JsonRpcRuntimeException(e); + } + } + + private static Map getTableColumns( + JSONObject obj) throws JsonRpcException { + Map cols; + JSONArray meta = obj.optJSONArray("_meta"); + if (meta != null) { + cols = parseTableCols(meta); + } else { + cols = new HashMap(); + } + return cols; + } + + private static ArrayList getTableRows(JSONObject obj, + RpcObjectMeta rpcObjectMeta) throws JsonRpcException { + ArrayList rows; + JSONArray rowsJson = obj.optJSONArray("rows"); + if (rowsJson == null) { + rows = new ArrayList(); + } else { + rows = doGetTableRows(rowsJson, rpcObjectMeta); + } + return rows; + } + + private static RpcObject getTableSumRow(JSONObject obj, + RpcObjectMeta meta) throws JsonRpcException { + RpcObject sumRow = null; + JSONArray sumRowJson = obj.optJSONArray("sumrow"); + if (sumRowJson != null) { + sumRow = RpcObject.create(meta, sumRowJson); + } + return sumRow; + } + + private static ArrayList doGetTableRows(JSONArray rows, + RpcObjectMeta meta) throws JsonRpcException { + ArrayList result = new ArrayList( + rows.length()); + for (int i = 0; i < rows.length(); i++) { + result.add(RpcObject.create(meta, Helper.getJsonArray(rows, i))); + } + return result; + } + + private static Map parseTableCols( + JSONArray _meta) throws JsonRpcException { + // use LinkedHashMap to maintain column order + Map result = new LinkedHashMap( + _meta.length()); + return RpcPropertyMeta.parse(result, _meta); + } + + } + + private static final class MapRpcType implements RpcType { + + private final RpcType mValueType; + + public MapRpcType(String rpcType) throws JsonRpcException { + checkMapKeyType(rpcType); + String valueType = getMapValueType(rpcType); + mValueType = parse(valueType); + } + + public Object fromJson(Object jsonValue) { + JSONObject obj = (JSONObject) jsonValue; + Map result = new HashMap( + obj.length()); + Iterator iter = obj.keys(); + while (iter.hasNext()) { + String key = iter.next(); + Object value; + try { + value = Helper.get(obj, key); + } catch (JsonRpcException e) { + throw new JsonRpcRuntimeException(e); + } + if (RpcTypeParser.isNull(value)) { + result.put(key, null); + } else { + result.put(key, mValueType.fromJson(value)); + } + } + return result; + } + + private static void checkMapKeyType(String rpcType) { + String keyType = getMapKeyType(rpcType); + if (!keyType.equals(STRING)) { + String message = "jsonrpc android client only support `str' map key. " + + rpcType; + throw new JsonRpcRuntimeException(message); + } + } + + private static String getMapKeyType(String rpcType) { + int end = rpcType.indexOf(':'); + return rpcType.substring(1, end); + } + } + + private static final class RpcTypeNullWrap implements RpcType { + private final RpcType mInner; + + public RpcTypeNullWrap(RpcType inner) { + mInner = inner; + } + + public Object fromJson(Object jsonValue) { + if (jsonValue == JSONObject.NULL) { + return null; + } + return mInner.fromJson(jsonValue); + } + + } + + private static final class ManyListRpcType implements RpcType { + + private final RpcObjectMeta mItemMeta; + private final RpcType mItemType; + + public ManyListRpcType(String rpcType) throws JsonRpcException { + String itemRpcType = getManyListItemType(rpcType); + mItemType = parse(itemRpcType); + mItemMeta = RpcObjectMeta.get(itemRpcType); + } + + public Object fromJson(Object jsonValue) { + try { + ManyList result = new ManyList(mItemMeta); + restoreFromJson((JSONObject) jsonValue, result); + return result; + } catch (JsonRpcException e) { + throw new JsonRpcRuntimeException(e); + } + } + + private void restoreFromJson(JSONObject json, ManyList result) + throws JsonRpcException { + JSONArray _data = json.optJSONArray("_data"); + if (_data != null && _data.length() != 0) { + fromJson(result, _data); + } + } + + private void fromJson(ManyList result, JSONArray _data) + throws JsonRpcException { + for (int i = 0; i < _data.length(); i++) { + JSONObject itemJson = getJsonObject(_data, i); + RpcObject item = (RpcObject) mItemType.fromJson(itemJson); + result.add(item); + } + result.acceptChanges(); + } + } + + private static final class DirectRpcType implements RpcType { + + public Object fromJson(Object jsonValue) { + return jsonValue; + } + } + + private static final class LongRpcType implements RpcType { + + public Object fromJson(Object jsonValue) { + if (jsonValue instanceof Long) { + return jsonValue; + } + return ((Integer) jsonValue).longValue(); + } + } + + private static final class ShortRpcType implements RpcType { + + public Object fromJson(Object jsonValue) { + return ((Integer) jsonValue).shortValue(); + } + + } + + private static final class ByteRpcType implements RpcType { + + public Object fromJson(Object jsonValue) { + short result = ((Integer) jsonValue).shortValue(); + + if (result > 255 || result < 0) { + throw new JsonRpcRuntimeException("can not cast value " + + result + " to dotnet byte type"); + } + return result; + } + + } + + private static final class DoubleRpcType implements RpcType { + + public Object fromJson(Object jsonValue) { + if (jsonValue instanceof Double) { + return jsonValue; + } + return ((Number) jsonValue).doubleValue(); + } + + } + + private static final class DecimalRpcType implements RpcType { + + public Object fromJson(Object jsonValue) { + return new BigDecimal(jsonValue.toString()); + } + + } + + private static final class ListRpcType implements RpcType { + + private final RpcType mItemType; + + public ListRpcType(String rpcType) throws JsonRpcException { + String itemRpcType = getListItemType(rpcType); + mItemType = parse(itemRpcType); + } + + public Object fromJson(Object jsonValue) { + JSONArray json = (JSONArray) jsonValue; + List result = new ArrayList(json.length()); + for (int i = 0; i < json.length(); i++) { + Object itemJson; + try { + itemJson = Helper.get(json, i); + } catch (JsonRpcException e) { + throw new JsonRpcRuntimeException(e); + } + result.add(mItemType.fromJson(itemJson)); + } + return result; + } + + } + + private static final class RpcObjectRpcType implements RpcType { + + private final RpcObjectMeta mMeta; + + public RpcObjectRpcType(RpcObjectMeta meta) { + mMeta = meta; + } + + public Object fromJson(Object jsonValue) { + try { + JSONObject json = (JSONObject) jsonValue; + RpcObject result = new RpcObject(mMeta); + for (RpcPropertyMeta prop : mMeta.getProps().values()) { + String name = prop.getName(); + Object value = json.opt(name); + if (value != null) { + result.put(name, + RpcTypeParser.fromJson(value, prop.getType())); + } else if (json.has(name)) { + result.put(name, null); + } + } + result.acceptChanges(); + return result; + } catch (JsonRpcException e) { + throw new JsonRpcRuntimeException(e); + } + } + + } + + private static final class DateRpcType implements RpcType { + + public Object fromJson(Object jsonValue) { + String s = (String) jsonValue; + if (!RpcFacade.sDebug || isDateString(s)) { + return new Date(parseMillsSinceEpoch(s)); + } + + String message = "can not convert '" + s + "' to Date"; + throw new JsonRpcRuntimeException(message); + } + + private static boolean isDateString(String s) { + return s.startsWith("/Date(") && s.endsWith("+0800)/"); + } + + private static final long parseMillsSinceEpoch(String s) { + int endIdx = s.indexOf('+'); + String numStr = s.substring(6, endIdx); + return Long.parseLong(numStr); + } + } + +} diff --git a/jsonrpc/src/main/res/values/strings.xml b/jsonrpc/src/main/res/values/strings.xml new file mode 100644 index 0000000..fbe7d1b --- /dev/null +++ b/jsonrpc/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + jsonrpc + diff --git a/jsonrpc/src/test/java/org/forks/jsonrpc/ExampleUnitTest.java b/jsonrpc/src/test/java/org/forks/jsonrpc/ExampleUnitTest.java new file mode 100644 index 0000000..6dbb1a1 --- /dev/null +++ b/jsonrpc/src/test/java/org/forks/jsonrpc/ExampleUnitTest.java @@ -0,0 +1,15 @@ +package org.forks.jsonrpc; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..0e91993 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app', ':jsonrpc'