public class ChatRoomClient extends BaseClient {

    private String mChatRoomPath;
    private String mUserPath;

    public ChatRoomClient(String appKey, String masterSecret) {
        this(appKey, masterSecret, null, JMessageConfig.getInstance());
    }

    public ChatRoomClient(String appKey, String masterSecret, HttpProxy proxy) {
        this(appKey, masterSecret, proxy, JMessageConfig.getInstance());
    }

    /**
     * Create a JMessage Base Client
     *
     * @param appKey       The KEY of one application on JPush.
     * @param masterSecret API access secret of the appKey.
     * @param proxy        The proxy, if there is no proxy, should be null.
     * @param config       The client configuration. Can use JMessageConfig.getInstance() as default.
     */
    public ChatRoomClient(String appKey, String masterSecret, HttpProxy proxy, JMessageConfig config) {
        super(appKey, masterSecret, proxy, config);
        mChatRoomPath = (String) config.get(JMessageConfig.CHAT_ROOM_PATH);
        mUserPath = (String) config.get(JMessageConfig.USER_PATH);
    }

    /**
     * Create chat room
     *
     * @param payload {@link ChatRoomPayload}
     * @return result contains room id
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public CreateChatRoomResult createChatRoom(ChatRoomPayload payload) throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(null != payload, "ChatRoomPayload should not be null");
        ResponseWrapper responseWrapper = _httpClient.sendPost(_baseUrl + mChatRoomPath, payload.toString());
        return CreateChatRoomResult.fromResponse(responseWrapper, CreateChatRoomResult.class);
    }

    /**
     * Get chat room information by room ids
     *
     * @param roomIds Array of room id
     * @return {@link ChatRoomListResult}
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public ChatRoomListResult getBatchChatRoomInfo(long... roomIds) throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(roomIds != null && roomIds.length > 0, "Room ids should not be null");
        JsonArray array = new JsonArray();
        for (long id : roomIds) {
            array.add(new JsonPrimitive(id));
        }
        ResponseWrapper responseWrapper = _httpClient.sendPost(_baseUrl + mChatRoomPath + "/batch", array.toString());
        return ChatRoomListResult.fromResponse(responseWrapper);
    }

    /**
     * Get user's whole chat room information
     *
     * @param username required
     * @return {@link ChatRoomListResult}
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public ChatRoomListResult getUserChatRoomInfo(String username) throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);
        ResponseWrapper responseWrapper = _httpClient.sendGet(_baseUrl + mUserPath + "/" + username + "/chatroom");
        return ChatRoomListResult.fromResponse(responseWrapper);
    }

    /**
     * Get application's chat room list
     *
     * @param start start index
     * @param count list count
     * @return {@link ChatRoomListResult}
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public ChatRoomListResult getAppChatRoomInfo(int start, int count)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(start >= 0 && count > 0, "Illegal argument");
        ResponseWrapper responseWrapper = _httpClient.sendGet(_baseUrl + mChatRoomPath + "?start=" + start + "&count=" + count);
        return ChatRoomListResult.fromResponse(responseWrapper, ChatRoomListResult.class);
    }

    /**
     * Update chat room info
     *
     * @param roomId        room id
     * @param ownerUsername owner username
     * @param name          new chat room name
     * @param desc          chat room description
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public ResponseWrapper updateChatRoomInfo(long roomId, String ownerUsername, String name, String desc)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(roomId > 0, "room id is invalid");
        StringUtils.checkUsername(ownerUsername);
        Preconditions.checkArgument(null != name, "Chat room name is null");
        Preconditions.checkArgument(null != desc, "Description is null");
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty(ChatRoomPayload.OWNER, ownerUsername);
        jsonObject.addProperty(ChatRoomPayload.NAME, name);
        jsonObject.addProperty(ChatRoomPayload.DESC, desc);
        return _httpClient.sendPut(_baseUrl + mChatRoomPath + "/" + roomId, _gson.toJson(jsonObject));
    }

    /**
     * Delete chat room by id
     *
     * @param roomId room id
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public ResponseWrapper deleteChatRoom(long roomId) throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(roomId > 0, "room id is invalid");
        return _httpClient.sendDelete(_baseUrl + mChatRoomPath + "/" + roomId);
    }

    /**
     * Update user speak status
     *
     * @param roomId   chat room id
     * @param username user to be modified
     * @param flag     0 means allow to speak, 1 means forbid to speak in chat room
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public ResponseWrapper updateUserSpeakStatus(long roomId, String username, int flag)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(roomId > 0, "room id is invalid");
        StringUtils.checkUsername(username);
        Preconditions.checkArgument(flag == 0 || flag == 1, "flag should be 0 or 1");
        return _httpClient.sendPut(_baseUrl + mChatRoomPath + "/" + roomId + "/forbidden/" + username + "?status=" + flag, null);
    }

    /**
     * Get all member info of chat room
     *
     * @param roomId chat room id
     * @param start  start index
     * @param count member count
     * @return {@link ChatRoomMemberList}
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public ChatRoomMemberList getChatRoomMembers(long roomId, int start, int count)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(roomId > 0, "room id is invalid");
        Preconditions.checkArgument(start >= 0 && count > 0, "Illegal argument");
        ResponseWrapper responseWrapper = _httpClient.sendGet(_baseUrl + mChatRoomPath + "/" + roomId + "/members"
                + "?start=" + start + "&count=" + count);
        return ChatRoomMemberList.fromResponse(responseWrapper, ChatRoomMemberList.class);
    }

    /**
     * Add members to chat room
     *
     * @param roomId  chat room id
     * @param members username array
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public ResponseWrapper addChatRoomMember(long roomId, String... members)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(roomId > 0, "room id is invalid");
        Preconditions.checkArgument(members != null && members.length > 0, "member should not be empty");
        JsonArray array = new JsonArray();
        for (String username : members) {
            array.add(new JsonPrimitive(username));
        }
        return _httpClient.sendPut(_baseUrl + mChatRoomPath + "/" + roomId + "/members", array.toString());
    }

    /**
     * Add members to chat room
     *
     * @param roomId  chat room id
     * @param members {@link Members}
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public ResponseWrapper addChatRoomMember(long roomId, Members members)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(roomId > 0, "room id is invalid");
        Preconditions.checkArgument(members != null, "members should not be empty");
        return _httpClient.sendPut(_baseUrl + mChatRoomPath + "/" + roomId + "/members", members.toString());
    }

    /**
     * remove members from chat room
     *
     * @param roomId  chat room id
     * @param members username array
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public ResponseWrapper removeChatRoomMembers(long roomId, String... members)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(roomId > 0, "room id is invalid");
        Preconditions.checkArgument(members != null && members.length > 0, "member should not be empty");
        JsonArray array = new JsonArray();
        for (String username : members) {
            array.add(new JsonPrimitive(username));
        }
        return _httpClient.sendDelete(_baseUrl + mChatRoomPath + "/" + roomId + "/members", array.toString());
    }

    /**
     * remove members from chat room
     *
     * @param roomId  chat room id
     * @param members {@link Members}
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public ResponseWrapper removeChatRoomMembers(long roomId, Members members)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(roomId > 0, "room id is invalid");
        Preconditions.checkArgument(members != null, "members should not be empty");
        return _httpClient.sendDelete(_baseUrl + mChatRoomPath + "/" + roomId + "/members", members.toString());
    }
} public class ChatRoomListResult extends BaseResult {

    @Expose private List<ChatRoomResult> rooms = new ArrayList<ChatRoomResult>();
    @Expose private Integer total;
    @Expose private ChatRoomResult[] roomsArray;
    @Expose private Integer start;
    @Expose private Integer count;

    public static ChatRoomListResult fromResponse(ResponseWrapper responseWrapper) {
        ChatRoomListResult result = new ChatRoomListResult();
        if (responseWrapper.isServerResponse()) {
            result.roomsArray = _gson.fromJson(responseWrapper.responseContent, ChatRoomResult[].class);
        }
        result.setResponseWrapper(responseWrapper);
        return result;
    }

    public ChatRoomResult[] getRooms() {
        return this.roomsArray;
    }

    public List<ChatRoomResult> getList() {
        return this.rooms;
    }

    public Integer getTotal() {
        return this.total;
    }

    public Integer getStart() {
        return this.start;
    }

    public Integer getCount() {
        return this.count;
    }
} public class ChatRoomMemberList extends BaseResult {

    @Expose private ChatRoomMember[] users;
    @Expose private Integer total;
    @Expose private Integer start;
    @Expose private Integer count;

    public static ChatRoomMemberList fromResponse(ResponseWrapper responseWrapper) {
        ChatRoomMemberList  result = new ChatRoomMemberList();
        if (responseWrapper.isServerResponse()) {
            result.users = _gson.fromJson(responseWrapper.responseContent, ChatRoomMember[].class);
        } else {
            // nothing
        }
        result.setResponseWrapper(responseWrapper);
        return result;
    }

    public class ChatRoomMember {
        @Expose String username;
        @Expose Integer flag;
        @Expose String room_ctime;
        @Expose String mtime;
        @Expose String ctime;

        public String getUsername() {
            return username;
        }

        public Integer getFlag() {
            return flag;
        }

        public String getRoom_ctime() {
            return room_ctime;
        }

        public String getMtime() {
            return mtime;
        }

        public String getCtime() {
            return ctime;
        }
    }

    public ChatRoomMember[] getMembers() {
        return this.users;
    }

    public Integer getTotal() {
        return total;
    }

    public Integer getStart() {
        return start;
    }

    public Integer getCount() {
        return count;
    }
} public class CreateChatRoomResult extends BaseResult {

    @Expose private Long chatroom_id;

    public Long getChatroom_id() {
        return chatroom_id;
    }
} public class ChatRoomPayload implements IModel {

    public static final String OWNER = "owner_username";
    public static final String NAME = "name";
    public static final String MEMBERS = "members_username";
    public static final String DESC = "description";
    public static final String FLAG = "flag";

    private static Gson gson = new Gson();

    private String owner;
    private String name;
    private Members members;
    private String desc;
    // 禁言标志，0 表示不禁言，1 表示禁言
    private int flag = -1;

    public ChatRoomPayload(String name, String ownerName, Members members, String desc, int flag) {
        this.name = name;
        this.owner = ownerName;
        this.members = members;
        this.desc = desc;
        this.flag = flag;
    }

    public static Builder newBuilder() {
        return new Builder();
    }


    @Override
    public JsonElement toJSON() {
        JsonObject jsonObject = new JsonObject();
        if (null != name) {
            jsonObject.addProperty(NAME, name);
        }

        if (null != owner) {
            jsonObject.addProperty(OWNER, owner);
        }

        if ( null != members ) {
            jsonObject.add(MEMBERS, members.toJSON());
        }

        if ( null != desc ) {
            jsonObject.addProperty(DESC, desc);
        }

        if (flag != -1) {
            jsonObject.addProperty(FLAG, flag);
        }

        return jsonObject;
    }

    public static class Builder {
        private String owner;
        private String name;
        private Members members;
        private String desc;
        private int flag = -1;

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setOwnerUsername(String owner) {
            this.owner = owner;
            return this;
        }

        public Builder setMembers(Members members) {
            this.members = members;
            return this;
        }

        public Builder setDesc(String desc) {
            this.desc = desc;
            return this;
        }

        public Builder setFlag(int flag) {
            this.flag = flag;
            return this;
        }

        public ChatRoomPayload build() {
            return new ChatRoomPayload(name, owner, members, desc, flag);
        }

    }

    @Override
    public String toString() {
        return gson.toJson(toJSON());
    }
} public class GroupPayload implements IModel {

    public static final String OWNER = "owner_username";
    public static final String GROUP_NAME = "name";
    public static final String MEMBERS = "members_username";
    public static final String DESC = "desc";
    public static final String AVATAR = "avatar";
    public static final String FLAG = "flag";

    private static Gson gson = new Gson();

    private String owner;
    private String name;
    private Members members;
    private String desc;
    // 上传接口后的 media_id
    private String avatar;
    // 类型，1 为私有群，2 为公开群，默认为 1
    private int flag = 1;

    private GroupPayload(String owner, String name, Members members, String desc, String avatar, int flag) {
        this.owner = owner;
        this.name = name;
        this.members = members;
        this.desc = desc;
        this.avatar = avatar;
        this.flag = flag;
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    @Override
    public JsonElement toJSON() {

        JsonObject json = new JsonObject();

        if ( null != owner ) {
            json.addProperty(OWNER, owner);
        }

        if ( null != name ) {
            json.addProperty(GROUP_NAME, name);
        }

        if ( null != members ) {
            json.add(MEMBERS, members.toJSON());
        }

        if ( null != desc ) {
            json.addProperty(DESC, desc);
        }

        if (null != avatar) {
            json.addProperty(AVATAR, avatar);
        }

        if (flag != 1) {
            json.addProperty(FLAG, flag);
        }

        return json;

    }

    @Override
    public String toString() {
        return gson.toJson(toJSON());
    }

    public static class Builder{

        private String owner;
        private String name;
        private Members members;
        private String desc;
        private String avatar;
        private int flag;

        public Builder setOwner(String owner) {
            this.owner = owner.trim();
            return this;
        }

        public Builder setName(String name) {
            this.name = name.trim();
            return this;
        }

        public Builder setMembers(Members members) {
            this.members = members;
            return this;
        }

        public Builder setDesc(String desc) {
            this.desc = desc.trim();
            return this;
        }

        public Builder setAvatar(String mediaId) {
            this.avatar = mediaId;
            return this;
        }

        public Builder setFlag(int flag) {
            Preconditions.checkArgument(flag == 1 || flag == 2, "Flag must be 1 or 2");
            this.flag = flag;
            return this;
        }

        public GroupPayload build() {

            Preconditions.checkArgument(StringUtils.isNotEmpty(owner), "The owner must not be empty.");
            Preconditions.checkArgument(StringUtils.isNotEmpty(name), "The group name must not be empty.");
            Preconditions.checkArgument(!StringUtils.isLineBroken(owner), 
            		"The owner name must not contain line feed character.");
            Preconditions.checkArgument(name.getBytes().length <= 64,
                    "The length of group name must not more than 64 bytes.");
            Preconditions.checkArgument( !StringUtils.isLineBroken(name),
                    "The group name must not contain line feed character.");

            if ( null != desc ) {
                Preconditions.checkArgument( desc.getBytes().length <= 250,
                        "The length of group description must not more than 250 bytes.");
            }

            return new GroupPayload(owner, name, members, desc, avatar, flag);
        }

    }
} public class GroupShieldPayload implements IModel {

    private static final String ADD = "add";
    private static final String REMOVE = "remove";

    private static final Gson gson = new Gson();

    private List<Long> addList;
    private List<Long> removeList;

    public GroupShieldPayload(List<Long> addList, List<Long> removeList) {
        this.addList = addList;
        this.removeList = removeList;
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    public static class Builder {
        private List<Long> addList = new ArrayList<Long>();
        private List<Long> removeList = new ArrayList<Long>();

        public Builder addGroupShield(Long...gid) {
            for (long id : gid) {
                addList.add(id);
            }
            return this;
        }

        public Builder setAddGroupShield(List<Long> list) {
            Preconditions.checkArgument(null != list, "group id list is null");
            addList = list;
            return this;
        }

        public Builder removeGroupShield(long...ids) {
            for (long id : ids) {
                removeList.add(id);
            }
            return this;
        }

        public Builder setRemoveGroupShield(List<Long> list) {
            Preconditions.checkArgument(null != list, "group id list is null");
            removeList = list;
            return this;
        }

        public GroupShieldPayload build() {
            return new GroupShieldPayload(addList, removeList);
        }
    }

    @Override
    public JsonElement toJSON() {
        JsonObject jsonObject = new JsonObject();
        if (null != addList && addList.size() > 0) {
            JsonArray jsonArray = new JsonArray();
            for (long id : addList) {
                jsonArray.add(new JsonPrimitive(id));
            }
            jsonObject.add(ADD, jsonArray);
        }

        if (null != removeList && removeList.size() > 0) {
            JsonArray jsonArray = new JsonArray();
            for (long id : removeList) {
                jsonArray.add(new JsonPrimitive(id));
            }
            jsonObject.add(REMOVE, jsonArray);
        }
        return jsonObject;
    }

    @Override
    public String toString() {
        return gson.toJson(toJSON());
    }
} public class CrossAppClient extends BaseClient {

    private String crossUserPath;
    private String crossGroupPath;

    public CrossAppClient(String appkey, String masterSecret) {
        this(appkey, masterSecret, null, JMessageConfig.getInstance());
    }

    public CrossAppClient(String appKey, String masterSecret, HttpProxy proxy) {
        this(appKey, masterSecret, proxy, JMessageConfig.getInstance());
    }

    /**
     * Create a JMessage Base Client
     *
     * @param appKey       The KEY of one application on JPush.
     * @param masterSecret API access secret of the appKey.
     * @param proxy        The proxy, if there is no proxy, should be null.
     * @param config       The client configuration. Can use JMessageConfig.getInstance() as default.
     */
    public CrossAppClient(String appKey, String masterSecret, HttpProxy proxy, JMessageConfig config) {
        super(appKey, masterSecret, proxy, config);
        this.crossUserPath = (String) config.get(JMessageConfig.CROSS_USER_PATH);
        this.crossGroupPath = (String) config.get(JMessageConfig.CROSS_GROUP_PATH);
    }

