illab.nabal.proxy.TwitterProxy.java Source code

Java tutorial

Introduction

Here is the source code for illab.nabal.proxy.TwitterProxy.java

Source

/*
 * Copyright (C) 2013-2014 Tan Jung
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package illab.nabal.proxy;

import illab.nabal.agent.Operation;
import illab.nabal.agent.dto.TwitterMessage;
import illab.nabal.agent.dto.TwitterPost;
import illab.nabal.agent.dto.TwitterProfile;
import illab.nabal.exception.NetworkException;
import illab.nabal.exception.SystemException;
import illab.nabal.settings.SnsProperties;
import illab.nabal.settings.SocialNetwork;
import illab.nabal.settings.SystemProperties;
import illab.nabal.util.ParameterHelper;
import illab.nabal.util.StringHelper;
import illab.nabal.util.Util;

import java.util.ArrayList;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONObject;

import android.content.Context;
import android.text.TextUtils;

/**
 * Implementations of Twitter APIs.
 * 
 * @version 1.0, 02/10/14
 * @author <a href="mailto:tanito.jung@gmail.com">Tan Jung</a>
 */
class TwitterProxy extends AbstractProxy implements TwitterDelegate {
    //private final static String TAG = "TwitterProxy";

    /**
     * Twitter web domain.
     */
    private final static String WEB_HOME_URL = "http://twitter.com/";

    /**
     * Dummy integer parameter for unspecified cursor.
     */
    private final static int UNSPECIFIED_CURSOR = -1;

    /**
     * Dummy integer parameter for unspecified GPS latitude/longitude.
     */
    private final static float UNSPECIFIED_GPS_COORDINATION = -999F;

    /**
     * Dummy integer parameter for unspecified post ID.
     */
    private final static long UNSPECIFIED_POST_ID = -1L;

    /**
     * Twitter network context.
     */
    private final TwitterContext mTwitterContext;

    /**
     * Constructor for Twitter covert op proxy.
     * 
     * @param context
      * @param systemProperties
      * @param snsProperties
     */
    TwitterProxy(Context context, SystemProperties systemProperties, SnsProperties snsProperties) {
        mContext = context;
        mTwitterContext = (TwitterContext) (mNetworkContext = new TwitterContext(mContext,
                new NetworkSessionListener(), systemProperties, snsProperties));
    }

    /**
     * Constructor for Twitter proxy.
     * 
     * @param alias
     * @param context
      * @param systemProperties
      * @param snsProperties
     */
    TwitterProxy(String alias, Context context, SystemProperties systemProperties, SnsProperties snsProperties) {
        mContext = context;
        mTwitterContext = (TwitterContext) (mNetworkContext = new TwitterContext(alias, mContext,
                new NetworkSessionListener(), systemProperties, snsProperties));
    }

    /**
      * Inject a Twitter session
     * 
     * @param accessToken
     * @param accessTokenSecret
     */
    void injectSession(String accessToken, String accessTokenSecret) {
        mTwitterContext.injectSession(accessToken, accessTokenSecret);
    }

    /* START OF ***************** API implementations ***********************/

