Commit 6c59c219a9e84390f5a1e90cff69c53650985668

Authored by wugian
1 parent 2c5bd7cb

新的长连接开门机制优化

@@ -6,7 +6,6 @@ import android.database.Cursor; @@ -6,7 +6,6 @@ import android.database.Cursor;
6 import android.database.sqlite.SQLiteDatabase; 6 import android.database.sqlite.SQLiteDatabase;
7 import android.text.TextUtils; 7 import android.text.TextUtils;
8 import android.util.Log; 8 import android.util.Log;
9 -  
10 import com.gimi.common.cinema.model.LocalMovieMessage; 9 import com.gimi.common.cinema.model.LocalMovieMessage;
11 import com.gimi.common.cinema.model.QueryType; 10 import com.gimi.common.cinema.model.QueryType;
12 import com.gimi.common.cinema.model.SourceType; 11 import com.gimi.common.cinema.model.SourceType;
@@ -458,7 +457,7 @@ public class NewDBManager { @@ -458,7 +457,7 @@ public class NewDBManager {
458 * 457 *
459 * @return persons 458 * @return persons
460 */ 459 */
461 - public List<LocalMovieMessage> queryPlayMovie(String md5) { 460 + public LocalMovieMessage queryPlayMovie(String md5) {
462 String sql = "SELECT * FROM movie_message where md5 = '" + md5 + "'"; 461 String sql = "SELECT * FROM movie_message where md5 = '" + md5 + "'";
463 462
464 ArrayList<LocalMovieMessage> persons = new ArrayList<LocalMovieMessage>(); 463 ArrayList<LocalMovieMessage> persons = new ArrayList<LocalMovieMessage>();
@@ -467,7 +466,10 @@ public class NewDBManager { @@ -467,7 +466,10 @@ public class NewDBManager {
467 Cursor c = db.rawQuery(sb.toString(), null); 466 Cursor c = db.rawQuery(sb.toString(), null);
468 readCursor2List(persons, c); 467 readCursor2List(persons, c);
469 c.close(); 468 c.close();
470 - return persons; 469 + if (persons.size() >= 1) {
  470 + return persons.get(0);
  471 + }
  472 + return null;
471 } 473 }
472 474
473 /** 475 /**
@@ -5,19 +5,17 @@ package com.qnbar.smc.service; @@ -5,19 +5,17 @@ package com.qnbar.smc.service;
5 */ 5 */
6 6
7 public class SocketResponse { 7 public class SocketResponse {
8 -  
9 -  
10 /** 8 /**
11 - * code : 0  
12 - * msg :  
13 - * data : []  
14 - * cmd : openDoor 9 + * code : 10007
  10 + * msg : unknown command!
  11 + * data : {"verify":"","user":0,"first":0}
  12 + * cmd : 10000
15 */ 13 */
16 14
17 private int code; 15 private int code;
18 private String msg; 16 private String msg;
19 - private String cmd;  
20 -// private List<?> data; 17 + private DataEntity data;
  18 + private int cmd;
21 19
22 public int getCode() { 20 public int getCode() {
23 return code; 21 return code;
@@ -35,19 +33,74 @@ public class SocketResponse { @@ -35,19 +33,74 @@ public class SocketResponse {
35 this.msg = msg; 33 this.msg = msg;
36 } 34 }
37 35
38 - public String getCmd() { 36 + public DataEntity getData() {
  37 + return data;
  38 + }
  39 +
  40 + public void setData(DataEntity data) {
  41 + this.data = data;
  42 + }
  43 +
  44 + public int getCmd() {
39 return cmd; 45 return cmd;
40 } 46 }
41 47
42 - public void setCmd(String cmd) { 48 + public void setCmd(int cmd) {
43 this.cmd = cmd; 49 this.cmd = cmd;
44 } 50 }
45 51
46 -// public List<?> getData() {  
47 -// return data;  
48 -// }  
49 -//  
50 -// public void setData(List<?> data) {  
51 -// this.data = data;  
52 -// } 52 + public static class DataEntity {
  53 + /**
  54 + * verify :
  55 + * user : 0
  56 + * first : 0
  57 + */
  58 +
  59 + private String verify;
  60 + private int user;
  61 + private int first;
  62 +
  63 + public String getVerify() {
  64 + return verify;
  65 + }
  66 +
  67 + public void setVerify(String verify) {
  68 + this.verify = verify;
  69 + }
  70 +
  71 + public int getUser() {
  72 + return user;
  73 + }
  74 +
  75 + public void setUser(int user) {
  76 + this.user = user;
  77 + }
  78 +
  79 + public int getFirst() {
  80 + return first;
  81 + }
  82 +
  83 + public void setFirst(int first) {
  84 + this.first = first;
  85 + }
  86 +
  87 + @Override
  88 + public String toString() {
  89 + return "DataEntity{" +
  90 + "verify='" + verify + '\'' +
  91 + ", user=" + user +
  92 + ", first=" + first +
  93 + '}';
  94 + }
  95 + }
  96 +
  97 + @Override
  98 + public String toString() {
  99 + return "SocketResponse{" +
  100 + "code=" + code +
  101 + ", msg='" + msg + '\'' +
  102 + ", data=" + data +
  103 + ", cmd=" + cmd +
  104 + '}';
  105 + }
53 } 106 }
@@ -4,25 +4,35 @@ package com.qnbar.smc.service; @@ -4,25 +4,35 @@ package com.qnbar.smc.service;
4 * Created by wugian on 2017/3/29 4 * Created by wugian on 2017/3/29
5 */ 5 */
6 public class SocketSendMsg { 6 public class SocketSendMsg {
7 - public static final String REGISTER = "register";  
8 - public static final String RECONNECT = "reconnect";  
9 - public static final String HEART_BEAT = "heartbeat";  
10 - /**  
11 - * cmd : register  
12 - * data : {"room_sn":"000002"}  
13 - */  
14 -  
15 - private String cmd; 7 + public static final int REGISTER = 20010;
  8 + public static final int VERIFY = 20020;
  9 + public static final int HEART_BEAT = 20025;
  10 + /* 'cmd'=>20010,//意义和下面的cmd对应
  11 + 'msg'=>'',
  12 + 'data'=>[
  13 + 'room_sn'=>'',终端串号
  14 + 'verify'=>'',//认证时发送
  15 + ],*/
  16 + private int cmd;
  17 + private String msg;
16 private DataEntity data; 18 private DataEntity data;
17 19
18 - public String getCmd() { 20 + public int getCmd() {
19 return cmd; 21 return cmd;
20 } 22 }
21 23
22 - public void setCmd(String cmd) { 24 + public void setCmd(int cmd) {
23 this.cmd = cmd; 25 this.cmd = cmd;
24 } 26 }
25 27
  28 + public String getMsg() {
  29 + return msg;
  30 + }
  31 +
  32 + public void setMsg(String msg) {
  33 + this.msg = msg;
  34 + }
  35 +
26 public DataEntity getData() { 36 public DataEntity getData() {
27 return data; 37 return data;
28 } 38 }
@@ -32,11 +42,8 @@ public class SocketSendMsg { @@ -32,11 +42,8 @@ public class SocketSendMsg {
32 } 42 }
33 43
34 public static class DataEntity { 44 public static class DataEntity {
35 - /**  
36 - * room_sn : 000002  
37 - */  
38 -  
39 private String room_sn; 45 private String room_sn;
  46 + private String verify;
40 47
41 public String getRoom_sn() { 48 public String getRoom_sn() {
42 return room_sn; 49 return room_sn;
@@ -45,30 +52,55 @@ public class SocketSendMsg { @@ -45,30 +52,55 @@ public class SocketSendMsg {
45 public void setRoom_sn(String room_sn) { 52 public void setRoom_sn(String room_sn) {
46 this.room_sn = room_sn; 53 this.room_sn = room_sn;
47 } 54 }
  55 +
  56 + public String getVerify() {
  57 + return verify;
  58 + }
  59 +
  60 + public void setVerify(String verify) {
  61 + this.verify = verify;
  62 + }
  63 +
  64 + @Override
  65 + public String toString() {
  66 + return "DataEntity{" +
  67 + "room_sn='" + room_sn + '\'' +
  68 + ", verify='" + verify + '\'' +
  69 + '}';
  70 + }
48 } 71 }
49 72
50 public SocketSendMsg contractRegisterMsg(String roomSn) { 73 public SocketSendMsg contractRegisterMsg(String roomSn) {
51 this.cmd = REGISTER; 74 this.cmd = REGISTER;
52 - getDataEntity(roomSn); 75 + getDataEntity(roomSn, null);
53 return this; 76 return this;
54 } 77 }
55 78
56 - private void getDataEntity(String roomSn) { 79 + private void getDataEntity(String roomSn, String varify) {
57 DataEntity dataEntity = new DataEntity(); 80 DataEntity dataEntity = new DataEntity();
58 dataEntity.setRoom_sn(roomSn); 81 dataEntity.setRoom_sn(roomSn);
  82 + dataEntity.setVerify(varify);
59 this.data = dataEntity; 83 this.data = dataEntity;
60 } 84 }
61 85
62 - public SocketSendMsg contractReconnectMsg(String roomSn) {  
63 - this.cmd = RECONNECT;  
64 - getDataEntity(roomSn); 86 + public SocketSendMsg contractVerifyMsg(String roomSn, String varifyMsg) {
  87 + this.cmd = VERIFY;
  88 + getDataEntity(roomSn, varifyMsg);
65 return this; 89 return this;
66 } 90 }
67 91
68 public SocketSendMsg contractHeartBeatMsg(String roomSn) { 92 public SocketSendMsg contractHeartBeatMsg(String roomSn) {
69 this.cmd = HEART_BEAT; 93 this.cmd = HEART_BEAT;
70 - getDataEntity(roomSn); 94 + getDataEntity(roomSn, null);
71 return this; 95 return this;
72 } 96 }
73 97
  98 + @Override
  99 + public String toString() {
  100 + return "SocketSendMsg{" +
  101 + "cmd=" + cmd +
  102 + ", msg='" + msg + '\'' +
  103 + ", data=" + data +
  104 + '}';
  105 + }
74 } 106 }
@@ -12,6 +12,7 @@ import com.gimi.common.cinema.model.RoomInfo; @@ -12,6 +12,7 @@ import com.gimi.common.cinema.model.RoomInfo;
12 import com.gimi.common.cinema.utils.Utils; 12 import com.gimi.common.cinema.utils.Utils;
13 import com.google.gson.Gson; 13 import com.google.gson.Gson;
14 import com.google.gson.JsonSyntaxException; 14 import com.google.gson.JsonSyntaxException;
  15 +import com.xgimi.smartscreen.encrypt.AuthCode;
15 import org.greenrobot.eventbus.EventBus; 16 import org.greenrobot.eventbus.EventBus;
16 17
17 import java.io.IOException; 18 import java.io.IOException;
@@ -23,11 +24,31 @@ import java.util.Arrays; @@ -23,11 +24,31 @@ import java.util.Arrays;
23 24
24 public class SocketService extends Service { 25 public class SocketService extends Service {
25 private static final String TAG = "BackService"; 26 private static final String TAG = "BackService";
26 - private static final long HEART_BEAT_RATE = 10 * 1000; 27 + private static final long HEART_BEAT_RATE = 20 * 1000;
27 28
28 - public static final int OPEN_DOOR_CMD = 1701; 29 + public static final int JUST_OPEN_DOOR = 1701;
  30 + public static final int USER_OPEN_DOOR_AND_GET_MOVIE = 1703;
  31 + public static final int USER_OPEN_DOOR = 1704;//get the movie again charge show new movie
  32 + // public static final int USER_OPEN_DOOR_AND_GET_MOVIE = 1703;
  33 + /*
  34 + * 10001 //服务器发送了命令(带命令的返回消息),这个一般是服务器主动发消息的时候code为这个值
  35 + 10002 //连接(认证)失败
  36 + 10003 //还未注册连接就进行认证
  37 + 10004 //终端编号未传
  38 + 10005 //心跳异常,没有找到房间连接信息
  39 + 10006 //心跳异常,连接标识符不一致
  40 + 10007 //收到未知命令
  41 + 10008 //收到无法解析的消息
  42 + 10009 //已经认证过了,不允许再认证
  43 + */
  44 + private static final int SUCCESS_MESSAGE = 10000;
  45 + private static final int AUTHENTICATION_FAILED = 10002; //连接(认证)失败
  46 + private static final int CONNECTION_BEFORE_AUTHENTICATION = 10003;//还未注册连接就进行认证
  47 + private static final int NOT_SEND_ROOM_SN = 10004;//终端编号未传
  48 + private static final int HEART_BEAR_ERROR = 10005; //心跳异常,没有找到房间连接信息
  49 + private static final int HEART_BEAR_ERROR_SYMBOL = 10006;//心跳异常,连接标识符不一致
  50 + private static final int OPEN_DOOR = 50001;//开门命令
29 51
30 - private static final int SUCCESS_MESSAGE = 0;  
31 private static final int ROOM_HAS_REGISTERED = 1001; 52 private static final int ROOM_HAS_REGISTERED = 1001;
32 private static final int ROOM_NOT_EXIST_M = 1002; 53 private static final int ROOM_NOT_EXIST_M = 1002;
33 private static final int HEAT_BEAT_RTN = 1003; 54 private static final int HEAT_BEAT_RTN = 1003;
@@ -36,7 +57,8 @@ public class SocketService extends Service { @@ -36,7 +57,8 @@ public class SocketService extends Service {
36 // public static final int PORT = 9501; 57 // public static final int PORT = 9501;
37 58
38 public static final String HOST = "10.10.4.6";// "192.168.1.21";// 59 public static final String HOST = "10.10.4.6";// "192.168.1.21";//
39 - public static final int PORT = 8899; 60 + // public static final int PORT = 8899;
  61 + public static final int PORT = 9501;
40 62
41 public static final String END_SYMBOL = "\\r\\n\\r\\n";//心跳包内容 63 public static final String END_SYMBOL = "\\r\\n\\r\\n";//心跳包内容
42 // public String testRoomSn = "R170413034374"; 64 // public String testRoomSn = "R170413034374";
@@ -184,6 +206,8 @@ public class SocketService extends Service { @@ -184,6 +206,8 @@ public class SocketService extends Service {
184 } 206 }
185 } 207 }
186 208
  209 + private String varifyMsg = "";
  210 +
187 // Thread to read content from Socket 211 // Thread to read content from Socket
188 class ReadThread extends Thread { 212 class ReadThread extends Thread {
189 private WeakReference<Socket> mWeakSocket; 213 private WeakReference<Socket> mWeakSocket;
@@ -231,27 +255,42 @@ public class SocketService extends Service { @@ -231,27 +255,42 @@ public class SocketService extends Service {
231 switch (socketResponse.getCode()) { 255 switch (socketResponse.getCode()) {
232 case SUCCESS_MESSAGE: 256 case SUCCESS_MESSAGE:
233 Log.d(TAG, "success:" + socketResponse.getCmd()); 257 Log.d(TAG, "success:" + socketResponse.getCmd());
  258 + if (!TextUtils.isEmpty(socketResponse.getData().getVerify())) {
  259 + varifyMsg = AuthCode.getDecodeStr(socketResponse.getData().getVerify());
  260 + SocketSendMsg ssm = new SocketSendMsg().contractVerifyMsg(testRoomSn, varifyMsg);
  261 + String msg = gson.toJson(ssm) + END_SYMBOL;
  262 + sendMsg(msg);
  263 + }
234 break; 264 break;
235 - case ROOM_HAS_REGISTERED:  
236 - SocketSendMsg ssm = new SocketSendMsg().contractReconnectMsg(testRoomSn);  
237 - String msg = gson.toJson(ssm) + END_SYMBOL;  
238 - sendMsg(msg);  
239 - Log.d(TAG, "send reconnect mes: " + msg);  
240 - break;  
241 - case ROOM_NOT_EXIST_M:  
242 - Log.d(TAG, "re init socket");  
243 - //sendRegister = false;  
244 - releaseLastSocket(mWeakSocket);  
245 - initSocket();  
246 - break;  
247 - case HEAT_BEAT_RTN:  
248 - Log.d(TAG, "HEAT_BEAT_RTN");  
249 - sendTime = System.currentTimeMillis(); 265 + case OPEN_DOOR:
  266 + if (socketResponse.getData() != null) {
  267 + switch (socketResponse.getData().getUser()) {
  268 + //10用户,20管理员,默认值为0
  269 + case 10:
  270 + if (socketResponse.getData().getFirst() == 1) {
  271 + openDoor(USER_OPEN_DOOR_AND_GET_MOVIE, "user first open the door");
  272 + } else {
  273 + openDoor(USER_OPEN_DOOR, "user open the door");
  274 + }
  275 + break;
  276 + case 20:
  277 + openDoor(JUST_OPEN_DOOR, "administrator open the door");
  278 + break;
  279 + case 0:
  280 + openDoor(JUST_OPEN_DOOR, "get zero none user or administrator open the door");
  281 + break;
  282 + default:
  283 + openDoor(JUST_OPEN_DOOR, "none user or administrator open the door");
  284 + break;
  285 + }
  286 + }
250 break; 287 break;
  288 + default:
  289 + Log.d(TAG, "print msg:" + socketResponse.toString());
251 } 290 }
252 if (("openDoor").equals(socketResponse.getCmd())) { 291 if (("openDoor").equals(socketResponse.getCmd())) {
253 MessageEvent messageEvent = new MessageEvent(); 292 MessageEvent messageEvent = new MessageEvent();
254 - messageEvent.setEventId(OPEN_DOOR_CMD); 293 + messageEvent.setEventId(JUST_OPEN_DOOR);
255 messageEvent.setMessage("click item"); 294 messageEvent.setMessage("click item");
256 EventBus.getDefault().post(messageEvent); 295 EventBus.getDefault().post(messageEvent);
257 } 296 }
@@ -278,6 +317,13 @@ public class SocketService extends Service { @@ -278,6 +317,13 @@ public class SocketService extends Service {
278 } 317 }
279 } 318 }
280 319
  320 + private void openDoor(int type, String msg) {
  321 + MessageEvent messageEvent = new MessageEvent();
  322 + messageEvent.setEventId(type);
  323 + messageEvent.setMessage(msg);
  324 + EventBus.getDefault().post(messageEvent);
  325 + }
  326 +
281 boolean sendRegister = false; 327 boolean sendRegister = false;
282 328
283 329
@@ -473,18 +473,17 @@ public class MainActivity extends BaseActivity implements IMainView, EventListen @@ -473,18 +473,17 @@ public class MainActivity extends BaseActivity implements IMainView, EventListen
473 Constant.count = durationMinutes + 1; 473 Constant.count = durationMinutes + 1;
474 PollingUtils.startPollingService(context, 60, CountService.class, CountService.STATUS_ACTION); 474 PollingUtils.startPollingService(context, 60, CountService.class, CountService.STATUS_ACTION);
475 475
476 - List<LocalMovieMessage> localMovieMessages = null; 476 + LocalMovieMessage localMovieMessages = null;
477 if (!TextUtils.isEmpty(data.getFilm_hash())) { 477 if (!TextUtils.isEmpty(data.getFilm_hash())) {
478 localMovieMessages = new NewDBManager(this).queryPlayMovie(data.getFilm_hash()); 478 localMovieMessages = new NewDBManager(this).queryPlayMovie(data.getFilm_hash());
479 } 479 }
480 - if (localMovieMessages == null || localMovieMessages.size() == 0) { 480 + if (localMovieMessages == null) {
481 show("没有获取到相应订单信息,即将为您播放银河护卫队"); 481 show("没有获取到相应订单信息,即将为您播放银河护卫队");
482 localMovieMessages = new NewDBManager(this).queryPlayMovie("C1584FFF17811A7FAE58BC564AE79488"); 482 localMovieMessages = new NewDBManager(this).queryPlayMovie("C1584FFF17811A7FAE58BC564AE79488");
483 } 483 }
484 - if (localMovieMessages != null && localMovieMessages.size() > 0) {  
485 - OpenMMUtils.openMMWithAds(this, localMovieMessages.get(0).getPlayPath(), null); 484 + if (localMovieMessages != null) {
  485 + OpenMMUtils.openMMWithAds(this, localMovieMessages.getPlayPath(), null);
486 } 486 }
487 -  
488 } 487 }
489 488
490 @Override 489 @Override
@@ -954,14 +953,47 @@ public class MainActivity extends BaseActivity implements IMainView, EventListen @@ -954,14 +953,47 @@ public class MainActivity extends BaseActivity implements IMainView, EventListen
954 @Subscribe(threadMode = ThreadMode.MAIN) 953 @Subscribe(threadMode = ThreadMode.MAIN)
955 public void onMoonEvent(MessageEvent messageEvent) { 954 public void onMoonEvent(MessageEvent messageEvent) {
956 switch (messageEvent.getEventId()) { 955 switch (messageEvent.getEventId()) {
957 - case SocketService.OPEN_DOOR_CMD: 956 + case SocketService.JUST_OPEN_DOOR:
  957 + openDoor();
  958 + if (rightSn) {
  959 + down();
  960 + }
  961 + Log.d("event bus", "open door" + messageEvent.getMessage());
  962 + break;
  963 + case SocketService.USER_OPEN_DOOR_AND_GET_MOVIE:
958 openDoor(); 964 openDoor();
959 if (rightSn) { 965 if (rightSn) {
960 down(); 966 down();
961 } 967 }
962 presenter.getOrderInfo(); 968 presenter.getOrderInfo();
963 - Log.d("event bus", messageEvent.getMessage());  
964 - Log.d("event bus", "open door"); 969 + Log.d("event bus", "open door" + messageEvent.getMessage());
  970 + break;
  971 + case SocketService.USER_OPEN_DOOR:
  972 + openDoor();
  973 + if (rightSn) {
  974 + down();
  975 + }
  976 + //check the movie
  977 + if (TextUtils.isEmpty(mApplication.getCurrentPlayUrl())) {
  978 + presenter.getOrderInfo();
  979 + } else {
  980 + if (roomStatusInfo == null || roomStatusInfo.getData() == null) {
  981 + presenter.getOrderInfo();
  982 + break;
  983 + }
  984 + if (!TextUtils.isEmpty(roomStatusInfo.getData().getFilm_hash())) {
  985 + LocalMovieMessage lmm = new NewDBManager(this).queryPlayMovie(roomStatusInfo.getData().getFilm_hash());
  986 + if (!mApplication.getCurrentPlayUrl().equals(lmm.getPlayPath())) {
  987 + //当前播放电影信息不正确正在重新拉取
  988 + show("当前播放电影信息不正确正在重新拉取");
  989 + presenter.getOrderInfo();
  990 + }
  991 + } else {
  992 + presenter.getOrderInfo();
  993 + break;
  994 + }
  995 + }
  996 + Log.d("event bus", "open door" + messageEvent.getMessage());
965 break; 997 break;
966 case 0x19910: 998 case 0x19910:
967 if (findHub && findSwitch) { 999 if (findHub && findSwitch) {
@@ -1005,8 +1037,6 @@ public class MainActivity extends BaseActivity implements IMainView, EventListen @@ -1005,8 +1037,6 @@ public class MainActivity extends BaseActivity implements IMainView, EventListen
1005 } 1037 }
1006 1038
1007 private void openDoor() { 1039 private void openDoor() {
1008 -// handler.post(reportRunnable);  
1009 -  
1010 bleBroadcastReceiver.setResponseObj(new GREENCITYBLEProtocolFactory.GREENCITYBleDataWritten() { 1040 bleBroadcastReceiver.setResponseObj(new GREENCITYBLEProtocolFactory.GREENCITYBleDataWritten() {
1011 1041
1012 @Override 1042 @Override
@@ -1035,10 +1065,6 @@ public class MainActivity extends BaseActivity implements IMainView, EventListen @@ -1035,10 +1065,6 @@ public class MainActivity extends BaseActivity implements IMainView, EventListen
1035 }); 1065 });
1036 String openCMD = "Open the door"; 1066 String openCMD = "Open the door";
1037 GREENBLE.send(this, lockMac, openCMD.getBytes()); 1067 GREENBLE.send(this, lockMac, openCMD.getBytes());
1038 -// List<LocalMovieMessage> localMovieMessages = new NewDBManager(this).queryRandomMovies();  
1039 -// if (localMovieMessages != null && localMovieMessages.size() > 0) {  
1040 -// OpenMMUtils.openMMWithAds(this, localMovieMessages.get(0).getPlayPath(), null);  
1041 -// }  
1042 } 1068 }
1043 1069
1044 //---------------------------about switch----------------------------------- 1070 //---------------------------about switch-----------------------------------
@@ -1097,7 +1123,7 @@ public class MainActivity extends BaseActivity implements IMainView, EventListen @@ -1097,7 +1123,7 @@ public class MainActivity extends BaseActivity implements IMainView, EventListen
1097 //---------------------------about switch----------------------------------- 1123 //---------------------------about switch-----------------------------------
1098 1124
1099 //---------------------------about screen----------------------------------- 1125 //---------------------------about screen-----------------------------------
1100 - private boolean rightSn = false; 1126 + private boolean rightSn = false;//是否有正确的智能幕布存在
1101 private boolean isSearching = false; 1127 private boolean isSearching = false;
1102 private String mDeviceIp = ""; 1128 private String mDeviceIp = "";
1103 private MessageThread messageThread; 1129 private MessageThread messageThread;
@@ -13,6 +13,7 @@ import com.telink.bluetooth.light.model.Mesh; @@ -13,6 +13,7 @@ import com.telink.bluetooth.light.model.Mesh;
13 13
14 public class FangTangApplication extends TelinkApplication { 14 public class FangTangApplication extends TelinkApplication {
15 private Mesh mesh; 15 private Mesh mesh;
  16 + private String currentPlayUrl;
16 17
17 @Override 18 @Override
18 public void onCreate() { 19 public void onCreate() {
@@ -62,4 +63,12 @@ public class FangTangApplication extends TelinkApplication { @@ -62,4 +63,12 @@ public class FangTangApplication extends TelinkApplication {
62 public boolean isEmptyMesh() { 63 public boolean isEmptyMesh() {
63 return this.mesh == null; 64 return this.mesh == null;
64 } 65 }
  66 +
  67 + public String getCurrentPlayUrl() {
  68 + return currentPlayUrl;
  69 + }
  70 +
  71 + public void setCurrentPlayUrl(String currentPlayUrl) {
  72 + this.currentPlayUrl = currentPlayUrl;
  73 + }
65 } 74 }
@@ -24,7 +24,6 @@ import android.os.IBinder; @@ -24,7 +24,6 @@ import android.os.IBinder;
24 import android.os.RemoteException; 24 import android.os.RemoteException;
25 import android.util.Log; 25 import android.util.Log;
26 import android.widget.Toast; 26 import android.widget.Toast;
27 -import com.gimi.common.cinema.model.MessageEvent;  
28 import com.gimi.common.cinema.utils.Utils; 27 import com.gimi.common.cinema.utils.Utils;
29 import com.qnbar.smc.model.Light; 28 import com.qnbar.smc.model.Light;
30 import com.qnbar.smc.model.Lights; 29 import com.qnbar.smc.model.Lights;
@@ -33,7 +32,6 @@ import com.telink.bluetooth.light.ConnectionStatus; @@ -33,7 +32,6 @@ import com.telink.bluetooth.light.ConnectionStatus;
33 import com.telink.bluetooth.light.DeviceInfo; 32 import com.telink.bluetooth.light.DeviceInfo;
34 import com.xgimi.gimicinema.ICinemaControl; 33 import com.xgimi.gimicinema.ICinemaControl;
35 import com.xgimi.gimicinema.application.FangTangApplication; 34 import com.xgimi.gimicinema.application.FangTangApplication;
36 -import org.greenrobot.eventbus.EventBus;  
37 35
38 import java.util.ArrayList; 36 import java.util.ArrayList;
39 import java.util.List; 37 import java.util.List;
@@ -93,25 +91,25 @@ public class CinemaControlService extends Service { @@ -93,25 +91,25 @@ public class CinemaControlService extends Service {
93 // if (true) { 91 // if (true) {
94 // return; 92 // return;
95 // } 93 // }
96 - MessageEvent msgEvent = new MessageEvent(); 94 +// MessageEvent msgEvent = new MessageEvent();
97 switch (state) { 95 switch (state) {
98 case 0://空闲 96 case 0://空闲
99 fadeIn(); 97 fadeIn();
100 - msgEvent.setEventId(0x19910);  
101 - msgEvent.setMessage("open_switch");  
102 - EventBus.getDefault().post(msgEvent); 98 +// msgEvent.setEventId(0x19910);
  99 +// msgEvent.setMessage("open_switch");
  100 +// EventBus.getDefault().post(msgEvent);
103 break; 101 break;
104 case 1://播放 102 case 1://播放
105 fadeOut(); 103 fadeOut();
106 - msgEvent.setEventId(0x19910);  
107 - msgEvent.setMessage("close_switch");  
108 - EventBus.getDefault().post(msgEvent); 104 +// msgEvent.setEventId(0x19910);
  105 +// msgEvent.setMessage("close_switch");
  106 +// EventBus.getDefault().post(msgEvent);
109 break; 107 break;
110 case 2://暂停 108 case 2://暂停
111 fadeIn(); 109 fadeIn();
112 - msgEvent.setEventId(0x19910);  
113 - msgEvent.setMessage("open_switch");  
114 - EventBus.getDefault().post(msgEvent); 110 +// msgEvent.setEventId(0x19910);
  111 +// msgEvent.setMessage("open_switch");
  112 +// EventBus.getDefault().post(msgEvent);
115 break; 113 break;
116 } 114 }
117 } 115 }
@@ -120,12 +118,18 @@ public class CinemaControlService extends Service { @@ -120,12 +118,18 @@ public class CinemaControlService extends Service {
120 @Override 118 @Override
121 public void addPlayPath(String path) throws RemoteException { 119 public void addPlayPath(String path) throws RemoteException {
122 movies.add(path);//strict contract 120 movies.add(path);//strict contract
  121 +// MessageEvent msgEvent = new MessageEvent();
  122 +// msgEvent.setEventId(0x19910);
  123 +// msgEvent.setMessage("open_switch");
  124 +// EventBus.getDefault().post(msgEvent);
123 } 125 }
124 126
125 @Override 127 @Override
126 public void setCurrentPlayPath(String path) throws RemoteException { 128 public void setCurrentPlayPath(String path) throws RemoteException {
127 // Log.d("aidl", "setCurrentPlayPath: " + path); 129 // Log.d("aidl", "setCurrentPlayPath: " + path);
128 CinemaControlService.this.currentPath = path; 130 CinemaControlService.this.currentPath = path;
  131 + FangTangApplication application = (FangTangApplication) getApplication();
  132 + application.setCurrentPlayUrl(path);
129 } 133 }
130 134
131 @Override 135 @Override
@@ -258,7 +262,7 @@ public class CinemaControlService extends Service { @@ -258,7 +262,7 @@ public class CinemaControlService extends Service {
258 } 262 }
259 Light byMeshAddress = Lights.getInstance().getByMeshAddress(connectDevice.meshAddress); 263 Light byMeshAddress = Lights.getInstance().getByMeshAddress(connectDevice.meshAddress);
260 if (byMeshAddress == null) { 264 if (byMeshAddress == null) {
261 - Toast.makeText(context, "若智能灯光是出厂设置,请重新配置",Toast.LENGTH_SHORT).show(); 265 + Toast.makeText(context, "若智能灯光是出厂设置,请重新配置", Toast.LENGTH_SHORT).show();
262 return; 266 return;
263 } 267 }
264 if (byMeshAddress.status == ConnectionStatus.ON) { 268 if (byMeshAddress.status == ConnectionStatus.ON) {
@@ -280,7 +284,7 @@ public class CinemaControlService extends Service { @@ -280,7 +284,7 @@ public class CinemaControlService extends Service {
280 } 284 }
281 Light byMeshAddress = Lights.getInstance().getByMeshAddress(connectDevice.meshAddress); 285 Light byMeshAddress = Lights.getInstance().getByMeshAddress(connectDevice.meshAddress);
282 if (byMeshAddress == null) { 286 if (byMeshAddress == null) {
283 - Toast.makeText(context, "若智能灯光是出厂设置,请重新配置",Toast.LENGTH_SHORT).show(); 287 + Toast.makeText(context, "若智能灯光是出厂设置,请重新配置", Toast.LENGTH_SHORT).show();
284 return; 288 return;
285 } 289 }
286 if (byMeshAddress.status == ConnectionStatus.OFF) { 290 if (byMeshAddress.status == ConnectionStatus.OFF) {
1 -package com.xgimi.smartscreen;  
2 -  
3 -import android.app.Service;  
4 -import android.content.Context;  
5 -import android.content.Intent;  
6 -import android.os.IBinder;  
7 -import android.util.Log;  
8 -import com.gimi.common.cinema.model.MessageEvent;  
9 -import com.gimi.common.cinema.utils.SystemUtils;  
10 -import com.google.gson.Gson;  
11 -import com.google.gson.JsonSyntaxException;  
12 -import com.qnbar.smc.service.SocketResponse;  
13 -import com.qnbar.smc.service.SocketSendMsg;  
14 -import com.xgimi.gimicinema.BuildConfig;  
15 -import org.greenrobot.eventbus.EventBus;  
16 -  
17 -import java.io.IOException;  
18 -import java.io.InputStream;  
19 -import java.io.OutputStream;  
20 -import java.lang.ref.WeakReference;  
21 -import java.net.Socket;  
22 -import java.util.Arrays;  
23 -  
24 -public class SocketService extends Service {  
25 - private static final String TAG = "BackService";  
26 - private static final long HEART_BEAT_RATE = 10 * 1000;  
27 -  
28 - public static final int OPEN_DOOR_CMD = 1701;  
29 -  
30 - private static final int SUCESS_MESSAGE = 0;  
31 - private static final int ROOM_HAS_REGISTERED = 1001;  
32 - private static final int ROOM_NOT_EXIST_M = 1002;  
33 - private static final int HEAT_BEAT_RTN = 1003;  
34 -  
35 - public static final String HOST = "121.43.189.162";// "192.168.1.21";//  
36 - public static final int PORT = 9501;  
37 -  
38 - public static final String END_SYMBOL = "\\r\\n\\r\\n";//心跳包内容  
39 - public String testRoomSn = "000003";  
40 -  
41 - private ReadThread mReadThread;  
42 - private HeatBeatThread mHeatBeatThread;  
43 - private Gson gson;  
44 -  
45 -// private LocalBroadcastManager mLocalBroadcastManager;  
46 -  
47 - private WeakReference<Socket> mSocket;  
48 -  
49 - // For heart Beat  
50 -// private Handler mHandler = new Handler();  
51 -// private Runnable heartBeatRunnable = new Runnable() {  
52 -//  
53 -// @Override  
54 -// public void run() {  
55 -// if (System.currentTimeMillis() - sendTime >= HEART_BEAT_RATE) {  
56 -// Log.d(TAG, "heart beat");  
57 -// boolean isSuccess = sendMsg(new Gson().toJson(new SocketSendMsg().contractHeartBeatMsg(testRoomSn)) + END_SYMBOL);//就发送一个HEART_BEAT_STRING过去 如果发送失败,就重新初始化一个socket  
58 -// if (!isSuccess) {  
59 -// Log.d(TAG, "heart beat error restart");  
60 -// mHandler.removeCallbacks(heartBeatRunnable);  
61 -// mReadThread.release();  
62 -// mHeatBeatThread.release();  
63 -// releaseLastSocket(mSocket);  
64 -// new InitSocketThread().start();  
65 -// }  
66 -// } else {  
67 -// Log.d(TAG, "heart beat less than beat rate");  
68 -// }  
69 -// mHandler.postDelayed(this, HEART_BEAT_RATE);  
70 -// }  
71 -// };  
72 -  
73 - private long sendTime = 0L;  
74 - private Context context;  
75 -  
76 - @Override  
77 - public IBinder onBind(Intent arg0) {  
78 - return null;  
79 - }  
80 -  
81 - @Override  
82 - public void onCreate() {  
83 - super.onCreate();  
84 - gson = new Gson();  
85 - context = this;  
86 - //TODO change the room sn  
87 - if (!"LETEST".equals(SystemUtils.getPid(context, BuildConfig.MACHINE_TYPE))) {  
88 - testRoomSn = "000002";  
89 - }  
90 - new InitSocketThread().start();  
91 - Log.d(TAG, "socket service onCreate");  
92 - }  
93 -  
94 - public boolean sendMsg(String msg) {  
95 - if (null == mSocket || null == mSocket.get()) {  
96 - return false;  
97 - }  
98 - Log.d(TAG, "send msg:" + msg);  
99 - Socket soc = mSocket.get();  
100 - try {  
101 - if (!soc.isClosed() && !soc.isOutputShutdown()) {  
102 - OutputStream os = soc.getOutputStream();  
103 - String message = msg;  
104 - os.write(message.getBytes());  
105 - os.flush();  
106 - sendTime = System.currentTimeMillis();//每次发送成数据,就改一下最后成功发送的时间,节省心跳间隔时间  
107 - } else {  
108 - return false;  
109 - }  
110 - } catch (IOException e) {  
111 - e.printStackTrace();  
112 - return false;  
113 - }  
114 - return true;  
115 - }  
116 -  
117 - private void initSocket() throws IOException {//初始化Socket  
118 - Socket so = new Socket(HOST, PORT);  
119 - mSocket = new WeakReference<Socket>(so);  
120 - mReadThread = new ReadThread(so);  
121 - mReadThread.start();  
122 -// new Thread(heartBeatRunnable).start();  
123 -// mHandler.post(heartBeatRunnable);//初始化成功后,就准备发送心跳包  
124 - mHeatBeatThread = new HeatBeatThread(so);  
125 - mHeatBeatThread.start();//上面的 one plus 3t NetworkOnMainThreadException!!!  
126 - }  
127 -  
128 - private void releaseLastSocket(WeakReference<Socket> mSocket) {  
129 - try {  
130 - if (null != mSocket) {  
131 - Socket sk = mSocket.get();  
132 - if (!sk.isClosed()) {  
133 - sk.close();  
134 - }  
135 - sk = null;  
136 - mSocket = null;  
137 - }  
138 - } catch (IOException e) {  
139 - e.printStackTrace();  
140 - }  
141 - }  
142 -  
143 - class InitSocketThread extends Thread {  
144 - @Override  
145 - public void run() {  
146 - super.run();  
147 - try {  
148 - initSocket();  
149 - } catch (IOException e) {  
150 - e.printStackTrace();  
151 - Log.d(TAG, "init socket thread error,restart again after 10's");  
152 - try {  
153 - Thread.sleep(HEART_BEAT_RATE);  
154 - } catch (InterruptedException e1) {  
155 - e1.printStackTrace();  
156 - }  
157 - this.run();  
158 - }  
159 - }  
160 - }  
161 -  
162 - // Thread to read content from Socket  
163 - class ReadThread extends Thread {  
164 - private WeakReference<Socket> mWeakSocket;  
165 - private boolean isStart = true;  
166 -  
167 - public ReadThread(Socket socket) {  
168 - mWeakSocket = new WeakReference<Socket>(socket);  
169 - }  
170 -  
171 - public void release() {  
172 - isStart = false;  
173 - releaseLastSocket(mWeakSocket);  
174 - }  
175 -  
176 - @Override  
177 - public void run() {  
178 - super.run();  
179 - Socket socket = mWeakSocket.get();  
180 - if (null != socket) {  
181 - try {  
182 - if (!sendRegister) {  
183 - Log.d(TAG, "send register mes");  
184 - SocketSendMsg ssm = new SocketSendMsg().contractRegisterMsg(testRoomSn);  
185 - String msg = gson.toJson(ssm) + END_SYMBOL;  
186 - sendMsg(msg);  
187 - Log.d(TAG, "" + msg);  
188 - sendRegister = true;  
189 - }  
190 - Log.d(TAG, "send register mes end");  
191 - InputStream is = socket.getInputStream();  
192 - byte[] buffer = new byte[1024 * 4];  
193 - int length = 0;  
194 - while (!socket.isClosed() && !socket.isInputShutdown()  
195 - && isStart && ((length = is.read(buffer)) != -1)) {  
196 - if (length > 0) {  
197 - String message = new String(Arrays.copyOf(buffer,  
198 - length)).trim();  
199 - Log.d(TAG, "end:" + message.contains(END_SYMBOL) + "");  
200 - Log.d(TAG, "recv msg:" + message);  
201 - try {  
202 - SocketResponse socketResponse = gson.fromJson(message.trim(), SocketResponse.class);  
203 - switch (socketResponse.getCode()) {  
204 - case SUCESS_MESSAGE:  
205 - Log.d(TAG, "success:" + socketResponse.getCmd());  
206 - break;  
207 - case ROOM_HAS_REGISTERED:  
208 - SocketSendMsg ssm = new SocketSendMsg().contractReconnectMsg(testRoomSn);  
209 - String msg = gson.toJson(ssm) + END_SYMBOL;  
210 - sendMsg(msg);  
211 - Log.d(TAG, "send reconnect mes: " + msg);  
212 - break;  
213 - case ROOM_NOT_EXIST_M:  
214 - Log.d(TAG, "re init socket");  
215 - //sendRegister = false;  
216 - releaseLastSocket(mWeakSocket);  
217 - initSocket();  
218 - break;  
219 - case HEAT_BEAT_RTN:  
220 - Log.d(TAG, "HEAT_BEAT_RTN");  
221 - sendTime = System.currentTimeMillis();  
222 - break;  
223 - }  
224 - if (("openDoor").equals(socketResponse.getCmd())) {  
225 - MessageEvent messageEvent = new MessageEvent();  
226 - messageEvent.setEventId(OPEN_DOOR_CMD);  
227 - messageEvent.setMessage("click item");  
228 - EventBus.getDefault().post(messageEvent);  
229 - }  
230 - } catch (JsonSyntaxException e) {  
231 - Log.d(TAG, message);  
232 - e.printStackTrace();  
233 - }  
234 - //收到服务器过来的消息,就通过Broadcast发送出去  
235 -// if (message.equals(END_SYMBOL)) {//处理心跳回复  
236 -// Intent intent = new Intent(HEART_BEAT_ACTION);  
237 -// mLocalBroadcastManager.sendBroadcast(intent);  
238 -// } else {  
239 -// //其他消息回复  
240 -// Intent intent = new Intent(MESSAGE_ACTION);  
241 -// intent.putExtra("message", message);  
242 -// mLocalBroadcastManager.sendBroadcast(intent);  
243 -// }  
244 - }  
245 - }  
246 - } catch (IOException e) {  
247 - e.printStackTrace();  
248 - }  
249 - }  
250 - }  
251 - }  
252 -  
253 - boolean sendRegister = false;  
254 -  
255 -  
256 - // Thread to read content from Socket  
257 - class HeatBeatThread extends Thread {  
258 - private WeakReference<Socket> mWeakSocket;  
259 - private boolean isStart = true;  
260 -  
261 - public HeatBeatThread(Socket socket) {  
262 - mWeakSocket = new WeakReference<Socket>(socket);  
263 - }  
264 -  
265 - public void release() {  
266 - isStart = false;  
267 - releaseLastSocket(mWeakSocket);  
268 - }  
269 -  
270 - @Override  
271 - public void run() {  
272 - super.run();  
273 - Socket socket = mWeakSocket.get();  
274 - if (null != socket) {  
275 - while (isStart) {  
276 - if (System.currentTimeMillis() - sendTime >= HEART_BEAT_RATE) {  
277 - Log.d(TAG, "heart beat:" + Thread.currentThread().getId());  
278 - boolean isSuccess = sendMsg(new Gson().toJson(new SocketSendMsg().contractHeartBeatMsg(testRoomSn)) + END_SYMBOL);//就发送一个HEART_BEAT_STRING过去 如果发送失败,就重新初始化一个socket  
279 - if (!isSuccess) {  
280 - Log.d(TAG, "heart beat error restart:" + Thread.currentThread().getId());  
281 -// mHandler.removeCallbacks(heartBeatRunnable);  
282 - mReadThread.release();  
283 - mHeatBeatThread.release();  
284 - sendRegister = false;  
285 - releaseLastSocket(mSocket);  
286 - new InitSocketThread().start();  
287 - }  
288 - } else {  
289 - Log.d(TAG, "heart beat less than beat rate:" + Thread.currentThread().getId());  
290 - }  
291 - try {  
292 - Thread.sleep(HEART_BEAT_RATE);  
293 - } catch (InterruptedException e) {  
294 - e.printStackTrace();  
295 - }  
296 - }  
297 - }  
298 - }  
299 - }  
300 -  
301 - @Override  
302 - public void onDestroy() {  
303 - super.onDestroy();  
304 - Log.d(TAG, "socket service destroy");  
305 - }  
306 -}  
  1 +package com.xgimi.smartscreen.encrypt;
  2 +
  3 +import java.io.File;
  4 +import java.security.MessageDigest;
  5 +import java.security.NoSuchAlgorithmException;
  6 +import java.util.Calendar;
  7 +import java.util.Random;
  8 +
  9 +public class AuthCode {
  10 +
  11 + public enum DiscuzAuthcodeMode {
  12 + Encode, Decode
  13 + }
  14 +
  15 + ;
  16 +
  17 + // private static MD5 md5 = new MD5();
  18 + // private static BASE64 base64 = new BASE64();
  19 +
  20 +
  21 + /**
  22 + * 从字符串的指定位置截取指定长度的子字符串
  23 + *
  24 + * @param str 原字符串
  25 + * @param startIndex 子字符串的起始位置
  26 + * @param length 子字符串的长度
  27 + * @return 子字符串
  28 + */
  29 + public static String CutString(String str, int startIndex, int length) {
  30 + if (startIndex >= 0) {
  31 + if (length < 0) {
  32 + length = length * -1;
  33 + if (startIndex - length < 0) {
  34 + length = startIndex;
  35 + startIndex = 0;
  36 + } else {
  37 + startIndex = startIndex - length;
  38 + }
  39 + }
  40 +
  41 + if (startIndex > str.length()) {
  42 + return "";
  43 + }
  44 +
  45 + } else {
  46 + if (length < 0) {
  47 + return "";
  48 + } else {
  49 + if (length + startIndex > 0) {
  50 + length = length + startIndex;
  51 + startIndex = 0;
  52 + } else {
  53 + return "";
  54 + }
  55 + }
  56 + }
  57 +
  58 + if (str.length() - startIndex < length) {
  59 +
  60 + length = str.length() - startIndex;
  61 + }
  62 +
  63 + return str.substring(startIndex, startIndex + length);
  64 + }
  65 +
  66 +
  67 + /**
  68 + * 从字符串的指定位置开始截取到字符串结尾的了符串
  69 + *
  70 + * @param str 原字符串
  71 + * @param startIndex 子字符串的起始位置
  72 + * @return 子字符串
  73 + */
  74 + public static String CutString(String str, int startIndex) {
  75 + return CutString(str, startIndex, str.length());
  76 + }
  77 +
  78 +
  79 + /**
  80 + * 返回文件是否存在
  81 + *
  82 + * @param filename 文件名
  83 + * @return 是否存在
  84 + */
  85 + public static boolean FileExists(String filename) {
  86 + File f = new File(filename);
  87 + return f.exists();
  88 + }
  89 +
  90 +
  91 + /**
  92 + * MD5函数
  93 + *
  94 + * @param str 原始字符串
  95 + * @return MD5结果
  96 + */
  97 + public static String MD5(String str) {
  98 + // return md5.convert(str);
  99 + StringBuffer sb = new StringBuffer();
  100 + String part = null;
  101 + try {
  102 + MessageDigest md = MessageDigest.getInstance("MD5");
  103 + byte[] md5 = md.digest(str.getBytes());
  104 +
  105 + for (int i = 0; i < md5.length; i++) {
  106 + part = Integer.toHexString(md5[i] & 0xFF);
  107 + if (part.length() == 1) {
  108 + part = "0" + part;
  109 + }
  110 + sb.append(part);
  111 + }
  112 +
  113 + } catch (NoSuchAlgorithmException ex) {
  114 + }
  115 + return sb.toString();
  116 + }
  117 +
  118 +
  119 + /**
  120 + * 字段串是否为Null或为""(空)
  121 + *
  122 + * @param str 要判断的字符串
  123 + * @return true为空, false不为空
  124 + */
  125 + public static boolean StrIsNullOrEmpty(String str) {
  126 + if (str == null || str.trim().equals("")) {
  127 + return true;
  128 + }
  129 +
  130 + return false;
  131 + }
  132 +
  133 +
  134 + /**
  135 + * 用于 RC4 处理密码
  136 + *
  137 + * @param pass 密码字串
  138 + * @param kLen 密钥长度,一般为 256
  139 + * @return
  140 + */
  141 + static private byte[] GetKey(byte[] pass, int kLen) {
  142 + byte[] mBox = new byte[kLen];
  143 +
  144 + for (int i = 0; i < kLen; i++) {
  145 + mBox[i] = (byte) i;
  146 + }
  147 +
  148 + int j = 0;
  149 + for (int i = 0; i < kLen; i++) {
  150 +
  151 + j = (j + (int) ((mBox[i] + 256) % 256) + pass[i % pass.length])
  152 + % kLen;
  153 +
  154 + byte temp = mBox[i];
  155 + mBox[i] = mBox[j];
  156 + mBox[j] = temp;
  157 + }
  158 +
  159 + return mBox;
  160 + }
  161 +
  162 +
  163 + /**
  164 + * 生成随机字符
  165 + *
  166 + * @param lens 随机字符长度
  167 + * @return String 生成的随机字符
  168 + */
  169 + public static String RandomString(int lens) {
  170 + char[] CharArray = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k',
  171 + 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
  172 + 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
  173 + int clens = CharArray.length;
  174 + String sCode = "";
  175 + Random random = new Random();
  176 + for (int i = 0; i < lens; i++) {
  177 + sCode += CharArray[Math.abs(random.nextInt(clens))];
  178 + }
  179 + return sCode;
  180 + }
  181 +
  182 + /**
  183 + * 使用 Discuz authcode 方法对字符串加密
  184 + *
  185 + * @param source 原始字符串
  186 + * @param key 密钥
  187 + * @param expiry 加密字串有效时间,单位是秒
  188 + * @return String 加密结果
  189 + */
  190 + public static String authcodeEncode(String source, String key, int expiry) {
  191 + return authcode(source, key, DiscuzAuthcodeMode.Encode, expiry);
  192 +
  193 + }
  194 +
  195 + /**
  196 + * 使用 Discuz authcode 方法对字符串加密
  197 + *
  198 + * @param source 原始字符串
  199 + * @param key 密钥
  200 + * @return String 加密结果
  201 + */
  202 + public static String authcodeEncode(String source, String key) {
  203 + return authcode(source, key, DiscuzAuthcodeMode.Encode, 0);
  204 +
  205 + }
  206 +
  207 + /**
  208 + * 使用 Discuz authcode 方法对字符串解密
  209 + *
  210 + * @param source 原始字符串
  211 + * @param key 密钥
  212 + * @return String解密结果
  213 + */
  214 + public static String authcodeDecode(String source, String key) {
  215 + return authcode(source, key, DiscuzAuthcodeMode.Decode, 0);
  216 +
  217 + }
  218 +
  219 + /**
  220 + * 使用 变形的 rc4 编码方法对字符串进行加密或者解密
  221 + *
  222 + * @param source 原始字符串
  223 + * @param key 密钥
  224 + * @param operation 操作 加密还是解密
  225 + * @param expiry 加密字串过期时间
  226 + * @return 加密或者解密后的字符串
  227 + */
  228 + private static String authcode(String source, String key,
  229 + DiscuzAuthcodeMode operation, int expiry) {
  230 + try {
  231 + if (source == null || key == null) {
  232 + return "";
  233 + }
  234 +
  235 + int ckey_length = 4;
  236 + String keya, keyb, keyc, cryptkey, result;
  237 +
  238 + key = MD5(key);
  239 +
  240 + keya = MD5(CutString(key, 0, 16));
  241 +
  242 + keyb = MD5(CutString(key, 16, 16));
  243 +
  244 + keyc = ckey_length > 0 ? (operation == DiscuzAuthcodeMode.Decode ? CutString(
  245 + source, 0, ckey_length) : RandomString(ckey_length))
  246 + : "";
  247 +
  248 + cryptkey = keya + MD5(keya + keyc);
  249 +
  250 + if (operation == DiscuzAuthcodeMode.Decode) {
  251 + byte[] temp;
  252 +
  253 + temp = Base64.decode(CutString(source, ckey_length));
  254 + result = new String(RC4(temp, cryptkey));
  255 + if (CutString(result, 10, 16).equals(
  256 + CutString(MD5(CutString(result, 26) + keyb), 0, 16))) {
  257 + return CutString(result, 26);
  258 + } else {
  259 + temp = Base64.decode(CutString(source + "=", ckey_length));
  260 + result = new String(RC4(temp, cryptkey));
  261 + if (CutString(result, 10, 16)
  262 + .equals(CutString(
  263 + MD5(CutString(result, 26) + keyb), 0, 16))) {
  264 + return CutString(result, 26);
  265 + } else {
  266 + temp = Base64.decode(CutString(source + "==",
  267 + ckey_length));
  268 + result = new String(RC4(temp, cryptkey));
  269 + if (CutString(result, 10, 16).equals(
  270 + CutString(MD5(CutString(result, 26) + keyb), 0,
  271 + 16))) {
  272 + return CutString(result, 26);
  273 + } else {
  274 + return "2";
  275 + }
  276 + }
  277 + }
  278 + } else {
  279 + source = "0000000000" + CutString(MD5(source + keyb), 0, 16)
  280 + + source;
  281 +
  282 + byte[] temp = RC4(source.getBytes("GBK"), cryptkey);
  283 +
  284 + return keyc + Base64.encodeBytes(temp);
  285 +
  286 + }
  287 + } catch (Exception e) {
  288 + return "";
  289 + }
  290 +
  291 + }
  292 +
  293 +
  294 + /**
  295 + * RC4 原始算法
  296 + *
  297 + * @param input 原始字串数组
  298 + * @param pass 密钥
  299 + * @return 处理后的字串数组
  300 + */
  301 + private static byte[] RC4(byte[] input, String pass) {
  302 + if (input == null || pass == null)
  303 + return null;
  304 +
  305 + byte[] output = new byte[input.length];
  306 + byte[] mBox = GetKey(pass.getBytes(), 256);
  307 +
  308 + // 加密
  309 + int i = 0;
  310 + int j = 0;
  311 +
  312 + for (int offset = 0; offset < input.length; offset++) {
  313 + i = (i + 1) % mBox.length;
  314 + j = (j + (int) ((mBox[i] + 256) % 256)) % mBox.length;
  315 +
  316 + byte temp = mBox[i];
  317 + mBox[i] = mBox[j];
  318 + mBox[j] = temp;
  319 + byte a = input[offset];
  320 +
  321 + // byte b = mBox[(mBox[i] + mBox[j] % mBox.Length) % mBox.Length];
  322 + // mBox[j] 一定比 mBox.Length 小,不需要在取模
  323 + byte b = mBox[(toInt(mBox[i]) + toInt(mBox[j])) % mBox.length];
  324 +
  325 + output[offset] = (byte) ((int) a ^ (int) toInt(b));
  326 + }
  327 +
  328 + return output;
  329 + }
  330 +
  331 + public static int toInt(byte b) {
  332 + return (int) ((b + 256) % 256);
  333 + }
  334 +
  335 + /**
  336 + * 获取Unix时间戳
  337 + *
  338 + * @return 得到的时间戳
  339 + */
  340 + public long getUnixTimestamp() {
  341 + Calendar cal = Calendar.getInstance();
  342 + return cal.getTimeInMillis() / 1000;
  343 + }
  344 +
  345 + public static String getDecodeStr(String encrypt) {
  346 + return AuthCode.authcodeDecode(encrypt, "room_secret_key");
  347 + }
  348 +
  349 + public static void main(String[] args) {
  350 + String enryptStr = "3a3fz3gQGz4VK2SmFEwuwc8yw11eCdzOPSsqMMEURNnByQ";
  351 + String test = "room_secret_key";
  352 + String key = "room_secret_key";
  353 + String afStr = AuthCode.authcodeEncode(test, key); //加密
  354 + System.out.println("--------encode:" + afStr);
  355 + long lStart = System.currentTimeMillis();
  356 + System.out.println("解码后:" + AuthCode.authcodeDecode(afStr, key));
  357 + long lUseTime = System.currentTimeMillis() - lStart;
  358 + System.out.println("加解密耗时:" + lUseTime + "毫秒");
  359 +
  360 + String deStr = AuthCode.authcodeDecode(enryptStr, key); //解密
  361 + System.out.println("--------decode:" + deStr);
  362 +
  363 + }
  364 +
  365 +}
  1 +package com.xgimi.smartscreen.encrypt;
  2 +
  3 +/**
  4 + * <p>
  5 + * Encodes and decodes to and from Base64 notation.
  6 + * </p>
  7 + * <p>
  8 + * Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.
  9 + * </p>
  10 + *
  11 + * <p>
  12 + * Example:
  13 + * </p>
  14 + *
  15 + * <code>String encoded = Base64.encode( myByteArray );</code> <br />
  16 + * <code>byte[] myByteArray = Base64.decode( encoded );</code>
  17 + *
  18 + * <p>
  19 + * The <tt>options</tt> parameter, which appears in a few places, is used to
  20 + * pass several pieces of information to the encoder. In the "higher level"
  21 + * methods such as encodeBytes( bytes, options ) the options parameter can be
  22 + * used to indicate such things as first gzipping the bytes before encoding
  23 + * them, not inserting linefeeds, and encoding using the URL-safe and Ordered
  24 + * dialects.
  25 + * </p>
  26 + *
  27 + * <p>
  28 + * Note, according to <a
  29 + * href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>, Section 2.1,
  30 + * implementations should not add line feeds unless explicitly told to do so.
  31 + * I've got Base64 set to this behavior now, although earlier versions broke
  32 + * lines by default.
  33 + * </p>
  34 + *
  35 + * <p>
  36 + * The constants defined in Base64 can be OR-ed together to combine options, so
  37 + * you might make a call like this:
  38 + * </p>
  39 + *
  40 + * <code>String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES );</code>
  41 + * <p>
  42 + * to compress the data before encoding it and then making the output have
  43 + * newline characters.
  44 + * </p>
  45 + * <p>
  46 + * Also...
  47 + * </p>
  48 + * <code>String encoded = Base64.encodeBytes( crazyString.getBytes() );</code>
  49 + *
  50 + *
  51 + *
  52 + * <p>
  53 + * Change Log:
  54 + * </p>
  55 + * <ul>
  56 + * <li>v2.3.4 - Fixed bug when working with gzipped streams whereby flushing the
  57 + * Base64.OutputStream closed the Base64 encoding (by padding with equals signs)
  58 + * too soon. Also added an option to suppress the automatic decoding of gzipped
  59 + * streams. Also added experimental support for specifying a class loader when
  60 + * using the
  61 + * {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)} method.
  62 + * </li>
  63 + * <li>v2.3.3 - Changed default char encoding to US-ASCII which reduces the
  64 + * internal Java footprint with its CharEncoders and so forth. Fixed some
  65 + * javadocs that were inconsistent. Removed imports and specified things like
  66 + * java.io.IOException explicitly inline.</li>
  67 + * <li>v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how
  68 + * big the final encoded data will be so that the code doesn't have to create
  69 + * two output arrays: an oversized initial one and then a final, exact-sized
  70 + * one. Big win when using the {@link #encodeBytesToBytes(byte[])} family of
  71 + * methods (and not using the gzip options which uses a different mechanism with
  72 + * streams and stuff).</li>
  73 + * <li>v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and
  74 + * some similar helper methods to be more efficient with memory by not returning
  75 + * a String but just a byte array.</li>
  76 + * <li>v2.3 - <strong>This is not a drop-in replacement!</strong> This is two
  77 + * years of comments and bug fixes queued up and finally executed. Thanks to
  78 + * everyone who sent me stuff, and I'm sorry I wasn't able to distribute your
  79 + * fixes to everyone else. Much bad coding was cleaned up including throwing
  80 + * exceptions where necessary instead of returning null values or something
  81 + * similar. Here are some changes that may affect you:
  82 + * <ul>
  83 + * <li><em>Does not break lines, by default.</em> This is to keep in compliance
  84 + * with <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>.</li>
  85 + * <li><em>Throws exceptions instead of returning null values.</em> Because some
  86 + * operations (especially those that may permit the GZIP option) use IO streams,
  87 + * there is a possiblity of an java.io.IOException being thrown. After some
  88 + * discussion and thought, I've changed the behavior of the methods to throw
  89 + * java.io.IOExceptions rather than return null if ever there's an error. I
  90 + * think this is more appropriate, though it will require some changes to your
  91 + * code. Sorry, it should have been done this way to begin with.</li>
  92 + * <li><em>Removed all references to System.out, System.err, and the like.</em>
  93 + * Shame on me. All I can say is sorry they were ever there.</li>
  94 + * <li><em>Throws NullPointerExceptions and IllegalArgumentExceptions</em> as
  95 + * needed such as when passed arrays are null or offsets are invalid.</li>
  96 + * <li>Cleaned up as much javadoc as I could to avoid any javadoc warnings. This
  97 + * was especially annoying before for people who were thorough in their own
  98 + * projects and then had gobs of javadoc warnings on this file.</li>
  99 + * </ul>
  100 + * <li>v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug when
  101 + * using very small files (~< 40 bytes).</li>
  102 + * <li>v2.2 - Added some helper methods for encoding/decoding directly from one
  103 + * file to the next. Also added a main() method to support command line
  104 + * encoding/decoding from one file to the next. Also added these Base64
  105 + * dialects:
  106 + * <ol>
  107 + * <li>The default is RFC3548 format.</li>
  108 + * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates
  109 + * URL and file name friendly format as described in Section 4 of RFC3548.
  110 + * http://www.faqs.org/rfcs/rfc3548.html</li>
  111 + * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates
  112 + * URL and file name friendly format that preserves lexical ordering as
  113 + * described in http://www.faqs.org/qa/rfcc-1940.html</li>
  114 + * </ol>
  115 + * Special thanks to Jim Kellerman at <a
  116 + * href="http://www.powerset.com/">http://www.powerset.com/</a> for contributing
  117 + * the new Base64 dialects.</li>
  118 + *
  119 + * <li>v2.1 - Cleaned up javadoc comments and unused variables and methods.
  120 + * Added some convenience methods for reading and writing to and from files.</li>
  121 + * <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on
  122 + * systems with other encodings (like EBCDIC).</li>
  123 + * <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the
  124 + * encoded data was a single byte.</li>
  125 + * <li>v2.0 - I got rid of methods that used booleans to set options. Now
  126 + * everything is more consolidated and cleaner. The code now detects when data
  127 + * that's being decoded is gzip-compressed and will decompress it automatically.
  128 + * Generally things are cleaner. You'll probably have to change some method
  129 + * calls that you were making to support the new options format (<tt>int</tt>s
  130 + * that you "OR" together).</li>
  131 + * <li>v1.5.1 - Fixed bug when decompressing and decoding to a byte[] using
  132 + * <tt>decode( String s, boolean gzipCompressed )</tt>. Added the ability to
  133 + * "suspend" encoding in the Output Stream so you can turn on and off the
  134 + * encoding if you need to embed base64 data in an otherwise "normal" stream
  135 + * (like an XML file).</li>
  136 + * <li>v1.5 - Output stream pases on flush() command but doesn't do anything
  137 + * itself. This helps when using GZIP streams. Added the ability to
  138 + * GZip-compress objects before encoding them.</li>
  139 + * <li>v1.4 - Added helper methods to read/write files.</li>
  140 + * <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
  141 + * <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input
  142 + * stream where last buffer being read, if not completely full, was not
  143 + * returned.</li>
  144 + * <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the
  145 + * wrong time.</li>
  146 + * <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
  147 + * </ul>
  148 + *
  149 + * <p>
  150 + * I am placing this code in the Public Domain. Do with it as you will. This
  151 + * software comes with no guarantees or warranties but with plenty of
  152 + * well-wishing instead! Please visit <a
  153 + * href="http://iharder.net/base64">http://iharder.net/base64</a> periodically
  154 + * to check for updates or to contribute improvements.
  155 + * </p>
  156 + *
  157 + * @author Robert Harder
  158 + * @author rob@iharder.net
  159 + * @version 2.3.3
  160 + */
  161 +public class Base64 {
  162 +
  163 + /* ******** P U B L I C F I E L D S ******** */
  164 +
  165 + /** No options specified. Value is zero. */
  166 + public final static int NO_OPTIONS = 0;
  167 +
  168 + /** Specify encoding in first bit. Value is one. */
  169 + public final static int ENCODE = 1;
  170 +
  171 + /** Specify decoding in first bit. Value is zero. */
  172 + public final static int DECODE = 0;
  173 +
  174 + /** Specify that data should be gzip-compressed in second bit. Value is two. */
  175 + public final static int GZIP = 2;
  176 +
  177 + /**
  178 + * Specify that gzipped data should <em>not</em> be automatically gunzipped.
  179 + */
  180 + public final static int DONT_GUNZIP = 4;
  181 +
  182 + /** Do break lines when encoding. Value is 8. */
  183 + public final static int DO_BREAK_LINES = 8;
  184 +
  185 + /**
  186 + * Encode using Base64-like encoding that is URL- and Filename-safe as
  187 + * described in Section 4 of RFC3548: <a
  188 + * href="http://www.faqs.org/rfcs/rfc3548.html"
  189 + * >http://www.faqs.org/rfcs/rfc3548.html</a>. It is important to note that
  190 + * data encoded this way is <em>not</em> officially valid Base64, or at the
  191 + * very least should not be called Base64 without also specifying that is
  192 + * was encoded using the URL- and Filename-safe dialect.
  193 + */
  194 + public final static int URL_SAFE = 16;
  195 +
  196 + /**
  197 + * Encode using the special "ordered" dialect of Base64 described here: <a
  198 + * href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-
  199 + * 1940.html</a>.
  200 + */
  201 + public final static int ORDERED = 32;
  202 +
  203 + /* ******** P R I V A T E F I E L D S ******** */
  204 +
  205 + /** Maximum line length (76) of Base64 output. */
  206 + private final static int MAX_LINE_LENGTH = 76;
  207 +
  208 + /** The equals sign (=) as a byte. */
  209 + private final static byte EQUALS_SIGN = (byte) '=';
  210 +
  211 + /** The new line character (\n) as a byte. */
  212 + private final static byte NEW_LINE = (byte) '\n';
  213 +
  214 + /** Preferred encoding. */
  215 + private final static String PREFERRED_ENCODING = "US-ASCII";
  216 +
  217 + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in
  218 + // encoding
  219 + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in
  220 + // encoding
  221 +
  222 + /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */
  223 +
  224 + /** The 64 valid Base64 values. */
  225 + /*
  226 + * Host platform me be something funny like EBCDIC, so we hardcode these
  227 + * values.
  228 + */
  229 + private final static byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B',
  230 + (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G',
  231 + (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L',
  232 + (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q',
  233 + (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V',
  234 + (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a',
  235 + (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f',
  236 + (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k',
  237 + (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p',
  238 + (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u',
  239 + (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z',
  240 + (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4',
  241 + (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9',
  242 + (byte) '+', (byte) '/' };
  243 +
  244 + /**
  245 + * Translates a Base64 value to either its 6-bit reconstruction value or a
  246 + * negative number indicating some other meaning.
  247 + **/
  248 + private final static byte[] _STANDARD_DECODABET = { -9, -9, -9, -9, -9, -9,
  249 + -9, -9, -9, // Decimal 0 - 8
  250 + -5, -5, // Whitespace: Tab and Linefeed
  251 + -9, -9, // Decimal 11 - 12
  252 + -5, // Whitespace: Carriage Return
  253 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 -
  254 + // 26
  255 + -9, -9, -9, -9, -9, // Decimal 27 - 31
  256 + -5, // Whitespace: Space
  257 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
  258 + 62, // Plus sign at decimal 43
  259 + -9, -9, -9, // Decimal 44 - 46
  260 + 63, // Slash at decimal 47
  261 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
  262 + -9, -9, -9, // Decimal 58 - 60
  263 + -1, // Equals sign at decimal 61
  264 + -9, -9, -9, // Decimal 62 - 64
  265 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through
  266 + // 'N'
  267 + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O'
  268 + // through 'Z'
  269 + -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
  270 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a'
  271 + // through 'm'
  272 + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n'
  273 + // through 'z'
  274 + -9, -9, -9, -9 // Decimal 123 - 126
  275 + /*
  276 + * ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
  277 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
  278 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
  279 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
  280 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
  281 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
  282 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
  283 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
  284 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
  285 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
  286 + */
  287 + };
  288 +
  289 + /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */
  290 +
  291 + /**
  292 + * Used in the URL- and Filename-safe dialect described in Section 4 of
  293 + * RFC3548: <a
  294 + * href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org
  295 + * /rfcs/rfc3548.html</a>. Notice that the last two bytes become "hyphen"
  296 + * and "underscore" instead of "plus" and "slash."
  297 + */
  298 + private final static byte[] _URL_SAFE_ALPHABET = { (byte) 'A', (byte) 'B',
  299 + (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G',
  300 + (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L',
  301 + (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q',
  302 + (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V',
  303 + (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a',
  304 + (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f',
  305 + (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k',
  306 + (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p',
  307 + (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u',
  308 + (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z',
  309 + (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4',
  310 + (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9',
  311 + (byte) '-', (byte) '_' };
  312 +
  313 + /**
  314 + * Used in decoding URL- and Filename-safe dialects of Base64.
  315 + */
  316 + private final static byte[] _URL_SAFE_DECODABET = { -9, -9, -9, -9, -9, -9,
  317 + -9, -9, -9, // Decimal 0 - 8
  318 + -5, -5, // Whitespace: Tab and Linefeed
  319 + -9, -9, // Decimal 11 - 12
  320 + -5, // Whitespace: Carriage Return
  321 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 -
  322 + // 26
  323 + -9, -9, -9, -9, -9, // Decimal 27 - 31
  324 + -5, // Whitespace: Space
  325 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
  326 + -9, // Plus sign at decimal 43
  327 + -9, // Decimal 44
  328 + 62, // Minus sign at decimal 45
  329 + -9, // Decimal 46
  330 + -9, // Slash at decimal 47
  331 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
  332 + -9, -9, -9, // Decimal 58 - 60
  333 + -1, // Equals sign at decimal 61
  334 + -9, -9, -9, // Decimal 62 - 64
  335 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through
  336 + // 'N'
  337 + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O'
  338 + // through 'Z'
  339 + -9, -9, -9, -9, // Decimal 91 - 94
  340 + 63, // Underscore at decimal 95
  341 + -9, // Decimal 96
  342 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a'
  343 + // through 'm'
  344 + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n'
  345 + // through 'z'
  346 + -9, -9, -9, -9 // Decimal 123 - 126
  347 + /*
  348 + * ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
  349 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
  350 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
  351 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
  352 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
  353 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
  354 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
  355 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
  356 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
  357 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
  358 + */
  359 + };
  360 +
  361 + /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */
  362 +
  363 + /**
  364 + * I don't get the point of this technique, but someone requested it, and it
  365 + * is described here: <a
  366 + * href="http://www.faqs.org/qa/rfcc-1940.html">http://
  367 + * www.faqs.org/qa/rfcc-1940.html</a>.
  368 + */
  369 + private final static byte[] _ORDERED_ALPHABET = { (byte) '-', (byte) '0',
  370 + (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
  371 + (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A',
  372 + (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
  373 + (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
  374 + (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
  375 + (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
  376 + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
  377 + (byte) '_', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd',
  378 + (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i',
  379 + (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n',
  380 + (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's',
  381 + (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x',
  382 + (byte) 'y', (byte) 'z' };
  383 +
  384 + /**
  385 + * Used in decoding the "ordered" dialect of Base64.
  386 + */
  387 + private final static byte[] _ORDERED_DECODABET = { -9, -9, -9, -9, -9, -9,
  388 + -9, -9, -9, // Decimal 0 - 8
  389 + -5, -5, // Whitespace: Tab and Linefeed
  390 + -9, -9, // Decimal 11 - 12
  391 + -5, // Whitespace: Carriage Return
  392 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 -
  393 + // 26
  394 + -9, -9, -9, -9, -9, // Decimal 27 - 31
  395 + -5, // Whitespace: Space
  396 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
  397 + -9, // Plus sign at decimal 43
  398 + -9, // Decimal 44
  399 + 0, // Minus sign at decimal 45
  400 + -9, // Decimal 46
  401 + -9, // Slash at decimal 47
  402 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine
  403 + -9, -9, -9, // Decimal 58 - 60
  404 + -1, // Equals sign at decimal 61
  405 + -9, -9, -9, // Decimal 62 - 64
  406 + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A'
  407 + // through 'M'
  408 + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N'
  409 + // through 'Z'
  410 + -9, -9, -9, -9, // Decimal 91 - 94
  411 + 37, // Underscore at decimal 95
  412 + -9, // Decimal 96
  413 + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a'
  414 + // through 'm'
  415 + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n'
  416 + // through 'z'
  417 + -9, -9, -9, -9 // Decimal 123 - 126
  418 + /*
  419 + * ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
  420 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
  421 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
  422 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
  423 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
  424 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
  425 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
  426 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
  427 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
  428 + * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
  429 + */
  430 + };
  431 +
  432 + /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */
  433 +
  434 + /**
  435 + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on the
  436 + * options specified. It's possible, though silly, to specify ORDERED
  437 + * <b>and</b> URLSAFE in which case one of them will be picked, though there
  438 + * is no guarantee as to which one will be picked.
  439 + */
  440 + private final static byte[] getAlphabet(int options) {
  441 + if ((options & URL_SAFE) == URL_SAFE) {
  442 + return _URL_SAFE_ALPHABET;
  443 + } else if ((options & ORDERED) == ORDERED) {
  444 + return _ORDERED_ALPHABET;
  445 + } else {
  446 + return _STANDARD_ALPHABET;
  447 + }
  448 + } // end getAlphabet
  449 +
  450 + /**
  451 + * Returns one of the _SOMETHING_DECODABET byte arrays depending on the
  452 + * options specified. It's possible, though silly, to specify ORDERED and
  453 + * URL_SAFE in which case one of them will be picked, though there is no
  454 + * guarantee as to which one will be picked.
  455 + */
  456 + private final static byte[] getDecodabet(int options) {
  457 + if ((options & URL_SAFE) == URL_SAFE) {
  458 + return _URL_SAFE_DECODABET;
  459 + } else if ((options & ORDERED) == ORDERED) {
  460 + return _ORDERED_DECODABET;
  461 + } else {
  462 + return _STANDARD_DECODABET;
  463 + }
  464 + } // end getAlphabet
  465 +
  466 + /** Defeats instantiation. */
  467 + private Base64() {
  468 + }
  469 +
  470 + /* ******** E N C O D I N G M E T H O D S ******** */
  471 +
  472 + /**
  473 + * Encodes up to the first three bytes of array <var>threeBytes</var> and
  474 + * returns a four-byte array in Base64 notation. The actual number of
  475 + * significant bytes in your array is given by <var>numSigBytes</var>. The
  476 + * array <var>threeBytes</var> needs only be as big as
  477 + * <var>numSigBytes</var>. Code can reuse a byte array by passing a
  478 + * four-byte array as <var>b4</var>.
  479 + *
  480 + * @param b4
  481 + * A reusable byte array to reduce array instantiation
  482 + * @param threeBytes
  483 + * the array to convert
  484 + * @param numSigBytes
  485 + * the number of significant bytes in your array
  486 + * @return four byte array in Base64 notation.
  487 + * @since 1.5.1
  488 + */
  489 + private static byte[] encode3to4(byte[] b4, byte[] threeBytes,
  490 + int numSigBytes, int options) {
  491 + encode3to4(threeBytes, 0, numSigBytes, b4, 0, options);
  492 + return b4;
  493 + } // end encode3to4
  494 +
  495 + /**
  496 + * <p>
  497 + * Encodes up to three bytes of the array <var>source</var> and writes the
  498 + * resulting four Base64 bytes to <var>destination</var>. The source and
  499 + * destination arrays can be manipulated anywhere along their length by
  500 + * specifying <var>srcOffset</var> and <var>destOffset</var>. This method
  501 + * does not check to make sure your arrays are large enough to accomodate
  502 + * <var>srcOffset</var> + 3 for the <var>source</var> array or
  503 + * <var>destOffset</var> + 4 for the <var>destination</var> array. The
  504 + * actual number of significant bytes in your array is given by
  505 + * <var>numSigBytes</var>.
  506 + * </p>
  507 + * <p>
  508 + * This is the lowest level of the encoding methods with all possible
  509 + * parameters.
  510 + * </p>
  511 + *
  512 + * @param source
  513 + * the array to convert
  514 + * @param srcOffset
  515 + * the index where conversion begins
  516 + * @param numSigBytes
  517 + * the number of significant bytes in your array
  518 + * @param destination
  519 + * the array to hold the conversion
  520 + * @param destOffset
  521 + * the index where output will be put
  522 + * @return the <var>destination</var> array
  523 + * @since 1.3
  524 + */
  525 + private static byte[] encode3to4(byte[] source, int srcOffset,
  526 + int numSigBytes, byte[] destination, int destOffset, int options) {
  527 +
  528 + byte[] ALPHABET = getAlphabet(options);
  529 +
  530 + // 1 2 3
  531 + // 01234567890123456789012345678901 Bit position
  532 + // --------000000001111111122222222 Array position from threeBytes
  533 + // --------| || || || | Six bit groups to index ALPHABET
  534 + // >>18 >>12 >> 6 >> 0 Right shift necessary
  535 + // 0x3f 0x3f 0x3f Additional AND
  536 +
  537 + // Create buffer with zero-padding if there are only one or two
  538 + // significant bytes passed in the array.
  539 + // We have to shift left 24 in order to flush out the 1's that appear
  540 + // when Java treats a value as negative that is cast from a byte to an
  541 + // int.
  542 + int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
  543 + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
  544 + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
  545 +
  546 + switch (numSigBytes) {
  547 + case 3:
  548 + destination[destOffset] = ALPHABET[(inBuff >>> 18)];
  549 + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
  550 + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
  551 + destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f];
  552 + return destination;
  553 +
  554 + case 2:
  555 + destination[destOffset] = ALPHABET[(inBuff >>> 18)];
  556 + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
  557 + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
  558 + destination[destOffset + 3] = EQUALS_SIGN;
  559 + return destination;
  560 +
  561 + case 1:
  562 + destination[destOffset] = ALPHABET[(inBuff >>> 18)];
  563 + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
  564 + destination[destOffset + 2] = EQUALS_SIGN;
  565 + destination[destOffset + 3] = EQUALS_SIGN;
  566 + return destination;
  567 +
  568 + default:
  569 + return destination;
  570 + } // end switch
  571 + } // end encode3to4
  572 +
  573 + /**
  574 + * Performs Base64 encoding on the <code>raw</code> ByteBuffer, writing it
  575 + * to the <code>encoded</code> ByteBuffer. This is an experimental feature.
  576 + * Currently it does not pass along any options (such as
  577 + * {@link #DO_BREAK_LINES} or {@link #GZIP}.
  578 + *
  579 + * @param raw
  580 + * input buffer
  581 + * @param encoded
  582 + * output buffer
  583 + * @since 2.3
  584 + */
  585 + public static void encode(java.nio.ByteBuffer raw,
  586 + java.nio.ByteBuffer encoded) {
  587 + byte[] raw3 = new byte[3];
  588 + byte[] enc4 = new byte[4];
  589 +
  590 + while (raw.hasRemaining()) {
  591 + int rem = Math.min(3, raw.remaining());
  592 + raw.get(raw3, 0, rem);
  593 + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS);
  594 + encoded.put(enc4);
  595 + } // end input remaining
  596 + }
  597 +
  598 + /**
  599 + * Performs Base64 encoding on the <code>raw</code> ByteBuffer, writing it
  600 + * to the <code>encoded</code> CharBuffer. This is an experimental feature.
  601 + * Currently it does not pass along any options (such as
  602 + * {@link #DO_BREAK_LINES} or {@link #GZIP}.
  603 + *
  604 + * @param raw
  605 + * input buffer
  606 + * @param encoded
  607 + * output buffer
  608 + * @since 2.3
  609 + */
  610 + public static void encode(java.nio.ByteBuffer raw,
  611 + java.nio.CharBuffer encoded) {
  612 + byte[] raw3 = new byte[3];
  613 + byte[] enc4 = new byte[4];
  614 +
  615 + while (raw.hasRemaining()) {
  616 + int rem = Math.min(3, raw.remaining());
  617 + raw.get(raw3, 0, rem);
  618 + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS);
  619 + for (int i = 0; i < 4; i++) {
  620 + encoded.put((char) (enc4[i] & 0xFF));
  621 + }
  622 + } // end input remaining
  623 + }
  624 +
  625 + /**
  626 + * Serializes an object and returns the Base64-encoded version of that
  627 + * serialized object.
  628 + *
  629 + * <p>
  630 + * As of v 2.3, if the object cannot be serialized or there is another
  631 + * error, the method will throw an java.io.IOException. <b>This is new to
  632 + * v2.3!</b> In earlier versions, it just returned a null value, but in
  633 + * retrospect that's a pretty poor way to handle it.
  634 + * </p>
  635 + *
  636 + * The object is not GZip-compressed before being encoded.
  637 + *
  638 + * @param serializableObject
  639 + * The object to encode
  640 + * @return The Base64-encoded object
  641 + * @throws java.io.IOException
  642 + * if there is an error
  643 + * @throws NullPointerException
  644 + * if serializedObject is null
  645 + * @since 1.4
  646 + */
  647 + public static String encodeObject(java.io.Serializable serializableObject)
  648 + throws java.io.IOException {
  649 + return encodeObject(serializableObject, NO_OPTIONS);
  650 + } // end encodeObject
  651 +
  652 + /**
  653 + * Serializes an object and returns the Base64-encoded version of that
  654 + * serialized object.
  655 + *
  656 + * <p>
  657 + * As of v 2.3, if the object cannot be serialized or there is another
  658 + * error, the method will throw an java.io.IOException. <b>This is new to
  659 + * v2.3!</b> In earlier versions, it just returned a null value, but in
  660 + * retrospect that's a pretty poor way to handle it.
  661 + * </p>
  662 + *
  663 + * The object is not GZip-compressed before being encoded.
  664 + * <p>
  665 + * Example options:
  666 + *
  667 + * <pre>
  668 + * GZIP: gzip-compresses object before encoding it.
  669 + * DO_BREAK_LINES: break lines at 76 characters
  670 + * </pre>
  671 + * <p>
  672 + * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
  673 + * <p>
  674 + * Example:
  675 + * <code>encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
  676 + *
  677 + * @param serializableObject
  678 + * The object to encode
  679 + * @param options
  680 + * Specified options
  681 + * @return The Base64-encoded object
  682 + * @see Base64#GZIP
  683 + * @see Base64#DO_BREAK_LINES
  684 + * @throws java.io.IOException
  685 + * if there is an error
  686 + * @since 2.0
  687 + */
  688 + public static String encodeObject(java.io.Serializable serializableObject,
  689 + int options) throws java.io.IOException {
  690 +
  691 + if (serializableObject == null) {
  692 + throw new NullPointerException("Cannot serialize a null object.");
  693 + } // end if: null
  694 +
  695 + // Streams
  696 + java.io.ByteArrayOutputStream baos = null;
  697 + java.io.OutputStream b64os = null;
  698 + java.util.zip.GZIPOutputStream gzos = null;
  699 + java.io.ObjectOutputStream oos = null;
  700 +
  701 + try {
  702 + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
  703 + baos = new java.io.ByteArrayOutputStream();
  704 + b64os = new Base64.OutputStream(baos, ENCODE | options);
  705 + if ((options & GZIP) != 0) {
  706 + // Gzip
  707 + gzos = new java.util.zip.GZIPOutputStream(b64os);
  708 + oos = new java.io.ObjectOutputStream(gzos);
  709 + } else {
  710 + // Not gzipped
  711 + oos = new java.io.ObjectOutputStream(b64os);
  712 + }
  713 + oos.writeObject(serializableObject);
  714 + } // end try
  715 + catch (java.io.IOException e) {
  716 + // Catch it and then throw it immediately so that
  717 + // the finally{} block is called for cleanup.
  718 + throw e;
  719 + } // end catch
  720 + finally {
  721 + try {
  722 + oos.close();
  723 + } catch (Exception e) {
  724 + }
  725 + try {
  726 + gzos.close();
  727 + } catch (Exception e) {
  728 + }
  729 + try {
  730 + b64os.close();
  731 + } catch (Exception e) {
  732 + }
  733 + try {
  734 + baos.close();
  735 + } catch (Exception e) {
  736 + }
  737 + } // end finally
  738 +
  739 + // Return value according to relevant encoding.
  740 + try {
  741 + return new String(baos.toByteArray(), PREFERRED_ENCODING);
  742 + } // end try
  743 + catch (java.io.UnsupportedEncodingException uue) {
  744 + // Fall back to some Java default
  745 + return new String(baos.toByteArray());
  746 + } // end catch
  747 +
  748 + } // end encode
  749 +
  750 + /**
  751 + * Encodes a byte array into Base64 notation. Does not GZip-compress data.
  752 + *
  753 + * @param source
  754 + * The data to convert
  755 + * @return The data in Base64-encoded form
  756 + * @throws NullPointerException
  757 + * if source array is null
  758 + * @since 1.4
  759 + */
  760 + public static String encodeBytes(byte[] source) {
  761 + // Since we're not going to have the GZIP encoding turned on,
  762 + // we're not going to have an java.io.IOException thrown, so
  763 + // we should not force the user to have to catch it.
  764 + String encoded = null;
  765 + try {
  766 + encoded = encodeBytes(source, 0, source.length, NO_OPTIONS);
  767 + } catch (java.io.IOException ex) {
  768 + assert false : ex.getMessage();
  769 + } // end catch
  770 + assert encoded != null;
  771 + return encoded;
  772 + } // end encodeBytes
  773 +
  774 + /**
  775 + * Encodes a byte array into Base64 notation.
  776 + * <p>
  777 + * Example options:
  778 + *
  779 + * <pre>
  780 + * GZIP: gzip-compresses object before encoding it.
  781 + * DO_BREAK_LINES: break lines at 76 characters
  782 + * <i>Note: Technically, this makes your encoding non-compliant.</i>
  783 + * </pre>
  784 + * <p>
  785 + * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
  786 + * <p>
  787 + * Example:
  788 + * <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
  789 + *
  790 + *
  791 + * <p>
  792 + * As of v 2.3, if there is an error with the GZIP stream, the method will
  793 + * throw an java.io.IOException. <b>This is new to v2.3!</b> In earlier
  794 + * versions, it just returned a null value, but in retrospect that's a
  795 + * pretty poor way to handle it.
  796 + * </p>
  797 + *
  798 + *
  799 + * @param source
  800 + * The data to convert
  801 + * @param options
  802 + * Specified options
  803 + * @return The Base64-encoded data as a String
  804 + * @see Base64#GZIP
  805 + * @see Base64#DO_BREAK_LINES
  806 + * @throws java.io.IOException
  807 + * if there is an error
  808 + * @throws NullPointerException
  809 + * if source array is null
  810 + * @since 2.0
  811 + */
  812 + public static String encodeBytes(byte[] source, int options)
  813 + throws java.io.IOException {
  814 + return encodeBytes(source, 0, source.length, options);
  815 + } // end encodeBytes
  816 +
  817 + /**
  818 + * Encodes a byte array into Base64 notation. Does not GZip-compress data.
  819 + *
  820 + * <p>
  821 + * As of v 2.3, if there is an error, the method will throw an
  822 + * java.io.IOException. <b>This is new to v2.3!</b> In earlier versions, it
  823 + * just returned a null value, but in retrospect that's a pretty poor way to
  824 + * handle it.
  825 + * </p>
  826 + *
  827 + *
  828 + * @param source
  829 + * The data to convert
  830 + * @param off
  831 + * Offset in array where conversion should begin
  832 + * @param len
  833 + * Length of data to convert
  834 + * @return The Base64-encoded data as a String
  835 + * @throws NullPointerException
  836 + * if source array is null
  837 + * @throws IllegalArgumentException
  838 + * if source array, offset, or length are invalid
  839 + * @since 1.4
  840 + */
  841 + public static String encodeBytes(byte[] source, int off, int len) {
  842 + // Since we're not going to have the GZIP encoding turned on,
  843 + // we're not going to have an java.io.IOException thrown, so
  844 + // we should not force the user to have to catch it.
  845 + String encoded = null;
  846 + try {
  847 + encoded = encodeBytes(source, off, len, NO_OPTIONS);
  848 + } catch (java.io.IOException ex) {
  849 + assert false : ex.getMessage();
  850 + } // end catch
  851 + assert encoded != null;
  852 + return encoded;
  853 + } // end encodeBytes
  854 +
  855 + /**
  856 + * Encodes a byte array into Base64 notation.
  857 + * <p>
  858 + * Example options:
  859 + *
  860 + * <pre>
  861 + * GZIP: gzip-compresses object before encoding it.
  862 + * DO_BREAK_LINES: break lines at 76 characters
  863 + * <i>Note: Technically, this makes your encoding non-compliant.</i>
  864 + * </pre>
  865 + * <p>
  866 + * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
  867 + * <p>
  868 + * Example:
  869 + * <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
  870 + *
  871 + *
  872 + * <p>
  873 + * As of v 2.3, if there is an error with the GZIP stream, the method will
  874 + * throw an java.io.IOException. <b>This is new to v2.3!</b> In earlier
  875 + * versions, it just returned a null value, but in retrospect that's a
  876 + * pretty poor way to handle it.
  877 + * </p>
  878 + *
  879 + *
  880 + * @param source
  881 + * The data to convert
  882 + * @param off
  883 + * Offset in array where conversion should begin
  884 + * @param len
  885 + * Length of data to convert
  886 + * @param options
  887 + * Specified options
  888 + * @return The Base64-encoded data as a String
  889 + * @see Base64#GZIP
  890 + * @see Base64#DO_BREAK_LINES
  891 + * @throws java.io.IOException
  892 + * if there is an error
  893 + * @throws NullPointerException
  894 + * if source array is null
  895 + * @throws IllegalArgumentException
  896 + * if source array, offset, or length are invalid
  897 + * @since 2.0
  898 + */
  899 + public static String encodeBytes(byte[] source, int off, int len,
  900 + int options) throws java.io.IOException {
  901 + byte[] encoded = encodeBytesToBytes(source, off, len, options);
  902 +
  903 + // Return value according to relevant encoding.
  904 + try {
  905 + return new String(encoded, PREFERRED_ENCODING);
  906 + } // end try
  907 + catch (java.io.UnsupportedEncodingException uue) {
  908 + return new String(encoded);
  909 + } // end catch
  910 +
  911 + } // end encodeBytes
  912 +
  913 + /**
  914 + * Similar to {@link #encodeBytes(byte[])} but returns a byte array instead
  915 + * of instantiating a String. This is more efficient if you're working with
  916 + * I/O streams and have large data sets to encode.
  917 + *
  918 + *
  919 + * @param source
  920 + * The data to convert
  921 + * @return The Base64-encoded data as a byte[] (of ASCII characters)
  922 + * @throws NullPointerException
  923 + * if source array is null
  924 + * @since 2.3.1
  925 + */
  926 + public static byte[] encodeBytesToBytes(byte[] source) {
  927 + byte[] encoded = null;
  928 + try {
  929 + encoded = encodeBytesToBytes(source, 0, source.length,
  930 + Base64.NO_OPTIONS);
  931 + } catch (java.io.IOException ex) {
  932 + assert false : "IOExceptions only come from GZipping, which is turned off: "
  933 + + ex.getMessage();
  934 + }
  935 + return encoded;
  936 + }
  937 +
  938 + /**
  939 + * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns a byte
  940 + * array instead of instantiating a String. This is more efficient if you're
  941 + * working with I/O streams and have large data sets to encode.
  942 + *
  943 + *
  944 + * @param source
  945 + * The data to convert
  946 + * @param off
  947 + * Offset in array where conversion should begin
  948 + * @param len
  949 + * Length of data to convert
  950 + * @param options
  951 + * Specified options
  952 + * @return The Base64-encoded data as a String
  953 + * @see Base64#GZIP
  954 + * @see Base64#DO_BREAK_LINES
  955 + * @throws java.io.IOException
  956 + * if there is an error
  957 + * @throws NullPointerException
  958 + * if source array is null
  959 + * @throws IllegalArgumentException
  960 + * if source array, offset, or length are invalid
  961 + * @since 2.3.1
  962 + */
  963 + public static byte[] encodeBytesToBytes(byte[] source, int off, int len,
  964 + int options) throws java.io.IOException {
  965 +
  966 + if (source == null) {
  967 + throw new NullPointerException("Cannot serialize a null array.");
  968 + } // end if: null
  969 +
  970 + if (off < 0) {
  971 + throw new IllegalArgumentException("Cannot have negative offset: "
  972 + + off);
  973 + } // end if: off < 0
  974 +
  975 + if (len < 0) {
  976 + throw new IllegalArgumentException("Cannot have length offset: "
  977 + + len);
  978 + } // end if: len < 0
  979 +
  980 + if (off + len > source.length) {
  981 + throw new IllegalArgumentException(
  982 + String.format(
  983 + "Cannot have offset of %d and length of %d with array of length %d",
  984 + off, len, source.length));
  985 + } // end if: off < 0
  986 +
  987 + // Compress?
  988 + if ((options & GZIP) != 0) {
  989 + java.io.ByteArrayOutputStream baos = null;
  990 + java.util.zip.GZIPOutputStream gzos = null;
  991 + Base64.OutputStream b64os = null;
  992 +
  993 + try {
  994 + // GZip -> Base64 -> ByteArray
  995 + baos = new java.io.ByteArrayOutputStream();
  996 + b64os = new Base64.OutputStream(baos, ENCODE | options);
  997 + gzos = new java.util.zip.GZIPOutputStream(b64os);
  998 +
  999 + gzos.write(source, off, len);
  1000 + gzos.close();
  1001 + } // end try
  1002 + catch (java.io.IOException e) {
  1003 + // Catch it and then throw it immediately so that
  1004 + // the finally{} block is called for cleanup.
  1005 + throw e;
  1006 + } // end catch
  1007 + finally {
  1008 + try {
  1009 + gzos.close();
  1010 + } catch (Exception e) {
  1011 + }
  1012 + try {
  1013 + b64os.close();
  1014 + } catch (Exception e) {
  1015 + }
  1016 + try {
  1017 + baos.close();
  1018 + } catch (Exception e) {
  1019 + }
  1020 + } // end finally
  1021 +
  1022 + return baos.toByteArray();
  1023 + } // end if: compress
  1024 +
  1025 + // Else, don't compress. Better not to use streams at all then.
  1026 + else {
  1027 + boolean breakLines = (options & DO_BREAK_LINES) > 0;
  1028 +
  1029 + // int len43 = len * 4 / 3;
  1030 + // byte[] outBuff = new byte[ ( len43 ) // Main 4:3
  1031 + // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
  1032 + // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
  1033 + // Try to determine more precisely how big the array needs to be.
  1034 + // If we get it right, we don't have to do an array copy, and
  1035 + // we save a bunch of memory.
  1036 + int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); // Bytes needed
  1037 + // for actual
  1038 + // encoding
  1039 + if (breakLines) {
  1040 + encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline
  1041 + // characters
  1042 + }
  1043 + byte[] outBuff = new byte[encLen];
  1044 +
  1045 + int d = 0;
  1046 + int e = 0;
  1047 + int len2 = len - 2;
  1048 + int lineLength = 0;
  1049 + for (; d < len2; d += 3, e += 4) {
  1050 + encode3to4(source, d + off, 3, outBuff, e, options);
  1051 +
  1052 + lineLength += 4;
  1053 + if (breakLines && lineLength >= MAX_LINE_LENGTH) {
  1054 + outBuff[e + 4] = NEW_LINE;
  1055 + e++;
  1056 + lineLength = 0;
  1057 + } // end if: end of line
  1058 + } // en dfor: each piece of array
  1059 +
  1060 + if (d < len) {
  1061 + encode3to4(source, d + off, len - d, outBuff, e, options);
  1062 + e += 4;
  1063 + } // end if: some padding needed
  1064 +
  1065 + // Only resize array if we didn't guess it right.
  1066 + if (e < outBuff.length - 1) {
  1067 + byte[] finalOut = new byte[e];
  1068 + System.arraycopy(outBuff, 0, finalOut, 0, e);
  1069 + // System.err.println("Having to resize array from " +
  1070 + // outBuff.length + " to " + e );
  1071 + return finalOut;
  1072 + } else {
  1073 + // System.err.println("No need to resize array.");
  1074 + return outBuff;
  1075 + }
  1076 +
  1077 + } // end else: don't compress
  1078 +
  1079 + } // end encodeBytesToBytes
  1080 +
  1081 + /* ******** D E C O D I N G M E T H O D S ******** */
  1082 +
  1083 + /**
  1084 + * Decodes four bytes from array <var>source</var> and writes the resulting
  1085 + * bytes (up to three of them) to <var>destination</var>. The source and
  1086 + * destination arrays can be manipulated anywhere along their length by
  1087 + * specifying <var>srcOffset</var> and <var>destOffset</var>. This method
  1088 + * does not check to make sure your arrays are large enough to accomodate
  1089 + * <var>srcOffset</var> + 4 for the <var>source</var> array or
  1090 + * <var>destOffset</var> + 3 for the <var>destination</var> array. This
  1091 + * method returns the actual number of bytes that were converted from the
  1092 + * Base64 encoding.
  1093 + * <p>
  1094 + * This is the lowest level of the decoding methods with all possible
  1095 + * parameters.
  1096 + * </p>
  1097 + *
  1098 + *
  1099 + * @param source
  1100 + * the array to convert
  1101 + * @param srcOffset
  1102 + * the index where conversion begins
  1103 + * @param destination
  1104 + * the array to hold the conversion
  1105 + * @param destOffset
  1106 + * the index where output will be put
  1107 + * @param options
  1108 + * alphabet type is pulled from this (standard, url-safe,
  1109 + * ordered)
  1110 + * @return the number of decoded bytes converted
  1111 + * @throws NullPointerException
  1112 + * if source or destination arrays are null
  1113 + * @throws IllegalArgumentException
  1114 + * if srcOffset or destOffset are invalid or there is not enough
  1115 + * room in the array.
  1116 + * @since 1.3
  1117 + */
  1118 + private static int decode4to3(byte[] source, int srcOffset,
  1119 + byte[] destination, int destOffset, int options) {
  1120 +
  1121 + // Lots of error checking and exception throwing
  1122 + if (source == null) {
  1123 + throw new NullPointerException("Source array was null.");
  1124 + } // end if
  1125 + if (destination == null) {
  1126 + throw new NullPointerException("Destination array was null.");
  1127 + } // end if
  1128 + if (srcOffset < 0 || srcOffset + 3 >= source.length) {
  1129 + throw new IllegalArgumentException(
  1130 + String.format(
  1131 + "Source array with length %d cannot have offset of %d and still process four bytes.",
  1132 + source.length, srcOffset));
  1133 + } // end if
  1134 + if (destOffset < 0 || destOffset + 2 >= destination.length) {
  1135 + throw new IllegalArgumentException(
  1136 + String.format(
  1137 + "Destination array with length %d cannot have offset of %d and still store three bytes.",
  1138 + destination.length, destOffset));
  1139 + } // end if
  1140 +
  1141 + byte[] DECODABET = getDecodabet(options);
  1142 +
  1143 + // Example: Dk==
  1144 + if (source[srcOffset + 2] == EQUALS_SIGN) {
  1145 + // Two ways to do the same thing. Don't know which way I like best.
  1146 + // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6
  1147 + // )
  1148 + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
  1149 + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
  1150 + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12);
  1151 +
  1152 + destination[destOffset] = (byte) (outBuff >>> 16);
  1153 + return 1;
  1154 + }
  1155 +
  1156 + // Example: DkL=
  1157 + else if (source[srcOffset + 3] == EQUALS_SIGN) {
  1158 + // Two ways to do the same thing. Don't know which way I like best.
  1159 + // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6
  1160 + // )
  1161 + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
  1162 + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
  1163 + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
  1164 + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
  1165 + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6);
  1166 +
  1167 + destination[destOffset] = (byte) (outBuff >>> 16);
  1168 + destination[destOffset + 1] = (byte) (outBuff >>> 8);
  1169 + return 2;
  1170 + }
  1171 +
  1172 + // Example: DkLE
  1173 + else {
  1174 + // Two ways to do the same thing. Don't know which way I like best.
  1175 + // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6
  1176 + // )
  1177 + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
  1178 + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
  1179 + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
  1180 + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
  1181 + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
  1182 + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6)
  1183 + | ((DECODABET[source[srcOffset + 3]] & 0xFF));
  1184 +
  1185 + destination[destOffset] = (byte) (outBuff >> 16);
  1186 + destination[destOffset + 1] = (byte) (outBuff >> 8);
  1187 + destination[destOffset + 2] = (byte) (outBuff);
  1188 +
  1189 + return 3;
  1190 + }
  1191 + } // end decodeToBytes
  1192 +
  1193 + /**
  1194 + * Low-level access to decoding ASCII characters in the form of a byte
  1195 + * array. <strong>Ignores GUNZIP option, if it's set.</strong> This is not
  1196 + * generally a recommended method, although it is used internally as part of
  1197 + * the decoding process. Special case: if len = 0, an empty array is
  1198 + * returned. Still, if you need more speed and reduced memory footprint (and
  1199 + * aren't gzipping), consider this method.
  1200 + *
  1201 + * @param source
  1202 + * The Base64 encoded data
  1203 + * @return decoded data
  1204 + * @since 2.3.1
  1205 + */
  1206 + public static byte[] decode(byte[] source) {
  1207 + byte[] decoded = null;
  1208 + try {
  1209 + decoded = decode(source, 0, source.length, Base64.NO_OPTIONS);
  1210 + } catch (java.io.IOException ex) {
  1211 + assert false : "IOExceptions only come from GZipping, which is turned off: "
  1212 + + ex.getMessage();
  1213 + }
  1214 + return decoded;
  1215 + }
  1216 +
  1217 + /**
  1218 + * Low-level access to decoding ASCII characters in the form of a byte
  1219 + * array. <strong>Ignores GUNZIP option, if it's set.</strong> This is not
  1220 + * generally a recommended method, although it is used internally as part of
  1221 + * the decoding process. Special case: if len = 0, an empty array is
  1222 + * returned. Still, if you need more speed and reduced memory footprint (and
  1223 + * aren't gzipping), consider this method.
  1224 + *
  1225 + * @param source
  1226 + * The Base64 encoded data
  1227 + * @param off
  1228 + * The offset of where to begin decoding
  1229 + * @param len
  1230 + * The length of characters to decode
  1231 + * @param options
  1232 + * Can specify options such as alphabet type to use
  1233 + * @return decoded data
  1234 + * @throws java.io.IOException
  1235 + * If bogus characters exist in source data
  1236 + * @since 1.3
  1237 + */
  1238 + public static byte[] decode(byte[] source, int off, int len, int options)
  1239 + throws java.io.IOException {
  1240 +
  1241 + // Lots of error checking and exception throwing
  1242 + if (source == null) {
  1243 + throw new NullPointerException("Cannot decode null source array.");
  1244 + } // end if
  1245 + if (off < 0 || off + len > source.length) {
  1246 + throw new IllegalArgumentException(
  1247 + String.format(
  1248 + "Source array with length %d cannot have offset of %d and process %d bytes.",
  1249 + source.length, off, len));
  1250 + } // end if
  1251 +
  1252 + if (len == 0) {
  1253 + return new byte[0];
  1254 + } else if (len < 4) {
  1255 + throw new IllegalArgumentException(
  1256 + "Base64-encoded string must have at least four characters, but length specified was "
  1257 + + len);
  1258 + } // end if
  1259 +
  1260 + byte[] DECODABET = getDecodabet(options);
  1261 +
  1262 + int len34 = len * 3 / 4; // Estimate on array size
  1263 + byte[] outBuff = new byte[len34]; // Upper limit on size of output
  1264 + int outBuffPosn = 0; // Keep track of where we're writing
  1265 +
  1266 + byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating
  1267 + // white space
  1268 + int b4Posn = 0; // Keep track of four byte input buffer
  1269 + int i = 0; // Source array counter
  1270 + byte sbiCrop = 0; // Low seven bits (ASCII) of input
  1271 + byte sbiDecode = 0; // Special value from DECODABET
  1272 +
  1273 + for (i = off; i < off + len; i++) { // Loop through source
  1274 +
  1275 + sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits
  1276 + sbiDecode = DECODABET[sbiCrop]; // Special value
  1277 +
  1278 + // White space, Equals sign, or legit Base64 character
  1279 + // Note the values such as -5 and -9 in the
  1280 + // DECODABETs at the top of the file.
  1281 + if (sbiDecode >= WHITE_SPACE_ENC) {
  1282 + if (sbiDecode >= EQUALS_SIGN_ENC) {
  1283 + b4[b4Posn++] = sbiCrop; // Save non-whitespace
  1284 + if (b4Posn > 3) { // Time to decode?
  1285 + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn,
  1286 + options);
  1287 + b4Posn = 0;
  1288 +
  1289 + // If that was the equals sign, break out of 'for' loop
  1290 + if (sbiCrop == EQUALS_SIGN) {
  1291 + break;
  1292 + } // end if: equals sign
  1293 + } // end if: quartet built
  1294 + } // end if: equals sign or better
  1295 + } // end if: white space, equals sign or better
  1296 + else {
  1297 + // There's a bad input character in the Base64 stream.
  1298 + throw new java.io.IOException(String.format(
  1299 + "Bad Base64 input character '%c' in array position %d",
  1300 + source[i], i));
  1301 + } // end else:
  1302 + } // each input character
  1303 +
  1304 + byte[] out = new byte[outBuffPosn];
  1305 + System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
  1306 + return out;
  1307 + } // end decode
  1308 +
  1309 + /**
  1310 + * Decodes data from Base64 notation, automatically detecting
  1311 + * gzip-compressed data and decompressing it.
  1312 + *
  1313 + * @param s
  1314 + * the string to decode
  1315 + * @return the decoded data
  1316 + * @throws java.io.IOException
  1317 + * If there is a problem
  1318 + * @since 1.4
  1319 + */
  1320 + public static byte[] decode(String s) throws java.io.IOException {
  1321 + return decode(s, NO_OPTIONS);
  1322 + }
  1323 +
  1324 + /**
  1325 + * Decodes data from Base64 notation, automatically detecting
  1326 + * gzip-compressed data and decompressing it.
  1327 + *
  1328 + * @param s
  1329 + * the string to decode
  1330 + * @param options
  1331 + * encode options such as URL_SAFE
  1332 + * @return the decoded data
  1333 + * @throws java.io.IOException
  1334 + * if there is an error
  1335 + * @throws NullPointerException
  1336 + * if <tt>s</tt> is null
  1337 + * @since 1.4
  1338 + */
  1339 + public static byte[] decode(String s, int options)
  1340 + throws java.io.IOException {
  1341 +
  1342 + if (s == null) {
  1343 + throw new NullPointerException("Input string was null.");
  1344 + } // end if
  1345 +
  1346 + byte[] bytes;
  1347 + try {
  1348 + bytes = s.getBytes(PREFERRED_ENCODING);
  1349 + } // end try
  1350 + catch (java.io.UnsupportedEncodingException uee) {
  1351 + bytes = s.getBytes();
  1352 + } // end catch
  1353 + // </change>
  1354 +
  1355 + // Decode
  1356 + bytes = decode(bytes, 0, bytes.length, options);
  1357 +
  1358 + // Check to see if it's gzip-compressed
  1359 + // GZIP Magic Two-Byte Number: 0x8b1f (35615)
  1360 + boolean dontGunzip = (options & DONT_GUNZIP) != 0;
  1361 + if ((bytes != null) && (bytes.length >= 4) && (!dontGunzip)) {
  1362 +
  1363 + int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
  1364 + if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) {
  1365 + java.io.ByteArrayInputStream bais = null;
  1366 + java.util.zip.GZIPInputStream gzis = null;
  1367 + java.io.ByteArrayOutputStream baos = null;
  1368 + byte[] buffer = new byte[2048];
  1369 + int length = 0;
  1370 +
  1371 + try {
  1372 + baos = new java.io.ByteArrayOutputStream();
  1373 + bais = new java.io.ByteArrayInputStream(bytes);
  1374 + gzis = new java.util.zip.GZIPInputStream(bais);
  1375 +
  1376 + while ((length = gzis.read(buffer)) >= 0) {
  1377 + baos.write(buffer, 0, length);
  1378 + } // end while: reading input
  1379 +
  1380 + // No error? Get new bytes.
  1381 + bytes = baos.toByteArray();
  1382 +
  1383 + } // end try
  1384 + catch (java.io.IOException e) {
  1385 + e.printStackTrace();
  1386 + // Just return originally-decoded bytes
  1387 + } // end catch
  1388 + finally {
  1389 + try {
  1390 + baos.close();
  1391 + } catch (Exception e) {
  1392 + }
  1393 + try {
  1394 + gzis.close();
  1395 + } catch (Exception e) {
  1396 + }
  1397 + try {
  1398 + bais.close();
  1399 + } catch (Exception e) {
  1400 + }
  1401 + } // end finally
  1402 +
  1403 + } // end if: gzipped
  1404 + } // end if: bytes.length >= 2
  1405 +
  1406 + return bytes;
  1407 + } // end decode
  1408 +
  1409 + /**
  1410 + * Attempts to decode Base64 data and deserialize a Java Object within.
  1411 + * Returns <tt>null</tt> if there was an error.
  1412 + *
  1413 + * @param encodedObject
  1414 + * The Base64 data to decode
  1415 + * @return The decoded and deserialized object
  1416 + * @throws NullPointerException
  1417 + * if encodedObject is null
  1418 + * @throws java.io.IOException
  1419 + * if there is a general error
  1420 + * @throws ClassNotFoundException
  1421 + * if the decoded object is of a class that cannot be found by
  1422 + * the JVM
  1423 + * @since 1.5
  1424 + */
  1425 + public static Object decodeToObject(String encodedObject)
  1426 + throws java.io.IOException, java.lang.ClassNotFoundException {
  1427 + return decodeToObject(encodedObject, NO_OPTIONS, null);
  1428 + }
  1429 +
  1430 + /**
  1431 + * Attempts to decode Base64 data and deserialize a Java Object within.
  1432 + * Returns <tt>null</tt> if there was an error. If <tt>loader</tt> is not
  1433 + * null, it will be the class loader used when deserializing.
  1434 + *
  1435 + * @param encodedObject
  1436 + * The Base64 data to decode
  1437 + * @param options
  1438 + * Various parameters related to decoding
  1439 + * @param loader
  1440 + * Optional class loader to use in deserializing classes.
  1441 + * @return The decoded and deserialized object
  1442 + * @throws NullPointerException
  1443 + * if encodedObject is null
  1444 + * @throws java.io.IOException
  1445 + * if there is a general error
  1446 + * @throws ClassNotFoundException
  1447 + * if the decoded object is of a class that cannot be found by
  1448 + * the JVM
  1449 + * @since 2.3.4
  1450 + */
  1451 + public static Object decodeToObject(String encodedObject, int options,
  1452 + final ClassLoader loader) throws java.io.IOException,
  1453 + java.lang.ClassNotFoundException {
  1454 +
  1455 + // Decode and gunzip if necessary
  1456 + byte[] objBytes = decode(encodedObject, options);
  1457 +
  1458 + java.io.ByteArrayInputStream bais = null;
  1459 + java.io.ObjectInputStream ois = null;
  1460 + Object obj = null;
  1461 +
  1462 + try {
  1463 + bais = new java.io.ByteArrayInputStream(objBytes);
  1464 +
  1465 + // If no custom class loader is provided, use Java's builtin OIS.
  1466 + if (loader == null) {
  1467 + ois = new java.io.ObjectInputStream(bais);
  1468 + } // end if: no loader provided
  1469 +
  1470 + // Else make a customized object input stream that uses
  1471 + // the provided class loader.
  1472 + else {
  1473 + ois = new java.io.ObjectInputStream(bais) {
  1474 + @Override
  1475 + public Class<?> resolveClass(
  1476 + java.io.ObjectStreamClass streamClass)
  1477 + throws java.io.IOException, ClassNotFoundException {
  1478 + Class c = Class.forName(streamClass.getName(), false,
  1479 + loader);
  1480 + if (c == null) {
  1481 + return super.resolveClass(streamClass);
  1482 + } else {
  1483 + return c; // Class loader knows of this class.
  1484 + } // end else: not null
  1485 + } // end resolveClass
  1486 + }; // end ois
  1487 + } // end else: no custom class loader
  1488 +
  1489 + obj = ois.readObject();
  1490 + } // end try
  1491 + catch (java.io.IOException e) {
  1492 + throw e; // Catch and throw in order to execute finally{}
  1493 + } // end catch
  1494 + catch (java.lang.ClassNotFoundException e) {
  1495 + throw e; // Catch and throw in order to execute finally{}
  1496 + } // end catch
  1497 + finally {
  1498 + try {
  1499 + bais.close();
  1500 + } catch (Exception e) {
  1501 + }
  1502 + try {
  1503 + ois.close();
  1504 + } catch (Exception e) {
  1505 + }
  1506 + } // end finally
  1507 +
  1508 + return obj;
  1509 + } // end decodeObject
  1510 +
  1511 + /**
  1512 + * Convenience method for encoding data to a file.
  1513 + *
  1514 + * <p>
  1515 + * As of v 2.3, if there is a error, the method will throw an
  1516 + * java.io.IOException. <b>This is new to v2.3!</b> In earlier versions, it
  1517 + * just returned false, but in retrospect that's a pretty poor way to handle
  1518 + * it.
  1519 + * </p>
  1520 + *
  1521 + * @param dataToEncode
  1522 + * byte array of data to encode in base64 form
  1523 + * @param filename
  1524 + * Filename for saving encoded data
  1525 + * @throws java.io.IOException
  1526 + * if there is an error
  1527 + * @throws NullPointerException
  1528 + * if dataToEncode is null
  1529 + * @since 2.1
  1530 + */
  1531 + public static void encodeToFile(byte[] dataToEncode, String filename)
  1532 + throws java.io.IOException {
  1533 +
  1534 + if (dataToEncode == null) {
  1535 + throw new NullPointerException("Data to encode was null.");
  1536 + } // end iff
  1537 +
  1538 + Base64.OutputStream bos = null;
  1539 + try {
  1540 + bos = new Base64.OutputStream(
  1541 + new java.io.FileOutputStream(filename), Base64.ENCODE);
  1542 + bos.write(dataToEncode);
  1543 + } // end try
  1544 + catch (java.io.IOException e) {
  1545 + throw e; // Catch and throw to execute finally{} block
  1546 + } // end catch: java.io.IOException
  1547 + finally {
  1548 + try {
  1549 + bos.close();
  1550 + } catch (Exception e) {
  1551 + }
  1552 + } // end finally
  1553 +
  1554 + } // end encodeToFile
  1555 +
  1556 + /**
  1557 + * Convenience method for decoding data to a file.
  1558 + *
  1559 + * <p>
  1560 + * As of v 2.3, if there is a error, the method will throw an
  1561 + * java.io.IOException. <b>This is new to v2.3!</b> In earlier versions, it
  1562 + * just returned false, but in retrospect that's a pretty poor way to handle
  1563 + * it.
  1564 + * </p>
  1565 + *
  1566 + * @param dataToDecode
  1567 + * Base64-encoded data as a string
  1568 + * @param filename
  1569 + * Filename for saving decoded data
  1570 + * @throws java.io.IOException
  1571 + * if there is an error
  1572 + * @since 2.1
  1573 + */
  1574 + public static void decodeToFile(String dataToDecode, String filename)
  1575 + throws java.io.IOException {
  1576 +
  1577 + Base64.OutputStream bos = null;
  1578 + try {
  1579 + bos = new Base64.OutputStream(
  1580 + new java.io.FileOutputStream(filename), Base64.DECODE);
  1581 + bos.write(dataToDecode.getBytes(PREFERRED_ENCODING));
  1582 + } // end try
  1583 + catch (java.io.IOException e) {
  1584 + throw e; // Catch and throw to execute finally{} block
  1585 + } // end catch: java.io.IOException
  1586 + finally {
  1587 + try {
  1588 + bos.close();
  1589 + } catch (Exception e) {
  1590 + }
  1591 + } // end finally
  1592 +
  1593 + } // end decodeToFile
  1594 +
  1595 + /**
  1596 + * Convenience method for reading a base64-encoded file and decoding it.
  1597 + *
  1598 + * <p>
  1599 + * As of v 2.3, if there is a error, the method will throw an
  1600 + * java.io.IOException. <b>This is new to v2.3!</b> In earlier versions, it
  1601 + * just returned false, but in retrospect that's a pretty poor way to handle
  1602 + * it.
  1603 + * </p>
  1604 + *
  1605 + * @param filename
  1606 + * Filename for reading encoded data
  1607 + * @return decoded byte array
  1608 + * @throws java.io.IOException
  1609 + * if there is an error
  1610 + * @since 2.1
  1611 + */
  1612 + public static byte[] decodeFromFile(String filename)
  1613 + throws java.io.IOException {
  1614 +
  1615 + byte[] decodedData = null;
  1616 + Base64.InputStream bis = null;
  1617 + try {
  1618 + // Set up some useful variables
  1619 + java.io.File file = new java.io.File(filename);
  1620 + byte[] buffer = null;
  1621 + int length = 0;
  1622 + int numBytes = 0;
  1623 +
  1624 + // Check for size of file
  1625 + if (file.length() > Integer.MAX_VALUE) {
  1626 + throw new java.io.IOException(
  1627 + "File is too big for this convenience method ("
  1628 + + file.length() + " bytes).");
  1629 + } // end if: file too big for int index
  1630 + buffer = new byte[(int) file.length()];
  1631 +
  1632 + // Open a stream
  1633 + bis = new Base64.InputStream(new java.io.BufferedInputStream(
  1634 + new java.io.FileInputStream(file)), Base64.DECODE);
  1635 +
  1636 + // Read until done
  1637 + while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
  1638 + length += numBytes;
  1639 + } // end while
  1640 +
  1641 + // Save in a variable to return
  1642 + decodedData = new byte[length];
  1643 + System.arraycopy(buffer, 0, decodedData, 0, length);
  1644 +
  1645 + } // end try
  1646 + catch (java.io.IOException e) {
  1647 + throw e; // Catch and release to execute finally{}
  1648 + } // end catch: java.io.IOException
  1649 + finally {
  1650 + try {
  1651 + bis.close();
  1652 + } catch (Exception e) {
  1653 + }
  1654 + } // end finally
  1655 +
  1656 + return decodedData;
  1657 + } // end decodeFromFile
  1658 +
  1659 + /**
  1660 + * Convenience method for reading a binary file and base64-encoding it.
  1661 + *
  1662 + * <p>
  1663 + * As of v 2.3, if there is a error, the method will throw an
  1664 + * java.io.IOException. <b>This is new to v2.3!</b> In earlier versions, it
  1665 + * just returned false, but in retrospect that's a pretty poor way to handle
  1666 + * it.
  1667 + * </p>
  1668 + *
  1669 + * @param filename
  1670 + * Filename for reading binary data
  1671 + * @return base64-encoded string
  1672 + * @throws java.io.IOException
  1673 + * if there is an error
  1674 + * @since 2.1
  1675 + */
  1676 + public static String encodeFromFile(String filename)
  1677 + throws java.io.IOException {
  1678 +
  1679 + String encodedData = null;
  1680 + Base64.InputStream bis = null;
  1681 + try {
  1682 + // Set up some useful variables
  1683 + java.io.File file = new java.io.File(filename);
  1684 + byte[] buffer = new byte[Math.max((int) (file.length() * 1.4), 40)]; // Need
  1685 + // max()
  1686 + // for
  1687 + // math
  1688 + // on
  1689 + // small
  1690 + // files
  1691 + // (v2.2.1)
  1692 + int length = 0;
  1693 + int numBytes = 0;
  1694 +
  1695 + // Open a stream
  1696 + bis = new Base64.InputStream(new java.io.BufferedInputStream(
  1697 + new java.io.FileInputStream(file)), Base64.ENCODE);
  1698 +
  1699 + // Read until done
  1700 + while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
  1701 + length += numBytes;
  1702 + } // end while
  1703 +
  1704 + // Save in a variable to return
  1705 + encodedData = new String(buffer, 0, length,
  1706 + Base64.PREFERRED_ENCODING);
  1707 +
  1708 + } // end try
  1709 + catch (java.io.IOException e) {
  1710 + throw e; // Catch and release to execute finally{}
  1711 + } // end catch: java.io.IOException
  1712 + finally {
  1713 + try {
  1714 + bis.close();
  1715 + } catch (Exception e) {
  1716 + }
  1717 + } // end finally
  1718 +
  1719 + return encodedData;
  1720 + } // end encodeFromFile
  1721 +
  1722 + /**
  1723 + * Reads <tt>infile</tt> and encodes it to <tt>outfile</tt>.
  1724 + *
  1725 + * @param infile
  1726 + * Input file
  1727 + * @param outfile
  1728 + * Output file
  1729 + * @throws java.io.IOException
  1730 + * if there is an error
  1731 + * @since 2.2
  1732 + */
  1733 + public static void encodeFileToFile(String infile, String outfile)
  1734 + throws java.io.IOException {
  1735 +
  1736 + String encoded = Base64.encodeFromFile(infile);
  1737 + java.io.OutputStream out = null;
  1738 + try {
  1739 + out = new java.io.BufferedOutputStream(
  1740 + new java.io.FileOutputStream(outfile));
  1741 + out.write(encoded.getBytes("US-ASCII")); // Strict, 7-bit output.
  1742 + } // end try
  1743 + catch (java.io.IOException e) {
  1744 + throw e; // Catch and release to execute finally{}
  1745 + } // end catch
  1746 + finally {
  1747 + try {
  1748 + out.close();
  1749 + } catch (Exception ex) {
  1750 + }
  1751 + } // end finally
  1752 + } // end encodeFileToFile
  1753 +
  1754 + /**
  1755 + * Reads <tt>infile</tt> and decodes it to <tt>outfile</tt>.
  1756 + *
  1757 + * @param infile
  1758 + * Input file
  1759 + * @param outfile
  1760 + * Output file
  1761 + * @throws java.io.IOException
  1762 + * if there is an error
  1763 + * @since 2.2
  1764 + */
  1765 + public static void decodeFileToFile(String infile, String outfile)
  1766 + throws java.io.IOException {
  1767 +
  1768 + byte[] decoded = Base64.decodeFromFile(infile);
  1769 + java.io.OutputStream out = null;
  1770 + try {
  1771 + out = new java.io.BufferedOutputStream(
  1772 + new java.io.FileOutputStream(outfile));
  1773 + out.write(decoded);
  1774 + } // end try
  1775 + catch (java.io.IOException e) {
  1776 + throw e; // Catch and release to execute finally{}
  1777 + } // end catch
  1778 + finally {
  1779 + try {
  1780 + out.close();
  1781 + } catch (Exception ex) {
  1782 + }
  1783 + } // end finally
  1784 + } // end decodeFileToFile
  1785 +
  1786 + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
  1787 +
  1788 + /**
  1789 + * A {@link Base64.InputStream} will read data from another
  1790 + * <tt>java.io.InputStream</tt>, given in the constructor, and encode/decode
  1791 + * to/from Base64 notation on the fly.
  1792 + *
  1793 + * @see Base64
  1794 + * @since 1.3
  1795 + */
  1796 + public static class InputStream extends java.io.FilterInputStream {
  1797 +
  1798 + private boolean encode; // Encoding or decoding
  1799 + private int position; // Current position in the buffer
  1800 + private byte[] buffer; // Small buffer holding converted data
  1801 + private int bufferLength; // Length of buffer (3 or 4)
  1802 + private int numSigBytes; // Number of meaningful bytes in the buffer
  1803 + private int lineLength;
  1804 + private boolean breakLines; // Break lines at less than 80 characters
  1805 + private int options; // Record options used to create the stream.
  1806 + private byte[] decodabet; // Local copies to avoid extra method calls
  1807 +
  1808 + /**
  1809 + * Constructs a {@link Base64.InputStream} in DECODE mode.
  1810 + *
  1811 + * @param in
  1812 + * the <tt>java.io.InputStream</tt> from which to read data.
  1813 + * @since 1.3
  1814 + */
  1815 + public InputStream(java.io.InputStream in) {
  1816 + this(in, DECODE);
  1817 + } // end constructor
  1818 +
  1819 + /**
  1820 + * Constructs a {@link Base64.InputStream} in either ENCODE or DECODE
  1821 + * mode.
  1822 + * <p>
  1823 + * Valid options:
  1824 + *
  1825 + * <pre>
  1826 + * ENCODE or DECODE: Encode or Decode as data is read.
  1827 + * DO_BREAK_LINES: break lines at 76 characters
  1828 + * (only meaningful when encoding)</i>
  1829 + * </pre>
  1830 + * <p>
  1831 + * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
  1832 + *
  1833 + *
  1834 + * @param in
  1835 + * the <tt>java.io.InputStream</tt> from which to read data.
  1836 + * @param options
  1837 + * Specified options
  1838 + * @see Base64#ENCODE
  1839 + * @see Base64#DECODE
  1840 + * @see Base64#DO_BREAK_LINES
  1841 + * @since 2.0
  1842 + */
  1843 + public InputStream(java.io.InputStream in, int options) {
  1844 +
  1845 + super(in);
  1846 + this.options = options; // Record for later
  1847 + this.breakLines = (options & DO_BREAK_LINES) > 0;
  1848 + this.encode = (options & ENCODE) > 0;
  1849 + this.bufferLength = encode ? 4 : 3;
  1850 + this.buffer = new byte[bufferLength];
  1851 + this.position = -1;
  1852 + this.lineLength = 0;
  1853 + this.decodabet = getDecodabet(options);
  1854 + } // end constructor
  1855 +
  1856 + /**
  1857 + * Reads enough of the input stream to convert to/from Base64 and
  1858 + * returns the next byte.
  1859 + *
  1860 + * @return next byte
  1861 + * @since 1.3
  1862 + */
  1863 + @Override
  1864 + public int read() throws java.io.IOException {
  1865 +
  1866 + // Do we need to get data?
  1867 + if (position < 0) {
  1868 + if (encode) {
  1869 + byte[] b3 = new byte[3];
  1870 + int numBinaryBytes = 0;
  1871 + for (int i = 0; i < 3; i++) {
  1872 + int b = in.read();
  1873 +
  1874 + // If end of stream, b is -1.
  1875 + if (b >= 0) {
  1876 + b3[i] = (byte) b;
  1877 + numBinaryBytes++;
  1878 + } else {
  1879 + break; // out of for loop
  1880 + } // end else: end of stream
  1881 +
  1882 + } // end for: each needed input byte
  1883 +
  1884 + if (numBinaryBytes > 0) {
  1885 + encode3to4(b3, 0, numBinaryBytes, buffer, 0, options);
  1886 + position = 0;
  1887 + numSigBytes = 4;
  1888 + } // end if: got data
  1889 + else {
  1890 + return -1; // Must be end of stream
  1891 + } // end else
  1892 + } // end if: encoding
  1893 +
  1894 + // Else decoding
  1895 + else {
  1896 + byte[] b4 = new byte[4];
  1897 + int i = 0;
  1898 + for (i = 0; i < 4; i++) {
  1899 + // Read four "meaningful" bytes:
  1900 + int b = 0;
  1901 + do {
  1902 + b = in.read();
  1903 + } while (b >= 0
  1904 + && decodabet[b & 0x7f] <= WHITE_SPACE_ENC);
  1905 +
  1906 + if (b < 0) {
  1907 + break; // Reads a -1 if end of stream
  1908 + } // end if: end of stream
  1909 +
  1910 + b4[i] = (byte) b;
  1911 + } // end for: each needed input byte
  1912 +
  1913 + if (i == 4) {
  1914 + numSigBytes = decode4to3(b4, 0, buffer, 0, options);
  1915 + position = 0;
  1916 + } // end if: got four characters
  1917 + else if (i == 0) {
  1918 + return -1;
  1919 + } // end else if: also padded correctly
  1920 + else {
  1921 + // Must have broken out from above.
  1922 + throw new java.io.IOException(
  1923 + "Improperly padded Base64 input.");
  1924 + } // end
  1925 +
  1926 + } // end else: decode
  1927 + } // end else: get data
  1928 +
  1929 + // Got data?
  1930 + if (position >= 0) {
  1931 + // End of relevant data?
  1932 + if ( /* !encode && */position >= numSigBytes) {
  1933 + return -1;
  1934 + } // end if: got data
  1935 +
  1936 + if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) {
  1937 + lineLength = 0;
  1938 + return '\n';
  1939 + } // end if
  1940 + else {
  1941 + lineLength++; // This isn't important when decoding
  1942 + // but throwing an extra "if" seems
  1943 + // just as wasteful.
  1944 +
  1945 + int b = buffer[position++];
  1946 +
  1947 + if (position >= bufferLength) {
  1948 + position = -1;
  1949 + } // end if: end
  1950 +
  1951 + return b & 0xFF; // This is how you "cast" a byte that's
  1952 + // intended to be unsigned.
  1953 + } // end else
  1954 + } // end if: position >= 0
  1955 +
  1956 + // Else error
  1957 + else {
  1958 + throw new java.io.IOException(
  1959 + "Error in Base64 code reading stream.");
  1960 + } // end else
  1961 + } // end read
  1962 +
  1963 + /**
  1964 + * Calls {@link #read()} repeatedly until the end of stream is reached
  1965 + * or <var>len</var> bytes are read. Returns number of bytes read into
  1966 + * array or -1 if end of stream is encountered.
  1967 + *
  1968 + * @param dest
  1969 + * array to hold values
  1970 + * @param off
  1971 + * offset for array
  1972 + * @param len
  1973 + * max number of bytes to read into array
  1974 + * @return bytes read into array or -1 if end of stream is encountered.
  1975 + * @since 1.3
  1976 + */
  1977 + @Override
  1978 + public int read(byte[] dest, int off, int len)
  1979 + throws java.io.IOException {
  1980 + int i;
  1981 + int b;
  1982 + for (i = 0; i < len; i++) {
  1983 + b = read();
  1984 +
  1985 + if (b >= 0) {
  1986 + dest[off + i] = (byte) b;
  1987 + } else if (i == 0) {
  1988 + return -1;
  1989 + } else {
  1990 + break; // Out of 'for' loop
  1991 + } // Out of 'for' loop
  1992 + } // end for: each byte read
  1993 + return i;
  1994 + } // end read
  1995 +
  1996 + } // end inner class InputStream
  1997 +
  1998 + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
  1999 +
  2000 + /**
  2001 + * A {@link Base64.OutputStream} will write data to another
  2002 + * <tt>java.io.OutputStream</tt>, given in the constructor, and
  2003 + * encode/decode to/from Base64 notation on the fly.
  2004 + *
  2005 + * @see Base64
  2006 + * @since 1.3
  2007 + */
  2008 + public static class OutputStream extends java.io.FilterOutputStream {
  2009 +
  2010 + private boolean encode;
  2011 + private int position;
  2012 + private byte[] buffer;
  2013 + private int bufferLength;
  2014 + private int lineLength;
  2015 + private boolean breakLines;
  2016 + private byte[] b4; // Scratch used in a few places
  2017 + private boolean suspendEncoding;
  2018 + private int options; // Record for later
  2019 + private byte[] decodabet; // Local copies to avoid extra method calls
  2020 +
  2021 + /**
  2022 + * Constructs a {@link Base64.OutputStream} in ENCODE mode.
  2023 + *
  2024 + * @param out
  2025 + * the <tt>java.io.OutputStream</tt> to which data will be
  2026 + * written.
  2027 + * @since 1.3
  2028 + */
  2029 + public OutputStream(java.io.OutputStream out) {
  2030 + this(out, ENCODE);
  2031 + } // end constructor
  2032 +
  2033 + /**
  2034 + * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE
  2035 + * mode.
  2036 + * <p>
  2037 + * Valid options:
  2038 + *
  2039 + * <pre>
  2040 + * ENCODE or DECODE: Encode or Decode as data is read.
  2041 + * DO_BREAK_LINES: don't break lines at 76 characters
  2042 + * (only meaningful when encoding)</i>
  2043 + * </pre>
  2044 + * <p>
  2045 + * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
  2046 + *
  2047 + * @param out
  2048 + * the <tt>java.io.OutputStream</tt> to which data will be
  2049 + * written.
  2050 + * @param options
  2051 + * Specified options.
  2052 + * @see Base64#ENCODE
  2053 + * @see Base64#DECODE
  2054 + * @see Base64#DO_BREAK_LINES
  2055 + * @since 1.3
  2056 + */
  2057 + public OutputStream(java.io.OutputStream out, int options) {
  2058 + super(out);
  2059 + this.breakLines = (options & DO_BREAK_LINES) != 0;
  2060 + this.encode = (options & ENCODE) != 0;
  2061 + this.bufferLength = encode ? 3 : 4;
  2062 + this.buffer = new byte[bufferLength];
  2063 + this.position = 0;
  2064 + this.lineLength = 0;
  2065 + this.suspendEncoding = false;
  2066 + this.b4 = new byte[4];
  2067 + this.options = options;
  2068 + this.decodabet = getDecodabet(options);
  2069 + } // end constructor
  2070 +
  2071 + /**
  2072 + * Writes the byte to the output stream after converting to/from Base64
  2073 + * notation. When encoding, bytes are buffered three at a time before
  2074 + * the output stream actually gets a write() call. When decoding, bytes
  2075 + * are buffered four at a time.
  2076 + *
  2077 + * @param theByte
  2078 + * the byte to write
  2079 + * @since 1.3
  2080 + */
  2081 + @Override
  2082 + public void write(int theByte) throws java.io.IOException {
  2083 + // Encoding suspended?
  2084 + if (suspendEncoding) {
  2085 + this.out.write(theByte);
  2086 + return;
  2087 + } // end if: supsended
  2088 +
  2089 + // Encode?
  2090 + if (encode) {
  2091 + buffer[position++] = (byte) theByte;
  2092 + if (position >= bufferLength) { // Enough to encode.
  2093 +
  2094 + this.out.write(encode3to4(b4, buffer, bufferLength, options));
  2095 +
  2096 + lineLength += 4;
  2097 + if (breakLines && lineLength >= MAX_LINE_LENGTH) {
  2098 + this.out.write(NEW_LINE);
  2099 + lineLength = 0;
  2100 + } // end if: end of line
  2101 +
  2102 + position = 0;
  2103 + } // end if: enough to output
  2104 + } // end if: encoding
  2105 +
  2106 + // Else, Decoding
  2107 + else {
  2108 + // Meaningful Base64 character?
  2109 + if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) {
  2110 + buffer[position++] = (byte) theByte;
  2111 + if (position >= bufferLength) { // Enough to output.
  2112 +
  2113 + int len = Base64.decode4to3(buffer, 0, b4, 0, options);
  2114 + out.write(b4, 0, len);
  2115 + position = 0;
  2116 + } // end if: enough to output
  2117 + } // end if: meaningful base64 character
  2118 + else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC) {
  2119 + throw new java.io.IOException(
  2120 + "Invalid character in Base64 data.");
  2121 + } // end else: not white space either
  2122 + } // end else: decoding
  2123 + } // end write
  2124 +
  2125 + /**
  2126 + * Calls {@link #write(int)} repeatedly until <var>len</var> bytes are
  2127 + * written.
  2128 + *
  2129 + * @param theBytes
  2130 + * array from which to read bytes
  2131 + * @param off
  2132 + * offset for array
  2133 + * @param len
  2134 + * max number of bytes to read into array
  2135 + * @since 1.3
  2136 + */
  2137 + @Override
  2138 + public void write(byte[] theBytes, int off, int len)
  2139 + throws java.io.IOException {
  2140 + // Encoding suspended?
  2141 + if (suspendEncoding) {
  2142 + this.out.write(theBytes, off, len);
  2143 + return;
  2144 + } // end if: supsended
  2145 +
  2146 + for (int i = 0; i < len; i++) {
  2147 + write(theBytes[off + i]);
  2148 + } // end for: each byte written
  2149 +
  2150 + } // end write
  2151 +
  2152 + /**
  2153 + * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer
  2154 + * without closing the stream.
  2155 + *
  2156 + * @throws java.io.IOException
  2157 + * if there's an error.
  2158 + */
  2159 + public void flushBase64() throws java.io.IOException {
  2160 + if (position > 0) {
  2161 + if (encode) {
  2162 + out.write(encode3to4(b4, buffer, position, options));
  2163 + position = 0;
  2164 + } // end if: encoding
  2165 + else {
  2166 + throw new java.io.IOException(
  2167 + "Base64 input not properly padded.");
  2168 + } // end else: decoding
  2169 + } // end if: buffer partially full
  2170 +
  2171 + } // end flush
  2172 +
  2173 + /**
  2174 + * Flushes and closes (I think, in the superclass) the stream.
  2175 + *
  2176 + * @since 1.3
  2177 + */
  2178 + @Override
  2179 + public void close() throws java.io.IOException {
  2180 + // 1. Ensure that pending characters are written
  2181 + flushBase64();
  2182 +
  2183 + // 2. Actually close the stream
  2184 + // Base class both flushes and closes.
  2185 + super.close();
  2186 +
  2187 + buffer = null;
  2188 + out = null;
  2189 + } // end close
  2190 +
  2191 + /**
  2192 + * Suspends encoding of the stream. May be helpful if you need to embed
  2193 + * a piece of base64-encoded data in a stream.
  2194 + *
  2195 + * @throws java.io.IOException
  2196 + * if there's an error flushing
  2197 + * @since 1.5.1
  2198 + */
  2199 + public void suspendEncoding() throws java.io.IOException {
  2200 + flushBase64();
  2201 + this.suspendEncoding = true;
  2202 + } // end suspendEncoding
  2203 +
  2204 + /**
  2205 + * Resumes encoding of the stream. May be helpful if you need to embed a
  2206 + * piece of base64-encoded data in a stream.
  2207 + *
  2208 + * @since 1.5.1
  2209 + */
  2210 + public void resumeEncoding() {
  2211 + this.suspendEncoding = false;
  2212 + } // end resumeEncoding
  2213 +
  2214 + } // end inner class OutputStream
  2215 +
  2216 +} // end class Base64
Please register or login to post a comment