    /**
     * Add or remove group members from a given group id.
     * @param gid Necessary, target group id.
     * @param groups Necessary
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper addOrRemoveCrossGroupMembers(long gid, CrossGroup[] groups)
            throws APIConnectionException, APIRequestException {
        CrossGroupPayload payload = new CrossGroupPayload.Builder()
                .setCrossGroups(groups)
                .build();
        Preconditions.checkArgument(0 != gid, "gid should not be empty");
        Preconditions.checkArgument(null != payload, "CrossGroup must not be null");
        return _httpClient.sendPost(_baseUrl + crossGroupPath + "/" + gid + "/members", payload.toString());
    }

    /**
     * Get members' info from cross group
     * @param gid Necessary, target group id
     * @return Member info array
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public MemberListResult getCrossGroupMembers(long gid)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(0 != gid, "gid must not be empty");
        ResponseWrapper response = _httpClient.sendGet(_baseUrl + crossGroupPath + "/" + gid + "/members/");
        return MemberListResult.fromResponse(response);
    }

    /**
     * Add blacklist whose users belong to another app to a given user.
     * @param username The owner of the blacklist
     * @param blacklists CrossBlacklist array
     * @return No Content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper addCrossBlacklist(String username, CrossBlacklist[] blacklists)
            throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);
        CrossBlacklistPayload payload = new CrossBlacklistPayload.Builder()
                .setCrossBlacklists(blacklists)
                .build();
        return _httpClient.sendPut(_baseUrl + crossUserPath + "/" + username +"/blacklist", payload.toString());
    }

    /**
     * Delete blacklist whose users belong to another app from a given user.
     * @param username The owner of the blacklist
     * @param blacklists CrossBlacklist array
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper deleteCrossBlacklist(String username, CrossBlacklist[] blacklists)
            throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);
        CrossBlacklistPayload payload = new CrossBlacklistPayload.Builder()
                .setCrossBlacklists(blacklists)
                .build();
        return _httpClient.sendDelete(_baseUrl + crossUserPath + "/" + username + "/blacklist", payload.toString());
    }

    /**
     * Get all user's info(contains appkey) of user's cross app blacklist
     * @param username Necessary, the owner of blacklist
     * @return UserInfo array
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public UserInfoResult[] getCrossBlacklist(String username)
            throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);
        ResponseWrapper response = _httpClient.sendGet(_baseUrl + crossUserPath + "/" + username + "/blacklist");
        return _gson.fromJson(response.responseContent, UserInfoResult[].class);
    }

    /**
     * Set cross app no disturb
     * https://docs.jiguang.cn/jmessage/server/rest_api_im/#api_1
     * @param username Necessary
     * @param array CrossNoDisturb array
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper setCrossNoDisturb(String username, CrossNoDisturb[] array)
            throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);
        CrossNoDisturbPayload payload = new CrossNoDisturbPayload.Builder()
                .setCrossNoDistrub(array)
                .build();
        return _httpClient.sendPost(_baseUrl + crossUserPath + "/" + username + "/nodisturb", payload.toString());
    }

    /**
     * Add users from cross app.
     * @param username Necessary
     * @param payload CrossFriendPayload
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper addCrossFriends(String username, CrossFriendPayload payload)
            throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);
        Preconditions.checkArgument(null != payload, "CrossFriendPayload should not be null");
        return _httpClient.sendPost(_baseUrl + crossUserPath + "/" + username + "/friends", payload.toString());
    }

    /**
     * Delete cross app friends
     * @param username Necessary
     * @param payload CrossFriendPayload
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper deleteCrossFriends(String username, CrossFriendPayload payload)
            throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);
        Preconditions.checkArgument(null != payload, "CrossFriendPayload should not be null");
        return _httpClient.sendDelete(_baseUrl + crossUserPath + "/" + username + "/friends", payload.toString());
    }

} public class JMessageConfig {

    private static ClientConfig clientConfig = ClientConfig.getInstance();

    private static JMessageConfig instance = new JMessageConfig();

    public static final String API_HOST_NAME = "im.api.host.name";
    public static final String API_REPORT_HOST_NAME = "im.api.report.host.name";

    public static final String ADMIN_PATH = "im.admin.path";

    public static final String USER_PATH = "im.user.path";
    public static final String V2_USER_PATH = "im.v2.user.path";

    public static final String GROUP_PATH = "im.group.path";
    public static final String V2_GROUP_PATH = "im.v2.group.path";

    public static final String MESSAGE_PATH = "im.message.path";
    public static final String V2_MESSAGE_PATH = "im.v2.message.path";
    public static final String V2_STATISTIC_PATH = "im.v2.statistic.path";
    public static final String V2_CHATROOM_PATH = "im.v2.chatroom.path";

    public static final String RESOURCE_PATH = "im.resource.path";

    public static final String CROSS_USER_PATH = "im.cross.user.path";
    public static final String CROSS_GROUP_PATH = "im.cross.group.path";

    public static final String SENSITIVE_WORD_PATH = "im.sensitive.word.path";

    public static final String CHAT_ROOM_PATH = "im.chat.room.path";

    public static final String MAX_RETRY_TIMES = ClientConfig.MAX_RETRY_TIMES;

    public static final String SEND_VERSION = "send.version";
    public static final Object SEND_VERSION_SCHMEA = Integer.class;

    private JMessageConfig() {
        clientConfig.put(API_HOST_NAME, "https://api.im.jpush.cn");
        clientConfig.put(API_REPORT_HOST_NAME, "https://report.im.jpush.cn");
        clientConfig.put(ADMIN_PATH, "/v1/admins");
        clientConfig.put(USER_PATH, "/v1/users");
        clientConfig.put(V2_USER_PATH, "/v2/users");
        clientConfig.put(GROUP_PATH, "/v1/groups");
        clientConfig.put(V2_GROUP_PATH, "/v2/groups");
        clientConfig.put(MESSAGE_PATH, "/v1/messages");
        clientConfig.put(V2_MESSAGE_PATH, "/v2/messages");
        clientConfig.put(RESOURCE_PATH, "/v1/resource");
        clientConfig.put(CROSS_USER_PATH, "/v1/cross/users");
        clientConfig.put(CROSS_GROUP_PATH, "/v1/cross/groups");
        clientConfig.put(SENSITIVE_WORD_PATH, "/v1/sensitiveword");
        clientConfig.put(CHAT_ROOM_PATH, "/v1/chatroom");
        clientConfig.put(V2_CHATROOM_PATH, "/v2/chatrooms");
        clientConfig.put(V2_STATISTIC_PATH, "/v2/statistic");
        clientConfig.put(MAX_RETRY_TIMES, 3);
        clientConfig.put(SEND_VERSION, 1);
    }

    public static JMessageConfig getInstance() {
        return instance;
    }

    public ClientConfig getClientConfig() {
        return clientConfig;
    }

    public JMessageConfig setApiHostName(String hostName) {
        clientConfig.put(API_HOST_NAME, hostName);
        return this;
    }

    public JMessageConfig setReportHostName(String hostName) {
        clientConfig.put(API_REPORT_HOST_NAME, hostName);
        return this;
    }

    public JMessageConfig setMaxRetryTimes(int maxRetryTimes) {
        clientConfig.setMaxRetryTimes(maxRetryTimes);
        return this;
    }

    public void put(String key, Object value) {
        clientConfig.put(key, value);
    }

    public Object get(String key) {
        return clientConfig.get(key);
    }

} public class CrossBlacklist implements IModel {

    private static String APP_KEY = "appkey";
    private static String USERNAMES = "usernames";

    private Gson gson = new Gson();

    private String appKey;
    private String[] users;

    private CrossBlacklist(String appKey, String[] users) {
        this.appKey = appKey;
        this.users = users;
    }

    public Builder newBuilder() {
        return new CrossBlacklist.Builder();
    }

    public static class Builder {
        private String appKey;
        private String[] users;

        public Builder setAppKey(String appKey) {
            this.appKey = appKey;
            return this;
        }

        public Builder addUsers(String...users) {
            this.users = users;
            return this;
        }

        public CrossBlacklist build() {
            Preconditions.checkArgument(null != appKey, "AppKey must not be null");
            Preconditions.checkArgument(null != users, "At least add one user");
            return new CrossBlacklist(appKey, users);
        }
    }

    @Override
    public JsonElement toJSON() {
        JsonObject json = new JsonObject();

        if (null != appKey) {
            json.addProperty(APP_KEY, appKey);
        }

        if (null != users) {
            JsonArray array = new JsonArray();
            for (String user : users) {
                array.add(new JsonPrimitive(user));
            }
            json.add(USERNAMES, array);
        }

        return json;
    }

    @Override
    public String toString() {
        return gson.toJson(toJSON());
    }
} public class CrossFriendPayload implements IModel {

    private final static String APP_KEY = "appkey";
    private final static String USERS = "users";
    private Gson gson = new Gson();
    private String appKey;
    private String[] users;

    public CrossFriendPayload(String appKey, String[] users) {
        this.appKey = appKey;
        this.users = users;
    }

    public CrossFriendPayload newBuilder() {
        return new Builder().build();
    }

    public static class Builder {
        private String appKey;
        private String[] users;

        public Builder setAppKey(String appKey) {
            this.appKey = appKey;
            return this;
        }

        public Builder setUsers(String...users) {
            this.users = users;
            return this;
        }

        public CrossFriendPayload build() {
            Preconditions.checkArgument(null != appKey, "AppKey should not be null!");
            Preconditions.checkArgument(null != users, "Users should not be null");
            return new CrossFriendPayload(appKey, users);
        }
    }


    @Override
    public JsonElement toJSON() {
        JsonObject jsonObject = new JsonObject();

        if (null != appKey) {
            jsonObject.addProperty(APP_KEY, appKey);
        }

        if (null != users) {
            JsonArray array = new JsonArray();
            for (String user : users) {
                array.add(new JsonPrimitive(user));
            }
            jsonObject.add(USERS, array);
        }

        return jsonObject;
    }

    @Override
    public String toString() {
        return gson.toJson(toJSON());
    }
} public class CrossGroup implements IModel {

    private static final String APP_KEY = "appkey";
    private static final String ADD = "add";
    private static final String REMOVE = "remove";

    private Gson gson = new Gson();

    private String appKey;
    private String[] add_users;
    private String[] remove_users;

    private CrossGroup(String appKey, String[] add_users, String[] remove_users) {
        this.appKey = appKey;
        this.add_users = add_users;
        this.remove_users = remove_users;
    }

    public CrossGroup newBuilder() {
        return new Builder().build();
    }

    public static class Builder {
        private String appKey;
        private String[] add_users;
        private String[] remove_users;

        public Builder setAppKey(String appKey) {
            this.appKey = appKey;
            return this;
        }

        public Builder setAddUsers(String...users) {
            this.add_users = users;
            return this;
        }

        public Builder setRemoveUsers(String...users) {
            this.remove_users = users;
            return this;
        }

        public CrossGroup build() {
            Preconditions.checkArgument(null != appKey, "AppKey must not be null");
            if (null == add_users && null == remove_users) {
                throw new IllegalArgumentException("At least one of add array or remove array should not be null");
            }
            return new CrossGroup(appKey, add_users, remove_users);
        }


    }

    @Override
    public JsonElement toJSON() {
        JsonObject json = new JsonObject();

        if (null != appKey) {
            json.addProperty(APP_KEY, appKey);
        }

        if (null != add_users) {
            JsonArray array = new JsonArray();
            for (String user : add_users) {
                array.add(new JsonPrimitive(user));
            }
            json.add(ADD, array);
        }

        if (null != remove_users) {
            JsonArray array = new JsonArray();
            for (String user : remove_users) {
                array.add(new JsonPrimitive(user));
            }
            json.add(REMOVE, array);
        }

        return json;
    }

    @Override
    public String toString() {
        return gson.toJson(toJSON());
    }
} public class CrossNoDisturb implements IModel {

    private static String APP_KEY = "appkey";
    private static String SINGLE = "single";
    private static String ADD = "add";
    private static String REMOVE = "remove";
    private static String GROUP = "group";

    private static Gson gson = new Gson();

    private String appKey;
    private String[] add_single_users;
    private String[] remove_single_users;
    private Long[] add_group_ids;
    private Long[] remove_group_ids;

    private CrossNoDisturb(String appKey, String[] add_single_users, String[] remove_single_users,
                           Long[] add_group_ids, Long[] remove_group_ids) {
        this.appKey = appKey;
        this.add_single_users = add_single_users;
        this.remove_single_users = remove_single_users;
        this.add_group_ids = add_group_ids;
        this.remove_group_ids = remove_group_ids;
    }


    @Override
    public JsonElement toJSON() {

        JsonObject json = new JsonObject();

        if (null != appKey) {
            json.addProperty(APP_KEY, appKey);
        }

        if (null != add_single_users) {
            JsonObject jsonObject = new JsonObject();
            JsonArray jsonArray = new JsonArray();
            for (String username : add_single_users) {
                jsonArray.add(new JsonPrimitive(username));
            }
            jsonObject.add(ADD, jsonArray);
            if (null != remove_single_users) {
                JsonArray jsonArray1 = new JsonArray();
                for (String username : remove_single_users) {
                    jsonArray1.add(new JsonPrimitive(username));
                }
                jsonObject.add(REMOVE, jsonArray1);
            }
            json.add(SINGLE, jsonObject);
        } else if (null != remove_single_users) {
            JsonObject jsonObject = new JsonObject();
            JsonArray jsonArray = new JsonArray();
            for (String username : remove_single_users) {
                jsonArray.add(new JsonPrimitive(username));
            }
            jsonObject.add(REMOVE, jsonArray);
            json.add(SINGLE, jsonObject);
        }

        if (null != add_group_ids) {
            JsonObject jsonObject = new JsonObject();
            JsonArray jsonArray = new JsonArray();
            for (Long groupId : add_group_ids) {
                jsonArray.add(new JsonPrimitive(groupId));
            }
            jsonObject.add(ADD, jsonArray);

            if (null != remove_group_ids) {
                JsonArray jsonArray1 = new JsonArray();
                for (Long groupId : remove_group_ids) {
                    jsonArray1.add(new JsonPrimitive(groupId));
                }
                jsonObject.add(REMOVE, jsonArray1);
            }
            json.add(GROUP, jsonObject);
        } else if (null != remove_group_ids) {
            JsonObject jsonObject = new JsonObject();
            JsonArray jsonArray = new JsonArray();
            for (Long groupId : remove_group_ids) {
                jsonArray.add(new JsonPrimitive(groupId));
            }
            jsonObject.add(REMOVE, jsonArray);
            json.add(GROUP, jsonObject);
        }

        return json;
    }

    @Override
    public String toString() {
        return gson.toJson(toJSON());
    }

    public static class Builder {
        private String appKey;
        private String[] add_single_users;
        private String[] remove_single_users;
        private Long[] add_group_ids;
        private Long[] remove_group_ids;
        private int global = 0;

        public Builder setAppKey(String appKey) {
            this.appKey = appKey;
            return this;
        }

        public Builder setAddSingleUsers(String...add_single_users) {
            this.add_single_users = add_single_users;
            return this;
        }

        public Builder setRemoveSingleUsers(String...remove_single_users) {
            this.remove_single_users = remove_single_users;
            return this;
        }

        public Builder setAddGroupIds(Long...add_group_ids) {
            this.add_group_ids = add_group_ids;
            return this;
        }

        public Builder setRemoveGroupIds(Long...remove_group_ids) {
            this.remove_group_ids = remove_group_ids;
            return this;
        }

        public Builder setGlobal(int global) {
            this.global = global;
            return this;
        }

        public CrossNoDisturb build() {
            Preconditions.checkArgument(null != appKey, "appkey must not be null");
            return new CrossNoDisturb(appKey, add_single_users, remove_single_users, add_group_ids, remove_group_ids);
        }
    }
} public class FriendNote implements IModel {

    private static final String NOTE_NAME = "note_name";
    private static final String OTHERS = "others";
    private static final String USERNAME = "username";

    private String note_name;
    private String others;
    private String username;
    private Gson gson = new Gson();

    private FriendNote(String note_name, String others, String username) {
        this.note_name = note_name;
        this.others = others;
        this.username = username;
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    public static class Builder {
        private String note_name;
        private String others;
        private String username;

        public Builder setNoteName(String noteName) {
            this.note_name = noteName;
            return this;
        }

        public Builder setOthers(String others) {
            this.others = others;
            return this;
        }

        public Builder setUsername(String username) {
            this.username = username;
            return this;
        }

        public FriendNote builder() {
            StringUtils.checkUsername(username);
            try {
                Preconditions.checkArgument(note_name.trim().getBytes("UTF-8").length <= 250, "length of note name should less than 250");
                Preconditions.checkArgument(others.getBytes("UTF-8").length <= 250, "length of others should not larger than 250");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            return new FriendNote(note_name, others, username);
        }
    }
    @Override
    public JsonElement toJSON() {
        JsonObject json = new JsonObject();
        if (null != note_name) {
            json.addProperty(NOTE_NAME, note_name);
        }

        if (null != others) {
            json.addProperty(OTHERS, others);
        }

        if (null != username) {
            json.addProperty(USERNAME, username);
        }
        return json;
    }

    @Override
    public String toString() {
        return gson.toJson(toJSON());
    }
} public class MessageBody implements IModel {

    private static final String MSG_BODY_TEXT = "text";
    private static final String MSG_BODY_EXTRAS = "extras";
    private static final String MEDIA_ID = "media_id";
    private static final String MEDIA_CRC32 = "media_crc32";
    private static final String WIDTH = "width";
    private static final String HEIGHT = "height";
    private static final String FORMAT = "format";
    private static final String FSIZE = "fsize";
    private static final String DURATION = "duration";
    private static final String HASH = "hash";

    private static Gson gson = new Gson();

    private Map<String, String> extras;
    private Map<String, Number> numberExtras;
    private Map<String, Boolean> booleanExtras;
    private String media_id;
    private Long media_crc32;
    private Integer width;
    private Integer height;
    private String format;
    private Integer fsize;
    private Integer duration = -1;
    private String hash;

    private String text;

    private MessageBody(String text,
                        Map<String, String> extra,
                        Map<String, Number> numberExtra,
                        Map<String, Boolean> booleanExtra) {
        this.text = text;
        this.extras = extra;
        this.numberExtras = numberExtra;
        this.booleanExtras = booleanExtra;
    }

    private MessageBody(String mediaId, Long crc32, Integer width, Integer height, String format, Integer fsize,
                        Integer duration, String hash) {
        this.media_id = mediaId;
        this.media_crc32 = crc32;
        this.width = width;
        this.height = height;
        this.format = format;
        this.fsize = fsize;
        this.duration = duration;
        this.hash = hash;
    }

    private MessageBody(String mediaId, Long crc32, Integer width, Integer height, String format, Integer fsize,
                        Integer duration, String hash, Map<String, String> extra,
                        Map<String, Number> numberExtra,
                        Map<String, Boolean> booleanExtra) {
        this.media_id = mediaId;
        this.media_crc32 = crc32;
        this.width = width;
        this.height = height;
        this.format = format;
        this.fsize = fsize;
        this.duration = duration;
        this.hash = hash;
        this.extras = extra;
        this.numberExtras = numberExtra;
        this.booleanExtras = booleanExtra;
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    public static MessageBody text(String text) {
        return new Builder().setText(text).build();
    }

    @Override
    public JsonElement toJSON() {

        JsonObject json = new JsonObject();

        if (null != text) {
            json.addProperty(MSG_BODY_TEXT, text);
        }

        JsonObject extrasObject = null;
        if (null != extras || null != numberExtras || null != booleanExtras) {
            extrasObject = new JsonObject();
        }

        if (null != extras) {
            for (String key : extras.keySet()) {
                if (extras.get(key) != null) {
                    extrasObject.add(key, new JsonPrimitive(extras.get(key)));
                } else {
                    extrasObject.add(key, JsonNull.INSTANCE);
                }
            }
        }
        if (null != numberExtras) {
            for (String key : numberExtras.keySet()) {
                extrasObject.add(key, new JsonPrimitive(numberExtras.get(key)));
            }
        }
        if (null != booleanExtras) {
            for (String key : booleanExtras.keySet()) {
                extrasObject.add(key, new JsonPrimitive(booleanExtras.get(key)));
            }
        }

        if (null != extras || null != numberExtras || null != booleanExtras) {
            json.add(MSG_BODY_EXTRAS, extrasObject);
        }

        if (null != media_id) {
            json.addProperty(MEDIA_ID, media_id);
        }

        if (null != media_crc32) {
            json.addProperty(MEDIA_CRC32, media_crc32);
        }

        if (null != width) {
            json.addProperty(WIDTH, width);
        }

        if (null != height) {
            json.addProperty(HEIGHT, height);
        }

        if (null != format) {
            json.addProperty(FORMAT, format);
        }

        if (null != fsize) {
            json.addProperty(FSIZE, fsize);
        }

        if (-1 != duration) {
            json.addProperty(DURATION, duration);
        }

        if (null != hash) {
            json.addProperty(HASH, hash);
        }

        return json;
    }

    @Override
    public String toString() {
        return gson.toJson(toJSON());
    }

    public static class Builder {
        private String text;
        private Map<String, String> extrasBuilder;
        private Map<String, Number> numberExtrasBuilder;
        private Map<String, Boolean> booleanExtrasBuilder;
        private String media_id;
        private Long media_crc32;
        private Integer width;
        private Integer height;
        private String format;
        private Integer fsize;
        private Integer duration = -1;
        private String hash;

        public Builder setText(String text) {
            this.text = text;
            return this;
        }

        public Builder addExtra(String key, String value) {
            Preconditions.checkArgument(!(null == key || null == value), "Key/Value should not be null.");
            if (null == extrasBuilder) {
                extrasBuilder = new HashMap<String, String>();
            }
            extrasBuilder.put(key, value);
            return this;
        }

        public Builder addExtras(Map<String, String> extras) {
            Preconditions.checkArgument(!(null == extras), "extras should not be null.");
            if (null == extrasBuilder) {
                extrasBuilder = new HashMap<String, String>();
            }
            for (String key : extras.keySet()) {
                extrasBuilder.put(key, extras.get(key));
            }
            return this;
        }

        public Builder addExtra(String key, Number value) {
            Preconditions.checkArgument(!(null == key || null == value), "Key/Value should not be null.");
            if (null == numberExtrasBuilder) {
                numberExtrasBuilder = new HashMap<String, Number>();
            }
            numberExtrasBuilder.put(key, value);
            return this;
        }

        public Builder addExtra(String key, Boolean value) {
            Preconditions.checkArgument(!(null == key || null == value), "Key/Value should not be null.");
            if (null == booleanExtrasBuilder) {
                booleanExtrasBuilder = new HashMap<String, Boolean>();
            }
            booleanExtrasBuilder.put(key, value);
            return this;
        }

        public Builder setMediaId(String mediaId) {
            this.media_id = mediaId;
            return this;
        }

        public Builder setMediaCrc32(Long crc32) {
            this.media_crc32 = crc32;
            return this;
        }

        public Builder setWidth(Integer width) {
            this.width = width;
            return this;
        }

        public Builder setHeight(Integer height) {
            this.height = height;
            return this;
        }

        public Builder setFormat(String format) {
            this.format = format;
            return this;
        }

        public Builder setFsize(Integer fsize) {
            this.fsize = fsize;
            return this;
        }

        public Builder setDuration(Integer duration) {
            this.duration = duration;
            return this;
        }

        public Builder setHash(String hash) {
            this.hash = hash;
            return this;
        }

        public MessageBody build() {
            if (null != media_id) {
                return new MessageBody(media_id, media_crc32, width, height, format, fsize, duration, hash, extrasBuilder, numberExtrasBuilder, booleanExtrasBuilder);
            }
            return new MessageBody(text, extrasBuilder, numberExtrasBuilder, booleanExtrasBuilder);
        }
    }
} public class MessagePayload implements IModel {

    private static final String VERSION = "version";
    private static final String TARGET_TYPE = "target_type";
    private static final String FROM_TYPE = "from_type";
    private static final String MSG_TYPE = "msg_type";
    private static final String TARGET_ID = "target_id";
    private static final String FROM_ID = "from_id";
    private static final String TARGET_APP_KEY = "target_appkey";
    private static final String FROM_NAME = "from_name";
    private static final String TARGET_NAME = "target_name";
    private static final String MSG_BODY = "msg_body";
    private static final String NO_OFFLINE = "no_offline";
    private static final String NO_NOTIFICATION = "no_notification";
    private static final String NOTIFICATION = "notification";


    private static Gson gson = new Gson();

    private Integer mVersion;
    private String mTargetType;
    private String mTargetId;
    private String mFromType;
    private String mFromId;
    private String mTargetAppKey;
    private String mFromName;
    private String mTargetName;
    // 默认为false，表示需要离线存储
    private boolean mNoOffline = false;
    // 默认为false，表示在通知栏展示
    private boolean mNoNotification = false;
    private MessageType mMsgType;
    private MessageBody mMsgBody;
    private Notification mNotification;

    public MessagePayload(Integer version, String targetType, String targetId, String fromType, String fromId,
                          String targetAppKey, String fromName, String targetName, boolean noOffline,
                          boolean noNotification, MessageType msgType, MessageBody msgBody, Notification notification) {
        this.mVersion = version;
        this.mTargetType = targetType;
        this.mTargetId = targetId;
        this.mFromType = fromType;
        this.mFromId = fromId;
        this.mTargetAppKey = targetAppKey;
        this.mFromName = fromName;
        this.mTargetName = targetName;
        this.mNoOffline = noOffline;
        this.mNoNotification = noNotification;
        this.mMsgType = msgType;
        this.mMsgBody = msgBody;
        this.mNotification = notification;
    }

    public static Builder newBuilder(){
        return new Builder();
    }

    @Override
    public JsonElement toJSON() {
        JsonObject json = new JsonObject();

        if (null != mVersion) {
            json.addProperty(VERSION, mVersion);
        }
        if (null != mTargetType) {
            json.addProperty(TARGET_TYPE, mTargetType);
        }
        if (null != mTargetId) {
            json.addProperty(TARGET_ID, mTargetId);
        }
        if (null != mFromType) {
            json.addProperty(FROM_TYPE, mFromType);
        }
        if (null != mFromId) {
            json.addProperty(FROM_ID, mFromId);
        }
        if (null != mTargetAppKey) {
            json.addProperty(TARGET_APP_KEY, mTargetAppKey);
        }
        if (null != mFromName) {
            json.addProperty(FROM_NAME, mFromName);
        }
        if (null != mTargetName) {
            json.addProperty(TARGET_NAME, mTargetName);
        }
        if (mNoOffline) {
            json.addProperty(NO_OFFLINE, mNoOffline);
        }
        if (mNoNotification) {
            json.addProperty(NO_NOTIFICATION, mNoNotification);
        }
        if (null != mMsgType) {
            json.addProperty(MSG_TYPE, mMsgType.getValue());
        }
        if (null != mMsgBody) {
            json.add(MSG_BODY, mMsgBody.toJSON());
        }

        if (null != mNotification) {
            json.add(NOTIFICATION, mNotification.toJSON());
        }

        return json;
    }

    @Override
    public String toString() {
        return gson.toJson(toJSON());
    }

    public static class Builder {
        private Integer mVersion;
        private String mTargetType;
        private String mTargetId;
        private String mFromType;
        private String mFromId;
        private String mTargetAppKey;
        private String mFromName;
        private String mTargetName;
        // 默认为false，表示需要离线存储
        private boolean mNoOffline = false;
        // 默认为false，表示在通知栏展示
        private boolean mNoNotification = false;
        private MessageType mMsgType;
        private MessageBody mMsgBody;
        private Notification mNotification;

        public Builder setVersion(Integer version) {
            this.mVersion = version;
            return this;
        }

        public Builder setTargetType(String targetType) {
            this.mTargetType = targetType.trim();
            return this;
        }

        public Builder setTargetId(String targetId) {
            this.mTargetId = targetId.trim();
            return this;
        }

        public Builder setFromType(String fromType) {
            this.mFromType = fromType.trim();
            return this;
        }

        public Builder setFromId(String fromId) {
            this.mFromId = fromId.trim();
            return this;
        }
        
        public Builder setTargetAppKey(String appKey) {
            this.mTargetAppKey = appKey;
            return this;
        }
        
        public Builder setFromName(String name) {
            this.mFromName = name;
            return this;
        }
        
        public Builder setTargetName(String name) {
            this.mTargetName = name;
            return this;
        }
        
        public Builder setNoOffline(boolean noOffline) {
            this.mNoOffline = noOffline;
            return this;
        }
        
        public Builder setNoNotification(boolean noNotification) {
            this.mNoNotification = noNotification;
            return this;
        }

        public Builder setMessageType(MessageType msgType) {
            this.mMsgType = msgType;
            return this;
        }

        public Builder setMessageBody(MessageBody msgBody) {
            this.mMsgBody = msgBody;
            return this;
        }

        public Builder setNotification(Notification notification) {
            this.mNotification = notification;
            return this;
        }

        public MessagePayload build() {
            Preconditions.checkArgument(null != mVersion, "The version must not be empty!");
            Preconditions.checkArgument(StringUtils.isNotEmpty(mTargetType), "The target type must not be empty!");
            StringUtils.checkUsername(mTargetId);
            Preconditions.checkArgument(StringUtils.isNotEmpty(mFromType), "The from type must not be empty!");
            StringUtils.checkUsername(mFromId);
            Preconditions.checkArgument(mMsgType != null, "The message type must not be empty!");
            Preconditions.checkArgument(null != mMsgBody, "The message body must not be empty!");

            return new MessagePayload(mVersion, mTargetType, mTargetId, mFromType, mFromId, mTargetAppKey, mFromName,
                    mTargetName, mNoOffline, mNoNotification, mMsgType, mMsgBody, mNotification);
        }
    }
} public class CreateGroupResult extends BaseResult {

    @Expose Long gid;
    @Expose String owner_username;
    @Expose String name;
    @Expose JsonArray members_username;
    @Expose String desc;
    @Expose String ctime;
    @Expose String mtime;
    @Expose String appkey;
    @Expose String avatar;
    @Expose Integer MaxMemberCount;

    public Long getGid() {
        return gid;
    }

    public String getOwner_username() {
        return owner_username;
    }

    public String getName() {
        return name;
    }

    public JsonArray getMembers_username() {
        return members_username;
    }

    public String getDesc() {
        return desc;
    }

    public Integer getMaxMemberCount() {
        return MaxMemberCount;
    }

    public String getAppkey() {
        return  appkey;
    }

    public String getAvatar() {
        return avatar;
    }

    public String getCtime() {
        return ctime;
    }

    public String getMtime() {
        return mtime;
    }
} public class GroupClient extends BaseClient {

    private static final Logger LOG = LoggerFactory.getLogger(GroupClient.class);

    private String groupPath;

    /**
     * Create a Group Client with default parameters.
     *
     * @param appkey       The key of one application on JPush.
     * @param masterSecret API access secret of the appKey.
     */
    public GroupClient(String appkey, String masterSecret) {
        this(appkey, masterSecret, null, JMessageConfig.getInstance());
    }