    @Override
    public void getSettings(final Operation<TwitterDelegate, JSONObject> operation) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getSettings());
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void verifyCredentials(final Operation<TwitterDelegate, TwitterProfile> operation) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(verifyCredentials());
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void updateMyProfile(final Operation<TwitterDelegate, TwitterProfile> operation, final String fullName,
            final String websiteUrl, final String location, final String description) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(updateMyProfile(fullName, websiteUrl, location, description));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void updateMyProfilePhoto(final Operation<TwitterDelegate, TwitterProfile> operation,
            final String absolutePathOfPhoto) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(updateMyProfilePhoto(absolutePathOfPhoto));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getProfileByUid(final Operation<TwitterDelegate, TwitterProfile> operation, final String userUid) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getProfileByUid(userUid));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getProfileByUserName(final Operation<TwitterDelegate, TwitterProfile> operation,
            final String userName) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getProfileByUserName(userName));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getProfilesByUid(final Operation<TwitterDelegate, List<TwitterProfile>> operation,
            final String[] userUids) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getProfilesByUid(userUids));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getProfilesByUserName(final Operation<TwitterDelegate, List<TwitterProfile>> operation,
            final String[] userNames) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getProfilesByUserName(userNames));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void searchUser(final Operation<TwitterDelegate, List<TwitterProfile>> operation,
            final String queryString, final int count, final int page) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(searchUser(queryString, count, page));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getBlockList(final Operation<TwitterDelegate, List<TwitterProfile>> operation, final long cursor) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getBlockList(cursor));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getBlockUidList(final Operation<TwitterDelegate, JSONObject> operation, final long cursor) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getBlockUidList(cursor));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void blockUserByUid(final Operation<TwitterDelegate, TwitterProfile> operation, final String userUid) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(blockUserByUid(userUid));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void blockUserByUserName(final Operation<TwitterDelegate, TwitterProfile> operation,
            final String userName) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(blockUserByUserName(userName));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void unblockUserByUid(final Operation<TwitterDelegate, TwitterProfile> operation, final String userUid) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(unblockUserByUid(userUid));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void unblockUserByUserName(final Operation<TwitterDelegate, TwitterProfile> operation,
            final String userName) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(unblockUserByUserName(userName));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getFavoritesByUid(final Operation<TwitterDelegate, List<TwitterPost>> operation,
            final String userUid, final long sincePostId, final long maxPostId, final int count) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getFavoritesByUid(userUid, sincePostId, maxPostId, count));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getFavoritesByUserName(final Operation<TwitterDelegate, List<TwitterPost>> operation,
            final String userUid, final long sincePostId, final long maxPostId, final int count) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getFavoritesByUserName(userUid, sincePostId, maxPostId, count));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void favoritePost(final Operation<TwitterDelegate, TwitterPost> operation, final String postId) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(favoritePost(postId));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void unfavoritePost(final Operation<TwitterDelegate, TwitterPost> operation, final String postId) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(unfavoritePost(postId));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getMyHomeTimeline(final Operation<TwitterDelegate, List<TwitterPost>> operation,
            final long sincePostId, final long maxPostId, final int count, final boolean excludeReplies,
            final boolean trimUser) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getMyHomeTimeline(sincePostId, maxPostId, count, excludeReplies, trimUser));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getMentionsTimeline(final Operation<TwitterDelegate, List<TwitterPost>> operation,
            final long sincePostId, final long maxPostId, final int count, final boolean trimUser) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getMentionsTimeline(sincePostId, maxPostId, count, trimUser));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getRetweetsOfMeTimeline(final Operation<TwitterDelegate, List<TwitterPost>> operation,
            final long sincePostId, final long maxPostId, final int count, final boolean trimUser) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getRetweetsOfMeTimeline(sincePostId, maxPostId, count, trimUser));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getTimelineByUid(final Operation<TwitterDelegate, List<TwitterPost>> operation,
            final String userUid, final long sincePostId, final long maxPostId, final int count,
            final boolean excludeReplies, final boolean trimUser) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(
                            getTimelineByUid(userUid, sincePostId, maxPostId, count, excludeReplies, trimUser));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getTimelineByUserName(final Operation<TwitterDelegate, List<TwitterPost>> operation,
            final String userName, final long sincePostId, final long maxPostId, final int count,
            final boolean excludeReplies, final boolean trimUser) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getTimelineByUserName(userName, sincePostId, maxPostId, count,
                            excludeReplies, trimUser));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getSubscribedListsByUid(final Operation<TwitterDelegate, JSONArray> operation,
            final String userUid) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getSubscribedListsByUid(userUid));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getSubscribedListsByUserName(final Operation<TwitterDelegate, JSONArray> operation,
            final String userName) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getSubscribedListsByUserName(userName));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getOwnedListsByUid(final Operation<TwitterDelegate, JSONObject> operation, final String userUid,
            final int count, final long cursor) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getOwnedListsByUid(userUid, count, cursor));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getOwnedListsByUserName(final Operation<TwitterDelegate, JSONObject> operation,
            final String userName, final int count, final long cursor) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getOwnedListsByUserName(userName, count, cursor));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getListTimelineByListId(final Operation<TwitterDelegate, List<TwitterPost>> operation,
            final String listId, final long sincePostId, final long maxPostId, final int count) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getListTimelineByListId(listId, sincePostId, maxPostId, count));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getListTimelineBySlug(final Operation<TwitterDelegate, List<TwitterPost>> operation,
            final String slug, final String ownerUserName, final long sincePostId, final long maxPostId,
            final int count) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getListTimelineBySlug(slug, ownerUserName, sincePostId, maxPostId, count));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getTweet(final Operation<TwitterDelegate, TwitterPost> operation, final String postId) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getTweet(postId));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void postStatus(final Operation<TwitterDelegate, TwitterPost> operation, final String status,
            final float latitude, final float longitude, final String placeId, final boolean displayCoordinates) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(postStatus(status, UNSPECIFIED_POST_ID, latitude, longitude, placeId,
                            displayCoordinates));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void postReply(final Operation<TwitterDelegate, TwitterPost> operation, final String status,
            final long postIdReplyTo, final float latitude, final float longitude, final String placeId,
            final boolean displayCoordinates) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(
                            postStatus(status, postIdReplyTo, latitude, longitude, placeId, displayCoordinates));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void postPhoto(final Operation<TwitterDelegate, TwitterPost> operation, final String absolutePathOfPhoto,
            final String status, final boolean isSensitive, final float latitude, final float longitude,
            final String placeId, final boolean displayCoordinates) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(postPhoto(status, absolutePathOfPhoto, isSensitive, UNSPECIFIED_POST_ID,
                            latitude, longitude, placeId, displayCoordinates));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void replyWithPhoto(final Operation<TwitterDelegate, TwitterPost> operation,
            final String absolutePathOfPhoto, final String status, final boolean isSensitive,
            final long postIdReplyTo, final float latitude, final float longitude, final String placeId,
            final boolean displayCoordinates) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(postPhoto(status, absolutePathOfPhoto, isSensitive, postIdReplyTo, latitude,
                            longitude, placeId, displayCoordinates));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void retweetPost(final Operation<TwitterDelegate, TwitterPost> operation, final String postId) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(retweetPost(postId));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void deletePost(final Operation<TwitterDelegate, TwitterPost> operation, final String postId) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(deletePost(postId));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getRetweeters(final Operation<TwitterDelegate, JSONObject> operation, final String postId,
            final long cursor) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getRetweeters(postId, cursor));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getReceivedDirectMessages(final Operation<TwitterDelegate, List<TwitterMessage>> operation,
            final long sincePostId, final long maxPostId, final int count) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getReceivedDirectMessages(sincePostId, maxPostId, count));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getSentDirectMessages(final Operation<TwitterDelegate, List<TwitterMessage>> operation,
            final long sincePostId, final long maxPostId, final int count, final int page) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getSentDirectMessages(sincePostId, maxPostId, count, page));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getDirectMessage(final Operation<TwitterDelegate, TwitterMessage> operation,
            final String messageId) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getDirectMessage(messageId));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void sendDirectMessageByUid(final Operation<TwitterDelegate, TwitterMessage> operation,
            final String userUid, final String message) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(sendDirectMessageByUid(userUid, message));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void sendDirectMessageByUserName(final Operation<TwitterDelegate, TwitterMessage> operation,
            final String userName, final String message) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(sendDirectMessageByUserName(userName, message));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void deleteDirectMessage(final Operation<TwitterDelegate, TwitterMessage> operation,
            final String messageId) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(deleteDirectMessage(messageId));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getFriendsByUid(final Operation<TwitterDelegate, List<TwitterProfile>> operation,
            final String userUid, final long cursor) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getFriendsByUid(userUid, cursor));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getFriendsByUserName(final Operation<TwitterDelegate, List<TwitterProfile>> operation,
            final String userName, final long cursor) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getFriendsByUserName(userName, cursor));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getFollowersByUid(final Operation<TwitterDelegate, List<TwitterProfile>> operation,
            final String userUid, final long cursor) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getFollowersByUid(userUid, cursor));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getFollowersByUserName(final Operation<TwitterDelegate, List<TwitterProfile>> operation,
            final String userName, final long cursor) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getFollowersByUserName(userName, cursor));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void followUserByUid(final Operation<TwitterDelegate, TwitterProfile> operation, final String userUid) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(followUserByUid(userUid));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void followUserByUserName(final Operation<TwitterDelegate, TwitterProfile> operation,
            final String userName) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(followUserByUserName(userName));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void unfollowUserByUid(final Operation<TwitterDelegate, TwitterProfile> operation,
            final String userUid) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(unfollowUserByUid(userUid));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void unfollowUserByUserName(final Operation<TwitterDelegate, TwitterProfile> operation,
            final String userName) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(unfollowUserByUserName(userName));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getPlaceId(final Operation<TwitterDelegate, JSONObject> operation, final float latitude,
            final float longitude, final int accuracy, final String granularity, final int maxResults) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getPlaceId(latitude, longitude, accuracy, granularity, maxResults));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    @Override
    public void getGeoInformation(final Operation<TwitterDelegate, JSONObject> operation, final String placeId) {
        requestJob(new ProxyJob() {
            public void onFail(Exception e) {
                operation.onFail(e);
            }

            public void run() {
                try {
                    operation.onResult(getGeoInformation(placeId));
                } catch (Exception e) {
                    onFail(e);
                }
            }
        });
    }

    /**
     * Get my Twitter settings.
     *  
     * @return JSONObject
     * @throws Exception
     */
    private JSONObject getSettings() throws Exception {

        String apiUri = "/account/settings.json";

        return getResponseJson(getHttpGet(apiUri, (List<NameValuePair>) null));
    }

    /**
     * Verify my credentials and return my Twitter profile.
     * 
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile verifyCredentials() throws Exception {

        String apiUri = "/account/verify_credentials.json";

        List<NameValuePair> params = ParameterHelper.addAllParams(
                new BasicNameValuePair("include_entities", "true"), new BasicNameValuePair("skip_status", "false"));

        return populateProfileBean(getResponseJson(getHttpGet(apiUri, params)));
    }

    /**
     * Update my profile info.
     * 
     * @param fullName
     * @param websiteUrl
     * @param location
     * @param description
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile updateMyProfile(String fullName, String websiteUrl, String location, String description)
            throws Exception {

        String apiUri = "/account/update_profile.json";

        return populateProfileBean(getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("name", fullName),
                new BasicNameValuePair("url", websiteUrl), new BasicNameValuePair("location", location),
                new BasicNameValuePair("description", description),
                new BasicNameValuePair("include_entities", "true"),
                new BasicNameValuePair("skip_status", "false"))));
    }

    /**
     * Upload and update my profile photo.
     * 
     * @param absolutePathOfPhoto
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile updateMyProfilePhoto(String absolutePathOfPhoto) throws Exception {

        String apiUri = "/account/update_profile_image.json";

        return populateProfileBean(getResponseJson(getHttpPost(apiUri,
                new BasicNameValuePair("image", Util.extractBase64StringFromFile(absolutePathOfPhoto, 700)),
                new BasicNameValuePair("include_entities", "true"),
                new BasicNameValuePair("skip_status", "false"))));
    }

    /**
     * Get a Twitter profile corresponding to given user UID.
     * 
     * @param userUid
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile getProfileByUid(String userUid) throws Exception {

        String apiUri = "/users/show.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        return populateProfileBean(getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("user_id", userUid),
                new BasicNameValuePair("include_entities", "true"))));
    }

    /**
     * Get a Twitter profile corresponding to given user name.
     * 
     * @param userName
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile getProfileByUserName(String userName) throws Exception {

        String apiUri = "/users/show.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        return populateProfileBean(
                getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("screen_name", userName),
                        new BasicNameValuePair("include_entities", "true"))));
    }

    /**
     * Get a Twitter profile corresponding to given user UID.
     * 
     * @param userUids
     * @return list of TwitterProfile objects
     * @throws Exception
     */
    private List<TwitterProfile> getProfilesByUid(String[] userUids) throws Exception {

        String apiUri = "/users/lookup.json";

        // throw an exception if user UID is invalid
        if (userUids == null || userUids.length == 0) {
            throw new SystemException("No user UID is specified.");
        }

        // check paramter length
        else if (userUids.length > 100) {
            throw new SystemException("user UIDs should be no more than 100.");
        }

        String commaSeparatedUserUids = TextUtils.join(",", userUids);

        return populateProfileList(getJsonArray(
                getResponseString(getHttpPost(apiUri, new BasicNameValuePair("user_id", commaSeparatedUserUids),
                        new BasicNameValuePair("include_entities", "true")))));
    }

    /**
     * Get a Twitter profile corresponding to given user name.
     * 
     * @param userNames
     * @return list of TwitterProfile objects
     * @throws Exception
     */
    private List<TwitterProfile> getProfilesByUserName(String[] userNames) throws Exception {

        String apiUri = "/users/lookup.json";

        // throw an exception if user name is invalid
        if (userNames == null || userNames.length == 0) {
            throw new SystemException("No user name is specified.");
        }

        // check paramter length
        else if (userNames.length > 100) {
            throw new SystemException("user names should be no more than 100.");
        }

        String commaSeparatedUserNames = TextUtils.join(",", userNames);

        return populateProfileList(getJsonArray(getResponseString(
                getHttpPost(apiUri, new BasicNameValuePair("screen_name", commaSeparatedUserNames),
                        new BasicNameValuePair("include_entities", "true")))));
    }

    /**
     * Search public user accounts on Twitter.
     * 
     * @param queryString
     * @param count
     * @param page
     * @return list of TwitterProfile objects
     */
    private List<TwitterProfile> searchUser(String queryString, int count, int page) throws Exception {

        String apiUri = "/users/search.json";

        // check query string
        if (StringHelper.isEmpty(queryString) == true) {
            throw new SystemException("Query string is empty.");
        }

        // check count
        if (count < 1 || count > 20) {
            throw new SystemException("Count must be an integer within 1 to 20.");
        }

        // check page
        if (page < 1) {
            throw new SystemException("Page must be an integer greater than 0.");
        }

        return populateProfileList(getJsonArray(getResponseString(getHttpGet(apiUri,
                new BasicNameValuePair("q", queryString), new BasicNameValuePair("count", "" + count),
                new BasicNameValuePair("page", "" + page), new BasicNameValuePair("include_entities", "true")))));
    }

    /**
     * Get my blocked user list.
     * 
     * @param cursor
     * @return list of TwitterProflie objects
     * @throws Exception
     */
    private List<TwitterProfile> getBlockList(long cursor) throws Exception {

        String apiUri = "/blocks/list.json";

        return populateProfileList(getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("cursor", "" + cursor),
                new BasicNameValuePair("include_entities", "true"),
                new BasicNameValuePair("skip_status", "false"))));
    }

    /**
     * Get my blocked user list.
     * 
     * @param cursor
     * @return list of TwitterProflie objects
     * @throws Exception
     */
    private JSONObject getBlockUidList(long cursor) throws Exception {

        String apiUri = "/blocks/ids.json";

        return getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("cursor", "" + cursor)));
    }

    /**
     * Block an user by user UID.
     * 
     * @param userUid
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile blockUserByUid(String userUid) throws Exception {

        String apiUri = "/blocks/create.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        return populateProfileBean(getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("user_id", userUid),
                new BasicNameValuePair("include_entities", "true"),
                new BasicNameValuePair("skip_status", "false"))));
    }

    /**
     * Block an user by user name.
     * 
     * @param userName
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile blockUserByUserName(String userName) throws Exception {

        String apiUri = "/blocks/create.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        return populateProfileBean(getResponseJson(getHttpPost(apiUri,
                new BasicNameValuePair("screen_name", userName), new BasicNameValuePair("include_entities", "true"),
                new BasicNameValuePair("skip_status", "false"))));
    }

    /**
     * Unblock an user by user UID.
     * 
     * @param userUid
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile unblockUserByUid(String userUid) throws Exception {

        String apiUri = "/blocks/destroy.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        return populateProfileBean(getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("user_id", userUid),
                new BasicNameValuePair("include_entities", "true"),
                new BasicNameValuePair("skip_status", "false"))));
    }

    /**
     * Unblock an user by user name.
     * 
     * @param userName
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile unblockUserByUserName(String userName) throws Exception {

        String apiUri = "/blocks/destroy.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        return populateProfileBean(getResponseJson(getHttpPost(apiUri,
                new BasicNameValuePair("screen_name", userName), new BasicNameValuePair("include_entities", "true"),
                new BasicNameValuePair("skip_status", "false"))));
    }

    /**
     * Get favorites of an user corresponding to given user UID.
     * 
     * @param userUid
     * @param sincePostId
     * @param maxPostId
     * @param count
     * @return list of TwitterPost objects
     * @throws Exception
     */
    private List<TwitterPost> getFavoritesByUid(String userUid, long sincePostId, long maxPostId, int count)
            throws Exception {

        String apiUri = "/favorites/list.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        // check count
        if (count < 1 || count > 200) {
            throw new SystemException("Count must be an integer within 1 to 200.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("user_id", userUid),
                new BasicNameValuePair("count", "" + count));

        // set since_id and max_id paramters
        setSinceMaxParams(params, sincePostId, maxPostId);

        return populatePostList(getJsonArray(getResponseString(getHttpGet(apiUri, params))));
    }

    /**
     * Get favorites of an user corresponding to given user name.
     * 
     * @param userName
     * @param sincePostId
     * @param maxPostId
     * @param count
     * @return list of TwitterPost objects
     * @throws Exception
     */
    private List<TwitterPost> getFavoritesByUserName(String userName, long sincePostId, long maxPostId, int count)
            throws Exception {

        String apiUri = "/favorites/list.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        // check count
        if (count < 1 || count > 200) {
            throw new SystemException("Count must be an integer within 1 to 200.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("screen_name", userName),
                new BasicNameValuePair("count", "" + count));

        // set since_id and max_id paramters
        setSinceMaxParams(params, sincePostId, maxPostId);

        return populatePostList(getJsonArray(getResponseString(getHttpGet(apiUri, params))));
    }

    /**
     * Favorite a post.
     * 
     * @param postId
     * @return TwitterPost
     * @throws Exception
     */
    private TwitterPost favoritePost(String postId) throws Exception {

        String apiUri = "/favorites/create.json";

        // throw an exception if post ID is invalid
        if (StringHelper.isEmpty(postId) == true) {
            throw new SystemException("Invalid post ID.");
        }

        return populatePostBean(getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("id", postId),
                new BasicNameValuePair("include_entities", "true"))));
    }

    /**
     * Unfavorite a post.
     * 
     * @param postId
     * @return TwitterPost
     * @throws Exception
     */
    private TwitterPost unfavoritePost(String postId) throws Exception {

        String apiUri = "/favorites/destroy.json";

        // throw an exception if post ID is invalid
        if (StringHelper.isEmpty(postId) == true) {
            throw new SystemException("Invalid post ID.");
        }

        return populatePostBean(getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("id", postId),
                new BasicNameValuePair("include_entities", "true"))));
    }

    /**
     * Get my home timeline.
     * 
     * @param count
     * @param sincePostId
     * @param maxPostId
     * @param excludeReplies
     * @param trimUser
     * @return list of TwitterPost objects
     * @throws Exception
     */
    private List<TwitterPost> getMyHomeTimeline(long sincePostId, long maxPostId, int count, boolean excludeReplies,
            boolean trimUser) throws Exception {

        String apiUri = "/statuses/home_timeline.json";

        // check count
        if (count < 1 || count > 200) {
            throw new SystemException("Count must be an integer within 1 to 200.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("count", "" + count),
                new BasicNameValuePair("include_entities", "true"));

        // set since_id and max_id paramters
        setSinceMaxParams(params, sincePostId, maxPostId);

        // set exclude_replies paramter if needed
        if (excludeReplies == true) {
            params.add(new BasicNameValuePair("exclude_replies", "" + excludeReplies));
        }

        // set trim_user paramter if needed
        if (trimUser == true) {
            params.add(new BasicNameValuePair("trim_user", "" + trimUser));
        }

        return populatePostList(getJsonArray(getResponseString(getHttpGet(apiUri, params))));
    }

    /**
     * Get mentions timeline.
     * 
     * @param sincePostId
     * @param maxPostId
     * @param count
     * @param trimUser
     * @return list of TwitterPost objects
     * @throws Exception
     */
    private List<TwitterPost> getMentionsTimeline(long sincePostId, long maxPostId, int count, boolean trimUser)
            throws Exception {

        String apiUri = "/statuses/mentions_timeline.json";

        // check count
        if (count < 1 || count > 200) {
            throw new SystemException("Count must be an integer within 1 to 200.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("count", "" + count),
                new BasicNameValuePair("include_entities", "true"));

        // set since_id and max_id paramters
        setSinceMaxParams(params, sincePostId, maxPostId);

        // set trim_user paramter if needed
        if (trimUser == true) {
            params.add(new BasicNameValuePair("trim_user", "" + trimUser));
        }

        return populatePostList(getJsonArray(getResponseString(getHttpGet(apiUri, params))));
    }

    /**
     * Get mentions timeline.
     * 
     * @param sincePostId
     * @param maxPostId
     * @param count
     * @param trimUser
     * @return list of TwitterPost objects
     * @throws Exception
     */
    private List<TwitterPost> getRetweetsOfMeTimeline(long sincePostId, long maxPostId, int count, boolean trimUser)
            throws Exception {

        String apiUri = "/statuses/retweets_of_me.json";

        // check count
        if (count < 1 || count > 200) {
            throw new SystemException("Count must be an integer within 1 to 200.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("count", "" + count),
                new BasicNameValuePair("include_entities", "true"),
                new BasicNameValuePair("include_user_entities", "true"));

        // set since_id and max_id paramters
        setSinceMaxParams(params, sincePostId, maxPostId);

        // set trim_user paramter if needed
        if (trimUser == true) {
            params.add(new BasicNameValuePair("trim_user", "" + trimUser));
        }

        return populatePostList(getJsonArray(getResponseString(getHttpGet(apiUri, params))));
    }

    /**
     * Get an user's timeline by user UID.
     * 
     * @param userUid
     * @param sincePostId
     * @param maxPostId
     * @param count
     * @param trimUser
     * @return list of TwitterPost objects
     * @throws Exception
     */
    private List<TwitterPost> getTimelineByUid(String userUid, long sincePostId, long maxPostId, int count,
            boolean excludeReplies, boolean trimUser) throws Exception {

        String apiUri = "/statuses/user_timeline.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        // check count
        if (count < 1 || count > 200) {
            throw new SystemException("Count must be an integer within 1 to 200.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("user_id", userUid),
                new BasicNameValuePair("count", "" + count));

        // set since_id and max_id paramters
        setSinceMaxParams(params, sincePostId, maxPostId);

        // set exclude_replies paramter if needed
        if (excludeReplies == true) {
            params.add(new BasicNameValuePair("exclude_replies", "" + excludeReplies));
        }

        // set trim_user paramter if needed
        if (trimUser == true) {
            params.add(new BasicNameValuePair("trim_user", "" + trimUser));
        }

        return populatePostList(getJsonArray(getResponseString(getHttpGet(apiUri, params))));
    }

    /**
     * Get an user's timeline by user name.
     * 
     * @param userName
     * @param sincePostId
     * @param maxPostId
     * @param count
     * @param trimUser
     * @return list of TwitterPost objects
     * @throws Exception
     */
    private List<TwitterPost> getTimelineByUserName(String userName, long sincePostId, long maxPostId, int count,
            boolean excludeReplies, boolean trimUser) throws Exception {

        String apiUri = "/statuses/user_timeline.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        // check count
        if (count < 1 || count > 200) {
            throw new SystemException("Count must be an integer within 1 to 200.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("screen_name", userName),
                new BasicNameValuePair("count", "" + count));

        // set since_id and max_id paramters
        setSinceMaxParams(params, sincePostId, maxPostId);

        // set exclude_replies paramter if needed
        if (excludeReplies == true) {
            params.add(new BasicNameValuePair("exclude_replies", "" + excludeReplies));
        }

        // set trim_user paramter if needed
        if (trimUser == true) {
            params.add(new BasicNameValuePair("trim_user", "" + trimUser));
        }

        return populatePostList(getJsonArray(getResponseString(getHttpGet(apiUri, params))));
    }

    /**
     * Get lists that an user corresponding to given user UID subscribes to.
     * 
     * @param userUid
     * @return JSONArray
     * @throws Exception
     */
    private JSONArray getSubscribedListsByUid(String userUid) throws Exception {

        String apiUri = "/lists/list.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        return getJsonArray(getResponseString(getHttpGet(apiUri, new BasicNameValuePair("user_id", userUid))));
    }

    /**
     * Get lists that an user corresponding to given user name subscribes to.
     * 
     * @param userName
     * @return JSONArray
     * @throws Exception
     */
    private JSONArray getSubscribedListsByUserName(String userName) throws Exception {

        String apiUri = "/lists/list.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        return getJsonArray(getResponseString(getHttpGet(apiUri, new BasicNameValuePair("screen_name", userName))));
    }

    /**
     * Get lists owned by an user corresponding to given user UID.
     * 
     * @param userUid
     * @param count
     * @param cursor
     * @return JSONObject
     * @throws Exception
     */
    private JSONObject getOwnedListsByUid(String userUid, int count, long cursor) throws Exception {

        String apiUri = "/lists/ownerships.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        // check count
        if (count < 1 || count > 1000) {
            throw new SystemException("Count must be an integer within 1 to 1000.");
        }

        return getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("user_id", userUid),
                new BasicNameValuePair("count", "" + count), new BasicNameValuePair("cursor", "" + cursor)));
    }

    /**
     * Get lists owned by an user corresponding to given user name.
     * 
     * @param userName
     * @param count
     * @param cursor
     * @return JSONObject
     * @throws Exception
     */
    private JSONObject getOwnedListsByUserName(String userName, int count, long cursor) throws Exception {

        String apiUri = "/lists/ownerships.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        // check count
        if (count < 1 || count > 1000) {
            throw new SystemException("Count must be an integer within 1 to 1000.");
        }

        return getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("screen_name", userName),
                new BasicNameValuePair("count", "" + count), new BasicNameValuePair("cursor", "" + cursor)));
    }

    /**
     * Get list timeline corresponding to given list ID.
     * 
     * @param listId
     * @param sincePostId
     * @param maxPostId
     * @param count
     * @return list of TwitterPost objects
     * @throws Exception
     */
    private List<TwitterPost> getListTimelineByListId(String listId, long sincePostId, long maxPostId, int count)
            throws Exception {

        String apiUri = "/lists/statuses.json";

        // check count
        if (count < 1 || count > 200) {
            throw new SystemException("Count must be an integer within 1 to 200.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("list_id", listId),
                new BasicNameValuePair("count", "" + count));

        // set since_id and max_id paramters
        setSinceMaxParams(params, sincePostId, maxPostId);

        return populatePostList(getJsonArray(getResponseString(getHttpGet(apiUri, params))));
    }

    /**
     * Get list timeline corresponding to given list slug and owner's user name.
     * 
     * @param slug
     * @param ownerUserName
     * @param sincePostId
     * @param maxPostId
     * @param count
     * @return list of TwitterPost objects
     * @throws Exception
     */
    private List<TwitterPost> getListTimelineBySlug(String slug, String ownerUserName, long sincePostId,
            long maxPostId, int count) throws Exception {

        String apiUri = "/lists/statuses.json";

        // throw an exception if list slug is invalid
        if (StringHelper.isEmpty(slug) == true) {
            throw new SystemException("Invalid list slug.");
        }

        // throw an exception if list owner's user name is invalid
        if (StringHelper.isEmpty(ownerUserName) == true) {
            throw new SystemException("Invalid list owner's user name.");
        }

        // check count
        if (count < 1 || count > 200) {
            throw new SystemException("Count must be an integer within 1 to 200.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("slug", slug),
                new BasicNameValuePair("owner_screen_name", ownerUserName),
                new BasicNameValuePair("count", "" + count), new BasicNameValuePair("include_entities", "true"));

        // set since_id and max_id paramters
        setSinceMaxParams(params, sincePostId, maxPostId);

        return populatePostList(getJsonArray(getResponseString(getHttpGet(apiUri, params))));
    }

    /**
     * Get a tweet corresponding to given post ID.
     * 
     * @param postId
     * @return TwitterPost
     * @throws Exception
     */
    private TwitterPost getTweet(String postId) throws Exception {

        String apiUri = "/statuses/show.json";

        // throw an exception if post ID is invalid
        if (StringHelper.isEmpty(postId) == true) {
            throw new SystemException("Invalid post ID.");
        }

        return populatePostBean(getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("id", postId),
                new BasicNameValuePair("include_my_retweet", "true"),
                new BasicNameValuePair("include_entities", "true"))));
    }

    /**
     * Post a tweet on my timeline.
     * 
     * @param status
     * @param postIdReplyTo
     * @param latitude
     * @param longitude
     * @param placeId
     * @param displayCoordinates
     * @return TwitterPost
     * @throws Exception
     */
    private TwitterPost postStatus(String status, long postIdReplyTo, float latitude, float longitude,
            String placeId, boolean displayCoordinates) throws Exception {

        String apiUri = "/statuses/update.json";

        // check status message
        if (StringHelper.isEmpty(status) == true) {
            throw new SystemException("Invalid status message.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("status", status),
                new BasicNameValuePair("displayCoordinates", "" + displayCoordinates));

        // add post ID that the current post replies to
        if (postIdReplyTo > UNSPECIFIED_POST_ID) {
            params.add(new BasicNameValuePair("in_reply_to_status_id", "" + postIdReplyTo));
        }

        // add WGS GPS coordination if needed
        if (latitude != UNSPECIFIED_GPS_COORDINATION && longitude != UNSPECIFIED_GPS_COORDINATION) {
            params.add(new BasicNameValuePair("latitude", "" + latitude));
            params.add(new BasicNameValuePair("longitude", "" + longitude));
        }

        // set place ID if exists
        if (StringHelper.isEmpty(placeId) == false) {
            params.add(new BasicNameValuePair("place_id", "" + placeId));
        }

        return populatePostBean(getResponseJson(getHttpPost(apiUri, params)));
    }

    /**
     * Post a photo on my timeline.
     * 
     * @param absolutePathOfPhoto
     * @param status
     * @param isSensitive
     * @param postIdReplyTo
     * @param latitude
     * @param longitude
     * @param placeId
     * @param displayCoordinates
     * @return TwitterPost
     * @throws Exception
     */
    private TwitterPost postPhoto(String status, String absolutePathOfPhoto, boolean isSensitive,
            long postIdReplyTo, float latitude, float longitude, String placeId, boolean displayCoordinates)
            throws Exception {

        String apiUri = "/statuses/update_with_media.json";

        // check status message
        if (StringHelper.isEmpty(status) == true) {
            throw new SystemException("Invalid status message.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("status", status),
                new BasicNameValuePair("isSensitive", "" + isSensitive),
                new BasicNameValuePair("displayCoordinates", "" + displayCoordinates));

        // add post ID that the current post replies to
        if (postIdReplyTo > UNSPECIFIED_POST_ID) {
            params.add(new BasicNameValuePair("in_reply_to_status_id", "" + postIdReplyTo));
        }

        // add WGS GPS coordination if needed
        if (latitude != UNSPECIFIED_GPS_COORDINATION && longitude != UNSPECIFIED_GPS_COORDINATION) {
            params.add(new BasicNameValuePair("latitude", "" + latitude));
            params.add(new BasicNameValuePair("longitude", "" + longitude));
        }

        // set place ID if exists
        if (StringHelper.isEmpty(placeId) == false) {
            params.add(new BasicNameValuePair("place_id", "" + placeId));
        }

        return populatePostBean(
                getResponseJson(getMultipartHttpPost(apiUri, params, "media[]", absolutePathOfPhoto)));
    }

    /**
     * Retweet(repost) a tweet.
     * 
     * @param postId
     * @return TwitterPost
     * @throws Exception
     */
    private TwitterPost retweetPost(String postId) throws Exception {

        // throw an exception if post ID is invalid
        if (StringHelper.isEmpty(postId) == true) {
            throw new SystemException("Invalid post ID.");
        }

        String apiUri = "/statuses/retweet/" + postId + ".json";

        return populatePostBean(getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("id", postId))));
    }

    /**
     * Delete a tweet.
     * 
     * @param postId
     * @return TwitterPost
     * @throws Exception
     */
    private TwitterPost deletePost(String postId) throws Exception {

        // throw an exception if post ID is invalid
        if (StringHelper.isEmpty(postId) == true) {
            throw new SystemException("Invalid post ID.");
        }

        String apiUri = "/statuses/destroy/" + postId + ".json";

        return populatePostBean(getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("id", postId))));
    }

    /**
     * Get list of retweeters of a post corresponding to given post ID.
     * 
     * @param postId
     * @param cursor
     * @return JSONObject
     * @throws Exception
     */
    private JSONObject getRetweeters(String postId, long cursor) throws Exception {

        String apiUri = "/statuses/retweeters/ids.json";

        // throw an exception if post ID is invalid
        if (StringHelper.isEmpty(postId) == true) {
            throw new SystemException("Invalid post ID.");
        }

        return getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("id", postId),
                new BasicNameValuePair("cursor", "" + cursor)));
    }

    /**
     * Get received direct messages.
     * 
     * @param sincePostId
     * @param maxPostId
     * @param count
     * @return list of TwitterMessage objects
     * @throws Exception
     */
    private List<TwitterMessage> getReceivedDirectMessages(long sincePostId, long maxPostId, int count)
            throws Exception {

        String apiUri = "/direct_messages.json";

        // check count
        if (count < 1 || count > 200) {
            throw new SystemException("Count must be an integer within 1 to 200.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("count", "" + count),
                new BasicNameValuePair("include_entities", "true"));

        // set since_id and max_id paramters
        setSinceMaxParams(params, sincePostId, maxPostId);

        return populateMessageList(getJsonArray(getResponseString(getHttpGet(apiUri, params))));
    }

    /**
     * Get sent direct messages.
     * 
     * @param sincePostId
     * @param maxPostId
     * @param count
     * @return list of TwitterMessage objects
     * @throws Exception
     */
    private List<TwitterMessage> getSentDirectMessages(long sincePostId, long maxPostId, int count, int page)
            throws Exception {

        String apiUri = "/direct_messages/sent.json";

        // check count
        if (count < 1 || count > 200) {
            throw new SystemException("Count must be an integer within 1 to 200.");
        }

        // check page
        if (page < 1) {
            throw new SystemException("Page must be an integer greater than 0.");
        }

        List<NameValuePair> params = ParameterHelper.addAllParams(new BasicNameValuePair("count", "" + count),
                new BasicNameValuePair("page", "" + page), new BasicNameValuePair("include_entities", "true"));

        // set since_id and max_id paramters
        setSinceMaxParams(params, sincePostId, maxPostId);

        return populateMessageList(getJsonArray(getResponseString(getHttpGet(apiUri, params))));
    }

    /**
     * Get a direct message corresponding to given message ID.
     * 
     * @param messageId
     * @return TwitterMessage
     * @throws Exception
     */
    private TwitterMessage getDirectMessage(String messageId) throws Exception {

        String apiUri = "/direct_messages/show.json";

        // throw an exception if message ID is invalid
        if (StringHelper.isEmpty(messageId) == true) {
            throw new SystemException("Invalid message ID.");
        }

        return populateMessageBean(getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("id", messageId))));
    }

    /**
     * Send a direct message.
     * 
     * @param userUid
     * @param message
     * @return TwitterMessage
     * @throws Exception
     */
    private TwitterMessage sendDirectMessageByUid(String userUid, String message) throws Exception {

        String apiUri = "/direct_messages/new.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        // check message
        if (StringHelper.isEmpty(message) == true) {
            throw new SystemException("Invalid message.");
        }

        return populateMessageBean(getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("user_id", userUid),
                new BasicNameValuePair("text", message))));
    }

    /**
     * Send a direct message.
     * 
     * @param userName
     * @param message
     * @return TwitterMessage
     * @throws Exception
     */
    private TwitterMessage sendDirectMessageByUserName(String userName, String message) throws Exception {

        String apiUri = "/direct_messages/new.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        // check message
        if (StringHelper.isEmpty(message) == true) {
            throw new SystemException("Invalid message.");
        }

        return populateMessageBean(getResponseJson(getHttpPost(apiUri,
                new BasicNameValuePair("screen_name", userName), new BasicNameValuePair("text", message))));
    }

    /**
     * Delete a direct message corresponding to given message ID.
     * 
     * @param messageId
     * @return TwitterMessage
     * @throws Exception
     */
    private TwitterMessage deleteDirectMessage(String messageId) throws Exception {

        String apiUri = "/direct_messages/destroy.json";

        // throw an exception if message ID is invalid
        if (StringHelper.isEmpty(messageId) == true) {
            throw new SystemException("Invalid message ID.");
        }

        return populateMessageBean(getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("id", messageId))));
    }

    /**
     * Get friends of an user corresponding to given user UID.
     * 
     * @param userUid
     * @param cursor
     * @return list of TwitterProfile objects
     * @throws Exception
     */
    private List<TwitterProfile> getFriendsByUid(String userUid, long cursor) throws Exception {

        String apiUri = "/friends/list.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        return populateProfileList(getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("user_id", userUid),
                new BasicNameValuePair("cursor", "" + cursor))));
    }

    /**
     * Get friends of an user corresponding to given user name.
     * 
     * @param userName
     * @param cursor
     * @return list of TwitterProfile objects
     * @throws Exception
     */
    private List<TwitterProfile> getFriendsByUserName(String userName, long cursor) throws Exception {

        String apiUri = "/friends/list.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        return populateProfileList(getResponseJson(getHttpGet(apiUri,
                new BasicNameValuePair("screen_name", userName), new BasicNameValuePair("cursor", "" + cursor))));
    }

    /**
     * Get followers of an user corresponding to given user UID.
     * 
     * @param userUid
     * @param cursor
     * @return list of TwitterProfile objects
     * @throws Exception
     */
    private List<TwitterProfile> getFollowersByUid(String userUid, long cursor) throws Exception {

        String apiUri = "/followers/list.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        return populateProfileList(getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("user_id", userUid),
                new BasicNameValuePair("cursor", "" + cursor))));
    }

    /**
     * Get followers of an user corresponding to given user name.
     * 
     * @param userName
     * @param cursor
     * @return list of TwitterProfile objects
     * @throws Exception
     */
    private List<TwitterProfile> getFollowersByUserName(String userName, long cursor) throws Exception {

        String apiUri = "/followers/list.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        return populateProfileList(getResponseJson(getHttpGet(apiUri,
                new BasicNameValuePair("screen_name", userName), new BasicNameValuePair("cursor", "" + cursor))));
    }

    /**
     * Follow an user corresponding to given user UID.
     * 
     * @param userUid
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile followUserByUid(String userUid) throws Exception {

        String apiUri = "/friendships/create.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        return populateProfileBean(
                getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("user_id", userUid))));
    }

    /**
     * Follow an user corresponding to given user name.
     * 
     * @param userName
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile followUserByUserName(String userName) throws Exception {

        String apiUri = "/friendships/create.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        return populateProfileBean(
                getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("screen_name", userName))));
    }

    /**
     * Unfollow an user corresponding to given user UID.
     * 
     * @param userUid
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile unfollowUserByUid(String userUid) throws Exception {

        String apiUri = "/friendships/destroy.json";

        // throw an exception if user UID is invalid
        if (StringHelper.isEmpty(userUid) == true) {
            throw new SystemException("Invalid user UID.");
        }

        return populateProfileBean(
                getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("user_id", userUid))));
    }

    /**
     * Unfollow an user corresponding to given user name.
     * 
     * @param userName
     * @return TwitterProfile
     * @throws Exception
     */
    private TwitterProfile unfollowUserByUserName(String userName) throws Exception {

        String apiUri = "/friendships/destroy.json";

        // throw an exception if user name is invalid
        if (StringHelper.isEmpty(userName) == true) {
            throw new SystemException("Invalid user name.");
        }

        return populateProfileBean(
                getResponseJson(getHttpPost(apiUri, new BasicNameValuePair("screen_name", userName))));
    }

    /**
     * Get place ID.
     * 
     * @param latitude
     * @param longitude
     * @param accuracy
     * @param granularity
     * @param maxResults
     * @return JSONObject
     * @throws Exception
     */
    private JSONObject getPlaceId(float latitude, float longitude, int accuracy, String granularity, int maxResults)
            throws Exception {

        String apiUri = "/geo/reverse_geocode.json";

        // check latitude
        if (latitude > 90.0F || latitude < -90.0F) {
            throw new SystemException("Invalid latitude value.");
        }

        // check longitude
        if (longitude > 180.0F || longitude < -180.0F) {
            throw new SystemException("Invalid longitude value.");
        }

        // check accuracy
        if (accuracy < 1) {
            throw new SystemException("Invalid accuracy value.");
        }

        // check granularity
        if (("poi".equals(granularity) || "neighborhood".equals(granularity) || "city".equals(granularity)
                || "admin ".equals(granularity) || "country".equals(granularity)) == false) {
            throw new SystemException("Invalid granularity value.");
        }

        // check maxResults
        if (maxResults < 1) {
            throw new SystemException("Invalid maxResults value.");
        }

        return getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("lat", "" + latitude),
                new BasicNameValuePair("long", "" + longitude), new BasicNameValuePair("accuracy", "" + accuracy),
                new BasicNameValuePair("granularity", granularity),
                new BasicNameValuePair("max_results", "" + maxResults)));
    }

    /**
     * Get all the information about a known place by given place ID.
     * 
     * @param placeId
     * @return JSONObject
     * @throws Exception
     */
    private JSONObject getGeoInformation(String placeId) throws Exception {

        // check place ID
        if (StringHelper.isEmpty(placeId) == true) {
            throw new SystemException("Invalid place ID.");
        }

        String apiUri = "/geo/id/" + placeId + ".json";

        return getResponseJson(getHttpGet(apiUri, new BasicNameValuePair("place_id", placeId)));
    }

    /* END OF ****************** API implementations ************************/

    /**
     * Set since_id and max_id parameters for Twitter timeline.
     * 
     * @param params
     * @param sincePostId
     * @param maxPostId
     * @return params
     */
    private List<NameValuePair> setSinceMaxParams(List<NameValuePair> params, long sincePostId, long maxPostId) {

        // if since_id is given
        if (sincePostId > UNSPECIFIED_POST_ID) {
            params.add(new BasicNameValuePair("since_id", "" + sincePostId));
        }

        // if max_id is given
        if (maxPostId > UNSPECIFIED_POST_ID) {
            params.add(new BasicNameValuePair("max_id", "" + maxPostId));
        }

        return params;
    }

    /**
     * Get the first media JSON object from given tweet JSON object.
     * 
     * @param postJson
     * @return mediaJson
     * @throws Exception
     */
    private JSONObject extractFirstMediaJson(JSONObject postJson) throws Exception {
        JSONObject mediaJson = null;
        if (postJson.has("entities") == true && postJson.getJSONObject("entities").has("media") == true) {
            mediaJson = postJson.getJSONObject("entities").getJSONArray("media").getJSONObject(0);
        }
        return mediaJson;
    }

    /**
     * Get TwitterProfile list from JSON array.
     * 
     * @param responseJson
     * @return list of TwitterProfile objects
     * @throws Exception
     */
    private List<TwitterProfile> populateProfileList(JSONObject responseJson) throws Exception {

        JSONArray profileJsonArray = responseJson.optJSONArray("users");

        // profile list must not be empty
        if (profileJsonArray == null || profileJsonArray.length() == 0) {
            throw new NetworkException("Profile list is empty.", responseJson.toString());
        }

        // extract cursor data
        long previousCursor = responseJson.optLong("previous_cursor");
        long nextCursor = responseJson.optLong("next_cursor");

        return populateProfileList(profileJsonArray, previousCursor, nextCursor);
    }

    /**
     * Get TwitterProfile list from JSON array.
     * 
     * @param profileJsonArray
     * @return list of TwitterProfile objects
     * @throws Exception
     */
    private List<TwitterProfile> populateProfileList(JSONArray profileJsonArray) throws Exception {

        // profile list must not be empty
        if (profileJsonArray == null || profileJsonArray.length() == 0) {
            throw new NetworkException("Profile list is empty.", profileJsonArray.toString());
        }

        return populateProfileList(profileJsonArray, UNSPECIFIED_CURSOR, UNSPECIFIED_CURSOR);
    }

    /**
     * Get TwitterProfile list from JSON array.
     * 
     * @param profileJsonArray
     * @param previousCursor
     * @param nextCursor
     * @return list of TwitterProfile objects
     * @throws Exception
     */
    private List<TwitterProfile> populateProfileList(JSONArray profileJsonArray, long previousCursor,
            long nextCursor) throws Exception {

        // iterate profile list and populate TwitterProfile beans
        List<TwitterProfile> profileList = new ArrayList<TwitterProfile>();
        for (int i = 0; i < profileJsonArray.length(); i++) {
            profileList.add(populateProfileBean(profileJsonArray.getJSONObject(i), previousCursor, nextCursor));
        }

        return profileList;
    }

    /**
     * Set Twitter profile data to a TwitterProfile bean.
     * 
     * @param profileJson
     * @return a TwitterProfile bean
     * @throws Exception
     */
    private TwitterProfile populateProfileBean(JSONObject profileJson) throws Exception {
        return populateProfileBean(profileJson, UNSPECIFIED_CURSOR, UNSPECIFIED_CURSOR);
    }

    /**
     * Set Twitter profile data to a TwitterProfile bean.
     * 
     * @param profileJson
     * @param previousCursor
     * @param nextCursor
     * @return a TwitterProfile bean
     * @throws Exception
     */
    private TwitterProfile populateProfileBean(JSONObject profileJson, long previousCursor, long nextCursor)
            throws Exception {

        // throw an exception if JSON is invalid
        if (profileJson == null || profileJson.has("id_str") == false) {
            if (profileJson == null) {
                throw new NetworkException("Profile JSON is null and cannot populate a Profile bean.");
            } else {
                throw new NetworkException("Invalid profile JSON.", profileJson.toString());
            }
        }

        // set profile data
        TwitterProfile twitterProfile = new TwitterProfile();
        twitterProfile.setSnsUid(SocialNetwork.TWITTER);
        twitterProfile.setProfileJson(profileJson);
        twitterProfile.setUid(profileJson.optString("id_str"));
        twitterProfile.setUserName(profileJson.optString("screen_name"));
        twitterProfile.setFullName(profileJson.optString("name"));
        twitterProfile.setProfilePageUrl(WEB_HOME_URL + profileJson.optString("screen_name"));
        twitterProfile.setDescription(profileJson.optString("description"));
        twitterProfile.setLocation(profileJson.optString("location"));
        twitterProfile.setWebsiteUrl(profileJson.optString("url"));
        twitterProfile.setProfilePhotoUrl(profileJson.optString("profile_image_url"));

        // set last post data
        if (profileJson.has("status") == true) {
            JSONObject postJson = profileJson.getJSONObject("status");
            TwitterPost lastPost = populatePostBean(postJson);
            // in case postJson has no user object
            if (StringHelper.isEmpty(lastPost.getPosterUid()) == true) {
                lastPost.setPosterUid(twitterProfile.getUid());
                lastPost.setPosterFullName(twitterProfile.getFullName());
                lastPost.setPosterUserName(twitterProfile.getUserName());
            }
            twitterProfile.setLastPost(lastPost);
        }

        // set cursors
        twitterProfile.setPreviousCursor(previousCursor);
        twitterProfile.setNextCursor(nextCursor);

        return twitterProfile;
    }

    /**
     * Get TwitterPost list from JSON array.
     * 
     * @param postJsonArray
     * @return list of TwitterPost objects
     * @throws Exception
     */
    private List<TwitterPost> populatePostList(JSONArray postJsonArray) throws Exception {

        // post list must not be empty
        if (postJsonArray == null || postJsonArray.length() == 0) {
            throw new NetworkException("Post list is empty.", postJsonArray.toString());
        }

        return populatePostList(postJsonArray, UNSPECIFIED_CURSOR, UNSPECIFIED_CURSOR);
    }

    /**
     * Get TwitterPost list from JSON array.
     * 
     * @param postJsonArray
     * @param previousCursor
     * @param nextCursor
     * @return list of TwitterPost objects
     * @throws Exception
     */
    private List<TwitterPost> populatePostList(JSONArray postJsonArray, long previousCursor, long nextCursor)
            throws Exception {

        // iterate post list and populate TwitterPost beans
        List<TwitterPost> postList = new ArrayList<TwitterPost>();
        for (int i = 0; i < postJsonArray.length(); i++) {
            postList.add(populatePostBean(postJsonArray.getJSONObject(i), previousCursor, nextCursor));
        }

        return postList;
    }

    /**
     * Set Twitter post data to a TwitterPost bean.
     * 
     * @param postJson
     * @return twitterPost
     * @throws Exception
     */
    private TwitterPost populatePostBean(JSONObject postJson) throws Exception {
        return populatePostBean(postJson, UNSPECIFIED_CURSOR, UNSPECIFIED_CURSOR);
    }

    /**
     * Set Twitter post data to a TwitterPost bean.
     * 
     * @param postJson
     * @param previousCursor
     * @param nextCursor
     * @return twitterPost
     * @throws Exception
     */
    private TwitterPost populatePostBean(JSONObject postJson, long previousCursor, long nextCursor)
            throws Exception {

        // throw an exception if JSON is invalid
        if (postJson == null || postJson.has("id") == false) {
            if (postJson == null) {
                throw new NetworkException("Post JSON is null and cannot populate a Post bean.");
            } else {
                throw new NetworkException("Invalid post JSON.", postJson.toString());
            }
        }

        TwitterPost twitterPost = new TwitterPost();
        twitterPost.setSnsUid(SocialNetwork.TWITTER);
        twitterPost.setPostJson(postJson);
        twitterPost.setPostId(postJson.optLong("id"));

        // set poster profile bean
        if (postJson.has("user") == true) {
            TwitterProfile posterProfile = populateProfileBean(postJson.getJSONObject("user"));
            twitterPost.setPosterUid(posterProfile.getUid());
            twitterPost.setPosterFullName(posterProfile.getFullName());
            twitterPost.setPosterUserName(posterProfile.getUserName());
            twitterPost.setPosterProfile(posterProfile);
        }

        twitterPost.setTextContent(postJson.optString("text"));
        twitterPost.setRetweetCount(postJson.optInt("retweet_count"));
        twitterPost.setPostIdReplyTo(postJson.optLong("in_reply_to_status_id"));
        twitterPost.setUserUidReplyTo(postJson.optLong("in_reply_to_user_id"));
        twitterPost.setUserNameReplyTo(postJson.optString("in_reply_to_screen_name"));
        twitterPost.setCreatedDate(postJson.optString("created_at"));

        JSONObject mediaJson = null;

        // if the current post is a retweet and has original post
        if (postJson.has("retweeted_status") == true) {
            JSONObject originalPostJson = postJson.getJSONObject("retweeted_status");
            mediaJson = extractFirstMediaJson(originalPostJson);
            twitterPost.setOriginalPost(populatePostBean(originalPostJson));
        }

        // if the current post is not a retweet
        else {
            mediaJson = extractFirstMediaJson(postJson);
        }

        // extract media data if exist
        if (mediaJson != null) {
            twitterPost.setMediaUrl(mediaJson.optString("media_url"));
            twitterPost.setMediaDisplayUrl(mediaJson.optString("url"));
        }

        // set cursors
        twitterPost.setPreviousCursor(previousCursor);
        twitterPost.setNextCursor(nextCursor);

        return twitterPost;
    }

    /**
     * Get TwitterMessage list from JSON array.
     * 
     * @param messageJsonArray
     * @return list of TwitterMessage objects
     * @throws Exception
     */
    private List<TwitterMessage> populateMessageList(JSONArray messageJsonArray) throws Exception {

        // message list must not be empty
        if (messageJsonArray == null || messageJsonArray.length() == 0) {
            throw new NetworkException("Message list is empty.", messageJsonArray.toString());
        }

        // iterate message list and populate TwitterMessage beans
        List<TwitterMessage> postList = new ArrayList<TwitterMessage>();
        for (int i = 0; i < messageJsonArray.length(); i++) {
            postList.add(populateMessageBean(messageJsonArray.getJSONObject(i)));
        }

        return postList;
    }

    /**
     * Set Twitter post data to a TwitterMessage bean.
     * 
     * @param messageJson
     * @return TwitterMessage
     * @throws Exception
     */
    private TwitterMessage populateMessageBean(JSONObject messageJson) throws Exception {

        // throw an exception if JSON is invalid
        if (messageJson == null || messageJson.has("id") == false) {
            if (messageJson == null) {
                throw new NetworkException("Message JSON is null and cannot populate a Post bean.");
            } else {
                throw new NetworkException("Invalid message JSON.", messageJson.toString());
            }
        }

        TwitterMessage twitterMessage = new TwitterMessage();
        twitterMessage.setSnsUid(SocialNetwork.TWITTER);
        twitterMessage.setMessageJson(messageJson);
        twitterMessage.setMessageId(messageJson.optLong("id"));
        twitterMessage.setTextContent(messageJson.optString("text"));
        twitterMessage.setCreatedDate(messageJson.optString("created_at"));

        twitterMessage.setSenderUid(messageJson.optLong("sender_id"));
        twitterMessage.setSenderUserName(messageJson.optString("sender_screen_name"));

        // set sender profile bean
        if (messageJson.has("sender")) {
            twitterMessage.setSenderProfile(populateProfileBean(messageJson.getJSONObject("sender")));
        }

        twitterMessage.setRecipientUid(messageJson.optLong("recipient_id"));
        twitterMessage.setRecipientUserName(messageJson.optString("recipient_screen_name"));

        // set recipient profile bean
        if (messageJson.has("recipient")) {
            twitterMessage.setRecipientProfile(populateProfileBean(messageJson.getJSONObject("recipient")));
        }

        return twitterMessage;
    }

}