implements MainContract.MainView {
28 |
29 | @Override
30 | public int initView(@Nullable Bundle savedInstanceState) {
31 | return R.layout.activity_main;
32 | }
33 |
34 | @Override
35 | public void initData(@Nullable Bundle savedInstanceState) {
36 |
37 | findViewById(R.id.get).setOnClickListener(v -> {
38 | mPresenter.getSurveyList("189ba5a013dc");
39 | });
40 |
41 | }
42 |
43 |
44 |
45 | @Override
46 | protected MainPresenter createPresenter() {
47 | return new MainPresenter(this);
48 | }
49 |
50 |
51 |
52 | @Override
53 | public void showLoading() {
54 | }
55 |
56 | @Override
57 | public void hideLoading() {
58 |
59 | }
60 |
61 | @Override
62 | public void onGetSurvey(Survey survey) {
63 |
64 | Log.d("成功" ,survey.toString());
65 | Toast.makeText(this, survey.toString(), Toast.LENGTH_SHORT).show();
66 | }
67 |
68 |
69 |
70 | }
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/base/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.base;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.app.AppCompatActivity;
6 |
7 | import com.trello.rxlifecycle2.components.support.RxAppCompatActivity;
8 | import com.trello.rxlifecycle2.components.support.RxFragment;
9 |
10 | import org.greenrobot.eventbus.EventBus;
11 |
12 | import butterknife.ButterKnife;
13 | import butterknife.Unbinder;
14 |
15 | /**
16 | * Incremental change is better than ambitious failure.
17 | *
18 | * @author : MysticCoder
19 | * @date : 2018/3/15
20 | * @desc :
21 | */
22 |
23 | /**
24 | * 这里采用{@link com.trello.rxlifecycle2.RxLifecycle}框架来管理RxJava订阅生命周期
25 | * 将Activity基类继承自{@link RxAppCompatActivity} 考虑到Java没有多继承,若随着项目需要可能会使用到其他的第三方库需要继承,
26 | * 最简单的方法就是把{@link RxAppCompatActivity}的源码直接对应填到BaseActivity 即可^_^
27 | */
28 | public abstract class BaseActivity extends RxAppCompatActivity implements IActivity{
29 | private Unbinder mUnbinder;
30 | @Override
31 | protected void onCreate(@Nullable Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 |
34 | try {
35 | int layoutResID = initView(savedInstanceState);
36 | //如果initView返回0,框架则不会调用setContentView(),当然也不会 Bind ButterKnife
37 | if (layoutResID != 0) {
38 | setContentView(layoutResID);
39 | //绑定到butterknife
40 | mUnbinder = ButterKnife.bind(this);
41 | //如果要使用 Eventbus 请将此方法返回 true
42 | if (useEventBus()) {
43 | //注册 Eventbus
44 | EventBus.getDefault().register(this);
45 | }
46 | }
47 | } catch (Exception e) {
48 | e.printStackTrace();
49 | }
50 | initData(savedInstanceState);
51 |
52 | }
53 |
54 |
55 | @Override
56 | protected void onDestroy() {
57 | super.onDestroy();
58 | if (mUnbinder != null && mUnbinder != Unbinder.EMPTY) {
59 | mUnbinder.unbind();
60 | this.mUnbinder = null;
61 | }
62 |
63 | //如果要使用 Eventbus 请将此方法返回 true
64 | if (useEventBus()) {
65 | //解除注册 Eventbus
66 | EventBus.getDefault().unregister(this);
67 | }
68 |
69 | }
70 |
71 | /**
72 | * 子类Activity要使用EventBus只需要重写此方法返回true即可
73 | * @return
74 | */
75 | @Override
76 | public boolean useEventBus() {
77 | return false;
78 | }
79 | }
80 |
81 |
82 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/base/BaseModel.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.base;
2 |
3 | import com.example.dl.hymvp.http.ApiEngine;
4 | import com.example.dl.hymvp.http.ApiService;
5 |
6 | /**
7 | * Incremental change is better than ambitious failure.
8 | *
9 | * @author : MysticCoder
10 | * @date : 2018/3/15
11 | * @desc :
12 | */
13 |
14 |
15 | public interface BaseModel {
16 |
17 | ApiService mApiService = ApiEngine.getInstance().getApiService();
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/base/BaseMvpActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.base;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 |
6 | /**
7 | * Incremental change is better than ambitious failure.
8 | *
9 | * @author : MysticCoder
10 | * @date : 2018/3/15
11 | * @desc :
12 | */
13 |
14 |
15 | public abstract class BaseMvpActivity extends BaseActivity {
16 |
17 | protected P mPresenter;
18 |
19 | @Override
20 | protected void onCreate(@Nullable Bundle savedInstanceState) {
21 |
22 | mPresenter = createPresenter();
23 | super.onCreate(savedInstanceState);
24 | }
25 |
26 | /**
27 | * create presenter
28 | * @return presenter
29 | */
30 | protected abstract P createPresenter();
31 |
32 |
33 | @Override
34 | protected void onDestroy() {
35 | super.onDestroy();
36 | if (mPresenter != null) {
37 | mPresenter.detachView();
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/base/BasePresenter.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.base;
2 |
3 | import android.app.Activity;
4 | import android.util.Log;
5 |
6 |
7 | import com.example.dl.hymvp.util.Preconditions;
8 | import com.trello.rxlifecycle2.RxLifecycle;
9 |
10 |
11 | import java.lang.ref.Reference;
12 | import java.lang.ref.WeakReference;
13 |
14 | import io.reactivex.disposables.CompositeDisposable;
15 | import io.reactivex.disposables.Disposable;
16 |
17 |
18 | /**
19 | * Incremental change is better than ambitious failure.
20 | *
21 | * @author : MysticCoder
22 | * @date : 2018/3/15
23 | * @desc :
24 | */
25 |
26 | public class BasePresenter implements IPresenter {
27 |
28 | /**
29 | * 由于Presenter 经常性的持有Activity 的强引用,如果在一些请求结束之前Activity 被销毁了,Activity对象将无法被回收,此时就会发生内存泄露。
30 | * 这里我们使用虚引用和泛型来对MVP中的内存泄漏问题进行改良。
31 | */
32 | protected Reference mView;
33 | protected M mModel;
34 |
35 | protected CompositeDisposable mCompositeDisposable;
36 |
37 |
38 | protected V getView() {
39 | return mView.get();
40 | }
41 | /**
42 | * 如果当前页面同时需要 Model 层和 View 层,则使用此构造函数(默认)
43 | *
44 | * @param model
45 | * @param view
46 | */
47 | public BasePresenter(M model, V view) {
48 | Preconditions.checkNotNull(model, "%s cannot be null", BaseModel.class.getName());
49 | Preconditions.checkNotNull(view, "%s cannot be null", BaseView.class.getName());
50 | this.mModel = model;
51 | // this.mView = view;
52 | attachView(view);
53 | }
54 |
55 | /**
56 | * 如果当前页面不需要操作数据,只需要 View 层,则使用此构造函数
57 | *
58 | * @param view
59 | */
60 | public BasePresenter(V view) {
61 | Preconditions.checkNotNull(view, "%s cannot be null", BaseView.class.getName());
62 | // this.mView = view;
63 | attachView(view);
64 | }
65 |
66 | // public BasePresenter() {
67 | // attachView();
68 | // }
69 |
70 |
71 | /*****************************************************************************************************/
72 | /**
73 | * 将 {@link Disposable} 添加到 {@link CompositeDisposable} 中统一管理
74 | * 可在 {@link Activity#onDestroy()} 中使用 {@link #unDispose()} 停止正在执行的 RxJava 任务,避免内存泄漏
75 | * 目前已使用 {@link RxLifecycle} 避免内存泄漏,此方法作为备用方案
76 | *
77 | * @param disposable
78 | */
79 | protected void addDisposabel(Disposable disposable) {
80 | if (mCompositeDisposable == null) {
81 | mCompositeDisposable = new CompositeDisposable();
82 | }
83 | //将所有 Disposable 放入集中处理
84 | mCompositeDisposable.add(disposable);
85 | Log.d("订阅",mCompositeDisposable.toString()+"个数"+mCompositeDisposable.size());
86 | }
87 |
88 | public void unDispose(){
89 |
90 | if (mCompositeDisposable != null) {
91 | mCompositeDisposable.clear();//保证 Activity 结束时取消所有正在执行的订阅
92 | }
93 | }
94 |
95 | /*****************************************************************************************************/
96 |
97 | @Override
98 | public void attachView(V view) {
99 |
100 | this.mView= new WeakReference<>(view);
101 | }
102 |
103 | @Override
104 | public void detachView() {
105 | if (mView != null) {
106 | mView.clear();
107 | mView = null;
108 | }
109 |
110 | unDispose();//备用方案
111 |
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/base/BaseView.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.base;
2 |
3 | import com.trello.rxlifecycle2.LifecycleTransformer;
4 |
5 | /**
6 | * Incremental change is better than ambitious failure.
7 | *
8 | * @author : MysticCoder
9 | * @date : 2018/3/15
10 | * @desc :
11 | */
12 |
13 |
14 | public interface BaseView {
15 |
16 | LifecycleTransformer bindToLifecycle();
17 |
18 |
19 | /**
20 | * 显示加载
21 | */
22 | void showLoading();
23 |
24 | /**
25 | * 隐藏加载
26 | */
27 | void hideLoading();
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/base/IActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.base;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.support.annotation.Nullable;
6 |
7 | /**
8 | * Incremental change is better than ambitious failure.
9 | *
10 | * @author : MysticCoder
11 | * @date : 2018/3/16
12 | * @desc :
13 | */
14 |
15 |
16 | public interface IActivity {
17 |
18 | /**
19 | * 初始化 View, 如果 {@link #initView(Bundle)} 返回 0, 则不会调用 {@link Activity#setContentView(int)}
20 | *
21 | * @param savedInstanceState
22 | * @return
23 | */
24 | int initView(@Nullable Bundle savedInstanceState);
25 |
26 | /**
27 | * 初始化数据
28 | *
29 | * @param savedInstanceState
30 | */
31 | void initData(@Nullable Bundle savedInstanceState);
32 |
33 |
34 | /**
35 | * 是否使用 EventBus
36 | *
37 | * @return
38 | */
39 | boolean useEventBus();
40 |
41 |
42 |
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/base/IPresenter.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.base;
2 |
3 | /**
4 | * Incremental change is better than ambitious failure.
5 | *
6 | * @author : MysticCoder
7 | * @date : 2018/3/15
8 | * @desc :
9 | */
10 |
11 |
12 | public interface IPresenter {
13 |
14 |
15 | void attachView(V view);
16 |
17 | void detachView();
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/bean/Gank.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.bean;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * Incremental change is better than ambitious failure.
7 | *
8 | * @author : MysticCoder
9 | * @date : 2018/3/15
10 | * @desc :
11 | */
12 | public class Gank {
13 |
14 | private boolean error;
15 |
16 | /**
17 | * _id : 58006bb3421aa95dd351b12a
18 | * createdAt : 2016-10-14T13:22:59.462Z
19 | * desc : 效果超棒的微笑下拉刷新。这是在 SwipeRefreshLayout基础上修改的下拉刷新库。
20 | * images : ["http://img.gank.io/616cb9ae-2d42-4f97-a7c9-28c291c7b66f"]
21 | * publishedAt : 2016-10-28T11:29:36.510Z
22 | * source : web
23 | * type : Android
24 | * url : https://github.com/songixan/SmileRefresh
25 | * used : true
26 | * who : null
27 | */
28 |
29 | private List results;
30 |
31 | public boolean isError() {
32 | return error;
33 | }
34 |
35 | public void setError(boolean error) {
36 | this.error = error;
37 | }
38 |
39 | public List getResults() {
40 | return results;
41 | }
42 |
43 | public void setResults(List results) {
44 | this.results = results;
45 | }
46 |
47 | public static class Result {
48 |
49 | private String _id;
50 | private String createdAt;
51 | private String desc;
52 | private String publishedAt;
53 | private String source;
54 | private String type;
55 | private String url;
56 | private boolean used;
57 | private String who;
58 | private List images;
59 |
60 | public String get_id() {
61 | return _id;
62 | }
63 |
64 | public void set_id(String _id) {
65 | this._id = _id;
66 | }
67 |
68 | public String getCreatedAt() {
69 | return createdAt;
70 | }
71 |
72 | public void setCreatedAt(String createdAt) {
73 | this.createdAt = createdAt;
74 | }
75 |
76 | public String getDesc() {
77 | return desc;
78 | }
79 |
80 | public void setDesc(String desc) {
81 | this.desc = desc;
82 | }
83 |
84 | public String getPublishedAt() {
85 | return publishedAt;
86 | }
87 |
88 | public void setPublishedAt(String publishedAt) {
89 | this.publishedAt = publishedAt;
90 | }
91 |
92 | public String getSource() {
93 | return source;
94 | }
95 |
96 | public void setSource(String source) {
97 | this.source = source;
98 | }
99 |
100 | public String getType() {
101 | return type;
102 | }
103 |
104 | public void setType(String type) {
105 | this.type = type;
106 | }
107 |
108 | public String getUrl() {
109 | return url;
110 | }
111 |
112 | public void setUrl(String url) {
113 | this.url = url;
114 | }
115 |
116 | public boolean isUsed() {
117 | return used;
118 | }
119 |
120 | public void setUsed(boolean used) {
121 | this.used = used;
122 | }
123 |
124 | public String getWho() {
125 | return who;
126 | }
127 |
128 | public void setWho(String who) {
129 | this.who = who;
130 | }
131 |
132 | public List getImages() {
133 | return images;
134 | }
135 |
136 | public void setImages(List images) {
137 | this.images = images;
138 | }
139 |
140 | @Override
141 | public String toString() {
142 | return "Result{" +
143 | "_id='" + _id + '\'' +
144 | ", createdAt='" + createdAt + '\'' +
145 | ", desc='" + desc + '\'' +
146 | ", publishedAt='" + publishedAt + '\'' +
147 | ", source='" + source + '\'' +
148 | ", type='" + type + '\'' +
149 | ", url='" + url + '\'' +
150 | ", used=" + used +
151 | ", who='" + who + '\'' +
152 | ", images=" + images +
153 | '}';
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/bean/Survey.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.bean;
2 |
3 | import java.io.Serializable;
4 | import java.util.List;
5 |
6 | /**
7 | * Incremental change is better than ambitious failure.
8 | *
9 | * @author : MysticCoder
10 | * @date : 2017/12/4
11 | * @desc :
12 | */
13 |
14 |
15 | public class Survey {
16 | private List surveylist;
17 |
18 | public List getSurveylist() {
19 | return surveylist;
20 | }
21 |
22 | public void setSurveylist(List surveylist) {
23 | this.surveylist = surveylist;
24 | }
25 |
26 | @Override
27 | public String toString() {
28 | return "SurveyModel{" +
29 | "surveylist=" + surveylist +
30 | '}';
31 | }
32 |
33 | public static class SurveylistBean implements Serializable {
34 | /**
35 | * author : 北京市垂杨柳医院
36 | * remark : 为不断改善医院住院服务质量,我院正在进行住院病人的满意度调查。非常感谢您抽出宝贵时间参加本次调查,提供您的看法与意见,能倾听您的意见,我们感到十分荣幸。谢谢!
37 | * id : 93
38 | * title : 住院病人满意度调查
39 | */
40 |
41 | private String author;
42 | private String remark;
43 | private int id;
44 |
45 | @Override
46 | public String toString() {
47 | return "SurveylistBean{" +
48 | "author='" + author + '\'' +
49 | ", remark='" + remark + '\'' +
50 | ", id=" + id +
51 | ", title='" + title + '\'' +
52 | '}';
53 | }
54 |
55 | private String title;
56 |
57 | public String getAuthor() {
58 | return author;
59 | }
60 |
61 | public void setAuthor(String author) {
62 | this.author = author;
63 | }
64 |
65 | public String getRemark() {
66 | return remark;
67 | }
68 |
69 | public void setRemark(String remark) {
70 | this.remark = remark;
71 | }
72 |
73 | public int getId() {
74 | return id;
75 | }
76 |
77 | public void setId(int id) {
78 | this.id = id;
79 | }
80 |
81 | public String getTitle() {
82 | return title;
83 | }
84 |
85 | public void setTitle(String title) {
86 | this.title = title;
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/http/ApiEngine.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.http;
2 |
3 | import android.util.Log;
4 |
5 | import com.example.dl.hymvp.App;
6 | import com.example.dl.hymvp.util.NetUtil;
7 | import com.google.gson.Gson;
8 | import com.google.gson.GsonBuilder;
9 |
10 | import java.io.File;
11 | import java.io.IOException;
12 | import java.io.UnsupportedEncodingException;
13 | import java.net.URLDecoder;
14 | import java.util.concurrent.TimeUnit;
15 |
16 |
17 | import okhttp3.Cache;
18 | import okhttp3.CacheControl;
19 | import okhttp3.Interceptor;
20 | import okhttp3.OkHttpClient;
21 | import okhttp3.Request;
22 | import okhttp3.Response;
23 | import okhttp3.logging.HttpLoggingInterceptor;
24 | import retrofit2.Retrofit;
25 | import retrofit2.converter.gson.GsonConverterFactory;
26 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
27 |
28 | /**
29 | * Incremental change is better than ambitious failure.
30 | *
31 | * @author : MysticCoder
32 | * @date : 2018/3/15
33 | * @desc :
34 | */
35 | public class ApiEngine {
36 |
37 | private static final long DEFAULT_TIMEOUT = 10;
38 |
39 |
40 | private volatile static ApiEngine apiEngine;
41 | private Retrofit retrofit;
42 |
43 | private ApiEngine() {
44 |
45 | //日志拦截器
46 | HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor((message)-> {
47 |
48 | try {
49 | String text = URLDecoder.decode(message, "utf-8");
50 | Log.e("OKHttp-----", text);
51 | } catch (UnsupportedEncodingException e) {
52 | e.printStackTrace();
53 | Log.e("OKHttp-----", message);
54 | }
55 | });
56 | loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
57 |
58 | //缓存
59 | int size = 1024 * 1024 * 100;
60 | File cacheFile = new File(App.getContext().getCacheDir(), "OkHttpCache");
61 | Cache cache = new Cache(cacheFile, size);
62 |
63 |
64 | // addInterceptor() 添加应用拦截器
65 | //● 不需要担心中间过程的响应,如重定向和重试.
66 | //● 总是只调用一次,即使HTTP响应是从缓存中获取.
67 | //● 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
68 | //● 允许短路而不调用 Chain.proceed(),即中止调用.
69 | //● 允许重试,使 Chain.proceed()调用多次.
70 |
71 |
72 | // addNetworkInterceptor() 添加网络拦截器
73 | //● 能够操作中间过程的响应,如重定向和重试.
74 | //● 当网络短路而返回缓存响应时不被调用.
75 | //● 只观察在网络上传输的数据.
76 | //● 携带请求来访问连接.
77 |
78 | OkHttpClient client = new OkHttpClient.Builder()
79 | .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
80 | .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
81 | .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
82 | .addNetworkInterceptor(new NetworkInterceptor())
83 | // .addNetworkInterceptor(loggingInterceptor)
84 | .addInterceptor(loggingInterceptor)
85 | .cache(cache)
86 | .build();
87 |
88 | retrofit = new Retrofit.Builder()
89 | .baseUrl(ApiService.BASE_URL)
90 | .client(client)
91 | //然后将下面的GsonConverterFactory.create()替换成我们自定义的ResponseConverterFactory.create()
92 | .addConverterFactory(ResponseConverterFactory.create())
93 | // .addConverterFactory(GsonConverterFactory.create())
94 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
95 | .build();
96 |
97 | }
98 |
99 | public static ApiEngine getInstance() {
100 | if (apiEngine == null) {
101 | synchronized (ApiEngine.class) {
102 | if (apiEngine == null) {
103 | apiEngine = new ApiEngine();
104 | }
105 | }
106 | }
107 | return apiEngine;
108 | }
109 |
110 | public ApiService getApiService() {
111 | return retrofit.create(ApiService.class);
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/http/ApiException.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.http;
2 |
3 | import com.google.gson.JsonParseException;
4 | import com.google.gson.JsonSerializer;
5 |
6 | import org.apache.http.conn.ConnectTimeoutException;
7 | import org.json.JSONException;
8 |
9 | import java.io.NotSerializableException;
10 | import java.net.ConnectException;
11 | import java.net.SocketTimeoutException;
12 | import java.net.UnknownHostException;
13 | import java.text.ParseException;
14 |
15 | import retrofit2.HttpException;
16 |
17 |
18 |
19 | public class ApiException extends Exception {
20 |
21 |
22 | //对应HTTP的状态码
23 | private static final int UNAUTHORIZED = 401;
24 | private static final int FORBIDDEN = 403;
25 | private static final int NOT_FOUND = 404;
26 | private static final int REQUEST_TIMEOUT = 408;
27 | private static final int INTERNAL_SERVER_ERROR = 500;
28 | private static final int BAD_GATEWAY = 502;
29 | private static final int SERVICE_UNAVAILABLE = 503;
30 | private static final int GATEWAY_TIMEOUT = 504;
31 |
32 |
33 |
34 | private final int code;
35 | private String message;
36 |
37 | public ApiException(Throwable throwable, int code) {
38 | super(throwable);
39 | this.code = code;
40 | this.message = throwable.getMessage();
41 | }
42 |
43 | public int getCode() {
44 | return code;
45 | }
46 |
47 | @Override
48 | public String getMessage() {
49 | return message;
50 | }
51 |
52 | public static ApiException handleException(Throwable e) {
53 |
54 | Throwable throwable = e;
55 | //获取最根源的异常
56 | while (throwable.getCause() != null) {
57 | e = throwable;
58 | throwable = throwable.getCause();
59 | }
60 |
61 | ApiException ex;
62 | if (e instanceof HttpException) { //HTTP错误
63 | HttpException httpException = (HttpException) e;
64 | ex = new ApiException(e, httpException.code());
65 | switch (httpException.code()) {
66 | case UNAUTHORIZED:
67 | case FORBIDDEN:
68 | //权限错误,需要实现重新登录操作
69 | // onPermissionError(ex);
70 | break;
71 | case NOT_FOUND:
72 | case REQUEST_TIMEOUT:
73 | case GATEWAY_TIMEOUT:
74 | case INTERNAL_SERVER_ERROR:
75 | case BAD_GATEWAY:
76 | case SERVICE_UNAVAILABLE:
77 | default:
78 | ex.message = "默认网络异常"; //均视为网络错误
79 | break;
80 | }
81 | return ex;
82 | } else if (e instanceof SocketTimeoutException) {
83 | ex = new ApiException(e, ERROR.TIMEOUT_ERROR);
84 | ex.message = "网络连接超时,请检查您的网络状态,稍后重试!";
85 | return ex;
86 | } else if (e instanceof ConnectException) {
87 | ex = new ApiException(e, ERROR.TIMEOUT_ERROR);
88 | ex.message = "网络连接异常,请检查您的网络状态,稍后重试!";
89 | return ex;
90 | } else if (e instanceof ConnectTimeoutException) {
91 | ex = new ApiException(e, ERROR.TIMEOUT_ERROR);
92 | ex.message = "网络连接超时,请检查您的网络状态,稍后重试!";
93 | return ex;
94 | } else if (e instanceof UnknownHostException) {
95 | ex = new ApiException(e, ERROR.TIMEOUT_ERROR);
96 | ex.message = "网络连接异常,请检查您的网络状态,稍后重试!";
97 | return ex;
98 | } else if (e instanceof NullPointerException) {
99 | ex = new ApiException(e, ERROR.NULL_POINTER_EXCEPTION);
100 | ex.message = "空指针异常";
101 | return ex;
102 | } else if (e instanceof javax.net.ssl.SSLHandshakeException) {
103 | ex = new ApiException(e, ERROR.SSL_ERROR);
104 | ex.message = "证书验证失败";
105 | return ex;
106 | } else if (e instanceof ClassCastException) {
107 | ex = new ApiException(e, ERROR.CAST_ERROR);
108 | ex.message = "类型转换错误";
109 | return ex;
110 | } else if (e instanceof JsonParseException
111 | || e instanceof JSONException
112 | // || e instanceof JsonSyntaxException
113 | || e instanceof JsonSerializer
114 | || e instanceof NotSerializableException
115 | || e instanceof ParseException) {
116 | ex = new ApiException(e, ERROR.PARSE_ERROR);
117 | ex.message = "解析错误";
118 | return ex;
119 | } else if (e instanceof IllegalStateException) {
120 | ex = new ApiException(e, ERROR.ILLEGAL_STATE_ERROR);
121 | ex.message = e.getMessage();
122 | return ex;
123 | } else {
124 | ex = new ApiException(e, ERROR.UNKNOWN);
125 | ex.message = "未知错误";
126 | return ex;
127 | }
128 | }
129 |
130 | /**
131 | * 约定异常
132 | */
133 | public static class ERROR {
134 | /**
135 | * 未知错误
136 | */
137 | public static final int UNKNOWN = 1000;
138 | /**
139 | * 连接超时
140 | */
141 | public static final int TIMEOUT_ERROR = 1001;
142 | /**
143 | * 空指针错误
144 | */
145 | public static final int NULL_POINTER_EXCEPTION = 1002;
146 |
147 | /**
148 | * 证书出错
149 | */
150 | public static final int SSL_ERROR = 1003;
151 |
152 | /**
153 | * 类转换错误
154 | */
155 | public static final int CAST_ERROR = 1004;
156 |
157 | /**
158 | * 解析错误
159 | */
160 | public static final int PARSE_ERROR = 1005;
161 |
162 | /**
163 | * 非法数据异常
164 | */
165 | public static final int ILLEGAL_STATE_ERROR = 1006;
166 |
167 | }
168 |
169 | }
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/http/ApiService.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.http;
2 |
3 |
4 | import com.example.dl.hymvp.bean.Gank;
5 | import com.example.dl.hymvp.bean.Survey;
6 |
7 |
8 | import io.reactivex.Observable;
9 | import retrofit2.http.Field;
10 | import retrofit2.http.FormUrlEncoded;
11 | import retrofit2.http.GET;
12 | import retrofit2.http.POST;
13 | import retrofit2.http.Path;
14 |
15 |
16 | /**
17 | * Incremental change is better than ambitious failure.
18 | *
19 | * @author : MysticCoder
20 | * @date : 2018/3/15
21 | * @desc :
22 | */
23 |
24 | public interface ApiService {
25 |
26 | // String BASE_URL="http://gank.io/";
27 | //
28 | // @GET("api/data/Android/10/{page}")
29 | // Observable getGank(@Path("page") String page);
30 |
31 |
32 | String BASE_URL = "http://api.medrd.com/";
33 |
34 |
35 | @FormUrlEncoded
36 | @POST("api/user/surveytitlelistNew")//获取调查问卷列表
37 | Observable getSurveyList(@Field("did")String did);
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/http/BaseObserver.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.http;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 |
6 | import io.reactivex.Observer;
7 | import io.reactivex.disposables.Disposable;
8 |
9 | /**
10 | * Incremental change is better than ambitious failure.
11 | *
12 | * @author : MysticCoder
13 | * @date : 2017/12/4
14 | * @desc :
15 | */
16 |
17 |
18 | public abstract class BaseObserver implements Observer {
19 |
20 |
21 |
22 | @Override
23 | public void onSubscribe(Disposable d) {
24 |
25 | }
26 |
27 | @Override
28 | public void onNext(T response) {
29 |
30 | onSuccess(response);
31 | }
32 |
33 | @Override
34 | public void onError(Throwable e) {
35 |
36 |
37 | if (e instanceof ResultException){
38 | onFailure(e.getMessage());
39 | }else {
40 |
41 | String error = ApiException.handleException(e).getMessage();
42 |
43 | _onError(error);
44 | }
45 |
46 |
47 | }
48 |
49 | @Override
50 | public void onComplete() {
51 | }
52 |
53 | /**
54 | * 请求成功
55 | *
56 | * @param response 服务器返回的数据
57 | */
58 | public abstract void onSuccess(T response);
59 |
60 | /**
61 | * 服务器返回数据,但code不在约定成功范围内
62 | *
63 | * @param msg 服务器返回的数据
64 | */
65 | public abstract void onFailure(String msg);
66 |
67 |
68 | // public abstract void onError(String errorMsg);
69 |
70 |
71 |
72 |
73 | private void _onSuccess(T responce){
74 |
75 | }
76 |
77 | private void _onFailure(String msg) {
78 |
79 | if (TextUtils.isEmpty(msg)) {
80 | // ToastUtils.show(R.string.response_return_error);
81 | } else {
82 | // ToastUtils.show(msg);
83 | }
84 | }
85 | private void _onError(String err ){
86 |
87 | Log.e("APIException",err);
88 |
89 | }
90 |
91 |
92 |
93 |
94 |
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/http/BaseResponse.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.http;
2 |
3 | /**
4 | * Incremental change is better than ambitious failure.
5 | *
6 | * @author : MysticCoder
7 | * @date : 2017/12/4
8 | * @desc :
9 | */
10 |
11 |
12 | public class BaseResponse{
13 |
14 | private int code;
15 | private String msg;
16 | private String data;
17 | private String seed;
18 |
19 | private T decryptedData;
20 |
21 | public T getDecryptedData() {
22 | return decryptedData;
23 | }
24 |
25 | public void setDecryptedData(T decryptedData) {
26 | this.decryptedData = decryptedData;
27 | }
28 |
29 | private boolean isSuccess;
30 |
31 |
32 | public String getData() {
33 | return data;
34 | }
35 |
36 | public void setData(String data) {
37 | this.data = data;
38 | }
39 |
40 | public String getMsg() {
41 | return msg;
42 | }
43 |
44 | public void setMsg(String msg) {
45 | this.msg = msg;
46 | }
47 |
48 | public int getCode() {
49 | return code;
50 | }
51 |
52 | public void setCode(int code) {
53 | this.code = code;
54 | }
55 |
56 | public String getSeed() {
57 | return seed;
58 | }
59 |
60 | public void setSeed(String seed) {
61 | this.seed = seed;
62 | }
63 |
64 | public void setSuccess(boolean isSuccess){
65 | this.isSuccess = isSuccess;
66 | }
67 |
68 | public boolean isSuccess(){
69 | return this.code >= 2000 && this.code < 4000;
70 | }
71 |
72 |
73 | @Override
74 | public String toString() {
75 | return "BaseResponse{" +
76 | "code=" + code +
77 | ", msg='" + msg + '\'' +
78 | ", data='" + data + '\'' +
79 | ", seed='" + seed + '\'' +
80 | ", decryptedData=" + decryptedData +
81 | ", isSuccess=" + isSuccess +
82 | '}';
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/http/DES.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.http;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.net.URLDecoder;
5 | import java.security.Key;
6 |
7 | import javax.crypto.Cipher;
8 | import javax.crypto.SecretKeyFactory;
9 | import javax.crypto.spec.DESKeySpec;
10 |
11 | public class DES {
12 |
13 | public static String decode(String str, String seed){
14 | DES des = new DES(seed);
15 | String deCode = str;
16 | try {
17 | if(str != null)
18 | deCode = des.getDesString(str);
19 | else if(deCode == null || deCode.length() == 0)
20 | deCode = str;
21 | } catch (Exception e) {
22 | deCode=str;
23 | }
24 | return deCode;
25 | }
26 |
27 | public static String encode(String str, String seed){
28 | DES des = new DES(seed);
29 | return des.getEncString(str);
30 | }
31 |
32 |
33 | Key key;
34 |
35 | public DES(String str) {
36 | setKey(str);// 生成密匙
37 | }
38 |
39 | // public DES() {
40 | // setKey();
41 | // }
42 |
43 | /**
44 | * 根据参数生成KEY
45 | */
46 | private void setKey(String strKey) {
47 | try {
48 | SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
49 | byte[] androidKey =getRawKey(strKey);
50 | DESKeySpec keySpec = new DESKeySpec(androidKey);
51 | this.key = keyFactory.generateSecret(keySpec);
52 | } catch (Exception e) {
53 |
54 | } }
55 |
56 | private byte[] getRawKey(String seed){
57 | if (seed != null) {
58 | byte[] bs = seed.getBytes();
59 | return new byte[]{bs[6],bs[3],bs[5],bs[2],bs[10],bs[4],bs[8],bs[2]};
60 | }
61 | return new byte[]{ 82, -65, 35, 88, -87, 105, 72, -34 };
62 | }
63 |
64 | private void setKey(){
65 | try {
66 | SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
67 | byte[] androidKey = new byte[]{ 82, -65, 35, 88, -87, 105, 72, -34 };
68 | DESKeySpec keySpec = new DESKeySpec(androidKey);
69 | this.key = keyFactory.generateSecret(keySpec);
70 | } catch (Exception e) {
71 |
72 | }
73 | }
74 |
75 | /**
76 | * 加密String明文输入,String密文输出
77 | */
78 | private String getEncString(String strMing) {
79 | byte[] byteMi = null;
80 | byte[] byteMing = null;
81 | String strMi = "";
82 | try {
83 | byteMing = strMing.getBytes("UTF8");
84 | byteMi = this.getEncCode(byteMing);
85 | strMi = MyBase64.encodeBytes(byteMi);
86 | } catch (Exception e) {
87 | } finally {
88 | byteMi = null;
89 | }
90 | return strMi;
91 | }
92 |
93 | /**
94 | * 解密 以String密文输入,String明文输出
95 | *
96 | * @param strMi
97 | * @return
98 | */
99 | private String getDesString(String strMi) {
100 | byte[] byteMing = null;
101 | byte[] byteMi = null;
102 | String strMing = strMi;
103 | try {
104 | byteMi = MyBase64.decode(strMi);
105 | if(byteMi.length >0){
106 | byteMing = this.getDesCode(byteMi);
107 | strMing = new String(byteMing, "UTF8");
108 | }else{
109 | strMing = strMi;
110 | }
111 | } catch (Exception e) {
112 | strMing = strMi;
113 | } finally {
114 | byteMing = null;
115 | byteMi = null;
116 | }
117 | return strMing;
118 | }
119 |
120 | /**
121 | * 加密以byte[]明文输入,byte[]密文输出
122 | *
123 | * @param byteS
124 | * @return
125 | */
126 | private byte[] getEncCode(byte[] byteS) {
127 | byte[] byteFina = null;
128 | Cipher cipher;
129 | try {
130 | cipher = Cipher.getInstance("DES");
131 | cipher.init(Cipher.ENCRYPT_MODE, key);
132 | byteFina = cipher.doFinal(byteS);
133 | } catch (Exception e) {
134 | } finally {
135 | cipher = null;
136 | }
137 | return byteFina;
138 | }
139 |
140 | /**
141 | * 解密以byte[]密文输入,以byte[]明文输出
142 | *
143 | * @param byteD
144 | * @return
145 | */
146 | private byte[] getDesCode(byte[] byteD) {
147 | Cipher cipher;
148 | byte[] byteFina = null;
149 | try {
150 | cipher = Cipher.getInstance("DES");
151 | cipher.init(Cipher.DECRYPT_MODE, key);
152 | byteFina = cipher.doFinal(byteD);
153 | } catch (Exception e) {
154 | } finally {
155 | cipher = null;
156 | }
157 | return byteFina;
158 | }
159 |
160 | private String filter(String str){
161 | String newString = "";
162 | try {
163 | newString = URLDecoder.decode(str, "UTF-8");
164 | } catch (UnsupportedEncodingException e) {
165 | e.printStackTrace();
166 | newString = "";
167 | }
168 | return newString;
169 | }
170 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/http/GsonResponseBodyConverter.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.http;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.google.gson.Gson;
6 |
7 | import java.io.IOException;
8 | import java.lang.reflect.Type;
9 |
10 | import okhttp3.ResponseBody;
11 | import retrofit2.Converter;
12 |
13 |
14 | /**
15 | * Incremental change is better than ambitious failure.
16 | *
17 | * @author : MysticCoder
18 | * @date : 2017/12/6
19 | * @desc :
20 | */
21 |
22 |
23 | public class GsonResponseBodyConverter implements Converter {
24 | private final Gson gson;
25 | private final Type type;
26 |
27 |
28 | public GsonResponseBodyConverter(Gson gson, Type type){
29 | this.gson = gson;
30 | this.type = type;
31 | }
32 | @Override
33 | public T convert(@NonNull ResponseBody value) throws IOException {
34 |
35 | String response = value.string();
36 |
37 | BaseResponse result = gson.fromJson(response, BaseResponse.class);
38 | if (result.isSuccess() && null!=result.getData()){
39 | String jsonStr = DES.decode(result.getData(), result.getSeed());
40 | return gson.fromJson(jsonStr,type);
41 |
42 |
43 | }else {
44 |
45 | //抛一个自定义ResultException 传入失败时候的状态码,和信息
46 | throw new ResultException(result.getCode(),result.getMsg());
47 |
48 | }
49 |
50 | }
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/http/MyBase64.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.http;
2 |
3 | /**
4 | * Encodes and decodes to and from Base64 notation.
5 | * Homepage: http://iharder.net/base64.
6 | *
7 | * Example:
8 | *
9 | * String encoded = Base64.encode( myByteArray );
10 | *
11 | * byte[] myByteArray = Base64.decode( encoded );
12 | *
13 | * The options parameter, which appears in a few places, is used to pass
14 | * several pieces of information to the encoder. In the "higher level" methods such as
15 | * encodeBytes( bytes, options ) the options parameter can be used to indicate such
16 | * things as first gzipping the bytes before encoding them, not inserting linefeeds,
17 | * and encoding using the URL-safe and Ordered dialects.
18 | *
19 | * Note, according to RFC3548,
20 | * Section 2.1, implementations should not add line feeds unless explicitly told
21 | * to do so. I've got Base64 set to this behavior now, although earlier versions
22 | * broke lines by default.
23 | *
24 | * The constants defined in Base64 can be OR-ed together to combine options, so you
25 | * might make a call like this:
26 | *
27 | * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES );
28 | * to compress the data before encoding it and then making the output have newline characters.
29 | * Also...
30 | * String encoded = Base64.encodeBytes( crazyString.getBytes() );
31 | *
32 | *
33 | *
34 | *
35 | * Change Log:
36 | *
37 | *
38 | * - v2.3.7 - Fixed subtle bug when base 64 input stream contained the
39 | * value 01111111, which is an invalid base 64 character but should not
40 | * throw an ArrayIndexOutOfBoundsException either. Led to discovery of
41 | * mishandling (or potential for better handling) of other bad input
42 | * characters. You should now get an IOException if you try decoding
43 | * something that has bad characters in it.
44 | * - v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded
45 | * string ended in the last column; the buffer was not properly shrunk and
46 | * contained an extra (null) byte that made it into the string.
47 | * - v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size
48 | * was wrong for files of size 31, 34, and 37 bytes.
49 | * - v2.3.4 - Fixed bug when working with gzipped streams whereby flushing
50 | * the Base64.OutputStream closed the Base64 encoding (by padding with equals
51 | * signs) too soon. Also added an option to suppress the automatic decoding
52 | * of gzipped streams. Also added experimental support for specifying a
53 | * class loader when using the
54 | * {@link #decodeToObject(String, int, ClassLoader)}
55 | * method.
56 | * - v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java
57 | * footprint with its CharEncoders and so forth. Fixed some javadocs that were
58 | * inconsistent. Removed imports and specified things like java.io.IOException
59 | * explicitly inline.
60 | * - v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the
61 | * final encoded data will be so that the code doesn't have to create two output
62 | * arrays: an oversized initial one and then a final, exact-sized one. Big win
63 | * when using the {@link #encodeBytesToBytes(byte[])} family of methods (and not
64 | * using the gzip options which uses a different mechanism with streams and stuff).
65 | * - v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some
66 | * similar helper methods to be nav_common efficient with memory by not returning a
67 | * String but just a byte array.
68 | * - v2.3 - This is not a drop-in replacement! This is two years of comments
69 | * and bug fixes queued up and finally executed. Thanks to everyone who sent
70 | * me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else.
71 | * Much bad coding was cleaned up including throwing exceptions where necessary
72 | * instead of returning null values or something similar. Here are some changes
73 | * that may affect you:
74 | *
75 | * - Does not break lines, by default. This is to keep in compliance with
76 | * RFC3548.
77 | * - Throws exceptions instead of returning null values. Because some operations
78 | * (especially those that may permit the GZIP option) use IO streams, there
79 | * is a possiblity of an java.io.IOException being thrown. After some discussion and
80 | * thought, I've changed the behavior of the methods to throw java.io.IOExceptions
81 | * rather than return null if ever there's an error. I think this is nav_common
82 | * appropriate, though it will require some changes to your code. Sorry,
83 | * it should have been done this way to begin with.
84 | * - Removed all references to System.out, System.err, and the like.
85 | * Shame on me. All I can say is sorry they were ever there.
86 | * - Throws NullPointerExceptions and IllegalArgumentExceptions as needed
87 | * such as when passed arrays are null or offsets are invalid.
88 | * - Cleaned up as much javadoc as I could to avoid any javadoc warnings.
89 | * This was especially annoying before for people who were thorough in their
90 | * own projects and then had gobs of javadoc warnings on this file.
91 | *
92 | * - v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug
93 | * when using very small files (~< 40 bytes).
94 | * - v2.2 - Added some helper methods for encoding/decoding directly from
95 | * one file to the next. Also added a main() method to support command line
96 | * encoding/decoding from one file to the next. Also added these Base64 dialects:
97 | *
98 | * - The default is RFC3548 format.
99 | * - Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates
100 | * URL and file name friendly format as described in Section 4 of RFC3548.
101 | * http://www.faqs.org/rfcs/rfc3548.html
102 | * - Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates
103 | * URL and file name friendly format that preserves lexical ordering as described
104 | * in http://www.faqs.org/qa/rfcc-1940.html
105 | *
106 | * Special thanks to Jim Kellerman at http://www.powerset.com/
107 | * for contributing the new Base64 dialects.
108 | *
109 | *
110 | * - v2.1 - Cleaned up javadoc comments and unused variables and methods. Added
111 | * some convenience methods for reading and writing to and from files.
112 | * - v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems
113 | * with other encodings (like EBCDIC).
114 | * - v2.0.1 - Fixed an error when decoding a single byte, that is, when the
115 | * encoded data was a single byte.
116 | * - v2.0 - I got rid of methods that used booleans to set options.
117 | * Now everything is nav_common consolidated and cleaner. The code now detects
118 | * when data that's being decoded is gzip-compressed and will decompress it
119 | * automatically. Generally things are cleaner. You'll probably have to
120 | * change some method calls that you were making to support the new
121 | * options format (ints that you "OR" together).
122 | * - v1.5.1 - Fixed bug when decompressing and decoding to a
123 | * byte[] using decode( String s, boolean gzipCompressed ).
124 | * Added the ability to "suspend" encoding in the Output Stream so
125 | * you can turn on and off the encoding if you need to embed base64
126 | * data in an otherwise "normal" stream (like an XML file).
127 | * - v1.5 - Output stream pases on flush() command but doesn't do anything itself.
128 | * This helps when using GZIP streams.
129 | * Added the ability to GZip-compress objects before encoding them.
130 | * - v1.4 - Added helper methods to read/write files.
131 | * - v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
132 | * - v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
133 | * where last buffer being read, if not completely full, was not returned.
134 | * - v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
135 | * - v1.3.3 - Fixed I/O streams which were totally messed up.
136 | *
137 | *
138 | *
139 | * I am placing this code in the Public Domain. Do with it as you will.
140 | * This software comes with no guarantees or warranties but with
141 | * plenty of well-wishing instead!
142 | * Please visit http://iharder.net/base64
143 | * periodically to check for updates or to contribute improvements.
144 | *
145 | *
146 | * @author Robert Harder
147 | * @author rob@iharder.net
148 | * @version 2.3.7
149 | */
150 | public class MyBase64
151 | {
152 |
153 | /* ******** P U B L I C F I E L D S ******** */
154 |
155 |
156 | /** No options specified. Value is zero. */
157 | public final static int NO_OPTIONS = 0;
158 |
159 | /** Specify encoding in first bit. Value is one. */
160 | public final static int ENCODE = 1;
161 |
162 |
163 | /** Specify decoding in first bit. Value is zero. */
164 | public final static int DECODE = 0;
165 |
166 |
167 | /** Specify that data should be gzip-compressed in second bit. Value is two. */
168 | public final static int GZIP = 2;
169 |
170 | /** Specify that gzipped data should not be automatically gunzipped. */
171 | public final static int DONT_GUNZIP = 4;
172 |
173 |
174 | /** Do break lines when encoding. Value is 8. */
175 | public final static int DO_BREAK_LINES = 8;
176 |
177 | /**
178 | * Encode using Base64-like encoding that is URL- and Filename-safe as described
179 | * in Section 4 of RFC3548:
180 | * http://www.faqs.org/rfcs/rfc3548.html.
181 | * It is important to note that data encoded this way is not officially valid Base64,
182 | * or at the very least should not be called Base64 without also specifying that is
183 | * was encoded using the URL- and Filename-safe dialect.
184 | */
185 | public final static int URL_SAFE = 16;
186 |
187 |
188 | /**
189 | * Encode using the special "ordered" dialect of Base64 described here:
190 | * http://www.faqs.org/qa/rfcc-1940.html.
191 | */
192 | public final static int ORDERED = 32;
193 |
194 |
195 | /* ******** P R I V A T E F I E L D S ******** */
196 |
197 |
198 | /** Maximum line length (76) of Base64 output. */
199 | private final static int MAX_LINE_LENGTH = 76;
200 |
201 |
202 | /** The equals sign (=) as a byte. */
203 | private final static byte EQUALS_SIGN = (byte)'=';
204 |
205 |
206 | /** The new line character (\n) as a byte. */
207 | private final static byte NEW_LINE = (byte)'\n';
208 |
209 |
210 | /** Preferred encoding. */
211 | private final static String PREFERRED_ENCODING = "US-ASCII";
212 |
213 |
214 | private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
215 | private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
216 |
217 |
218 | /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */
219 |
220 | /** The 64 valid Base64 values. */
221 | /* Host platform me be something funny like EBCDIC, so we hardcode these values. */
222 | private final static byte[] _STANDARD_ALPHABET = {
223 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
224 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
225 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
226 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
227 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
228 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
229 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
230 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
231 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
232 | (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/'
233 | };
234 |
235 |
236 | /**
237 | * Translates a Base64 value to either its 6-bit reconstruction value
238 | * or a negative number indicating some other meaning.
239 | **/
240 | private final static byte[] _STANDARD_DECODABET = {
241 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
242 | -5,-5, // Whitespace: Tab and Linefeed
243 | -9,-9, // Decimal 11 - 12
244 | -5, // Whitespace: Carriage Return
245 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
246 | -9,-9,-9,-9,-9, // Decimal 27 - 31
247 | -5, // Whitespace: Space
248 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
249 | 62, // Plus sign at decimal 43
250 | -9,-9,-9, // Decimal 44 - 46
251 | 63, // Slash at decimal 47
252 | 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine
253 | -9,-9,-9, // Decimal 58 - 60
254 | -1, // Equals sign at decimal 61
255 | -9,-9,-9, // Decimal 62 - 64
256 | 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N'
257 | 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z'
258 | -9,-9,-9,-9,-9,-9, // Decimal 91 - 96
259 | 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm'
260 | 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z'
261 | -9,-9,-9,-9,-9 // Decimal 123 - 127
262 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
263 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
264 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
265 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
266 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
267 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
268 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
269 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
270 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
271 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
272 | };
273 |
274 |
275 | /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */
276 |
277 | /**
278 | * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548:
279 | * http://www.faqs.org/rfcs/rfc3548.html.
280 | * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash."
281 | */
282 | private final static byte[] _URL_SAFE_ALPHABET = {
283 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
284 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
285 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
286 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
287 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
288 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
289 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
290 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
291 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
292 | (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_'
293 | };
294 |
295 | /**
296 | * Used in decoding URL- and Filename-safe dialects of Base64.
297 | */
298 | private final static byte[] _URL_SAFE_DECODABET = {
299 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
300 | -5,-5, // Whitespace: Tab and Linefeed
301 | -9,-9, // Decimal 11 - 12
302 | -5, // Whitespace: Carriage Return
303 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
304 | -9,-9,-9,-9,-9, // Decimal 27 - 31
305 | -5, // Whitespace: Space
306 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
307 | -9, // Plus sign at decimal 43
308 | -9, // Decimal 44
309 | 62, // Minus sign at decimal 45
310 | -9, // Decimal 46
311 | -9, // Slash at decimal 47
312 | 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine
313 | -9,-9,-9, // Decimal 58 - 60
314 | -1, // Equals sign at decimal 61
315 | -9,-9,-9, // Decimal 62 - 64
316 | 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N'
317 | 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z'
318 | -9,-9,-9,-9, // Decimal 91 - 94
319 | 63, // Underscore at decimal 95
320 | -9, // Decimal 96
321 | 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm'
322 | 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z'
323 | -9,-9,-9,-9,-9 // Decimal 123 - 127
324 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
325 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
326 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
327 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
328 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
329 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
330 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
331 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
332 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
333 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
334 | };
335 |
336 |
337 |
338 | /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */
339 |
340 | /**
341 | * I don't get the point of this technique, but someone requested it,
342 | * and it is described here:
343 | * http://www.faqs.org/qa/rfcc-1940.html.
344 | */
345 | private final static byte[] _ORDERED_ALPHABET = {
346 | (byte)'-',
347 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4',
348 | (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9',
349 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
350 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
351 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
352 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
353 | (byte)'_',
354 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
355 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
356 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
357 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z'
358 | };
359 |
360 | /**
361 | * Used in decoding the "ordered" dialect of Base64.
362 | */
363 | private final static byte[] _ORDERED_DECODABET = {
364 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
365 | -5,-5, // Whitespace: Tab and Linefeed
366 | -9,-9, // Decimal 11 - 12
367 | -5, // Whitespace: Carriage Return
368 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
369 | -9,-9,-9,-9,-9, // Decimal 27 - 31
370 | -5, // Whitespace: Space
371 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
372 | -9, // Plus sign at decimal 43
373 | -9, // Decimal 44
374 | 0, // Minus sign at decimal 45
375 | -9, // Decimal 46
376 | -9, // Slash at decimal 47
377 | 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine
378 | -9,-9,-9, // Decimal 58 - 60
379 | -1, // Equals sign at decimal 61
380 | -9,-9,-9, // Decimal 62 - 64
381 | 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M'
382 | 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z'
383 | -9,-9,-9,-9, // Decimal 91 - 94
384 | 37, // Underscore at decimal 95
385 | -9, // Decimal 96
386 | 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm'
387 | 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z'
388 | -9,-9,-9,-9,-9 // Decimal 123 - 127
389 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
390 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
391 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
392 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
393 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
394 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
395 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
396 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
397 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
398 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
399 | };
400 |
401 |
402 | /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */
403 |
404 |
405 | /**
406 | * Returns one of the _SOMETHING_ALPHABET byte arrays depending on
407 | * the options specified.
408 | * It's possible, though silly, to specify ORDERED and URLSAFE
409 | * in which case one of them will be picked, though there is
410 | * no guarantee as to which one will be picked.
411 | */
412 | private final static byte[] getAlphabet( int options ) {
413 | if ((options & URL_SAFE) == URL_SAFE) {
414 | return _URL_SAFE_ALPHABET;
415 | } else if ((options & ORDERED) == ORDERED) {
416 | return _ORDERED_ALPHABET;
417 | } else {
418 | return _STANDARD_ALPHABET;
419 | }
420 | } // end getAlphabet
421 |
422 |
423 | /**
424 | * Returns one of the _SOMETHING_DECODABET byte arrays depending on
425 | * the options specified.
426 | * It's possible, though silly, to specify ORDERED and URL_SAFE
427 | * in which case one of them will be picked, though there is
428 | * no guarantee as to which one will be picked.
429 | */
430 | private final static byte[] getDecodabet( int options ) {
431 | if( (options & URL_SAFE) == URL_SAFE) {
432 | return _URL_SAFE_DECODABET;
433 | } else if ((options & ORDERED) == ORDERED) {
434 | return _ORDERED_DECODABET;
435 | } else {
436 | return _STANDARD_DECODABET;
437 | }
438 | } // end getAlphabet
439 |
440 |
441 |
442 | /** Defeats instantiation. */
443 | private MyBase64(){}
444 |
445 |
446 |
447 |
448 | /* ******** E N C O D I N G M E T H O D S ******** */
449 |
450 |
451 | /**
452 | * Encodes up to the first three bytes of array threeBytes
453 | * and returns a four-byte array in Base64 notation.
454 | * The actual number of significant bytes in your array is
455 | * given by numSigBytes.
456 | * The array threeBytes needs only be as big as
457 | * numSigBytes.
458 | * Code can reuse a byte array by passing a four-byte array as b4.
459 | *
460 | * @param b4 A reusable byte array to reduce array instantiation
461 | * @param threeBytes the array to convert
462 | * @param numSigBytes the number of significant bytes in your array
463 | * @return four byte array in Base64 notation.
464 | * @since 1.5.1
465 | */
466 | private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) {
467 | encode3to4( threeBytes, 0, numSigBytes, b4, 0, options );
468 | return b4;
469 | } // end encode3to4
470 |
471 |
472 | /**
473 | * Encodes up to three bytes of the array source
474 | * and writes the resulting four Base64 bytes to destination.
475 | * The source and destination arrays can be manipulated
476 | * anywhere along their length by specifying
477 | * srcOffset and destOffset.
478 | * This method does not check to make sure your arrays
479 | * are large enough to accomodate srcOffset + 3 for
480 | * the source array or destOffset + 4 for
481 | * the destination array.
482 | * The actual number of significant bytes in your array is
483 | * given by numSigBytes.
484 | * This is the lowest level of the encoding methods with
485 | * all possible parameters.
486 | *
487 | * @param source the array to convert
488 | * @param srcOffset the index where conversion begins
489 | * @param numSigBytes the number of significant bytes in your array
490 | * @param destination the array to hold the conversion
491 | * @param destOffset the index where output will be put
492 | * @return the destination array
493 | * @since 1.3
494 | */
495 | private static byte[] encode3to4(
496 | byte[] source, int srcOffset, int numSigBytes,
497 | byte[] destination, int destOffset, int options ) {
498 |
499 | byte[] ALPHABET = getAlphabet( options );
500 |
501 | // 1 2 3
502 | // 01234567890123456789012345678901 Bit position
503 | // --------000000001111111122222222 Array position from threeBytes
504 | // --------| || || || | Six bit groups to index ALPHABET
505 | // >>18 >>12 >> 6 >> 0 Right shift necessary
506 | // 0x3f 0x3f 0x3f Additional AND
507 |
508 | // Create buffer with zero-padding if there are only one or two
509 | // significant bytes passed in the array.
510 | // We have to shift left 24 in order to flush out the 1's that appear
511 | // when Java treats a value as negative that is cast from a byte to an int.
512 | int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 )
513 | | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 )
514 | | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 );
515 |
516 | switch( numSigBytes )
517 | {
518 | case 3:
519 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
520 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
521 | destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
522 | destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ];
523 | return destination;
524 |
525 | case 2:
526 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
527 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
528 | destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
529 | destination[ destOffset + 3 ] = EQUALS_SIGN;
530 | return destination;
531 |
532 | case 1:
533 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
534 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
535 | destination[ destOffset + 2 ] = EQUALS_SIGN;
536 | destination[ destOffset + 3 ] = EQUALS_SIGN;
537 | return destination;
538 |
539 | default:
540 | return destination;
541 | } // end switch
542 | } // end encode3to4
543 |
544 |
545 |
546 | /**
547 | * Performs Base64 encoding on the raw
ByteBuffer,
548 | * writing it to the encoded
ByteBuffer.
549 | * This is an experimental feature. Currently it does not
550 | * pass along any options (such as {@link #DO_BREAK_LINES}
551 | * or {@link #GZIP}.
552 | *
553 | * @param raw input buffer
554 | * @param encoded output buffer
555 | * @since 2.3
556 | */
557 | public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){
558 | byte[] raw3 = new byte[3];
559 | byte[] enc4 = new byte[4];
560 |
561 | while( raw.hasRemaining() ){
562 | int rem = Math.min(3,raw.remaining());
563 | raw.get(raw3,0,rem);
564 | MyBase64.encode3to4(enc4, raw3, rem, MyBase64.NO_OPTIONS );
565 | encoded.put(enc4);
566 | } // end input remaining
567 | }
568 |
569 |
570 | /**
571 | * Performs Base64 encoding on the raw
ByteBuffer,
572 | * writing it to the encoded
CharBuffer.
573 | * This is an experimental feature. Currently it does not
574 | * pass along any options (such as {@link #DO_BREAK_LINES}
575 | * or {@link #GZIP}.
576 | *
577 | * @param raw input buffer
578 | * @param encoded output buffer
579 | * @since 2.3
580 | */
581 | public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){
582 | byte[] raw3 = new byte[3];
583 | byte[] enc4 = new byte[4];
584 |
585 | while( raw.hasRemaining() ){
586 | int rem = Math.min(3,raw.remaining());
587 | raw.get(raw3,0,rem);
588 | MyBase64.encode3to4(enc4, raw3, rem, MyBase64.NO_OPTIONS );
589 | for( int i = 0; i < 4; i++ ){
590 | encoded.put( (char)(enc4[i] & 0xFF) );
591 | }
592 | } // end input remaining
593 | }
594 |
595 |
596 |
597 |
598 | /**
599 | * Serializes an object and returns the Base64-encoded
600 | * version of that serialized object.
601 | *
602 | * As of v 2.3, if the object
603 | * cannot be serialized or there is another error,
604 | * the method will throw an java.io.IOException. This is new to v2.3!
605 | * In earlier versions, it just returned a null value, but
606 | * in retrospect that's a pretty poor way to handle it.
607 | *
608 | * The object is not GZip-compressed before being encoded.
609 | *
610 | * @param serializableObject The object to encode
611 | * @return The Base64-encoded object
612 | * @throws java.io.IOException if there is an error
613 | * @throws NullPointerException if serializedObject is null
614 | * @since 1.4
615 | */
616 | public static String encodeObject(java.io.Serializable serializableObject )
617 | throws java.io.IOException {
618 | return encodeObject( serializableObject, NO_OPTIONS );
619 | } // end encodeObject
620 |
621 |
622 |
623 | /**
624 | * Serializes an object and returns the Base64-encoded
625 | * version of that serialized object.
626 | *
627 | * As of v 2.3, if the object
628 | * cannot be serialized or there is another error,
629 | * the method will throw an java.io.IOException. This is new to v2.3!
630 | * In earlier versions, it just returned a null value, but
631 | * in retrospect that's a pretty poor way to handle it.
632 | *
633 | * The object is not GZip-compressed before being encoded.
634 | *
635 | * Example options:
636 | * GZIP: gzip-compresses object before encoding it.
637 | * DO_BREAK_LINES: break lines at 76 characters
638 | *
639 | *
640 | * Example: encodeObject( myObj, Base64.GZIP )
or
641 | *
642 | * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES )
643 | *
644 | * @param serializableObject The object to encode
645 | * @param options Specified options
646 | * @return The Base64-encoded object
647 | * @see MyBase64#GZIP
648 | * @see MyBase64#DO_BREAK_LINES
649 | * @throws java.io.IOException if there is an error
650 | * @since 2.0
651 | */
652 | public static String encodeObject(java.io.Serializable serializableObject, int options )
653 | throws java.io.IOException {
654 |
655 | if( serializableObject == null ){
656 | throw new NullPointerException( "Cannot serialize a null object." );
657 | } // end if: null
658 |
659 | // Streams
660 | java.io.ByteArrayOutputStream baos = null;
661 | java.io.OutputStream b64os = null;
662 | java.util.zip.GZIPOutputStream gzos = null;
663 | java.io.ObjectOutputStream oos = null;
664 |
665 |
666 | //noinspection CaughtExceptionImmediatelyRethrown
667 | try {
668 | // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
669 | baos = new java.io.ByteArrayOutputStream();
670 | b64os = new OutputStream( baos, ENCODE | options );
671 | if( (options & GZIP) != 0 ){
672 | // Gzip
673 | gzos = new java.util.zip.GZIPOutputStream(b64os);
674 | oos = new java.io.ObjectOutputStream( gzos );
675 | } else {
676 | // Not gzipped
677 | oos = new java.io.ObjectOutputStream( b64os );
678 | }
679 | oos.writeObject( serializableObject );
680 | } // end try
681 | catch( java.io.IOException e ) {
682 | // Catch it and then throw it immediately so that
683 | // the finally{} block is called for cleanup.
684 | throw e;
685 | } // end catch
686 | finally {
687 | try{ oos.close(); } catch( Exception e ){}
688 | try{ gzos.close(); } catch( Exception e ){}
689 | try{ b64os.close(); } catch( Exception e ){}
690 | try{ baos.close(); } catch( Exception e ){}
691 | } // end finally
692 |
693 | // Return value according to relevant encoding.
694 | try {
695 | return new String( baos.toByteArray(), PREFERRED_ENCODING );
696 | } // end try
697 | catch (java.io.UnsupportedEncodingException uue){
698 | // Fall back to some Java default
699 | return new String( baos.toByteArray() );
700 | } // end catch
701 |
702 | } // end encode
703 |
704 |
705 |
706 | /**
707 | * Encodes a byte array into Base64 notation.
708 | * Does not GZip-compress data.
709 | *
710 | * @param source The data to convert
711 | * @return The data in Base64-encoded form
712 | * @throws NullPointerException if source array is null
713 | * @since 1.4
714 | */
715 | public static String encodeBytes(byte[] source ) {
716 | // Since we're not going to have the GZIP encoding turned on,
717 | // we're not going to have an java.io.IOException thrown, so
718 | // we should not force the user to have to catch it.
719 | String encoded = null;
720 | try {
721 | encoded = encodeBytes(source, 0, source.length, NO_OPTIONS);
722 | } catch (java.io.IOException ex) {
723 | assert false : ex.getMessage();
724 | } // end catch
725 | assert encoded != null;
726 | return encoded;
727 | } // end encodeBytes
728 |
729 |
730 |
731 | /**
732 | * Encodes a byte array into Base64 notation.
733 | *
734 | * Example options:
735 | * GZIP: gzip-compresses object before encoding it.
736 | * DO_BREAK_LINES: break lines at 76 characters
737 | * Note: Technically, this makes your encoding non-compliant.
738 | *
739 | *
740 | * Example: encodeBytes( myData, Base64.GZIP )
or
741 | *
742 | * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )
743 | *
744 | *
745 | *
As of v 2.3, if there is an error with the GZIP stream,
746 | * the method will throw an java.io.IOException. This is new to v2.3!
747 | * In earlier versions, it just returned a null value, but
748 | * in retrospect that's a pretty poor way to handle it.
749 | *
750 | *
751 | * @param source The data to convert
752 | * @param options Specified options
753 | * @return The Base64-encoded data as a String
754 | * @see MyBase64#GZIP
755 | * @see MyBase64#DO_BREAK_LINES
756 | * @throws java.io.IOException if there is an error
757 | * @throws NullPointerException if source array is null
758 | * @since 2.0
759 | */
760 | public static String encodeBytes(byte[] source, int options ) throws java.io.IOException {
761 | return encodeBytes( source, 0, source.length, options );
762 | } // end encodeBytes
763 |
764 |
765 | /**
766 | * Encodes a byte array into Base64 notation.
767 | * Does not GZip-compress data.
768 | *
769 | * As of v 2.3, if there is an error,
770 | * the method will throw an java.io.IOException. This is new to v2.3!
771 | * In earlier versions, it just returned a null value, but
772 | * in retrospect that's a pretty poor way to handle it.
773 | *
774 | *
775 | * @param source The data to convert
776 | * @param off Offset in array where conversion should begin
777 | * @param len Length of data to convert
778 | * @return The Base64-encoded data as a String
779 | * @throws NullPointerException if source array is null
780 | * @throws IllegalArgumentException if source array, offset, or length are invalid
781 | * @since 1.4
782 | */
783 | public static String encodeBytes(byte[] source, int off, int len ) {
784 | // Since we're not going to have the GZIP encoding turned on,
785 | // we're not going to have an java.io.IOException thrown, so
786 | // we should not force the user to have to catch it.
787 | String encoded = null;
788 | try {
789 | encoded = encodeBytes( source, off, len, NO_OPTIONS );
790 | } catch (java.io.IOException ex) {
791 | assert false : ex.getMessage();
792 | } // end catch
793 | assert encoded != null;
794 | return encoded;
795 | } // end encodeBytes
796 |
797 |
798 |
799 | /**
800 | * Encodes a byte array into Base64 notation.
801 | *
802 | * Example options:
803 | * GZIP: gzip-compresses object before encoding it.
804 | * DO_BREAK_LINES: break lines at 76 characters
805 | * Note: Technically, this makes your encoding non-compliant.
806 | *
807 | *
808 | * Example: encodeBytes( myData, Base64.GZIP )
or
809 | *
810 | * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )
811 | *
812 | *
813 | *
As of v 2.3, if there is an error with the GZIP stream,
814 | * the method will throw an java.io.IOException. This is new to v2.3!
815 | * In earlier versions, it just returned a null value, but
816 | * in retrospect that's a pretty poor way to handle it.
817 | *
818 | *
819 | * @param source The data to convert
820 | * @param off Offset in array where conversion should begin
821 | * @param len Length of data to convert
822 | * @param options Specified options
823 | * @return The Base64-encoded data as a String
824 | * @see MyBase64#GZIP
825 | * @see MyBase64#DO_BREAK_LINES
826 | * @throws java.io.IOException if there is an error
827 | * @throws NullPointerException if source array is null
828 | * @throws IllegalArgumentException if source array, offset, or length are invalid
829 | * @since 2.0
830 | */
831 | public static String encodeBytes(byte[] source, int off, int len, int options ) throws java.io.IOException {
832 | byte[] encoded = encodeBytesToBytes( source, off, len, options );
833 |
834 | // Return value according to relevant encoding.
835 | try {
836 | return new String( encoded, PREFERRED_ENCODING );
837 | } // end try
838 | catch (java.io.UnsupportedEncodingException uue) {
839 | return new String( encoded );
840 | } // end catch
841 |
842 | } // end encodeBytes
843 |
844 |
845 |
846 |
847 | /**
848 | * Similar to {@link #encodeBytes(byte[])} but returns
849 | * a byte array instead of instantiating a String. This is nav_common efficient
850 | * if you're working with I/O streams and have large data sets to encode.
851 | *
852 | *
853 | * @param source The data to convert
854 | * @return The Base64-encoded data as a byte[] (of ASCII characters)
855 | * @throws NullPointerException if source array is null
856 | * @since 2.3.1
857 | */
858 | public static byte[] encodeBytesToBytes( byte[] source ) {
859 | byte[] encoded = null;
860 | try {
861 | encoded = encodeBytesToBytes( source, 0, source.length, MyBase64.NO_OPTIONS );
862 | } catch( java.io.IOException ex ) {
863 | assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
864 | }
865 | return encoded;
866 | }
867 |
868 |
869 | /**
870 | * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns
871 | * a byte array instead of instantiating a String. This is nav_common efficient
872 | * if you're working with I/O streams and have large data sets to encode.
873 | *
874 | *
875 | * @param source The data to convert
876 | * @param off Offset in array where conversion should begin
877 | * @param len Length of data to convert
878 | * @param options Specified options
879 | * @return The Base64-encoded data as a String
880 | * @see MyBase64#GZIP
881 | * @see MyBase64#DO_BREAK_LINES
882 | * @throws java.io.IOException if there is an error
883 | * @throws NullPointerException if source array is null
884 | * @throws IllegalArgumentException if source array, offset, or length are invalid
885 | * @since 2.3.1
886 | */
887 | public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException {
888 |
889 | if( source == null ){
890 | throw new NullPointerException( "Cannot serialize a null array." );
891 | } // end if: null
892 |
893 | if( off < 0 ){
894 | throw new IllegalArgumentException( "Cannot have negative offset: " + off );
895 | } // end if: off < 0
896 |
897 | if( len < 0 ){
898 | throw new IllegalArgumentException( "Cannot have length offset: " + len );
899 | } // end if: len < 0
900 |
901 | if( off + len > source.length ){
902 | throw new IllegalArgumentException(
903 | String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length));
904 | } // end if: off < 0
905 |
906 |
907 |
908 | // Compress?
909 | if( (options & GZIP) != 0 ) {
910 | java.io.ByteArrayOutputStream baos = null;
911 | java.util.zip.GZIPOutputStream gzos = null;
912 | OutputStream b64os = null;
913 |
914 | //noinspection CaughtExceptionImmediatelyRethrown
915 | try {
916 | // GZip -> Base64 -> ByteArray
917 | baos = new java.io.ByteArrayOutputStream();
918 | b64os = new OutputStream( baos, ENCODE | options );
919 | gzos = new java.util.zip.GZIPOutputStream( b64os );
920 |
921 | gzos.write( source, off, len );
922 | gzos.close();
923 | } // end try
924 | catch( java.io.IOException e ) {
925 | // Catch it and then throw it immediately so that
926 | // the finally{} block is called for cleanup.
927 | throw e;
928 | } // end catch
929 | finally {
930 | try{ gzos.close(); } catch( Exception e ){}
931 | try{ b64os.close(); } catch( Exception e ){}
932 | try{ baos.close(); } catch( Exception e ){}
933 | } // end finally
934 |
935 | return baos.toByteArray();
936 | } // end if: compress
937 |
938 | // Else, don't compress. Better not to use streams at all then.
939 | else {
940 | boolean breakLines = (options & DO_BREAK_LINES) != 0;
941 |
942 | //int len43 = len * 4 / 3;
943 | //byte[] outBuff = new byte[ ( len43 ) // Main 4:3
944 | // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
945 | // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
946 | // Try to determine nav_common precisely how big the array needs to be.
947 | // If we get it right, we don't have to do an array copy, and
948 | // we save a bunch of memory.
949 | int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding
950 | if( breakLines ){
951 | encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters
952 | }
953 | byte[] outBuff = new byte[ encLen ];
954 |
955 |
956 | int d = 0;
957 | int e = 0;
958 | int len2 = len - 2;
959 | int lineLength = 0;
960 | for( ; d < len2; d+=3, e+=4 ) {
961 | encode3to4( source, d+off, 3, outBuff, e, options );
962 |
963 | lineLength += 4;
964 | if( breakLines && lineLength >= MAX_LINE_LENGTH )
965 | {
966 | outBuff[e+4] = NEW_LINE;
967 | e++;
968 | lineLength = 0;
969 | } // end if: end of line
970 | } // en dfor: each piece of array
971 |
972 | if( d < len ) {
973 | encode3to4( source, d+off, len - d, outBuff, e, options );
974 | e += 4;
975 | } // end if: some padding needed
976 |
977 |
978 | // Only resize array if we didn't guess it right.
979 | if( e <= outBuff.length - 1 ){
980 | // If breaking lines and the last byte falls right at
981 | // the line length (76 bytes per line), there will be
982 | // one extra byte, and the array will need to be resized.
983 | // Not too bad of an estimate on array size, I'd say.
984 | byte[] finalOut = new byte[e];
985 | System.arraycopy(outBuff,0, finalOut,0,e);
986 | //System.err.println("Having to resize array from " + outBuff.length + " to " + e );
987 | return finalOut;
988 | } else {
989 | //System.err.println("No need to resize array.");
990 | return outBuff;
991 | }
992 |
993 | } // end else: don't compress
994 |
995 | } // end encodeBytesToBytes
996 |
997 |
998 |
999 |
1000 |
1001 | /* ******** D E C O D I N G M E T H O D S ******** */
1002 |
1003 |
1004 | /**
1005 | * Decodes four bytes from array source
1006 | * and writes the resulting bytes (up to three of them)
1007 | * to destination.
1008 | * The source and destination arrays can be manipulated
1009 | * anywhere along their length by specifying
1010 | * srcOffset and destOffset.
1011 | * This method does not check to make sure your arrays
1012 | * are large enough to accomodate srcOffset + 4 for
1013 | * the source array or destOffset + 3 for
1014 | * the destination array.
1015 | * This method returns the actual number of bytes that
1016 | * were converted from the Base64 encoding.
1017 | * This is the lowest level of the decoding methods with
1018 | * all possible parameters.
1019 | *
1020 | *
1021 | * @param source the array to convert
1022 | * @param srcOffset the index where conversion begins
1023 | * @param destination the array to hold the conversion
1024 | * @param destOffset the index where output will be put
1025 | * @param options alphabet type is pulled from this (standard, url-safe, ordered)
1026 | * @return the number of decoded bytes converted
1027 | * @throws NullPointerException if source or destination arrays are null
1028 | * @throws IllegalArgumentException if srcOffset or destOffset are invalid
1029 | * or there is not enough room in the array.
1030 | * @since 1.3
1031 | */
1032 | private static int decode4to3(
1033 | byte[] source, int srcOffset,
1034 | byte[] destination, int destOffset, int options ) {
1035 |
1036 | // Lots of error checking and exception throwing
1037 | if( source == null ){
1038 | throw new NullPointerException( "Source array was null." );
1039 | } // end if
1040 | if( destination == null ){
1041 | throw new NullPointerException( "Destination array was null." );
1042 | } // end if
1043 | if( srcOffset < 0 || srcOffset + 3 >= source.length ){
1044 | throw new IllegalArgumentException( String.format(
1045 | "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) );
1046 | } // end if
1047 | if( destOffset < 0 || destOffset +2 >= destination.length ){
1048 | throw new IllegalArgumentException( String.format(
1049 | "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) );
1050 | } // end if
1051 |
1052 |
1053 | byte[] DECODABET = getDecodabet( options );
1054 |
1055 | // Example: Dk==
1056 | if( source[ srcOffset + 2] == EQUALS_SIGN ) {
1057 | // Two ways to do the same thing. Don't know which way I like best.
1058 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
1059 | // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
1060 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
1061 | | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
1062 |
1063 | destination[ destOffset ] = (byte)( outBuff >>> 16 );
1064 | return 1;
1065 | }
1066 |
1067 | // Example: DkL=
1068 | else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) {
1069 | // Two ways to do the same thing. Don't know which way I like best.
1070 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
1071 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
1072 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
1073 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
1074 | | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
1075 | | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 );
1076 |
1077 | destination[ destOffset ] = (byte)( outBuff >>> 16 );
1078 | destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 );
1079 | return 2;
1080 | }
1081 |
1082 | // Example: DkLE
1083 | else {
1084 | // Two ways to do the same thing. Don't know which way I like best.
1085 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
1086 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
1087 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
1088 | // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
1089 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
1090 | | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
1091 | | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6)
1092 | | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) );
1093 |
1094 |
1095 | destination[ destOffset ] = (byte)( outBuff >> 16 );
1096 | destination[ destOffset + 1 ] = (byte)( outBuff >> 8 );
1097 | destination[ destOffset + 2 ] = (byte)( outBuff );
1098 |
1099 | return 3;
1100 | }
1101 | } // end decodeToBytes
1102 |
1103 |
1104 |
1105 |
1106 |
1107 | /**
1108 | * Low-level access to decoding ASCII characters in
1109 | * the form of a byte array. Ignores GUNZIP option, if
1110 | * it's set. This is not generally a recommended method,
1111 | * although it is used internally as part of the decoding process.
1112 | * Special case: if len = 0, an empty array is returned. Still,
1113 | * if you need nav_common speed and reduced memory footprint (and aren't
1114 | * gzipping), consider this method.
1115 | *
1116 | * @param source The Base64 encoded data
1117 | * @return decoded data
1118 | * @since 2.3.1
1119 | */
1120 | public static byte[] decode( byte[] source )
1121 | throws java.io.IOException {
1122 | byte[] decoded = null;
1123 | // try {
1124 | decoded = decode( source, 0, source.length, MyBase64.NO_OPTIONS );
1125 | // } catch( java.io.IOException ex ) {
1126 | // assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
1127 | // }
1128 | return decoded;
1129 | }
1130 |
1131 |
1132 |
1133 | /**
1134 | * Low-level access to decoding ASCII characters in
1135 | * the form of a byte array. Ignores GUNZIP option, if
1136 | * it's set. This is not generally a recommended method,
1137 | * although it is used internally as part of the decoding process.
1138 | * Special case: if len = 0, an empty array is returned. Still,
1139 | * if you need nav_common speed and reduced memory footprint (and aren't
1140 | * gzipping), consider this method.
1141 | *
1142 | * @param source The Base64 encoded data
1143 | * @param off The offset of where to begin decoding
1144 | * @param len The length of characters to decode
1145 | * @param options Can specify options such as alphabet type to use
1146 | * @return decoded data
1147 | * @throws java.io.IOException If bogus characters exist in source data
1148 | * @since 1.3
1149 | */
1150 | public static byte[] decode( byte[] source, int off, int len, int options )
1151 | throws java.io.IOException {
1152 |
1153 | // Lots of error checking and exception throwing
1154 | if( source == null ){
1155 | throw new NullPointerException( "Cannot decode null source array." );
1156 | } // end if
1157 | if( off < 0 || off + len > source.length ){
1158 | throw new IllegalArgumentException( String.format(
1159 | "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) );
1160 | } // end if
1161 |
1162 | if( len == 0 ){
1163 | return new byte[0];
1164 | }else if( len < 4 ){
1165 | throw new IllegalArgumentException(
1166 | "Base64-encoded string must have at least four characters, but length specified was " + len );
1167 | } // end if
1168 |
1169 | byte[] DECODABET = getDecodabet( options );
1170 |
1171 | int len34 = len * 3 / 4; // Estimate on array size
1172 | byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
1173 | int outBuffPosn = 0; // Keep track of where we're writing
1174 |
1175 | byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space
1176 | int b4Posn = 0; // Keep track of four byte input buffer
1177 | int i = 0; // Source array counter
1178 | byte sbiDecode = 0; // Special value from DECODABET
1179 |
1180 | for( i = off; i < off+len; i++ ) { // Loop through source
1181 |
1182 | sbiDecode = DECODABET[ source[i]&0xFF ];
1183 |
1184 | // White space, Equals sign, or legit Base64 character
1185 | // Note the values such as -5 and -9 in the
1186 | // DECODABETs at the top of the file.
1187 | if( sbiDecode >= WHITE_SPACE_ENC ) {
1188 | if( sbiDecode >= EQUALS_SIGN_ENC ) {
1189 | b4[ b4Posn++ ] = source[i]; // Save non-whitespace
1190 | if( b4Posn > 3 ) { // Time to decode?
1191 | outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options );
1192 | b4Posn = 0;
1193 |
1194 | // If that was the equals sign, break out of 'for' loop
1195 | if( source[i] == EQUALS_SIGN ) {
1196 | break;
1197 | } // end if: equals sign
1198 | } // end if: quartet built
1199 | } // end if: equals sign or better
1200 | } // end if: white space, equals sign or better
1201 | else {
1202 | // There's a bad input character in the Base64 stream.
1203 | throw new java.io.IOException( String.format(
1204 | "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) );
1205 | } // end else:
1206 | } // each input character
1207 |
1208 | byte[] out = new byte[ outBuffPosn ];
1209 | System.arraycopy( outBuff, 0, out, 0, outBuffPosn );
1210 | return out;
1211 | } // end decode
1212 |
1213 |
1214 |
1215 |
1216 | /**
1217 | * Decodes data from Base64 notation, automatically
1218 | * detecting gzip-compressed data and decompressing it.
1219 | *
1220 | * @param s the string to decode
1221 | * @return the decoded data
1222 | * @throws java.io.IOException If there is a problem
1223 | * @since 1.4
1224 | */
1225 | public static byte[] decode( String s ) throws java.io.IOException {
1226 | return decode( s, NO_OPTIONS );
1227 | }
1228 |
1229 |
1230 |
1231 | /**
1232 | * Decodes data from Base64 notation, automatically
1233 | * detecting gzip-compressed data and decompressing it.
1234 | *
1235 | * @param s the string to decode
1236 | * @param options encode options such as URL_SAFE
1237 | * @return the decoded data
1238 | * @throws java.io.IOException if there is an error
1239 | * @throws NullPointerException if s is null
1240 | * @since 1.4
1241 | */
1242 | public static byte[] decode(String s, int options ) throws java.io.IOException {
1243 |
1244 | if( s == null ){
1245 | throw new NullPointerException( "Input string was null." );
1246 | } // end if
1247 |
1248 | byte[] bytes;
1249 | try {
1250 | bytes = s.getBytes( PREFERRED_ENCODING );
1251 | } // end try
1252 | catch( java.io.UnsupportedEncodingException uee ) {
1253 | bytes = s.getBytes();
1254 | } // end catch
1255 | //
1256 |
1257 | // Decode
1258 | bytes = decode( bytes, 0, bytes.length, options );
1259 |
1260 | // Check to see if it's gzip-compressed
1261 | // GZIP Magic Two-Byte Number: 0x8b1f (35615)
1262 | boolean dontGunzip = (options & DONT_GUNZIP) != 0;
1263 | if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) {
1264 |
1265 | int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
1266 | if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) {
1267 | java.io.ByteArrayInputStream bais = null;
1268 | java.util.zip.GZIPInputStream gzis = null;
1269 | java.io.ByteArrayOutputStream baos = null;
1270 | byte[] buffer = new byte[2048];
1271 | int length = 0;
1272 |
1273 | try {
1274 | baos = new java.io.ByteArrayOutputStream();
1275 | bais = new java.io.ByteArrayInputStream( bytes );
1276 | gzis = new java.util.zip.GZIPInputStream( bais );
1277 |
1278 | while( ( length = gzis.read( buffer ) ) >= 0 ) {
1279 | baos.write(buffer,0,length);
1280 | } // end while: reading input
1281 |
1282 | // No error? Get new bytes.
1283 | bytes = baos.toByteArray();
1284 |
1285 | } // end try
1286 | catch( java.io.IOException e ) {
1287 | e.printStackTrace();
1288 | // Just return originally-decoded bytes
1289 | } // end catch
1290 | finally {
1291 | try{ baos.close(); } catch( Exception e ){}
1292 | try{ gzis.close(); } catch( Exception e ){}
1293 | try{ bais.close(); } catch( Exception e ){}
1294 | } // end finally
1295 |
1296 | } // end if: gzipped
1297 | } // end if: bytes.length >= 2
1298 |
1299 | return bytes;
1300 | } // end decode
1301 |
1302 |
1303 |
1304 | /**
1305 | * Attempts to decode Base64 data and deserialize a Java
1306 | * Object within. Returns null if there was an error.
1307 | *
1308 | * @param encodedObject The Base64 data to decode
1309 | * @return The decoded and deserialized object
1310 | * @throws NullPointerException if encodedObject is null
1311 | * @throws java.io.IOException if there is a general error
1312 | * @throws ClassNotFoundException if the decoded object is of a
1313 | * class that cannot be found by the JVM
1314 | * @since 1.5
1315 | */
1316 | public static Object decodeToObject(String encodedObject )
1317 | throws java.io.IOException, ClassNotFoundException {
1318 | return decodeToObject(encodedObject,NO_OPTIONS,null);
1319 | }
1320 |
1321 |
1322 | /**
1323 | * Attempts to decode Base64 data and deserialize a Java
1324 | * Object within. Returns null if there was an error.
1325 | * If loader is not null, it will be the class loader
1326 | * used when deserializing.
1327 | *
1328 | * @param encodedObject The Base64 data to decode
1329 | * @param options Various parameters related to decoding
1330 | * @param loader Optional class loader to use in deserializing classes.
1331 | * @return The decoded and deserialized object
1332 | * @throws NullPointerException if encodedObject is null
1333 | * @throws java.io.IOException if there is a general error
1334 | * @throws ClassNotFoundException if the decoded object is of a
1335 | * class that cannot be found by the JVM
1336 | * @since 2.3.4
1337 | */
1338 | public static Object decodeToObject(
1339 | String encodedObject, int options, final ClassLoader loader )
1340 | throws java.io.IOException, ClassNotFoundException {
1341 |
1342 | // Decode and gunzip if necessary
1343 | byte[] objBytes = decode( encodedObject, options );
1344 |
1345 | java.io.ByteArrayInputStream bais = null;
1346 | java.io.ObjectInputStream ois = null;
1347 | Object obj = null;
1348 |
1349 | //noinspection CaughtExceptionImmediatelyRethrown,CaughtExceptionImmediatelyRethrown
1350 | try {
1351 | bais = new java.io.ByteArrayInputStream( objBytes );
1352 |
1353 | // If no custom class loader is provided, use Java's builtin OIS.
1354 | if( loader == null ){
1355 | ois = new java.io.ObjectInputStream( bais );
1356 | } // end if: no loader provided
1357 |
1358 | // Else make a customized object input stream that uses
1359 | // the provided class loader.
1360 | else {
1361 | ois = new java.io.ObjectInputStream(bais){
1362 | @Override
1363 | public Class> resolveClass(java.io.ObjectStreamClass streamClass)
1364 | throws java.io.IOException, ClassNotFoundException {
1365 | Class> c = Class.forName(streamClass.getName(), false, loader);
1366 | if( c == null ){
1367 | return super.resolveClass(streamClass);
1368 | } else {
1369 | return c; // Class loader knows of this class.
1370 | } // end else: not null
1371 | } // end resolveClass
1372 | }; // end ois
1373 | } // end else: no custom class loader
1374 |
1375 | obj = ois.readObject();
1376 | } // end try
1377 | catch( java.io.IOException e ) {
1378 | throw e; // Catch and throw in order to execute finally{}
1379 | } // end catch
1380 | catch( ClassNotFoundException e ) {
1381 | throw e; // Catch and throw in order to execute finally{}
1382 | } // end catch
1383 | finally {
1384 | try{ bais.close(); } catch( Exception e ){}
1385 | try{ ois.close(); } catch( Exception e ){}
1386 | } // end finally
1387 |
1388 | return obj;
1389 | } // end decodeObject
1390 |
1391 |
1392 |
1393 | /**
1394 | * Convenience method for encoding data to a file.
1395 | *
1396 | * As of v 2.3, if there is a error,
1397 | * the method will throw an java.io.IOException. This is new to v2.3!
1398 | * In earlier versions, it just returned false, but
1399 | * in retrospect that's a pretty poor way to handle it.
1400 | *
1401 | * @param dataToEncode byte array of data to encode in base64 form
1402 | * @param filename Filename for saving encoded data
1403 | * @throws java.io.IOException if there is an error
1404 | * @throws NullPointerException if dataToEncode is null
1405 | * @since 2.1
1406 | */
1407 | public static void encodeToFile( byte[] dataToEncode, String filename )
1408 | throws java.io.IOException {
1409 |
1410 | if( dataToEncode == null ){
1411 | throw new NullPointerException( "Data to encode was null." );
1412 | } // end iff
1413 |
1414 | OutputStream bos = null;
1415 | //noinspection CaughtExceptionImmediatelyRethrown
1416 | try {
1417 | bos = new OutputStream(
1418 | new java.io.FileOutputStream( filename ), MyBase64.ENCODE );
1419 | bos.write( dataToEncode );
1420 | } // end try
1421 | catch( java.io.IOException e ) {
1422 | throw e; // Catch and throw to execute finally{} block
1423 | } // end catch: java.io.IOException
1424 | finally {
1425 | try{ bos.close(); } catch( Exception e ){}
1426 | } // end finally
1427 |
1428 | } // end encodeToFile
1429 |
1430 |
1431 | /**
1432 | * Convenience method for decoding data to a file.
1433 | *
1434 | * As of v 2.3, if there is a error,
1435 | * the method will throw an java.io.IOException. This is new to v2.3!
1436 | * In earlier versions, it just returned false, but
1437 | * in retrospect that's a pretty poor way to handle it.
1438 | *
1439 | * @param dataToDecode Base64-encoded data as a string
1440 | * @param filename Filename for saving decoded data
1441 | * @throws java.io.IOException if there is an error
1442 | * @since 2.1
1443 | */
1444 | public static void decodeToFile(String dataToDecode, String filename )
1445 | throws java.io.IOException {
1446 |
1447 | OutputStream bos = null;
1448 | //noinspection CaughtExceptionImmediatelyRethrown
1449 | try{
1450 | bos = new OutputStream(
1451 | new java.io.FileOutputStream( filename ), MyBase64.DECODE );
1452 | bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) );
1453 | } // end try
1454 | catch( java.io.IOException e ) {
1455 | throw e; // Catch and throw to execute finally{} block
1456 | } // end catch: java.io.IOException
1457 | finally {
1458 | try{ bos.close(); } catch( Exception e ){}
1459 | } // end finally
1460 |
1461 | } // end decodeToFile
1462 |
1463 |
1464 |
1465 |
1466 | /**
1467 | * Convenience method for reading a base64-encoded
1468 | * file and decoding it.
1469 | *
1470 | * As of v 2.3, if there is a error,
1471 | * the method will throw an java.io.IOException. This is new to v2.3!
1472 | * In earlier versions, it just returned false, but
1473 | * in retrospect that's a pretty poor way to handle it.
1474 | *
1475 | * @param filename Filename for reading encoded data
1476 | * @return decoded byte array
1477 | * @throws java.io.IOException if there is an error
1478 | * @since 2.1
1479 | */
1480 | public static byte[] decodeFromFile( String filename )
1481 | throws java.io.IOException {
1482 |
1483 | byte[] decodedData = null;
1484 | InputStream bis = null;
1485 | //noinspection CaughtExceptionImmediatelyRethrown
1486 | try
1487 | {
1488 | // Set up some useful variables
1489 | java.io.File file = new java.io.File( filename );
1490 | byte[] buffer = null;
1491 | int length = 0;
1492 | int numBytes = 0;
1493 |
1494 | // Check for size of file
1495 | if( file.length() > Integer.MAX_VALUE )
1496 | {
1497 | throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." );
1498 | } // end if: file too big for int index
1499 | buffer = new byte[ (int)file.length() ];
1500 |
1501 | // Open a stream
1502 | bis = new InputStream(
1503 | new java.io.BufferedInputStream(
1504 | new java.io.FileInputStream( file ) ), MyBase64.DECODE );
1505 |
1506 | // Read until done
1507 | while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) {
1508 | length += numBytes;
1509 | } // end while
1510 |
1511 | // Save in a variable to return
1512 | decodedData = new byte[ length ];
1513 | System.arraycopy( buffer, 0, decodedData, 0, length );
1514 |
1515 | } // end try
1516 | catch( java.io.IOException e ) {
1517 | throw e; // Catch and release to execute finally{}
1518 | } // end catch: java.io.IOException
1519 | finally {
1520 | try{ bis.close(); } catch( Exception e) {}
1521 | } // end finally
1522 |
1523 | return decodedData;
1524 | } // end decodeFromFile
1525 |
1526 |
1527 |
1528 | /**
1529 | * Convenience method for reading a binary file
1530 | * and base64-encoding it.
1531 | *
1532 | * As of v 2.3, if there is a error,
1533 | * the method will throw an java.io.IOException. This is new to v2.3!
1534 | * In earlier versions, it just returned false, but
1535 | * in retrospect that's a pretty poor way to handle it.
1536 | *
1537 | * @param filename Filename for reading binary data
1538 | * @return base64-encoded string
1539 | * @throws java.io.IOException if there is an error
1540 | * @since 2.1
1541 | */
1542 | public static String encodeFromFile(String filename )
1543 | throws java.io.IOException {
1544 |
1545 | String encodedData = null;
1546 | InputStream bis = null;
1547 | //noinspection CaughtExceptionImmediatelyRethrown
1548 | try
1549 | {
1550 | // Set up some useful variables
1551 | java.io.File file = new java.io.File( filename );
1552 | byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5)
1553 | int length = 0;
1554 | int numBytes = 0;
1555 |
1556 | // Open a stream
1557 | bis = new InputStream(
1558 | new java.io.BufferedInputStream(
1559 | new java.io.FileInputStream( file ) ), MyBase64.ENCODE );
1560 |
1561 | // Read until done
1562 | while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) {
1563 | length += numBytes;
1564 | } // end while
1565 |
1566 | // Save in a variable to return
1567 | encodedData = new String( buffer, 0, length, MyBase64.PREFERRED_ENCODING );
1568 |
1569 | } // end try
1570 | catch( java.io.IOException e ) {
1571 | throw e; // Catch and release to execute finally{}
1572 | } // end catch: java.io.IOException
1573 | finally {
1574 | try{ bis.close(); } catch( Exception e) {}
1575 | } // end finally
1576 |
1577 | return encodedData;
1578 | } // end encodeFromFile
1579 |
1580 | /**
1581 | * Reads infile and encodes it to outfile.
1582 | *
1583 | * @param infile Input file
1584 | * @param outfile Output file
1585 | * @throws java.io.IOException if there is an error
1586 | * @since 2.2
1587 | */
1588 | public static void encodeFileToFile(String infile, String outfile )
1589 | throws java.io.IOException {
1590 |
1591 | String encoded = MyBase64.encodeFromFile( infile );
1592 | java.io.OutputStream out = null;
1593 | //noinspection CaughtExceptionImmediatelyRethrown
1594 | try{
1595 | out = new java.io.BufferedOutputStream(
1596 | new java.io.FileOutputStream( outfile ) );
1597 | out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output.
1598 | } // end try
1599 | catch( java.io.IOException e ) {
1600 | throw e; // Catch and release to execute finally{}
1601 | } // end catch
1602 | finally {
1603 | try { out.close(); }
1604 | catch( Exception ex ){}
1605 | } // end finally
1606 | } // end encodeFileToFile
1607 |
1608 |
1609 | /**
1610 | * Reads infile and decodes it to outfile.
1611 | *
1612 | * @param infile Input file
1613 | * @param outfile Output file
1614 | * @throws java.io.IOException if there is an error
1615 | * @since 2.2
1616 | */
1617 | public static void decodeFileToFile(String infile, String outfile )
1618 | throws java.io.IOException {
1619 |
1620 | byte[] decoded = MyBase64.decodeFromFile( infile );
1621 | java.io.OutputStream out = null;
1622 | //noinspection CaughtExceptionImmediatelyRethrown
1623 | try{
1624 | out = new java.io.BufferedOutputStream(
1625 | new java.io.FileOutputStream( outfile ) );
1626 | out.write( decoded );
1627 | } // end try
1628 | catch( java.io.IOException e ) {
1629 | throw e; // Catch and release to execute finally{}
1630 | } // end catch
1631 | finally {
1632 | try { out.close(); }
1633 | catch( Exception ex ){}
1634 | } // end finally
1635 | } // end decodeFileToFile
1636 |
1637 |
1638 | /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
1639 |
1640 |
1641 |
1642 | /**
1643 | * A {@link InputStream} will read data from another
1644 | * java.io.InputStream, given in the constructor,
1645 | * and encode/decode to/from Base64 notation on the fly.
1646 | *
1647 | * @see MyBase64
1648 | * @since 1.3
1649 | */
1650 | public static class InputStream extends java.io.FilterInputStream {
1651 |
1652 | private boolean encode; // Encoding or decoding
1653 | private int position; // Current position in the buffer
1654 | private byte[] buffer; // Small buffer holding converted data
1655 | private int bufferLength; // Length of buffer (3 or 4)
1656 | private int numSigBytes; // Number of meaningful bytes in the buffer
1657 | private int lineLength;
1658 | private boolean breakLines; // Break lines at less than 80 characters
1659 | private int options; // Record options used to create the stream.
1660 | private byte[] decodabet; // Local copies to avoid extra method calls
1661 |
1662 |
1663 | /**
1664 | * Constructs a {@link InputStream} in DECODE mode.
1665 | *
1666 | * @param in the java.io.InputStream from which to read data.
1667 | * @since 1.3
1668 | */
1669 | public InputStream( java.io.InputStream in ) {
1670 | this( in, DECODE );
1671 | } // end constructor
1672 |
1673 |
1674 | /**
1675 | * Constructs a {@link InputStream} in
1676 | * either ENCODE or DECODE mode.
1677 | *
1678 | * Valid options:
1679 | * ENCODE or DECODE: Encode or Decode as data is read.
1680 | * DO_BREAK_LINES: break lines at 76 characters
1681 | * (only meaningful when encoding)
1682 | *
1683 | *
1684 | * Example: new Base64.InputStream( in, Base64.DECODE )
1685 | *
1686 | *
1687 | * @param in the java.io.InputStream from which to read data.
1688 | * @param options Specified options
1689 | * @see MyBase64#ENCODE
1690 | * @see MyBase64#DECODE
1691 | * @see MyBase64#DO_BREAK_LINES
1692 | * @since 2.0
1693 | */
1694 | public InputStream( java.io.InputStream in, int options ) {
1695 |
1696 | super( in );
1697 | this.options = options; // Record for later
1698 | this.breakLines = (options & DO_BREAK_LINES) > 0;
1699 | this.encode = (options & ENCODE) > 0;
1700 | this.bufferLength = encode ? 4 : 3;
1701 | this.buffer = new byte[ bufferLength ];
1702 | this.position = -1;
1703 | this.lineLength = 0;
1704 | this.decodabet = getDecodabet(options);
1705 | } // end constructor
1706 |
1707 | /**
1708 | * Reads enough of the input stream to convert
1709 | * to/from Base64 and returns the next byte.
1710 | *
1711 | * @return next byte
1712 | * @since 1.3
1713 | */
1714 | @Override
1715 | public int read() throws java.io.IOException {
1716 |
1717 | // Do we need to get data?
1718 | if( position < 0 ) {
1719 | if( encode ) {
1720 | byte[] b3 = new byte[3];
1721 | int numBinaryBytes = 0;
1722 | for( int i = 0; i < 3; i++ ) {
1723 | int b = in.read();
1724 |
1725 | // If end of stream, b is -1.
1726 | if( b >= 0 ) {
1727 | b3[i] = (byte)b;
1728 | numBinaryBytes++;
1729 | } else {
1730 | break; // out of for loop
1731 | } // end else: end of stream
1732 |
1733 | } // end for: each needed input byte
1734 |
1735 | if( numBinaryBytes > 0 ) {
1736 | encode3to4( b3, 0, numBinaryBytes, buffer, 0, options );
1737 | position = 0;
1738 | numSigBytes = 4;
1739 | } // end if: got data
1740 | else {
1741 | return -1; // Must be end of stream
1742 | } // end else
1743 | } // end if: encoding
1744 |
1745 | // Else decoding
1746 | else {
1747 | byte[] b4 = new byte[4];
1748 | int i = 0;
1749 | for( i = 0; i < 4; i++ ) {
1750 | // Read four "meaningful" bytes:
1751 | int b = 0;
1752 | do{ b = in.read(); }
1753 | while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC );
1754 |
1755 | if( b < 0 ) {
1756 | break; // Reads a -1 if end of stream
1757 | } // end if: end of stream
1758 |
1759 | b4[i] = (byte)b;
1760 | } // end for: each needed input byte
1761 |
1762 | if( i == 4 ) {
1763 | numSigBytes = decode4to3( b4, 0, buffer, 0, options );
1764 | position = 0;
1765 | } // end if: got four characters
1766 | else if( i == 0 ){
1767 | return -1;
1768 | } // end else if: also padded correctly
1769 | else {
1770 | // Must have broken out from above.
1771 | throw new java.io.IOException( "Improperly padded Base64 input." );
1772 | } // end
1773 |
1774 | } // end else: decode
1775 | } // end else: get data
1776 |
1777 | // Got data?
1778 | if( position >= 0 ) {
1779 | // End of relevant data?
1780 | if( /*!encode &&*/ position >= numSigBytes ){
1781 | return -1;
1782 | } // end if: got data
1783 |
1784 | if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) {
1785 | lineLength = 0;
1786 | return '\n';
1787 | } // end if
1788 | else {
1789 | lineLength++; // This isn't important when decoding
1790 | // but throwing an extra "if" seems
1791 | // just as wasteful.
1792 |
1793 | int b = buffer[ position++ ];
1794 |
1795 | if( position >= bufferLength ) {
1796 | position = -1;
1797 | } // end if: end
1798 |
1799 | return b & 0xFF; // This is how you "cast" a byte that's
1800 | // intended to be unsigned.
1801 | } // end else
1802 | } // end if: position >= 0
1803 |
1804 | // Else error
1805 | else {
1806 | throw new java.io.IOException( "Error in Base64 code reading stream." );
1807 | } // end else
1808 | } // end read
1809 |
1810 |
1811 | /**
1812 | * Calls {@link #read()} repeatedly until the end of stream
1813 | * is reached or len bytes are read.
1814 | * Returns number of bytes read into array or -1 if
1815 | * end of stream is encountered.
1816 | *
1817 | * @param dest array to hold values
1818 | * @param off offset for array
1819 | * @param len max number of bytes to read into array
1820 | * @return bytes read into array or -1 if end of stream is encountered.
1821 | * @since 1.3
1822 | */
1823 | @Override
1824 | public int read( byte[] dest, int off, int len )
1825 | throws java.io.IOException {
1826 | int i;
1827 | int b;
1828 | for( i = 0; i < len; i++ ) {
1829 | b = read();
1830 |
1831 | if( b >= 0 ) {
1832 | dest[off + i] = (byte) b;
1833 | }
1834 | else if( i == 0 ) {
1835 | return -1;
1836 | }
1837 | else {
1838 | break; // Out of 'for' loop
1839 | } // Out of 'for' loop
1840 | } // end for: each byte read
1841 | return i;
1842 | } // end read
1843 |
1844 | } // end inner class InputStream
1845 |
1846 |
1847 |
1848 |
1849 |
1850 |
1851 | /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
1852 |
1853 |
1854 |
1855 | /**
1856 | * A {@link OutputStream} will write data to another
1857 | * java.io.OutputStream, given in the constructor,
1858 | * and encode/decode to/from Base64 notation on the fly.
1859 | *
1860 | * @see MyBase64
1861 | * @since 1.3
1862 | */
1863 | public static class OutputStream extends java.io.FilterOutputStream {
1864 |
1865 | private boolean encode;
1866 | private int position;
1867 | private byte[] buffer;
1868 | private int bufferLength;
1869 | private int lineLength;
1870 | private boolean breakLines;
1871 | private byte[] b4; // Scratch used in a few places
1872 | private boolean suspendEncoding;
1873 | private int options; // Record for later
1874 | private byte[] decodabet; // Local copies to avoid extra method calls
1875 |
1876 | /**
1877 | * Constructs a {@link OutputStream} in ENCODE mode.
1878 | *
1879 | * @param out the java.io.OutputStream to which data will be written.
1880 | * @since 1.3
1881 | */
1882 | public OutputStream( java.io.OutputStream out ) {
1883 | this( out, ENCODE );
1884 | } // end constructor
1885 |
1886 |
1887 | /**
1888 | * Constructs a {@link OutputStream} in
1889 | * either ENCODE or DECODE mode.
1890 | *
1891 | * Valid options:
1892 | * ENCODE or DECODE: Encode or Decode as data is read.
1893 | * DO_BREAK_LINES: don't break lines at 76 characters
1894 | * (only meaningful when encoding)
1895 | *
1896 | *
1897 | * Example: new Base64.OutputStream( out, Base64.ENCODE )
1898 | *
1899 | * @param out the java.io.OutputStream to which data will be written.
1900 | * @param options Specified options.
1901 | * @see MyBase64#ENCODE
1902 | * @see MyBase64#DECODE
1903 | * @see MyBase64#DO_BREAK_LINES
1904 | * @since 1.3
1905 | */
1906 | public OutputStream( java.io.OutputStream out, int options ) {
1907 | super( out );
1908 | this.breakLines = (options & DO_BREAK_LINES) != 0;
1909 | this.encode = (options & ENCODE) != 0;
1910 | this.bufferLength = encode ? 3 : 4;
1911 | this.buffer = new byte[ bufferLength ];
1912 | this.position = 0;
1913 | this.lineLength = 0;
1914 | this.suspendEncoding = false;
1915 | this.b4 = new byte[4];
1916 | this.options = options;
1917 | this.decodabet = getDecodabet(options);
1918 | } // end constructor
1919 |
1920 |
1921 | /**
1922 | * Writes the byte to the output stream after
1923 | * converting to/from Base64 notation.
1924 | * When encoding, bytes are buffered three
1925 | * at a time before the output stream actually
1926 | * gets a write() call.
1927 | * When decoding, bytes are buffered four
1928 | * at a time.
1929 | *
1930 | * @param theByte the byte to write
1931 | * @since 1.3
1932 | */
1933 | @Override
1934 | public void write(int theByte)
1935 | throws java.io.IOException {
1936 | // Encoding suspended?
1937 | if( suspendEncoding ) {
1938 | this.out.write( theByte );
1939 | return;
1940 | } // end if: supsended
1941 |
1942 | // Encode?
1943 | if( encode ) {
1944 | buffer[ position++ ] = (byte)theByte;
1945 | if( position >= bufferLength ) { // Enough to encode.
1946 |
1947 | this.out.write( encode3to4( b4, buffer, bufferLength, options ) );
1948 |
1949 | lineLength += 4;
1950 | if( breakLines && lineLength >= MAX_LINE_LENGTH ) {
1951 | this.out.write( NEW_LINE );
1952 | lineLength = 0;
1953 | } // end if: end of line
1954 |
1955 | position = 0;
1956 | } // end if: enough to output
1957 | } // end if: encoding
1958 |
1959 | // Else, Decoding
1960 | else {
1961 | // Meaningful Base64 character?
1962 | if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) {
1963 | buffer[ position++ ] = (byte)theByte;
1964 | if( position >= bufferLength ) { // Enough to output.
1965 |
1966 | int len = MyBase64.decode4to3( buffer, 0, b4, 0, options );
1967 | out.write( b4, 0, len );
1968 | position = 0;
1969 | } // end if: enough to output
1970 | } // end if: meaningful base64 character
1971 | else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) {
1972 | throw new java.io.IOException( "Invalid character in Base64 data." );
1973 | } // end else: not white space either
1974 | } // end else: decoding
1975 | } // end write
1976 |
1977 |
1978 |
1979 | /**
1980 | * Calls {@link #write(int)} repeatedly until len
1981 | * bytes are written.
1982 | *
1983 | * @param theBytes array from which to read bytes
1984 | * @param off offset for array
1985 | * @param len max number of bytes to read into array
1986 | * @since 1.3
1987 | */
1988 | @Override
1989 | public void write( byte[] theBytes, int off, int len )
1990 | throws java.io.IOException {
1991 | // Encoding suspended?
1992 | if( suspendEncoding ) {
1993 | this.out.write( theBytes, off, len );
1994 | return;
1995 | } // end if: supsended
1996 |
1997 | for( int i = 0; i < len; i++ ) {
1998 | write( theBytes[ off + i ] );
1999 | } // end for: each byte written
2000 |
2001 | } // end write
2002 |
2003 |
2004 |
2005 | /**
2006 | * Method added by PHIL. [Thanks, PHIL. -Rob]
2007 | * This pads the buffer without closing the stream.
2008 | * @throws java.io.IOException if there's an error.
2009 | */
2010 | public void flushBase64() throws java.io.IOException {
2011 | if( position > 0 ) {
2012 | if( encode ) {
2013 | out.write( encode3to4( b4, buffer, position, options ) );
2014 | position = 0;
2015 | } // end if: encoding
2016 | else {
2017 | throw new java.io.IOException( "Base64 input not properly padded." );
2018 | } // end else: decoding
2019 | } // end if: buffer partially full
2020 |
2021 | } // end flush
2022 |
2023 |
2024 | /**
2025 | * Flushes and closes (I think, in the superclass) the stream.
2026 | *
2027 | * @since 1.3
2028 | */
2029 | @Override
2030 | public void close() throws java.io.IOException {
2031 | // 1. Ensure that pending characters are written
2032 | flushBase64();
2033 |
2034 | // 2. Actually close the stream
2035 | // Base class both flushes and closes.
2036 | super.close();
2037 |
2038 | buffer = null;
2039 | out = null;
2040 | } // end close
2041 |
2042 |
2043 |
2044 | /**
2045 | * Suspends encoding of the stream.
2046 | * May be helpful if you need to embed a piece of
2047 | * base64-encoded data in a stream.
2048 | *
2049 | * @throws java.io.IOException if there's an error flushing
2050 | * @since 1.5.1
2051 | */
2052 | public void suspendEncoding() throws java.io.IOException {
2053 | flushBase64();
2054 | this.suspendEncoding = true;
2055 | } // end suspendEncoding
2056 |
2057 |
2058 | /**
2059 | * Resumes encoding of the stream.
2060 | * May be helpful if you need to embed a piece of
2061 | * base64-encoded data in a stream.
2062 | *
2063 | * @since 1.5.1
2064 | */
2065 | public void resumeEncoding() {
2066 | this.suspendEncoding = false;
2067 | } // end resumeEncoding
2068 |
2069 |
2070 |
2071 | } // end inner class OutputStream
2072 |
2073 |
2074 | } // end class Base64
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/http/NetworkInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.http;
2 |
3 | import com.example.dl.hymvp.App;
4 | import com.example.dl.hymvp.util.NetUtil;
5 |
6 | import java.io.IOException;
7 |
8 |
9 | import okhttp3.CacheControl;
10 | import okhttp3.Interceptor;
11 | import okhttp3.Request;
12 | import okhttp3.Response;
13 |
14 | /**
15 | * Incremental change is better than ambitious failure.
16 | *
17 | * @author : MysticCoder
18 | * @date : 2018/3/15
19 | * @desc :
20 | */
21 | public class NetworkInterceptor implements Interceptor {
22 |
23 | @Override
24 | public Response intercept(Chain chain) throws IOException {
25 |
26 | Request request = chain.request();
27 |
28 | //无网络时强制使用缓存
29 | if (!NetUtil.isConnected(App.getContext())) {
30 | request = request.newBuilder()
31 | .cacheControl(CacheControl.FORCE_CACHE)
32 | .build();
33 | }
34 |
35 | Response response = chain.proceed(request);
36 |
37 | if (NetUtil.isConnected(App.getContext())) {
38 | // 有网络时,设置超时为0
39 | int maxStale = 0;
40 | response.newBuilder()
41 | .header("Cache-Control", "public, max-age=" + maxStale)
42 | .removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
43 | .build();
44 | } else {
45 | // 无网络时,设置超时为3周
46 | int maxStale = 60 * 60 * 24 * 21;
47 | response.newBuilder()
48 | .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
49 | .removeHeader("Pragma")
50 | .build();
51 | }
52 |
53 | return response;
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/http/ResponseConverterFactory.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.http;
2 |
3 | import com.google.gson.Gson;
4 |
5 | import java.lang.annotation.Annotation;
6 | import java.lang.reflect.Type;
7 |
8 | import okhttp3.RequestBody;
9 | import okhttp3.ResponseBody;
10 | import retrofit2.Converter;
11 | import retrofit2.Retrofit;
12 |
13 | /**
14 | * Incremental change is better than ambitious failure.
15 | *
16 | * @author : MysticCoder
17 | * @date : 2017/12/7
18 | * @desc :
19 | */
20 |
21 |
22 | public class ResponseConverterFactory extends Converter.Factory {
23 | public static ResponseConverterFactory create() {
24 | return create(new Gson());
25 | }
26 |
27 |
28 | public static ResponseConverterFactory create(Gson gson) {
29 | return new ResponseConverterFactory(gson);
30 | }
31 |
32 | private final Gson gson;
33 |
34 | private ResponseConverterFactory(Gson gson) {
35 | if (gson == null) {throw new NullPointerException("gson == null");}
36 | this.gson = gson;
37 | }
38 |
39 | @Override
40 | public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
41 | //返回我们自定义的Gson响应体变换器
42 | return new GsonResponseBodyConverter<>(gson, type);
43 | }
44 |
45 | // @Override
46 | // public Converter, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
47 | // return new GsonResponseBodyConverter<>(gson,type);
48 | // }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/http/ResultException.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.http;
2 |
3 | /**
4 | * Incremental change is better than ambitious failure.
5 | *
6 | * @author : MysticCoder
7 | * @date : 2017/12/7
8 | * @desc :
9 | */
10 |
11 |
12 | public class ResultException extends RuntimeException{
13 |
14 |
15 | private int code;
16 | private String message;
17 |
18 | public ResultException(int code, String message){
19 | this.code = code;
20 | this.message = message;
21 | }
22 |
23 | public int getCode() {
24 | return code;
25 | }
26 |
27 | public void setCode(int code) {
28 | this.code = code;
29 | }
30 |
31 | @Override
32 | public String getMessage() {
33 | return message;
34 | }
35 |
36 | public void setMessage(String message) {
37 | this.message = message;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/mvp/contract/MainContract.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.mvp.contract;
2 |
3 | import com.example.dl.hymvp.base.BaseModel;
4 |
5 | import com.example.dl.hymvp.base.BaseView;
6 | import com.example.dl.hymvp.bean.Gank;
7 | import com.example.dl.hymvp.bean.Survey;
8 |
9 | import io.reactivex.Observable;
10 |
11 | /**
12 | * Incremental change is better than ambitious failure.
13 | *
14 | * @author : MysticCoder
15 | * @date : 2018/3/16
16 | * @desc :
17 | */
18 |
19 | public interface MainContract {
20 |
21 | interface MainView extends BaseView {
22 |
23 | void onGetSurvey(Survey survey);
24 | }
25 |
26 | interface MainModel extends BaseModel {
27 |
28 | Observable getSurvey(String did);
29 | }
30 |
31 |
32 | // abstract class MainPresenter extends BasePresenter {
33 | // public abstract void getSurvey(String did);
34 | // }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/mvp/model/MainModel.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.mvp.model;
2 |
3 | import com.example.dl.hymvp.bean.Gank;
4 | import com.example.dl.hymvp.bean.Survey;
5 | import com.example.dl.hymvp.http.ApiEngine;
6 | import com.example.dl.hymvp.mvp.contract.MainContract;
7 |
8 |
9 | import io.reactivex.Observable;
10 |
11 | /**
12 | * Incremental change is better than ambitious failure.
13 | *
14 | * @author : MysticCoder
15 | * @date : 2018/3/16
16 | * @desc :
17 | */
18 |
19 |
20 | public class MainModel implements MainContract.MainModel {
21 |
22 |
23 | @Override
24 | public Observable getSurvey(String did) {
25 | return mApiService.getSurveyList(did);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/mvp/presenter/MainPresenter.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.mvp.presenter;
2 |
3 | import android.database.Observable;
4 |
5 | import com.example.dl.hymvp.base.BasePresenter;
6 | import com.example.dl.hymvp.bean.Gank;
7 | import com.example.dl.hymvp.bean.Survey;
8 | import com.example.dl.hymvp.http.BaseObserver;
9 | import com.example.dl.hymvp.mvp.contract.MainContract;
10 | import com.example.dl.hymvp.mvp.model.MainModel;
11 | import com.example.dl.hymvp.rx.RxTransformer;
12 |
13 | import io.reactivex.Observer;
14 | import io.reactivex.disposables.Disposable;
15 | import io.reactivex.functions.Consumer;
16 |
17 | /**
18 | * Incremental change is better than ambitious failure.
19 | *
20 | * @author : MysticCoder
21 | * @date : 2018/3/20
22 | * @desc :
23 | */
24 |
25 |
26 | public class MainPresenter extends BasePresenter {
27 |
28 | public MainPresenter(MainContract.MainView view) {
29 | super(new MainModel(), view);
30 | }
31 |
32 |
33 |
34 | // public void getGank(){
35 | // mModel.getGank()
36 | // .compose(RxTransformer.transformWithLoading(mView))
37 | // .subscribe(new BaseObserver() {
38 | // @Override
39 | // public void onSuccess(Gank response) {
40 | //
41 | // }
42 | //
43 | // @Override
44 | // public void onFailure(String msg) {
45 | //
46 | // }
47 | // });
48 | //
49 | // }
50 |
51 | public void getSurveyList(String did){
52 |
53 | // //生命周期管理,备用方案,订阅的时候添加Disposable对象到容器
54 | // mModel.getSurvey(did)
55 | // .compose(RxTransformer.transform())
56 | // .doOnSubscribe(this::addDisposabel)
57 | // .subscribe(new BaseObserver() {
58 | // @Override
59 | // public void onSuccess(Survey response) {
60 | // getView().onGetSurvey(response);
61 | // }
62 | //
63 | // @Override
64 | // public void onFailure(String msg) {
65 | //
66 | // }
67 | // });
68 |
69 |
70 |
71 | //
72 | // Disposable disposable = mModel.getSurvey(did)
73 | // .compose(RxTransformer.transformWithLoading(getView()))
74 | // .subscribe(survey -> getView().onGetSurvey(survey));
75 | //
76 | // addDisposabel(disposable);
77 |
78 |
79 |
80 |
81 | mModel.getSurvey(did)
82 | .compose(RxTransformer.transformWithLoading(getView()))
83 | .subscribe(new BaseObserver() {
84 | @Override
85 | public void onSuccess(Survey response) {
86 | getView().onGetSurvey(response);
87 | }
88 |
89 | @Override
90 | public void onFailure(String msg) {
91 |
92 | }
93 | });
94 |
95 |
96 |
97 |
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/rx/RxTransformer.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.rx;
2 |
3 |
4 | import com.example.dl.hymvp.base.BasePresenter;
5 | import com.example.dl.hymvp.base.BaseView;
6 |
7 | import io.reactivex.Observable;
8 | import io.reactivex.ObservableSource;
9 | import io.reactivex.ObservableTransformer;
10 | import io.reactivex.android.schedulers.AndroidSchedulers;
11 | import io.reactivex.annotations.NonNull;
12 | import io.reactivex.disposables.Disposable;
13 | import io.reactivex.functions.Action;
14 | import io.reactivex.functions.Consumer;
15 | import io.reactivex.schedulers.Schedulers;
16 |
17 | /**
18 | * Incremental change is better than ambitious failure.
19 | *
20 | * @author : MysticCoder
21 | * @date : 2018/3/15
22 | * @desc :
23 | */
24 |
25 | public class RxTransformer {
26 |
27 | /**
28 | * 无参数 仅做切换线程
29 | *
30 | * @param 泛型
31 | * @return 返回Observable
32 | */
33 | public static ObservableTransformer transform() {
34 | return upstream -> upstream
35 | .subscribeOn(Schedulers.io())
36 | .unsubscribeOn(Schedulers.io())
37 | .subscribeOn(AndroidSchedulers.mainThread())
38 | .observeOn(AndroidSchedulers.mainThread());
39 | }
40 |
41 |
42 | /**
43 | * 界面请求,不需要加载和隐藏loading时调用 使用RxLifeCycle
44 | * 传入view接口,Activity,Fragment等实现了view接口,Activity,Fragment继承于{@link com.trello.rxlifecycle2.components.support.RxAppCompatActivity}
45 | * 也就实现了bindToLifecycle方法
46 | * @param view View
47 | * @param 泛型
48 | * @return
49 | */
50 | public static ObservableTransformer transform(final BaseView view) {
51 | return observable -> observable.subscribeOn(Schedulers.io())
52 | .observeOn(AndroidSchedulers.mainThread())
53 | .compose(view.bindToLifecycle());
54 | }
55 |
56 |
57 | /**
58 | * 界面请求,需要加载和隐藏loading时调用,使用RxLifeCycle
59 | * 传入view接口,Activity,Fragment等实现了view接口,Activity,Fragment继承于{@link com.trello.rxlifecycle2.components.support.RxAppCompatActivity}
60 | * 也就实现了bindToLifecycle方法
61 | * @param view View
62 | * @param 泛型
63 | * @return
64 | */
65 | public static ObservableTransformer transformWithLoading(final BaseView view) {
66 | //隐藏进度条
67 | return observable -> observable.subscribeOn(Schedulers.io())
68 | .doOnSubscribe(disposable -> {
69 | view.showLoading();//显示进度条
70 | })
71 | .observeOn(AndroidSchedulers.mainThread())
72 | .doFinally(view::hideLoading).compose(view.bindToLifecycle());
73 | }
74 |
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/util/NetUtil.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.util;
2 |
3 | import android.content.Context;
4 | import android.net.ConnectivityManager;
5 | import android.net.NetworkInfo;
6 |
7 | /**
8 | * Incremental change is better than ambitious failure.
9 | *
10 | * @author : MysticCoder
11 | * @date : 2018/3/15
12 | * @desc :
13 | */
14 |
15 | public class NetUtil {
16 |
17 | NetUtil() {/* cannot be instantiated */}
18 |
19 | /**
20 | * 判断网络是否连接
21 | *
22 | * @param context Context
23 | * @return 网络是否连接
24 | */
25 | public static boolean isConnected(Context context) {
26 |
27 | ConnectivityManager connectivity = (ConnectivityManager) context
28 | .getSystemService(Context.CONNECTIVITY_SERVICE);
29 |
30 | if (null != connectivity) {
31 | NetworkInfo info = connectivity.getActiveNetworkInfo();
32 | if (null != info && info.isConnected()) {
33 | if (info.getState() == NetworkInfo.State.CONNECTED) {
34 | return true;
35 | }
36 | }
37 | }
38 | return false;
39 | }
40 |
41 | /**
42 | * 判断是否是wifi连接
43 | *
44 | * @param context Context
45 | * @return 是否是wifi连接
46 | */
47 | public static boolean isWifi(Context context) {
48 | ConnectivityManager cm = (ConnectivityManager) context
49 | .getSystemService(Context.CONNECTIVITY_SERVICE);
50 | return cm != null && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI;
51 |
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/dl/hymvp/util/Preconditions.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp.util;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | /**
6 | * Incremental change is better than ambitious failure.
7 | *
8 | * @author : MysticCoder
9 | * @date : 2018/3/15
10 | * @desc :
11 | */
12 |
13 | public final class Preconditions {
14 |
15 | private Preconditions() {
16 | throw new IllegalStateException("you can't instantiate me!");
17 | }
18 |
19 | public static void checkArgument(boolean expression) {
20 | if(!expression) {
21 | throw new IllegalArgumentException();
22 | }
23 | }
24 |
25 | public static void checkArgument(boolean expression, @Nullable Object errorMessage) {
26 | if(!expression) {
27 | throw new IllegalArgumentException(String.valueOf(errorMessage));
28 | }
29 | }
30 |
31 | public static void checkArgument(boolean expression, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) {
32 | if(!expression) {
33 | throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs));
34 | }
35 | }
36 |
37 | public static void checkState(boolean expression) {
38 | if(!expression) {
39 | throw new IllegalStateException();
40 | }
41 | }
42 |
43 | public static void checkState(boolean expression, @Nullable Object errorMessage) {
44 | if(!expression) {
45 | throw new IllegalStateException(String.valueOf(errorMessage));
46 | }
47 | }
48 |
49 | public static void checkState(boolean expression, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) {
50 | if(!expression) {
51 | throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs));
52 | }
53 | }
54 |
55 | public static T checkNotNull(T reference) {
56 | if(reference == null) {
57 | throw new NullPointerException();
58 | } else {
59 | return reference;
60 | }
61 | }
62 |
63 | public static T checkNotNull(T reference, @Nullable Object errorMessage) {
64 | if(reference == null) {
65 | throw new NullPointerException(String.valueOf(errorMessage));
66 | } else {
67 | return reference;
68 | }
69 | }
70 |
71 | public static T checkNotNull(T reference, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) {
72 | if(reference == null) {
73 | throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
74 | } else {
75 | return reference;
76 | }
77 | }
78 |
79 | public static int checkElementIndex(int index, int size) {
80 | return checkElementIndex(index, size, "index");
81 | }
82 |
83 | public static int checkElementIndex(int index, int size, @Nullable String desc) {
84 | if(index >= 0 && index < size) {
85 | return index;
86 | } else {
87 | throw new IndexOutOfBoundsException(badElementIndex(index, size, desc));
88 | }
89 | }
90 |
91 | private static String badElementIndex(int index, int size, String desc) {
92 | if(index < 0) {
93 | return format("%s (%s) must not be negative", new Object[]{desc, Integer.valueOf(index)});
94 | } else if(size < 0) {
95 | throw new IllegalArgumentException((new StringBuilder(26)).append("negative size: ").append(size).toString());
96 | } else {
97 | return format("%s (%s) must be less than size (%s)", new Object[]{desc, Integer.valueOf(index), Integer.valueOf(size)});
98 | }
99 | }
100 |
101 | public static int checkPositionIndex(int index, int size) {
102 | return checkPositionIndex(index, size, "index");
103 | }
104 |
105 | public static int checkPositionIndex(int index, int size, @Nullable String desc) {
106 | if(index >= 0 && index <= size) {
107 | return index;
108 | } else {
109 | throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc));
110 | }
111 | }
112 |
113 | private static String badPositionIndex(int index, int size, String desc) {
114 | if(index < 0) {
115 | return format("%s (%s) must not be negative", new Object[]{desc, Integer.valueOf(index)});
116 | } else if(size < 0) {
117 | throw new IllegalArgumentException((new StringBuilder(26)).append("negative size: ").append(size).toString());
118 | } else {
119 | return format("%s (%s) must not be greater than size (%s)", new Object[]{desc, Integer.valueOf(index), Integer.valueOf(size)});
120 | }
121 | }
122 |
123 | public static void checkPositionIndexes(int start, int end, int size) {
124 | if(start < 0 || end < start || end > size) {
125 | throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size));
126 | }
127 | }
128 |
129 | private static String badPositionIndexes(int start, int end, int size) {
130 | return start >= 0 && start <= size?(end >= 0 && end <= size?format("end index (%s) must not be less than start index (%s)", new Object[]{Integer.valueOf(end), Integer.valueOf(start)}):badPositionIndex(end, size, "end index")):badPositionIndex(start, size, "start index");
131 | }
132 |
133 | static String format(String template, @Nullable Object... args) {
134 | template = String.valueOf(template);
135 | StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
136 | int templateStart = 0;
137 |
138 | int i;
139 | int placeholderStart;
140 | for(i = 0; i < args.length; templateStart = placeholderStart + 2) {
141 | placeholderStart = template.indexOf("%s", templateStart);
142 | if(placeholderStart == -1) {
143 | break;
144 | }
145 |
146 | builder.append(template.substring(templateStart, placeholderStart));
147 | builder.append(args[i++]);
148 | }
149 |
150 | builder.append(template.substring(templateStart));
151 | if(i < args.length) {
152 | builder.append(" [");
153 | builder.append(args[i++]);
154 |
155 | while(i < args.length) {
156 | builder.append(", ");
157 | builder.append(args[i++]);
158 | }
159 |
160 | builder.append(']');
161 | }
162 |
163 | return builder.toString();
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LegendaryMystic/HYMVP/c98ee668203f910d2cfbc09bdbac79a38cc98073/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LegendaryMystic/HYMVP/c98ee668203f910d2cfbc09bdbac79a38cc98073/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LegendaryMystic/HYMVP/c98ee668203f910d2cfbc09bdbac79a38cc98073/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LegendaryMystic/HYMVP/c98ee668203f910d2cfbc09bdbac79a38cc98073/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LegendaryMystic/HYMVP/c98ee668203f910d2cfbc09bdbac79a38cc98073/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LegendaryMystic/HYMVP/c98ee668203f910d2cfbc09bdbac79a38cc98073/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LegendaryMystic/HYMVP/c98ee668203f910d2cfbc09bdbac79a38cc98073/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LegendaryMystic/HYMVP/c98ee668203f910d2cfbc09bdbac79a38cc98073/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LegendaryMystic/HYMVP/c98ee668203f910d2cfbc09bdbac79a38cc98073/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LegendaryMystic/HYMVP/c98ee668203f910d2cfbc09bdbac79a38cc98073/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | HYMVP
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/dl/hymvp/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.dl.hymvp;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | apply from: "config.gradle"
3 |
4 | buildscript {
5 |
6 | repositories {
7 | google()
8 | jcenter()
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.0.1'
12 |
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/config.gradle:
--------------------------------------------------------------------------------
1 | ext{
2 |
3 | android = [
4 | compileSdkVersion : 26,
5 | buildToolsVersion : "26.0.2",
6 | minSdkVersion : 14,
7 | targetSdkVersion : 26,
8 | versionCode : 150,
9 | versionName : "2.3.5"
10 | ]
11 |
12 | version = [
13 | androidSupportSdkVersion: "27.0.2",
14 | retrofitSdkVersion : "2.4.0",
15 | butterknifeSdkVersion : "8.8.1",
16 | ]
17 |
18 |
19 |
20 | dependencies = [
21 | //support
22 | "appcompat-v7" : "com.android.support:appcompat-v7:${version["androidSupportSdkVersion"]}",
23 | "recyclerview-v7" : "com.android.support:recyclerview-v7:${version["androidSupportSdkVersion"]}",
24 |
25 | //network
26 | "retrofit" : "com.squareup.retrofit2:retrofit:${version["retrofitSdkVersion"]}",
27 |
28 | //view
29 | "butterknife" : "com.jakewharton:butterknife:${version["butterknifeSdkVersion"]}",
30 | "butterknife-compiler" : "com.jakewharton:butterknife-compiler:${version["butterknifeSdkVersion"]}",
31 |
32 |
33 | //rx
34 | "rxjava" : "io.reactivex.rxjava2:rxjava:2.1.10",
35 | "rxandroid" : "io.reactivex.rxjava2:rxandroid:2.0.2",
36 | "rxpermissions" : "com.tbruyelle.rxpermissions2:rxpermissions:0.9.5@aar",
37 |
38 | //tools
39 |
40 |
41 | //test
42 | "junit" : "junit:junit:4.12",
43 | "androidJUnitRunner" : "android.support.test.runner.AndroidJUnitRunner",
44 | "runner" : "com.android.support.test:runner:1.0.1",
45 | "espresso" : "com.android.support.test.espresso:espresso-core:3.0.1",
46 |
47 |
48 | ]
49 |
50 |
51 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LegendaryMystic/HYMVP/c98ee668203f910d2cfbc09bdbac79a38cc98073/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Mar 15 16:15:57 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------