    /**
     * Create a Group Client with a proxy.
     *
     * @param appkey       The key of one application on JPush.
     * @param masterSecret API access secret of the appKey.
     * @param proxy        The proxy, if there is no proxy, should be null.
     */
    public GroupClient(String appkey, String masterSecret, HttpProxy proxy) {
        this(appkey, masterSecret, proxy, JMessageConfig.getInstance());
    }

    /**
     * Create a Group Client by custom config.
     * If you are using JMessage privacy cloud or custom parameters, maybe this constructor is what you needed.
     *
     * @param appkey       The key of one application on JPush.
     * @param masterSecret API access secret of the appKey.
     * @param proxy        The proxy, if there is no proxy, should be null.
     * @param config       The client configuration. Can use JMessageConfig.getInstance() as default.
     */
    public GroupClient(String appkey, String masterSecret, HttpProxy proxy, JMessageConfig config) {
        super(appkey, masterSecret, proxy, config);
        this.groupPath = (String) config.get(JMessageConfig.GROUP_PATH);
    }

    public GroupInfoResult getGroupInfo(long gid)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(gid > 0, "gid should more than 0.");

        ResponseWrapper response = _httpClient.sendGet(_baseUrl + groupPath + "/" + gid);

        return GroupInfoResult.fromResponse(response, GroupInfoResult.class);
    }

    public MemberListResult getGroupMembers(long gid)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(gid > 0, "gid should more than 0.");

        ResponseWrapper response = _httpClient.sendGet(_baseUrl + groupPath + "/" + gid + "/members");

        return MemberListResult.fromResponse(response);
    }

    public GroupListResult getGroupListByAppkey(int start, int count)
            throws APIConnectionException, APIRequestException {
        if (start < 0 || count <= 0 || count > 500) {
            throw new IllegalArgumentException("negative index or count must more than 0 and less than 501");
        }
        ResponseWrapper response = _httpClient.sendGet(_baseUrl + groupPath + "?start=" + start + "&count=" + count);
        return GroupListResult.fromResponse(response, GroupListResult.class);
    }

    public CreateGroupResult createGroup(GroupPayload payload)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(!(null == payload), "group payload should not be null");

        ResponseWrapper response = _httpClient.sendPost(_baseUrl + groupPath, payload.toString());
        return CreateGroupResult.fromResponse(response, CreateGroupResult.class);
    }

    public ResponseWrapper addOrRemoveMembers(long gid, Members add, Members remove)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(gid > 0, "gid should more than 0.");

        if (null == add && null == remove) {
            Preconditions.checkArgument(false, "add list or remove list should not be null at the same time.");
        }

        JsonObject json = new JsonObject();

        if (null != add) {
            json.add("add", add.toJSON());
        }

        if (null != remove) {
            json.add("remove", remove.toJSON());
        }

        return _httpClient.sendPost(_baseUrl + groupPath + "/" + gid + "/members", json.toString());

    }

    public ResponseWrapper deleteGroup(long gid)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(gid > 0, "gid should more than 0.");
        return _httpClient.sendDelete(_baseUrl + groupPath + "/" + gid);
    }

    public ResponseWrapper updateGroupInfo(long gid, String groupName, String groupDesc, String avatar)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(gid > 0, "gid should more than 0.");

        if (StringUtils.isTrimedEmpty(groupName) && StringUtils.isTrimedEmpty(groupDesc)
                && StringUtils.isTrimedEmpty(avatar)) {
            Preconditions.checkArgument(false, "group name, group description and group avatar should not be null at the same time.");
        }

        JsonObject json = new JsonObject();
        if (StringUtils.isNotEmpty(groupName)) {
            groupName = groupName.trim();
            Preconditions.checkArgument(groupName.getBytes().length <= 64,
                    "The length of group name must not more than 64 bytes.");
            Preconditions.checkArgument(!StringUtils.isLineBroken(groupName),
                    "The group name must not contain line feed character.");
            json.addProperty(GroupPayload.GROUP_NAME, groupName);
        }

        if (StringUtils.isNotEmpty(groupDesc)) {
            groupDesc = groupDesc.trim();
            Preconditions.checkArgument(groupDesc.getBytes().length <= 250,
                    "The length of group description must not more than 250 bytes.");
            json.addProperty(GroupPayload.DESC, groupDesc);
        }

        if (StringUtils.isNotEmpty(avatar)) {
            avatar = avatar.trim();
            json.addProperty(GroupPayload.AVATAR, avatar);
        }

        return _httpClient.sendPut(_baseUrl + groupPath + "/" + gid, json.toString());
    }

    public ResponseWrapper changeGroupAdmin(long gid, String appKey, String username)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(gid > 0, "gid should more than 0.");

        if (StringUtils.isTrimedEmpty(appKey) && StringUtils.isTrimedEmpty(username)) {
            Preconditions.checkArgument(false, "appKey and username should not be null at the same time.");
        }

        JsonObject json = new JsonObject();
        if (StringUtils.isNotEmpty(appKey)){
            appKey=appKey.trim();
            json.addProperty("appKey",appKey);
        }

        if (StringUtils.isNotEmpty(username)){
            username=username.trim();
            json.addProperty("username",username);
        }

        return _httpClient.sendPut(_baseUrl + groupPath + "/owner/" + gid,json.toString());
    }

    public ResponseWrapper setGroupMemberSilence(long gid, boolean status, String[] usernames)
        throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(gid > 0, "gid should more than 0.");
        Preconditions.checkArgument(usernames != null && usernames.length > 0, "username array is invalid");
        return _httpClient.sendPut(_baseUrl + groupPath + "/messages/" + gid + "/silence?status=" + (status ? "true" : "false"), new Gson().toJson(usernames));
    }

} public class GroupInfoResult extends BaseResult {

    @Expose private Long gid;
    @Expose private String name;
    @Expose private String desc;
    @Expose private String appkey;
    @Expose private Integer level;
    @Expose private String ctime;
    @Expose private String mtime;
    @Expose private Integer MaxMemberCount;

    public Long getGid() {
        return gid;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

    public String getAppkey() {
        return appkey;
    }

    public Integer getLevel() {
        return level;
    }

    public String getCtime() {
        return ctime;
    }

    public String getMtime() {
        return mtime;
    }

    public Integer getMaxMemberCount() {
        return MaxMemberCount;
    }
} public class GroupListResult extends BaseResult {

    @Expose Integer total;
    @Expose Integer start;
    @Expose Integer count;
    @Expose List<GroupInfoResult> groups;

    public Integer getTotal() {
        return total;
    }

    public Integer getStart() {
        return start;
    }

    public Integer getCount() {
        return count;
    }

    public List<GroupInfoResult> getGroups() {
        return groups;
    }
} public class MemberListResult extends BaseResult {

    private MemberResult[] members;

    public static MemberListResult fromResponse(ResponseWrapper responseWrapper) {
        MemberListResult  result = new MemberListResult();
        if (responseWrapper.isServerResponse()) {
            result.members = _gson.fromJson(responseWrapper.responseContent, MemberResult[].class);
        } else {
            // nothing
        }
        result.setResponseWrapper(responseWrapper);
        return result;
    }

    public MemberResult[] getMembers() {
        return members;
    }
} public class MessageClient extends BaseClient {

    //private static final Logger LOG = LoggerFactory.getLogger(MessageClient.class);

    private String messagePath;
    private String v2_messagePath;
    private String reportBaseUrl;
    private String v2_userPath;
    private String v2_chatroomPath;


    public MessageClient(String appKey, String masterSecret) {
        this(appKey, masterSecret, null, JMessageConfig.getInstance());
    }

    public MessageClient(String appKey, String masterSecret, HttpProxy proxy, JMessageConfig config) {
        super(appKey, masterSecret, proxy, config);
        this.messagePath = (String) config.get(JMessageConfig.MESSAGE_PATH);
        this.reportBaseUrl = (String) config.get(JMessageConfig.API_REPORT_HOST_NAME);
        this.v2_userPath = (String) config.get(JMessageConfig.V2_USER_PATH);
        this.v2_messagePath = (String) config.get(JMessageConfig.V2_MESSAGE_PATH);
        this.v2_chatroomPath = (String) config.get(JMessageConfig.V2_CHATROOM_PATH);
    }

    public SendMessageResult sendMessage(MessagePayload payload)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(!(null == payload), "Message payload should not be null");

        ResponseWrapper response = _httpClient.sendPost(_baseUrl + messagePath, payload.toString());
        return SendMessageResult.fromResponse(response, SendMessageResult.class);
    }

    /**
     * Please use {@link cn.jmessage.api.reportv2.ReportClient#v2GetMessageList(int, String, String)}
     * Get message list from history, messages will store 60 days.
     * @param count Necessary parameter. The count of the message list.
     * @param begin_time Necessary parameter. The format must follow by 'yyyy-MM-dd HH:mm:ss'
     * @param end_time Necessary parameter. The format must follow by 'yyyy-MM-dd HH:mm:ss'
     * @return MessageListResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    @Deprecated
    public MessageListResult getMessageList(int count, String begin_time, String end_time)
            throws APIConnectionException, APIRequestException {
        if(count <= 0 || count > 1000) {
            throw new IllegalArgumentException("count must more than 0 and less than 1001");
        }

        String requestUrl = reportBaseUrl + v2_messagePath + "?count=" + count;
        String beginEncoded = null;
        String endEncoded = null;
        if (null != begin_time && StringUtils.isNotEmpty(begin_time) && null != end_time
                && StringUtils.isNotEmpty(end_time)) {
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date beginDate = format.parse(begin_time);
                Date endDate = format.parse(end_time);
                if (endDate.getTime() - beginDate.getTime() < 0) {
                    throw new IllegalArgumentException("end time must lager than begin time");
                } else if (endDate.getTime() - beginDate.getTime() > 7 * 24 * 60 * 60 * 1000) {
                    throw new IllegalArgumentException("end time lager than begin time over 7 days");
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }

            try {
                beginEncoded = URLEncoder.encode(begin_time, "utf-8");
                endEncoded = URLEncoder.encode(end_time, "utf-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
            requestUrl = requestUrl + "&begin_time=" + beginEncoded + "&end_time=" + endEncoded;
        } else {
            throw new IllegalArgumentException("begin time or end time is null or empty");
        }

        ResponseWrapper response = _httpClient.sendGet(requestUrl);
        return MessageListResult.fromResponse(response, MessageListResult.class);
    }

    /**
     * Please use {@link cn.jmessage.api.reportv2.ReportClient#v2GetUserMessagesByCursor(String, String)}
     * Get message list with cursor, the cursor will effective in 120 seconds. And will
     * return same count of messages as first request.
     * @param cursor First request will return cursor
     * @return MessageListResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    @Deprecated
    public MessageListResult getMessageListByCursor(String cursor)
            throws APIConnectionException, APIRequestException {
        if (null != cursor) {
            String requestUrl = reportBaseUrl + v2_messagePath + "?cursor=" + cursor;
            ResponseWrapper response = _httpClient.sendGet(requestUrl);
            return MessageListResult.fromResponse(response, MessageListResult.class);
        } else {
            throw new IllegalArgumentException("the cursor parameter should not be null");
        }
    }

    /**
     * Please use {@link cn.jmessage.api.reportv2.ReportClient#v2GetUserMessages(String, int, String, String)}
     * Get message list from user's record, messages will store 60 days.
     * @param username Necessary parameter.
     * @param count Necessary parameter. The count of the message list.
     * @param begin_time Optional parameter. The format must follow by 'yyyy-MM-dd HH:mm:ss'
     * @param end_time Optional parameter. The format must follow by 'yyyy-MM-dd HH:mm:ss'
     * @return MessageListResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    @Deprecated
    public MessageListResult getUserMessages(String username, int count, String begin_time, String end_time)
            throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);

        if(count <= 0 || count > 1000) {
            throw new IllegalArgumentException("count must more than 0 and less than 1001");
        }

        String requestUrl = reportBaseUrl + v2_userPath + "/" + username + "/messages?count=" + count;
        String beginEncoded = null;
        String endEncoded = null;
        if (null != begin_time && StringUtils.isNotEmpty(begin_time) && null != end_time
                && StringUtils.isNotEmpty(end_time)) {
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date beginDate = format.parse(begin_time);
                Date endDate = format.parse(end_time);
                if (endDate.getTime() - beginDate.getTime() < 0) {
                    throw new IllegalArgumentException("end time must lager than begin time");
                } else if (endDate.getTime() - beginDate.getTime() > 7 * 24 * 60 * 60 * 1000) {
                    throw new IllegalArgumentException("end time lager than begin time over 7 days");
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }

            try {
                beginEncoded = URLEncoder.encode(begin_time, "utf-8");
                endEncoded = URLEncoder.encode(end_time, "utf-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
            requestUrl = requestUrl + "&begin_time=" + beginEncoded + "&end_time=" + endEncoded;
        } else {
            throw new IllegalArgumentException("begin time or end time is null or empty");
        }

        ResponseWrapper response = _httpClient.sendGet(requestUrl);
        return MessageListResult.fromResponse(response, MessageListResult.class);
    }

    /**
     * Please use {@link cn.jmessage.api.reportv2.ReportClient#v2GetUserMessagesByCursor}
     * Get user's message list with cursor, the cursor will effective in 120 seconds.
     * And will return same count of messages as first request.
     * @param username Necessary parameter.
     * @param cursor First request will return cursor
     * @return MessageListResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    @Deprecated
    public MessageListResult getUserMessagesByCursor(String username, String cursor)
            throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);
        if (null != cursor) {
            String requestUrl = reportBaseUrl + v2_userPath + "/" + username + "/messages?cursor=" + cursor;
            ResponseWrapper response = _httpClient.sendGet(requestUrl);
            return MessageListResult.fromResponse(response, MessageListResult.class);
        } else {
            throw new IllegalArgumentException("the cursor parameter should not be null");
        }
    }

    /**
     * retract message
     * 消息撤回
     * @param username 用户名
     * @param messageId message id
     * @return No Content， Error Code：
     * 855001 out of retract message time, the effective time is within 3 minutes after sending message
     * 855003 the retract message is not exist
     * 855004 the message had been retracted
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper retractMessage(String username, long messageId)
            throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);
        return _httpClient.sendPost(_baseUrl + messagePath + "/" + username + "/" + messageId + "/retract", null);
    }

    /**
     * get chatroom history message
     * @param chatroomid
     * @param count must greater than zero and less than 1000
     * @param beginTime format yyyy-MM-dd HH:mm:ss
     * @param endTime format yyyy-MM-dd HH:mm:ss
     * @return
     */
    public ChatRoomHistoryResult getChatRoomHistory(Long chatroomid, int count, String beginTime, String endTime)
        throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(count > 0 && count <= 1000, "count is invalid");
        String beginEncoded = null;
        String endEncoded = null;
        if (null != beginTime && StringUtils.isNotEmpty(beginTime) && null != endTime
            && StringUtils.isNotEmpty(endTime)) {
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date beginDate = format.parse(beginTime);
                Date endDate = format.parse(endTime);
                if (endDate.getTime() - beginDate.getTime() < 0) {
                    throw new IllegalArgumentException("end time must lager than begin time");
                } else if (endDate.getTime() - beginDate.getTime() > 7 * 24 * 60 * 60 * 1000) {
                    throw new IllegalArgumentException("end time lager than begin time over 7 days");
                }
            } catch (ParseException e) {
                throw new IllegalArgumentException("parse time exception");
            }

            try {
                beginEncoded = URLEncoder.encode(beginTime, "utf-8");
                endEncoded = URLEncoder.encode(endTime, "utf-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            throw new IllegalArgumentException("begin time or end time is null or empty");
        }
        String requestUrl = reportBaseUrl + v2_chatroomPath + "/" + chatroomid + "/" + "messages" + "?count=" + count + "&begin_time=" + beginEncoded + "&end_time=" + endEncoded;
        ResponseWrapper response = _httpClient.sendGet(requestUrl);
        return ChatRoomHistoryResult.fromResponse(response, ChatRoomHistoryResult.class);
    }

    /**
     * get chatroom history message
     * @param chatroomid
     * @param cursor must greater than zero and less than 1000
     * @return
     */
    public ChatRoomHistoryResult getChatRoomHistory(Long chatroomid, String cursor)
        throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(cursor != null && cursor.length() > 0, "count is invalid");
        String requestUrl = reportBaseUrl + v2_chatroomPath + "/" + chatroomid + "/" + "messages" + "?cursor=" + cursor;
        ResponseWrapper response = _httpClient.sendGet(requestUrl);
        return ChatRoomHistoryResult.fromResponse(response, ChatRoomHistoryResult.class);
    }

} public class MessageListResult extends BaseResult {

    @Expose Integer total;
    @Expose String cursor;
    @Expose Integer count;
    @Expose MessageResult[] messages;

    public static MessageListResult fromResponse(ResponseWrapper responseWrapper) {
        MessageListResult result = new MessageListResult();
        if (responseWrapper.isServerResponse()) {
            result.messages = _gson.fromJson(responseWrapper.responseContent, MessageResult[].class);
        }
        result.setResponseWrapper(responseWrapper);
        return result;
    }


    public Integer getTotal() {
        return total;
    }

    public String getCursor() {
        return cursor;
    }

    public Integer getCount() {
        return count;
    }

    public MessageResult[] getMessages() {
        return messages;
    }

} public enum MessageType {
    TEXT("text"),
    IMAGE("image"),
    VOICE("voice"),
    CUSTOM("custom");

    private String value;

    private MessageType(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
} public class SendMessageResult extends BaseResult{

    @Expose Long msg_id;
    @Expose Long msg_ctime;

    public Long getMsg_id() {
        return msg_id;
    }

    public Long getMsgCtime() {
        return msg_ctime;
    }
} public class GroupStatListResult extends BaseResult {

    @Expose private GroupStatResult[] array;

    public static GroupStatListResult fromResponse(ResponseWrapper responseWrapper) {
        GroupStatListResult result = new GroupStatListResult();
        if (responseWrapper.isServerResponse()) {
            result.array = _gson.fromJson(responseWrapper.responseContent, GroupStatResult[].class);
        }
        result.setResponseWrapper(responseWrapper);
        return result;
    }

    public GroupStatResult[] getArray() {
        return array;
    }

    public class GroupStatResult {
        @Expose private String date;
        @Expose private Integer active_group;
        @Expose private Integer total_group;
        @Expose private Integer new_group;

        public String getDate() {
            return date;
        }

        public Integer getActive_group() {
            return active_group;
        }

        public Integer getTotal_group() {
            return total_group;
        }

        public Integer getNew_group() {
            return new_group;
        }
    }

} public class MessageStatListResult extends BaseResult {

    @Expose private List<SendMsgStat> send_msg_stat = new ArrayList<SendMsgStat>();
    @Expose private MsgStat group_msg_stat;
    @Expose private MsgStat single_msg_stat;

    public List<SendMsgStat> getSend_msg_stat() {
        return send_msg_stat;
    }

    public MsgStat getGroup_msg_stat() {
        return group_msg_stat;
    }

    public MsgStat getSingle_msg_stat() {
        return single_msg_stat;
    }

    public class SendMsgStat {
        @Expose private String time;
        @Expose private Long group_send_msg;
        @Expose private Long single_send_msg;

        public String getTime() {
            return time;
        }

        public Long getGroup_send_msg() {
            return group_send_msg;
        }

        public Long getSingle_send_msg() {
            return single_send_msg;
        }
    }

    public class MsgStat {
        @Expose private Long txt_msg;
        @Expose private Long image_msg;
        @Expose private Long voice_msg;
        @Expose private Long other_msg;

        public Long getTxt_msg() {
            return txt_msg;
        }

        public Long getImage_msg() {
            return image_msg;
        }

        public Long getVoice_msg() {
            return voice_msg;
        }

        public Long getOther_msg() {
            return other_msg;
        }
    }
} public class ReportClient extends BaseClient {

    private String mBaseReportPath;
    private String mV2MessagePath;
    private String mV2UserPath;
    private String mV2GroupPath;
    private String mV2StatisticPath;

    public ReportClient(String appKey, String masterSecret) {
        this(appKey, masterSecret, null, JMessageConfig.getInstance());
    }

    public ReportClient(String appKey, String masterSecret, HttpProxy proxy) {
        this(appKey, masterSecret, proxy, JMessageConfig.getInstance());
    }

    /**
     * Create a JMessage Base Client
     *
     * @param appKey       The KEY of one application on JPush.
     * @param masterSecret API access secret of the appKey.
     * @param proxy        The proxy, if there is no proxy, should be null.
     * @param config       The client configuration. Can use JMessageConfig.getInstance() as default.
     */
    public ReportClient(String appKey, String masterSecret, HttpProxy proxy, JMessageConfig config) {
        super(appKey, masterSecret, proxy, config);
        mBaseReportPath = (String) config.get(JMessageConfig.API_REPORT_HOST_NAME);
        mV2MessagePath = (String) config.get(JMessageConfig.V2_MESSAGE_PATH);
        mV2UserPath = (String) config.get(JMessageConfig.V2_USER_PATH);
        mV2GroupPath = (String) config.get(JMessageConfig.V2_GROUP_PATH);
        mV2StatisticPath = (String) config.get(JMessageConfig.V2_STATISTIC_PATH);
    }

    /**
     * Get message list from history, messages will store 60 days.
     * @param count Necessary parameter. The count of the message list.
     * @param begin_time Necessary parameter. The format must follow by 'yyyy-MM-dd HH:mm:ss'
     * @param end_time Necessary parameter. The format must follow by 'yyyy-MM-dd HH:mm:ss'
     * @return MessageListResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public MessageListResult v2GetMessageList(int count, String begin_time, String end_time)
            throws APIConnectionException, APIRequestException {
        if(count <= 0 || count > 1000) {
            throw new IllegalArgumentException("count must more than 0 and less than 1001");
        }

        String requestUrl = mBaseReportPath + mV2MessagePath + "?count=" + count;
        String beginEncoded = null;
        String endEncoded = null;
        if (null != begin_time && StringUtils.isNotEmpty(begin_time) && null != end_time
                && StringUtils.isNotEmpty(end_time)) {
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date beginDate = format.parse(begin_time);
                Date endDate = format.parse(end_time);
                if (endDate.getTime() - beginDate.getTime() < 0) {
                    throw new IllegalArgumentException("end time must lager than begin time");
                } else if (endDate.getTime() - beginDate.getTime() > 7 * 24 * 60 * 60 * 1000) {
                    throw new IllegalArgumentException("end time lager than begin time over 7 days");
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }

            try {
                beginEncoded = URLEncoder.encode(begin_time, "utf-8");
                endEncoded = URLEncoder.encode(end_time, "utf-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
            requestUrl = requestUrl + "&begin_time=" + beginEncoded + "&end_time=" + endEncoded;
        } else {
            throw new IllegalArgumentException("begin time or end time is null or empty");
        }

        ResponseWrapper response = _httpClient.sendGet(requestUrl);
        return MessageListResult.fromResponse(response, MessageListResult.class);
    }

    /**
     * Get message list with cursor, the cursor will effective in 120 seconds. And will
     * return same count of messages as first request.
     * @param cursor First request will return cursor
     * @return MessageListResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public MessageListResult v2GetMessageListByCursor(String cursor)
            throws APIConnectionException, APIRequestException {
        if (null != cursor) {
            String requestUrl = mBaseReportPath + mV2MessagePath + "?cursor=" + cursor;
            ResponseWrapper response = _httpClient.sendGet(requestUrl);
            return MessageListResult.fromResponse(response, MessageListResult.class);
        } else {
            throw new IllegalArgumentException("the cursor parameter should not be null");
        }
    }

    /**
     * Get message list from user's record, messages will store 60 days.
     * @param username Necessary parameter.
     * @param count Necessary parameter. The count of the message list.
     * @param begin_time Optional parameter. The format must follow by 'yyyy-MM-dd HH:mm:ss'
     * @param end_time Optional parameter. The format must follow by 'yyyy-MM-dd HH:mm:ss'
     * @return MessageListResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public MessageListResult v2GetUserMessages(String username, int count, String begin_time, String end_time)
            throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);

        if(count <= 0 || count > 1000) {
            throw new IllegalArgumentException("count must more than 0 and less than 1001");
        }

        String requestUrl = mBaseReportPath + mV2UserPath + "/" + username + "/messages?count=" + count;
        String beginEncoded = null;
        String endEncoded = null;
        if (null != begin_time && StringUtils.isNotEmpty(begin_time) && null != end_time
                && StringUtils.isNotEmpty(end_time)) {
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date beginDate = format.parse(begin_time);
                Date endDate = format.parse(end_time);
                if (endDate.getTime() - beginDate.getTime() < 0) {
                    throw new IllegalArgumentException("end time must lager than begin time");
                } else if (endDate.getTime() - beginDate.getTime() > 7 * 24 * 60 * 60 * 1000) {
                    throw new IllegalArgumentException("end time lager than begin time over 7 days");
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }

            try {
                beginEncoded = URLEncoder.encode(begin_time, "utf-8");
                endEncoded = URLEncoder.encode(end_time, "utf-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
            requestUrl = requestUrl + "&begin_time=" + beginEncoded + "&end_time=" + endEncoded;
        } else {
            throw new IllegalArgumentException("begin time or end time is null or empty");
        }

        ResponseWrapper response = _httpClient.sendGet(requestUrl);
        return MessageListResult.fromResponse(response, MessageListResult.class);
    }

    /**
     * Get user's message list with cursor, the cursor will effective in 120 seconds.
     * And will return same count of messages as first request.
     * @param username Necessary parameter.
     * @param cursor First request will return cursor
     * @return MessageListResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public MessageListResult v2GetUserMessagesByCursor(String username, String cursor)
            throws APIConnectionException, APIRequestException {
        StringUtils.checkUsername(username);
        if (null != cursor) {
            String requestUrl = mBaseReportPath + mV2UserPath + "/" + username + "/messages?cursor=" + cursor;
            ResponseWrapper response = _httpClient.sendGet(requestUrl);
            return MessageListResult.fromResponse(response, MessageListResult.class);
        } else {
            throw new IllegalArgumentException("the cursor parameter should not be null");
        }
    }

    /**
     * Get group message list
     * @param groupId group id
     * @param count message list count
     * @param begin_time Necessary parameter. The format must follow by 'yyyy-MM-dd HH:mm:ss'
     * @param end_time Necessary parameter. The format must follow by 'yyyy-MM-dd HH:mm:ss'
     * @return {@link MessageListResult}
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public MessageListResult v2GetGroupMessageList(long groupId, int count, String begin_time, String end_time)
            throws APIConnectionException, APIRequestException{
        if(count <= 0 || count > 1000) {
            throw new IllegalArgumentException("count must more than 0 and less than 1001");
        }
        String requestUrl = mBaseReportPath + mV2GroupPath + "/" + groupId + "/messages?count=" + count;
        String beginEncoded = null;
        String endEncoded = null;
        if (null != begin_time && StringUtils.isNotEmpty(begin_time) && null != end_time
                && StringUtils.isNotEmpty(end_time)) {
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date beginDate = format.parse(begin_time);
                Date endDate = format.parse(end_time);
                if (endDate.getTime() - beginDate.getTime() < 0) {
                    throw new IllegalArgumentException("end time must lager than begin time");
                } else if (endDate.getTime() - beginDate.getTime() > 7 * 24 * 60 * 60 * 1000) {
                    throw new IllegalArgumentException("end time lager than begin time over 7 days");
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }

            try {
                beginEncoded = URLEncoder.encode(begin_time, "utf-8");
                endEncoded = URLEncoder.encode(end_time, "utf-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
            requestUrl = requestUrl + "&begin_time=" + beginEncoded + "&end_time=" + endEncoded;
        } else {
            throw new IllegalArgumentException("begin time or end time is null or empty");
        }

        ResponseWrapper response = _httpClient.sendGet(requestUrl);
        return MessageListResult.fromResponse(response, MessageListResult.class);
    }

    // ==========================       VIP Only     =======================
    /**
     * Get user statistic, now time unit only supports DAY
     * @param startTime start time, format: yyyy-MM-dd
     * @param duration 0-60
     * @return {@link UserStatListResult}
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public UserStatListResult getUserStatistic(String startTime, int duration)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(verifyDateFormat("DAY", startTime), "Illegal date format");
        Preconditions.checkArgument(0 <= duration && duration <= 60, " 0 <= duration <= 60");
        String url = mBaseReportPath + mV2StatisticPath + "/users?time_unit=DAY&start=" + startTime + "&duration=" + duration;
        ResponseWrapper responseWrapper = _httpClient.sendGet(url);
        return UserStatListResult.fromResponse(responseWrapper);
    }

    /**
     * Get message statistic. Detail instructions please refer to https://docs.jiguang.cn/jmessage/server/rest_api_im_report_v2/#_6
     * @param timeUnit MUST be one of HOUR, DAY, MONTH
     * @param start start time, when timeUnit is HOUR, format: yyyy-MM-dd HH,
     * @param duration depends on timeUnit, the duration has limit
     * @return {@link MessageStatListResult}
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public MessageStatListResult getMessageStatistic(String timeUnit, String start, int duration)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(verifyDateFormat(timeUnit, start), "Time format error, please check your argument");
        if (timeUnit.equals("HOUR")) {
            Preconditions.checkArgument(0 <= duration && duration <= 24, "time unit is HOUR, duration must between 0 and 24 ");
        } else if (timeUnit.equals("DAY")) {
            Preconditions.checkArgument(0 <= duration && duration <= 60, "time unit is DAY, duration must between 0 and 60");
        } else if (timeUnit.equals("MONTH")) {
            Preconditions.checkArgument(0 <= duration && duration <= 2, "time unit is MONTH, duration must between 0 and 2");
        } else throw new IllegalArgumentException("Time unit error");
        String url = mBaseReportPath + mV2StatisticPath + "/messages?time_unit=" + timeUnit + "&start=" + start + "&duration=" + duration;
        ResponseWrapper responseWrapper = _httpClient.sendGet(url);
        return MessageStatListResult.fromResponse(responseWrapper, MessageStatListResult.class);
    }

    /**
     * Get group statistic, time unit only supports DAY now.
     * @param start Format: yyyy-MM-dd
     * @param duration duration must between 0 and 60
     * @return {@link GroupStatListResult}
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public GroupStatListResult getGroupStatistic(String start, int duration)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(verifyDateFormat("DAY", start), "Illegal date format");
        Preconditions.checkArgument(0 <= duration && duration <= 60, " 0 <= duration <= 60");
        String url = mBaseReportPath + mV2StatisticPath + "/groups?time_unit=DAY&start=" + start + "&duration=" + duration;
        ResponseWrapper responseWrapper = _httpClient.sendGet(url);
        return GroupStatListResult.fromResponse(responseWrapper);
    }

    private boolean verifyDateFormat(String timeUnit, String formatStr) {
        try {
            String format;
            if (timeUnit.equals("HOUR")) {
                format = "yyyy-MM-dd HH";
            } else if (timeUnit.equals("DAY")) {
                format = "yyyy-MM-dd";
            } else format = "yyyy-MM";
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
            simpleDateFormat.parse(formatStr);
        } catch (ParseException e) {
            return false;
        }
        return true;
    }

} public class UserStatListResult extends BaseResult {

    private UserStatResult[] array;

    public static UserStatListResult fromResponse(ResponseWrapper responseWrapper) {
        UserStatListResult result = new UserStatListResult();
        if (responseWrapper.isServerResponse()) {
            result.array = _gson.fromJson(responseWrapper.responseContent, UserStatResult[].class);
        }
        result.setResponseWrapper(responseWrapper);
        return result;
    }

    public UserStatResult[] getArray() {
        return array;
    }
} public class DownloadResult extends BaseResult {

    @Expose String url;

    public String getUrl() {
        return url;
    }
} public class ResourceClient extends BaseClient {

    private static Logger LOG = LoggerFactory.getLogger(ResourceClient.class);

    private String resourcePath;
    private String authCode;


    public ResourceClient(String appkey, String masterSecret) {
        this(appkey, masterSecret, null, JMessageConfig.getInstance());
    }

    public ResourceClient(String appkey, String masterSecret, HttpProxy proxy) {
        this(appkey, masterSecret, proxy, JMessageConfig.getInstance());
    }

    /**
     * Create a JMessage Base Client
     *
     * @param appkey       The KEY of one application on JPush.
     * @param masterSecret API access secret of the appKey.
     * @param proxy        The proxy, if there is no proxy, should be null.
     * @param config       The client configuration. Can use JMessageConfig.getInstance() as default.
     */
    public ResourceClient(String appkey, String masterSecret, HttpProxy proxy, JMessageConfig config) {
        super(appkey, masterSecret, proxy, config);
        this.resourcePath = (String) config.get(JMessageConfig.RESOURCE_PATH);
        this.authCode = ServiceHelper.getBasicAuthorization(appkey, masterSecret);
    }

    /**
     * Download file with mediaId, will return DownloadResult which include url.
     * @param mediaId Necessary
     * @return DownloadResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public DownloadResult downloadFile(String mediaId)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(null != mediaId, "mediaId is necessary");

        ResponseWrapper response = _httpClient.sendGet(_baseUrl + resourcePath + "?mediaId=" + mediaId);
        return DownloadResult.fromResponse(response, DownloadResult.class);
    }

    /**
     * Upload file, only support image file(jpg, bmp, gif, png) currently,
     * file size should not larger than 8M.
     * @param path Necessary, the native path of the file you want to upload
     * @param fileType should be "image" or "file" or "voice"
     * @return UploadResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public UploadResult uploadFile(String path, String fileType)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(null != path, "filename is necessary");
        Preconditions.checkArgument(fileType.equals("image") || fileType.equals("file") || fileType.equals("voice"), "Illegal file type!");
        File file = new File(path);
        if (file.exists() && file.isFile()) {
            long fileSize = file.length();
            if (fileSize > 8 * 1024 * 1024) {
                throw new IllegalArgumentException("File size should not larger than 8M");
            }
            try {
                // 换行符
                final String newLine = "\r\n";
                final String boundaryPrefix = "--";
                // 定义数据分隔线
                String BOUNDARY = "========7d4a6d158c9";
                URL url = new URL(_baseUrl + resourcePath + "?type=" + fileType);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setDoOutput(true);
                conn.setDoInput(true);
                conn.setUseCaches(false);
                conn.setRequestProperty("connection", "Keep-Alive");
                conn.setRequestProperty("Charset", "UTF-8");
                conn.setRequestProperty("Authorization", this.authCode);
                conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
                OutputStream out = new DataOutputStream(conn.getOutputStream());
                StringBuilder sb = new StringBuilder();
                sb.append(boundaryPrefix);
                sb.append(BOUNDARY);
                sb.append(newLine);
                sb.append("Content-Disposition: form-data;name=\"" + fileType + "\";filename=\"" + path + "\"" + newLine);
                sb.append("Content-Type:application/octet-stream");
                // 参数头设置完以后需要两个换行，然后才是参数内容
                sb.append(newLine);
                sb.append(newLine);
                out.write(sb.toString().getBytes());
                DataInputStream dataInputStream = new DataInputStream(new FileInputStream(file));
                byte[] bufferOut = new byte[1024];
                int bytes = 0;
                while ((bytes = dataInputStream.read(bufferOut)) != -1) {
                    out.write(bufferOut, 0, bytes);
                }
                out.write(newLine.getBytes());
                dataInputStream.close();
                // 定义最后数据分隔线，即--加上BOUNDARY再加上--。
                byte[] end_data = (newLine + boundaryPrefix + BOUNDARY + boundaryPrefix + newLine)
                        .getBytes();
                // 写上结尾标识
                out.write(end_data);
                out.flush();
                out.close();
                LOG.debug("Send request - POST"  + " " + url);

                int status1 = conn.getResponseCode();
                StringBuffer stringBuffer = new StringBuffer();
                InputStream in = null;
                if(status1 / 100 == 2) {
                    in = conn.getInputStream();
                } else {
                    in = conn.getErrorStream();
                }
                if(null != in) {
                    InputStreamReader responseContent = new InputStreamReader(in, "UTF-8");
                    char[] quota = new char[1024];

                    int remaining;
                    while((remaining = responseContent.read(quota)) > 0) {
                        stringBuffer.append(quota, 0, remaining);
                    }
                }
                ResponseWrapper wrapper = new ResponseWrapper();
                String responseContent1 = stringBuffer.toString();
                wrapper.responseCode = status1;
                wrapper.responseContent = responseContent1;
                String quota1 = conn.getHeaderField("X-Rate-Limit-Limit");
                String remaining1 = conn.getHeaderField("X-Rate-Limit-Remaining");
                String reset = conn.getHeaderField("X-Rate-Limit-Reset");
                wrapper.setRateLimit(quota1, remaining1, reset);
                if(status1 >= 200 && status1 < 300) {
                    LOG.debug("Succeed to get response OK - responseCode:" + status1);
                    LOG.debug("Response Content - " + responseContent1);
                } else {
                    if(status1 < 300 || status1 >= 400) {
                        LOG.warn("Got error response - responseCode:" + status1 + ", responseContent:" + responseContent1);
                        wrapper.setErrorObject();
                        throw new APIRequestException(wrapper);
                    }

                    LOG.warn("Normal response but unexpected - responseCode:" + status1 + ", responseContent:" + responseContent1);
                }
                return UploadResult.fromResponse(wrapper, UploadResult.class);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            throw new IllegalArgumentException("File name is invalid, please check again");
        }
        return null;
    }

} public class UploadResult extends BaseResult {

    @Expose String media_id;
    @Expose Long media_crc32;
    @Expose Integer width;
    @Expose Integer height;
    @Expose String format;
    @Expose Integer fsize;
    @Expose String hash;
    @Expose String fname;

    public String getMediaId() {
        return media_id;
    }

    public Long getMediaCrc32() {
        return media_crc32;
    }

    public Integer getWidth() {
        return width;
    }

    public Integer getHeight() {
        return height;
    }

    public String getFormat() {
        return format;
    }

    public Integer getFileSize() {
        return fsize;
    }

    public String getHash() {
        return hash;
    }

    public String getFileName() {
        return fname;
    }
} public class SensitiveWordClient extends BaseClient {

    private String sensitiveWordPath;

    public SensitiveWordClient(String appKey, String masterSecret) {
        this(appKey, masterSecret, null, JMessageConfig.getInstance());
    }

    /**
     * Create a JMessage Base Client
     *
     * @param appkey       The KEY of one application on JPush.
     * @param masterSecret API access secret of the appKey.
     * @param proxy        The proxy, if there is no proxy, should be null.
     * @param config       The client configuration. Can use JMessageConfig.getInstance() as default.
     */
    public SensitiveWordClient(String appkey, String masterSecret, HttpProxy proxy, JMessageConfig config) {
        super(appkey, masterSecret, proxy, config);
        this.sensitiveWordPath = (String) config.get(JMessageConfig.SENSITIVE_WORD_PATH);
    }

    /**
     * Add sensitive words
     * @param words String array
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper addSensitiveWords(String...words) throws APIConnectionException, APIRequestException {
        JsonArray array = new JsonArray();
        for (String word: words) {
            Preconditions.checkArgument(word.length() <= 10, "one word's max length is 10");
            array.add(new JsonPrimitive(word));
        }
        return _httpClient.sendPost(_baseUrl + sensitiveWordPath, array.toString());
    }

    /**
     * Add sensitive words
     * @param words String Set
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper addSensitiveWords(Set<String> words) throws APIConnectionException, APIRequestException {
        JsonArray array = new JsonArray();
        for (String word : words) {
            Preconditions.checkArgument(word.length() <= 10, "one word's max length is 10");
            array.add(new JsonPrimitive(word));
        }
        return _httpClient.sendPost(_baseUrl + sensitiveWordPath, array.toString());
    }

    /**
     * Update sensitive word
     * @param newWord new word
     * @param oldWord old word
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper updateSensitiveWord(String newWord, String oldWord)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(newWord.length() <= 10, "one word's max length is 10");
        Preconditions.checkArgument(oldWord.length() <= 10, "one word's max length is 10");
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("new_word", newWord);
        jsonObject.addProperty("old_word", oldWord);
        return _httpClient.sendPut(_baseUrl + sensitiveWordPath, jsonObject.toString());
    }

    /**
     * Delete sensitive word
     * @param word word to be deleted
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper deleteSensitiveWord(String word) throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(word.length() <= 10, "one word's max length is 10");
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("word", word);
        return _httpClient.sendDelete(_baseUrl + sensitiveWordPath, jsonObject.toString());
    }

    /**
     * Get sensitive word list
     * @param start the begin of the list
     * @param count the count of the list
     * @return SensitiveWordListResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public SensitiveWordListResult getSensitiveWordList(int start, int count)
            throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(start >= 0, "start should not less than 0");
        Preconditions.checkArgument(count <= 2000, "count should not bigger than 2000");
        ResponseWrapper responseWrapper = _httpClient.sendGet(_baseUrl + sensitiveWordPath + "?start=" + start + "&count=" + count);
        return _gson.fromJson(responseWrapper.responseContent, SensitiveWordListResult.class);
    }

    /**
     * Update sensitive word status
     * @param status 1 represent turn on filtering, 0 represent turn off.
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper updateSensitiveWordStatus(int status) throws APIConnectionException, APIRequestException {
        Preconditions.checkArgument(status == 0 || status == 1, "status should be 0 or 1");
        return _httpClient.sendPut(_baseUrl + sensitiveWordPath + "/status?status=" + status, null);
    }

    /**
     * Get sensitive word status
     * @return status of sensitive word
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public SensitiveWordStatusResult getSensitiveWordStatus() throws APIConnectionException, APIRequestException {
        ResponseWrapper responseWrapper = _httpClient.sendGet(_baseUrl + sensitiveWordPath + "/status");
        return _gson.fromJson(responseWrapper.responseContent, SensitiveWordStatusResult.class);
    }
} public class SensitiveWordListResult extends BaseResult {

    @Expose Integer start;
    @Expose Integer count;
    @Expose Integer total;
    @Expose List<Word> words = new ArrayList<Word>();

    public static class Word {
        @Expose String name;
        @Expose String itime;

        public String getName() {
            return name;
        }

        public String getItime() {
            return itime;
        }
    }

    public Integer getStart() {
        return start;
    }

    public Integer getCount() {
        return count;
    }

    public Integer getTotal() {
        return total;
    }

    public List<Word> getWords() {
        return words;
    }
} public class SensitiveWordStatusResult extends BaseResult {

    @Expose Integer status;

    public Integer getStatus() {
        return status;
    }
}
import cn.jiguang.common.connection.HttpProxy;
import cn.jiguang.common.connection.IHttpClient;
import cn.jiguang.common.resp.APIConnectionException;
import cn.jiguang.common.resp.APIRequestException;
import cn.jiguang.common.resp.ResponseWrapper;
import cn.jmessage.api.chatroom.ChatRoomClient;
import cn.jmessage.api.chatroom.ChatRoomListResult;
import cn.jmessage.api.chatroom.ChatRoomMemberList;
import cn.jmessage.api.chatroom.CreateChatRoomResult;
import cn.jmessage.api.common.model.chatroom.ChatRoomPayload;
import cn.jmessage.api.common.model.group.GroupPayload;
import cn.jmessage.api.common.model.group.GroupShieldPayload;
import cn.jmessage.api.crossapp.CrossAppClient;
import cn.jmessage.api.common.JMessageConfig;
import cn.jmessage.api.common.model.*;
import cn.jmessage.api.common.model.cross.CrossBlacklist;
import cn.jmessage.api.common.model.cross.CrossFriendPayload;
import cn.jmessage.api.common.model.cross.CrossGroup;
import cn.jmessage.api.common.model.cross.CrossNoDisturb;
import cn.jmessage.api.common.model.friend.FriendNote;
import cn.jmessage.api.common.model.message.MessageBody;
import cn.jmessage.api.common.model.message.MessagePayload;
import cn.jmessage.api.group.CreateGroupResult;
import cn.jmessage.api.group.GroupClient;
import cn.jmessage.api.group.GroupInfoResult;
import cn.jmessage.api.group.GroupListResult;
import cn.jmessage.api.group.MemberListResult;
import cn.jmessage.api.message.MessageClient;
import cn.jmessage.api.message.MessageListResult;
import cn.jmessage.api.message.MessageType;
import cn.jmessage.api.message.SendMessageResult;
import cn.jmessage.api.reportv2.GroupStatListResult;
import cn.jmessage.api.reportv2.MessageStatListResult;
import cn.jmessage.api.reportv2.ReportClient;
import cn.jmessage.api.reportv2.UserStatListResult;
import cn.jmessage.api.resource.DownloadResult;
import cn.jmessage.api.resource.ResourceClient;
import cn.jmessage.api.resource.UploadResult;
import cn.jmessage.api.sensitiveword.SensitiveWordClient;
import cn.jmessage.api.sensitiveword.SensitiveWordListResult;
import cn.jmessage.api.sensitiveword.SensitiveWordStatusResult;
import cn.jmessage.api.user.*;
import java.util.Set;
    }

    /**
     * Add users from cross app.
     * @param username Necessary
     * @param payload CrossFriendPayload
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper addCrossFriends(String username, CrossFriendPayload payload)
            throws APIConnectionException, APIRequestException {
        return _crossAppClient.addCrossFriends(username, payload);
    }

    /**
     * Delete cross app friends
     * @param username Necessary
     * @param payload CrossFriendPayload
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper deleteCrossFriends(String username, CrossFriendPayload payload)
            throws APIConnectionException, APIRequestException {
        return _crossAppClient.deleteCrossFriends(username, payload);
    }

    // ------------------------------- Sensitive Word API

    /**
     * Add sensitive words
     * @param words String array
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper addSensitiveWords(String...words) throws APIConnectionException, APIRequestException {
        return _sensitiveWordClient.addSensitiveWords(words);
    }

    /**
     * Add sensitive words
     * @param words String Set
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper addSensitiveWords(Set<String> words) throws APIConnectionException, APIRequestException {
        return _sensitiveWordClient.addSensitiveWords(words);
    }

    /**
     * Update sensitive word
     * @param newWord new word
     * @param oldWord old word
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper updateSensitiveWord(String newWord, String oldWord)
            throws APIConnectionException, APIRequestException {
        return _sensitiveWordClient.updateSensitiveWord(newWord, oldWord);
    }

    /**
     * Delete sensitive word
     * @param word word to be deleted
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper deleteSensitiveWord(String word) throws APIConnectionException, APIRequestException {
        return _sensitiveWordClient.deleteSensitiveWord(word);
    }

    /**
     * Get sensitive word list
     * @param start the begin of the list
     * @param count the count of the list
     * @return SensitiveWordListResult
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public SensitiveWordListResult getSensitiveWordList(int start, int count)
            throws APIConnectionException, APIRequestException {
        return _sensitiveWordClient.getSensitiveWordList(start, count);
    }

    /**
     * Update sensitive word status
     * @param status 1 represent turn on filtering, 0 represent turn off.
     * @return No content
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public ResponseWrapper updateSensitiveWordStatus(int status) throws APIConnectionException, APIRequestException {
        return _sensitiveWordClient.updateSensitiveWordStatus(status);
    }

    /**
     * Get sensitive word status
     * @return status of sensitive word
     * @throws APIConnectionException connect exception
     * @throws APIRequestException request exception
     */
    public SensitiveWordStatusResult getSensitiveWordStatus() throws APIConnectionException, APIRequestException {
        return _sensitiveWordClient.getSensitiveWordStatus();
    }

    // =========================    Chat room API    ==============================

    /**
     * Create chat room
     *
     * @param payload {@link ChatRoomPayload}
     * @return result contains room id
     * @throws APIConnectionException connect exception
     * @throws APIRequestException    request exception
     */
    public CreateChatRoomResult createChatRoom(ChatRoomPayload payload) throws APIConnectionException, APIRequestException {